123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471 |
- import { PlusOutlined, ExclamationCircleOutlined } from '@ant-design/icons'
- import { Button, Col, Input, InputNumber, message, Modal, Radio, Row, Space, Tag, Tooltip } from 'antd'
- import { RadioChangeEvent } from 'antd/lib/radio'
- import React, { useCallback, useEffect, useState } from 'react'
- import MsgWxLeft from './msgWxLeft'
- import style from './graphic.less'
- import { useModel } from 'umi'
- import WxMpnews from '@/components/MaterialModal/wxMpnews'
- import WxHistoryBD from '@/components/MaterialModal/wxHistorBD'
- import { isArray } from 'lodash'
- import { exportMediaByUrl1 } from '@/services/operating/material'
- import useIndexDB from '@/Hook/useIndexDB'
- import SmMaterialModal from '@/components/MaterialModal/smModalBox'
- import filePng from '../../../public/file.png'
- import WxSelect from '../WxSelect'
- /**客服,延迟,立即回复,公众号菜单使用中 */
- /**
- * @param isProhibitMs 是否禁止多图文按钮 默认 false 不禁用
- * @param tooltipTxt 多图文单图文按钮提示文字配置
- * @param ImportBtShow 导入按钮控制 {1: 历史图文, 2: 素材图文,3: 全部展示} 默认值 3
- * @param historyType //历史图文type 默认 0: 智能互动消息历史 1:客服消息历史
- */
- type Props = {
- visible: boolean,
- onCancel: () => void,
- onOK: (props: any, type?: number) => void,
- defaultData: any,
- msgType?: number,
- isProhibitMs?: boolean, // 是否禁止多图文按钮
- tooltipTxt?: string, // 提示信息
- ImportBtShow?: number, // {1: 历史图文, 2: 素材图文,3: 全部展示} 默认值 3
- historyType?: number,
- title?: string,//弹窗标题
- isKnews?: boolean, // 是否k图文新建
- }
- const MsgWxGraphicListModal: React.FC<Props> = (props) => {
- const { drawerState: { dataArr, actionId, AllData }, dispatchMate } = useModel('useOperating.useMsgMaterialDrawer', model => ({ drawerState: model.state, dispatchMate: model.dispatch }))
- const { visible, onCancel, onOK, defaultData, msgType, isProhibitMs = false, tooltipTxt = "", ImportBtShow = 3, historyType = 0, title, isKnews = false } = props
- const [isShow, setIsShow] = useState<boolean>(false)//图片弹窗
- const [ref, setRef] = useState<any>(null)//存放实例
- const [range, setRange] = useState<Range>()//存放光标丢失位置
- const { state: { actionWX, selectWx }, initSelectWx } = useModel('useOperating.useWxGroupList')
- const [it, setIt] = useState<number>(0)//单图文多图文切换
- const [indexId, setIndexId] = useState<number>(0) //前面列表id
- const [mpVisible, setMpVisible] = useState<boolean>(false) // 素材弹窗
- const [showNews, setShowNews] = useState<boolean>(false)//导入文章
- const [newsUrl, setNewsUrl] = useState<string>('')//文章地址
- const [showBD, setShowBD] = useState<boolean>(false)//BD导入弹窗
- const [titleNum, setTitleNum] = useState<number>(0)
- const [v, setV] = useState<number>(1)//图篇或k图文 filebox使用
- const [sort, setSort] = useState<number>(0)
- const [noFile, setNoFile] = useState<boolean>(false)
- //DB
- const { add } = useIndexDB()
- //导入文章
- const handleImportNews = useCallback(() => {
- let str = newsUrl
- if (str.indexOf(',') === -1) {
- str = str.replace(/\s/ig, ',')
- }
- let arr = str.split(/[,,]/)
- let newArr: string[] = []
- arr.forEach((url: string) => {
- url = url.replace(/\s*/g, '')
- if (url !== "" && url) {
- newArr.push(url)
- }
- })
- let isOk = newArr.every((url: string, index: number) => {
- if (url.search(/http[s]?:\/\/mp.weixin.qq.com/ig) === 0) {
- return true
- } else {
- message.error(`第${index + 1}的地址错误,请输入正确的文章地址!`)
- return false
- }
- })
- if (newArr.length > (9 - actionId)) {
- message.error('你输入的链接条数超过了当前剩余可新增的条数')
- return
- }
- if (isOk) {
- let promiseAll: any[] = []
- newArr.forEach((url: string, index: number) => {
- // promiseAll.push(exportMediaByUrl1(url).then((res) => { return res.json() }))
- promiseAll.push(exportMediaByUrl1(encodeURIComponent(url)).then((res) => { return res.json() }))
- })
- Promise.all(promiseAll).then((res: any) => {
- let arr: any[] = []
- if (res) {
- res.forEach((r: { data: any, code: 200 }, index: number) => {
- if (r.code === 200) {
- let { content, contentSourceUrl, digest, thumbUrl, title, thumbMediaUrl } = r.data
- contentSourceUrl = contentSourceUrl.replace(/['"]*/g, '')
- content = content.replace('data-src', 'src').replace(/\<mpvoice[\s\S]*\<\/mpvoice\>/ig, '')
- arr.push({ menuId: actionId + index, content, contentSourceUrl, digest, thumbUrl: thumbUrl || thumbMediaUrl, title })
- }
- })
- arr = arr.sort((a: { menuId: number }, b: { menuId: number }) => { return a.menuId - b.menuId })
- dispatchMate({ type: 'importNews', params: { data: { dataArrs: arr, actionId } } })
- }
- }).catch(err => { console.log(err) })
- setShowNews(false)
- }
- }, [newsUrl, actionId])
- useEffect(() => {
- if (it === 1) {
- dispatchMate({ type: 'action', params: { menuId: dataArr[0].menuId } })
- }
- }, [it]) // 单图文默认1
- useEffect(() => { // 禁止设置选中单图文链接按钮
- if (isProhibitMs) {
- setIt(1)
- }
- }, [isProhibitMs])
- useEffect(() => {
- if (msgType === 1) {
- setIt(1)
- }
- }, [msgType])
- //确定
- const callback = useCallback((isBD?: boolean) => {
- let oldarr: any = dataArr
- if (it === 1) {
- let ac = oldarr[actionId - 1]
- oldarr = [ac]
- }
- let arr = oldarr.filter((item: any) => JSON.stringify(item) !== '{}')//筛除空对象||
- arr = arr.filter((item: any) => {
- return item.title
- })
- arr = arr.map((item: any) => {
- console.log(item)
- let obj: any = {
- knewsThumbUrl: item.thumbUrl, // 图片
- title: item.title.replace(/<[/]?span[^>]*>/ig, '#'), // 图文标题
- description: item.digest, // 描述内容
- knewsLink: item.contentSourceUrl, // 链接设置
- knewsThumbInfo: item?.knewsThumbInfo
- }
- if (item?.knewsThumbId) {
- obj['knewsThumbId'] = item?.knewsThumbId
- }
- return obj
- })
- console.log(arr)
- let isOk: boolean = arr.length > 0 && arr.every((item: any) => (item.knewsThumbUrl || item?.knewsThumbId || item?.knewsThumbInfo) && item.description && item.knewsLink && item.title)//检测全部必填项
- if (isBD) {
- add({ data: arr })//保存进DB
- return
- }
- if (isOk) {
- let params: any = { newsList: arr, indexId: indexId, mpIds: selectWx }
- if (isKnews) {
- params.sort = sort
- }
- onOK(params, 5)
- initSelectWx()
- dispatchMate({ type: 'initData' })
- } else {
- message.error('请检测标题,图片,描述,链接是否填写完整!不需要的篇章请全部留空')
- }
- }, [dataArr, it, AllData, actionId, add, selectWx, sort])
- const handelRadioIt = useCallback((e: RadioChangeEvent) => {
- let v = e.target.value
- setIt(v)
- }, [])
- //开关图片弹窗
- const closeModal = useCallback((v?: any) => {
- console.log(v)
- if (v) {
- setV(v)
- }
- setIsShow(!isShow)
- }, [isShow])
- /**选中图片,单独选择图片在传id 选择k图文直接映射*/
- const handleOk = (data: any) => {
- let obj: any = { menuId: actionId, thumbUrl: data.url }
- if (data?.id && !data?.mediaId) {//存在ID不存在媒体ID证明是本地素材才添加
- obj['knewsThumbId'] = data?.id
- }
- obj['knewsThumbInfo'] = {
- folder: data?.folder,
- title: data?.title,
- number: data?.number
- }
- if (v === 6) {
- obj['title'] = data?.title
- obj['contentSourceUrl'] = data?.knewsLink
- obj['digest'] = data?.description
- obj['thumbUrl'] = data?.knewsThumbUrl
- obj['knewsThumbId'] = data?.knewsThumbId
- obj['knewsThumbInfo'] = { folder: data?.folder }
- }
- dispatchMate({ type: 'pushData', params: obj })
- closeModal()
- }
- //插入粉丝昵称
- const pushName = useCallback(() => {
- let span = document.createElement('span')
- span.className = style.nickName
- span.setAttribute('contentEditable', 'false')
- span.innerText = `粉丝昵称`
- range?.insertNode(span)
- dispatchMate({ type: 'pushData', params: { menuId: actionId, title: ref.innerHTML } })
- }, [range, actionId])
- //光标丢失记录位置
- const onBlur = useCallback(() => {
- let selection = window.getSelection()
- let range = selection?.getRangeAt(0)
- setRange(range)
- if (ref.innerHTML) {
- dispatchMate({ type: 'pushData', params: { menuId: actionId, title: ref.innerHTML.replace(/"/ig, '"').replace(/&/ig, '&').replace(/</ig, '<').replace(/>/ig, '>').replace(/<\s+/g, '<') } })
- }
- }, [ref, actionId])
- useEffect(() => {
- ref?.focus()
- }, [ref])
- //连接输入
- const handelLink = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
- let v = e.target.value
- dispatchMate({ type: 'pushData', params: { menuId: actionId, contentSourceUrl: v } })
- }, [actionId])
- //摘要输入
- const handelText = useCallback((e: React.ChangeEvent<HTMLTextAreaElement>) => {
- let v = e.target.value
- dispatchMate({ type: 'pushData', params: { menuId: actionId, digest: v } })
- }, [actionId])
- //复制只取纯文本
- function textPaste(event: any) {
- event.preventDefault();
- let text;
- let clp = (event.originalEvent || event).clipboardData;
- // 兼容chorme或hotfire
- text = clp.getData('text')
- if (text !== "") {
- document.execCommand('insertHtml', false, text);
- }
- }
- /**编辑回填 */
- useEffect(() => {
- if (defaultData?.newsList) {
- let arr = defaultData?.newsList.map((item: any) => {
- let obj = {
- title: item.title.replace('#粉丝昵称#', `<span class=${style.nickName} contenteditable="false">粉丝昵称</span>`),
- thumbUrl: item.knewsThumbUrl,
- contentSourceUrl: item.knewsLink,
- digest: item.description,
- knewsThumbInfo: item?.knewsThumbInfo,
- knewsThumbId: item?.knewsThumbId
- }//转义
- setSort(item?.sort || 0)
- return obj
- })
- // arr = [...arr, ...Object.values(Array(8 - arr.length).fill({})).map(n => n)] //不足8个填充
- arr = arr.map((item: any, i: number) => {
- return { menuId: i + 1, ...item }
- })
- setIndexId(defaultData?.indexId)
- dispatchMate({ type: 'initData', params: { data: { dataArr: arr } } })
- if (arr.length === 1) {
- setIt(1)
- dispatchMate({ type: 'action', params: { menuId: dataArr[0].menuId } })
- }
- }
- return () => {
- dispatchMate({ type: 'initData' })
- }
- }, [defaultData])
- return <Modal
- title={title || '编辑图文内容'}
- width={1100}
- open={visible}
- onCancel={() => {
- onCancel()
- dispatchMate({ type: 'initData' })
- }}
- footer={<div>
- <Button onClick={() => {
- onCancel()
- dispatchMate({ type: 'initData' })
- }}>取消</Button>
- <Button onClick={() => callback()} type='primary'>确定</Button>
- <Button onClick={() => callback(true)}>保存到浏览器本地</Button>
- </div>}
- destroyOnClose
- >
- <div>
- <div style={{ marginBottom: '10px' }}>
- <Space>
- <Radio.Group value={it} onChange={handelRadioIt}>
- <Radio.Button value={0} disabled={msgType === 1}>多图文链接</Radio.Button>
- <Radio.Button value={1}>单图文链接</Radio.Button>
- </Radio.Group>
- <Tooltip title={tooltipTxt !== "" ? tooltipTxt : "多图文链接只有立即回复(关注与扫码)可设置且只能放在第一条,延迟消息只支持单图文链接"}>
- <ExclamationCircleOutlined />
- </Tooltip>
- </Space>
- </div>
- <div className={style.header}>
- <label>导入方式:</label>
- <Space>
- {
- ImportBtShow === 1 ?
- null
- : ImportBtShow === 2 ?
- <Button type="primary" size='small' onClick={() => setMpVisible(true)}>素材库导入</Button>
- : <Button type="primary" size='small' onClick={() => setMpVisible(true)}>素材库导入</Button>
- }
- <Button type='primary' size="small" onClick={() => setShowNews(true)}>链接导入</Button>
- <Button type='primary' size="small" onClick={() => setShowBD(true)}>浏览器本地导入</Button>
- <Button type='primary' size="small" onClick={() => closeModal(6)}>K-图文</Button>
- </Space>
- </div>
- {
- // 自定义设置
- <div className={style.centent}>
- <div >
- <MsgWxLeft it={it} dataArr={dataArr}></MsgWxLeft>
- </div>
- <div className={style.graphic_list}>
- {
- dataArr?.map((list: any, index: number) => {
- return actionId === list?.menuId && <div key={index}>
- <Row style={{ width: '80%' }}>
- <Col span={24}>
- <label >图文标题 :<Space><span>(必填)</span><span>(<span style={{ color: titleNum > 64 ? 'red' : '#000' }}>{titleNum}</span>/64)</span></Space></label>
- <div
- contentEditable="true"
- className={style.editor}
- ref={(ref) => setRef(ref)}
- onBlur={onBlur}
- dangerouslySetInnerHTML={{ __html: list?.title }}
- onPaste={textPaste}
- onKeyUp={() => {
- setTitleNum(ref?.innerHTML?.length || 0)
- }}
- />
- {/* <label >点击插入:<Tag color='orange' onClick={pushName}>粉丝昵称</Tag></label> */}
- </Col>
- <Col span={24}>
- <label >描述内容 :</label>
- <Input.TextArea rows={5} placeholder='填写图文描述' onChange={handelText} value={list?.digest} />
- </Col>
- <Col span={24}>
- <label >链接设置 :<span>(必填)</span></label>
- <Input placeholder='请输入跳转链接,且必须以http://或https://开头' onChange={handelLink} value={list?.contentSourceUrl} />
- </Col>
- {isKnews && <Col span={24}>
- <label >排序 :<span>(必填)</span><Tag color='warning'>数值越大越靠前</Tag></label>
- <InputNumber
- style={{ width: '100%' }}
- value={sort}
- placeholder='请输入序号, 数值越大越靠前'
- onChange={(e) => {
- setSort(e || 0)
- }}
- />
- </Col>}
- <WxSelect />
- </Row>
- <div>
- <label >图文封面:</label>
- <span className={list?.thumbUrl ? style.imgupload : ''} onClick={() => { closeModal(1); setNoFile(true) }}>
- {
- // (list?.thumbUrl || list?.knewsThumbInfo) ? <img src={list?.knewsThumbInfo?.folder ? filePng : list?.knewsThumbInfo?.id ? list?.knewsThumbInfo?.url : list?.thumbUrl} /> : <PlusOutlined style={{ fontSize: 30 }} />
- (list?.thumbUrl || list?.knewsThumbId) ? <img src={list?.thumbUrl || (list.knewsThumbId ? filePng : 'https://s.weituibao.com/static/1552098829922/bigfm.png')} /> : <PlusOutlined style={{ fontSize: 30 }} />
- // list?.knewsThumbInfo?.folder ? <img src={filePng} /> : (list?.thumbUrl) ? <img src={list?.thumbUrl} /> : <PlusOutlined style={{ fontSize: 30 }} />
- }
- <div><PlusOutlined style={{ fontSize: 30, color: '#fff' }} /> </div>
- </span>
- <div style={{ display: 'flex', flexFlow: 'column', width: 100 }}>
- <span>名称:{list?.knewsThumbInfo?.title || '网图'} </span>
- <span>编号:{list?.knewsThumbInfo?.number || '网图'}</span>
- </div>
- </div>
- </div>
- })
- }
- </div>
- {isShow && <SmMaterialModal
- visible={visible}
- onCancel={() => { closeModal(); setNoFile(false) }}
- title={`选择图片`}
- onOk={(props: any) => {
- handleOk(props)
- setNoFile(false)
- }}
- mediaType={v}
- isShowWx={v === 1 ? true : false}
- isAllData
- noFile={noFile}
- />}
- </div>
- }
- {mpVisible && <WxMpnews
- visible={mpVisible}
- onCancel={() => setMpVisible(false)}
- onOK={(data: any) => {
- if (data) {
- let articles: any[] = data?.news
- if (isArray(articles) && articles.length > 0) {
- dispatchMate({ type: 'initData', params: { data: { dataArr: articles.map((ar: any, index: number) => { return { menuId: index + 1, digest: ar.digest, contentSourceUrl: ar.contentSourceUrl, title: ar.title, thumbUrl: ar.thumbUrl || ar.thumbMediaUrl } }) } } })
- setMpVisible(false)
- }
- }
- }}
- defaultData=''
- wx={actionWX}
- />}
- <WxHistoryBD
- visible={showBD}
- onCancel={() => setShowBD(false)}
- onOK={(data: any) => {
- if (data) {
- dispatchMate({
- type: 'initData', params: {
- data: {
- dataArr: data.map((arr: any, index: number) => {
- return { menuId: index + 1, digest: arr.description || arr.newsDescription, contentSourceUrl: arr.knewsLink || arr.newsUrl, title: arr.title || arr.newsTitle, thumbUrl: arr.knewsThumbUrl || arr.newsPicUrl, knewsThumbInfo: arr?.knewsThumbInfo, knewsThumbId: arr?.knewsThumbId }
- })
- }
- }
- })
- setShowBD(false)
- }
- }}
- />
- {/* 链接导入弹窗 */}
- <Modal
- open={showNews}
- title='导入文章'
- onCancel={() => { setShowNews(false) }}
- onOk={handleImportNews}
- destroyOnClose
- >
- <Input.TextArea
- placeholder={
- ` 请填写文章url地址,支持批量,批量传入需要在每个链接后面加上逗号或者换行,从你当前选中的篇数开始导入替换,一次只能导入8篇,输入的链接地址不能超过可新建的篇数,假如你从第5篇开始导入那只能导入4篇
-
- 方式1:支持末尾加,号分割导入 https://mp.weixin.qq.com/s/C4qcDnPwfAlf4Y2P9qeThA,https://mp.weixin.qq.com/s/tVGyg6rxR6BoppjqIbKoGg
- 方式2:支持换行分割导入
- https://mp.weixin.qq.com/s/C4qcDnPwfAlf4Y2P9qeThA
- https://mp.weixin.qq.com/s/tVGyg6rxR6BoppjqIbKoGg
- `
- }
- rows={10}
- onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) => {
- let url = e.target.value
- if (url) {
- setNewsUrl(url)
- }
- }}
- />
- </Modal>
- </div>
- </Modal>
- }
- export default React.memo(MsgWxGraphicListModal, (a, b) => {
- if (JSON.stringify(a) === JSON.stringify(b)) {
- return true
- } else {
- return false
- }
- })
|