shenwu 7 hónapja
szülő
commit
2a7c390175

A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 5 - 0
src/components/TextEditor/Expression.tsx


+ 56 - 0
src/components/TextEditor/index.less

@@ -0,0 +1,56 @@
+.expression {
+  width: 420px;
+  overflow: hidden;
+  display: flex;
+  position: relative;
+  padding-bottom: 10px;
+
+  .box {
+    width: 420px;
+    display: flex;
+    flex-flow: row wrap;
+    flex-shrink: 0;
+    transition: all .5s;
+
+    >span {
+      font-size: 20px;
+      padding: 5px;
+      cursor: pointer;
+    }
+  }
+
+  .bottom {
+    position: absolute;
+    bottom: 0px;
+    width: 100%;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+  }
+
+  .yd {
+    width: 10px;
+    height: 10px;
+    border-radius: 50%;
+    background-color: #efefef;
+    margin-right: 5px;
+    cursor: pointer;
+  }
+
+  .action {
+    background-color: yellowgreen;
+  }
+}
+
+.quill_editor {
+  width: 100%;
+
+  img {
+    width: 20px;
+    height: 20px;
+  }
+}
+
+.myPre img {
+  vertical-align: bottom;
+}

+ 198 - 0
src/components/TextEditor/index.tsx

@@ -0,0 +1,198 @@
+import { Button, List, Space, Typography } from "antd"
+import { useCallback, useEffect, useRef, useState } from "react"
+import Expression, { emoList } from "./Expression";
+const { Text } = Typography
+import style from './index.less'
+
+type Props = {
+    /**是否展示底部*/
+    footer: null | string | JSX.Element,
+    /**插入变量按钮的按钮名称*/
+    btnNames: string[],
+    /**初始默认插入的按钮*/
+    initBtnNames?: string[],
+    /**是否开启表情 */
+    emo?: boolean,
+    value?: any
+    onChange?: (value: any) => void,
+    maxStr?: number,//最大字数
+    isOnInput?: boolean,
+}
+export function TextEditor(props: Props) {
+    const { footer, btnNames, initBtnNames, emo = true, onChange, value = '', maxStr, isOnInput } = props
+    const ref: { current: any } = useRef(null)
+    const [text, setText] = useState('')
+    const [range, setRange] = useState<Range>()//丢失焦点存放焦点位置
+    const [strLength, setStrLength] = useState(0)
+    /** 回填 */
+    useEffect(() => {
+        backfill()
+    }, [value])
+    //回填处理
+    const backfill = useCallback(() => {
+        if (value) {
+            if (typeof value === 'string') {
+                setStrLength(value.length || 0)
+                let newValue = value || ''
+                let newEmo = emoList.reduce((prev, cur) => {
+                    return [...prev, ...cur]
+                }, [])
+                let emos = newValue.match(/\[[\u4e00-\u9fa5]+\]/g)
+                if (emos && emos?.length) {
+                    emos.forEach(emo => {
+                        let emoData = newEmo.find(o => `[${o.name}]` === emo)
+                        if (emoData) {
+                            newValue = newValue.replace(emo, `<img src="${emoData.url}" alt="${emoData.name}" style="width:20px;height:20px"/>`)
+                        }
+                    })
+                }
+                setText(newValue?.replaceAll('\n', '<br/>'))
+            } else {
+                setText(value?.innerHTML)
+            }
+        }
+    }, [value])
+
+    // 焦点丢失记录
+    const onBlur = (e: any) => {
+        try {
+            let selection = window.getSelection()
+            let range = selection?.getRangeAt(0)
+            setRange(range)
+            //点击表情不更新
+            if (!e.relatedTarget.className.includes('myEmo')) {
+                onChange?.(ref.current)
+            }
+        } catch (err) {
+        }
+    }
+    // 插入按钮
+    const btnClick = useCallback((btnName: string) => {
+        if (range) {
+            let str = "" //` <span style="display: inline-block;margin:0 1px;position: relative; border: 1px solid ${token.colorBorder}; padding: ${token.paddingXS}px; border-radius: ${token.borderRadius}px;color: ${token.colorTextBase}; background: ${token.colorSuccess};color:${token.colorTextLightSolid}" contenteditable="false">${btnName}<strong data-name="${btnName}" style="padding: 0 6px;cursor:pointer" onclick="let html =document.querySelector('[data-name=${btnName}]').parentElement.parentElement.innerHTML;let span = ' '+document.querySelector('[data-name=${btnName}]').parentElement.outerHTML+' ';console.log('=',html,'=');console.log('=',span,'=');document.execCommand('selectAll');document.execCommand('delete'); document.execCommand('insertHTML', true, html.replace(span,''));">X</strong></span> `
+            let selection = window.getSelection()
+            selection?.empty()//清空选择range
+            selection?.addRange(range as Range)//插入新的range
+            document.execCommand('insertHTML', false, str);//插入内容
+            range.collapse()
+            selection?.collapseToEnd()//光标插入到末尾
+        } else {
+            ref.current.focus()
+        }
+    }, [range, ref])
+    /**
+     * 插入表情
+     */
+    const addEmo = useCallback((str: string, range: Range) => {
+        if (range) {
+            let selection = window.getSelection()
+            selection?.empty()//清空选择range
+            selection?.addRange(range)//插入新的range
+            document.execCommand('insertHTML', false, str);
+        }
+    }, [])
+
+    //初始化设置
+    useEffect(() => {
+        if (initBtnNames && initBtnNames?.length > 0) {
+            let newText = ''
+            initBtnNames?.map(btnName => {
+                newText += "" //` <span style="display: inline-block;margin:0 1px;position: relative; border: 1px solid ${token.colorBorder}; padding: ${token.paddingXS}px; border-radius: ${token.borderRadius}px;color: ${token.colorTextBase}; background: ${token.colorSuccess};color:${token.colorTextLightSolid}" contenteditable="false">${btnName}<strong data-name="${btnName}" style="padding: 0 6px;cursor:pointer" onclick="let html =document.querySelector('[data-name=${btnName}]').parentElement.parentElement.innerHTML;let span = ' '+document.querySelector('[data-name=${btnName}]').parentElement.outerHTML+' ';console.log('=',html,'=');console.log('=',span,'=');document.execCommand('selectAll');document.execCommand('delete'); document.execCommand('insertHTML', true, html.replace(span,''));">X</strong></span> `
+            })
+            setText(newText)
+        }
+    }, [initBtnNames])
+    //复制只取纯文本
+    function textPaste(event: any) {
+        event.preventDefault();
+        let text;
+        let clp = (event.originalEvent || event).clipboardData;
+        // 兼容chorme或hotfire
+        text = (clp.getData('text/plain') || clp.getData('text'))
+            .replace(/&quot;/ig, '"')
+            .replace(/&amp;/ig, '&')
+            .replace(/&lt;/ig, '<')
+            .replace(/&gt;/ig, '>')
+            .replace(/<\s+/g, '<')
+            .replace(/href="weixin/ig, 'href=weixin')
+        if (text !== "") {
+            document.execCommand('insertHTML', false, text);
+        }
+    }
+    return <List
+        header={<div style={{ display: 'flex', justifyContent: 'space-between' }}>
+            <Space wrap>
+                {emo && <Expression addEmo={addEmo} range={range as Range} />}
+                {btnNames?.map(name => {
+                    return <Button onClick={() => { btnClick(name) }} key={name}>{name}</Button>
+                })}
+            </Space>
+            {maxStr && <Typography.Text type='secondary'><Typography.Text type={strLength > maxStr ? 'danger' : 'secondary'} >{strLength}</Typography.Text>/{maxStr}</Typography.Text>}
+            {/* <Button type="link">复制</Button> */}
+        </div>}
+        footer={typeof footer === 'string' ? <Text >{footer}</Text> : footer}
+        bordered
+        dataSource={['']}
+        renderItem={(item) => (
+            <List.Item>
+                <pre
+                    className={style.myPre}
+                    style={{
+                        fontFamily: ' sans-serif',
+                        width: '100%',
+                        minHeight: '40px',
+                        padding: 5,
+                        resize: 'none',
+                        whiteSpace: 'pre-wrap',
+                        margin: 0,
+                        textAlign: 'left',
+                        wordBreak: 'break-all'
+                    }}
+                    onInput={(e) => {
+                        if (isOnInput) {
+                            onChange?.(e.target)
+                            // setText((e.target as any).innerHTML)
+                        }
+                    }}
+                    contentEditable="true"
+                    dangerouslySetInnerHTML={{
+                        __html: text
+                    }}
+                    ref={ref}
+                    onKeyDown={(e: React.KeyboardEvent<HTMLPreElement>) => {
+                        if (e.key === 'Enter') {
+                            document.execCommand('insertHTML', false, '<br/>\n')
+                            e.preventDefault()
+                        }
+                    }}
+                    onKeyUp={(e: React.KeyboardEvent<HTMLPreElement>) => {
+                        if (e.key === 'Enter') {
+                            e.preventDefault()
+                        } else {
+                            if (maxStr) {
+                                let childrens = (e.target as any).childNodes
+                                let newText: string = ''
+                                for (const children of childrens) {
+                                    if (children.tagName === 'IMG') {
+                                        newText += `[${children.alt}]`
+                                    } else if (children.tagName === 'BR') {
+                                        newText += `\n`
+                                    } else if (children.tagName === 'SPAN') {
+                                        let name = children.children[0].dataset.name
+                                        newText += `%${name}%`
+                                    } else {
+                                        newText += children.textContent
+                                    }
+                                }
+                                setStrLength(newText.length)
+                            }
+                        }
+                    }}
+                    onBlur={onBlur}//焦点丢失
+                    onPaste={textPaste}
+                />
+            </List.Item>
+        )}
+        style={{ marginTop: 10 }}
+    />
+}

+ 2 - 2
src/components/uploadImg/index.tsx

@@ -38,9 +38,9 @@ const UploadImg: React.FC<Props> = ({ value, onChange, size, type, isCropper, is
       </div>
       <div style={{ display: 'flex', flexFlow: 'column' }}>
         <span>上传图片</span>
-        <span style={{ color: '#999', fontSize: 10 }}>
+        {size && <span style={{ color: '#999', fontSize: 10 }}>
           &gt;={size?.width} *  &gt;={size?.height}
-        </span>
+        </span>}
       </div>
     </div>
   );

+ 59 - 0
src/pages/MiniApp/EntWeChat/Welcome/components/link.tsx

@@ -0,0 +1,59 @@
+import { PlusOutlined } from "@ant-design/icons"
+import { Button, Card, Modal, Tabs } from "antd"
+import { useEffect, useState } from "react"
+import Book from '@/pages/MiniApp/Extend/Book'
+import Page from '@/pages/MiniApp/Extend/Page'
+function AddLink(props: { value?: any, onChange?: (v: any) => void }) {
+    let { value, onChange } = props
+    let [open, setOpen] = useState(false)
+    let [activeKey, setActiveKey] = useState("book")
+    const close = () => {
+        setOpen(false)
+    }
+    // 当存在value时打开弹窗以条件判断跳转到对应tab
+    useEffect(() => {
+        if (value) {
+            setActiveKey(value?.bookName ? "book" : "page")
+        }
+    }, [])
+    return <div>
+        {value ? <Card size="small" title={value?.bookName ? "作品推广" : "页面推广"} style={{ cursor: 'pointer' }} onClick={() => { setOpen(true) }}>
+            <div>链接名称:<span style={{ color: "#777" }}>{value?.linkName}</span></div>
+            {value?.pageName && <div>页面名称:<span style={{ color: "#777" }}>{value?.pageName}</span></div>}
+            {value?.bookName && <div>作品名称:<span style={{ color: "#777" }}>{value?.bookName}</span></div>}
+            {value?.bookChapterName && <div>章节名称:<span style={{ color: "#777" }}>{value?.bookChapterName}</span></div>}
+            {value?.pagePath && <div>路径地址:<span style={{ color: "#777" }}>{value?.pagePath}</span></div>}
+
+        </Card> : <a onClick={() => { setOpen(true) }}><PlusOutlined /> 插入链接</a>
+        }
+        <Modal
+            title="插入链接"
+            open={open}
+            destroyOnClose
+            footer={false}
+            onCancel={close}
+            width={"60%"}
+            height={500}
+        >
+            <Tabs
+                activeKey={activeKey}
+                onChange={(ak) => {
+                    setActiveKey(ak)
+                }}
+                items={[
+                    {
+                        key: "book",
+                        label: "作品推广",
+                        children: <Book isModal={true} onChange={onChange} onClose={close} value={value} />
+                    },
+                    {
+                        key: "page",
+                        label: "页面推广",
+                        children: <Page isModal={true} onChange={onChange} onClose={close} value={value} />
+                    },
+                ]}
+            />
+        </Modal>
+    </div>
+}
+export default AddLink

+ 76 - 12
src/pages/MiniApp/EntWeChat/Welcome/content.tsx

@@ -1,8 +1,9 @@
 import { Form } from "antd"
 import { forwardRef, useEffect, useImperativeHandle, } from "react"
-import { ProForm, ProFormSelect, ProFormText } from "@ant-design/pro-components"
+import { ProForm, ProFormSelect, ProFormText, ProFormTextArea } from "@ant-design/pro-components"
 import { useModel } from "@umijs/max"
 import UploadImg from "@/components/uploadImg"
+import AddLink from "./components/link"
 
 
 const MyForm = forwardRef((props: { index: any, arr: any[], appId: any, set: (v: any) => void }, ref) => {
@@ -11,10 +12,10 @@ const MyForm = forwardRef((props: { index: any, arr: any[], appId: any, set: (v:
     const [form] = Form.useForm();
     // 将子组件的 ref 暴露给父组件
     useImperativeHandle(ref, () => ({
-       form
+        form
     }));
     useEffect(() => {
-        console.log("arr[index]",arr[index])
+        console.log("arr[index]", arr[index])
         form?.setFieldsValue(arr[index])
     }, [arr])
     return <ProForm
@@ -23,7 +24,7 @@ const MyForm = forwardRef((props: { index: any, arr: any[], appId: any, set: (v:
         submitter={false}
         labelCol={{ span: 3 }}
         onValuesChange={(changedValues: any, values: any) => {
-            console.log("values",values)
+            console.log("values", values)
             let newArrCopy: any = JSON.parse(JSON.stringify(arr));
             newArrCopy[index] = values
             set(newArrCopy)
@@ -33,7 +34,22 @@ const MyForm = forwardRef((props: { index: any, arr: any[], appId: any, set: (v:
             {(form) => {
                 return (
                     <ProFormSelect
-                        options={getEnum("MEDIA_TYPE", "arr")}
+                        options={getEnum("MEDIA_TYPE", "arr")?.map((item: any) => {
+                            let textIndex = arr?.findIndex(i => i.mediaType === "text")
+                            if (textIndex === -1) {
+                                return item
+                            } else {
+                                if (textIndex === index) {
+                                    return item
+                                } else {
+                                    if (item.value === 'text') {
+                                        return { ...item, disabled: true }
+                                    } else {
+                                        return item
+                                    }
+                                }
+                            }
+                        })}
                         width="md"
                         name="mediaType"
                         label={`消息类型`}
@@ -60,12 +76,23 @@ const MyForm = forwardRef((props: { index: any, arr: any[], appId: any, set: (v:
                     case "text":
                         return <>文本</>
                     case "link":
-                        return <>图文</>
-                    case "miniprogram":
-                        return <div >
+                        return <div>
                             <ProFormText
                                 label="标题"
-                                name="miniprogramTitle"
+                                name="linkTitle"
+                                rules={[
+                                    {
+                                        max: 21
+                                    },
+                                    {
+                                        required: true,
+                                        message: '此项为必填项',
+                                    }
+                                ]}
+                            />
+                            <ProFormTextArea
+                                label="描述"
+                                name="linkDesc"
                                 rules={[
                                     {
                                         max: 21
@@ -76,16 +103,45 @@ const MyForm = forwardRef((props: { index: any, arr: any[], appId: any, set: (v:
                                     }
                                 ]}
                             />
+                            <Form.Item label="图片" name="linkPicurl" rules={[
+                                {
+                                    required: true,
+                                    message: '此项为必填项',
+                                }
+                            ]}>
+                                <UploadImg
+                                    type="image"
+                                />
+                            </Form.Item>
+                            <Form.Item label="链接" name="linkUrl" rules={[
+                                {
+                                    required: true,
+                                    message: '此项为必填项',
+                                }
+                            ]}>
+                                <AddLink />
+                            </Form.Item>
+                            <Form.Item hidden={true} >
+                                <ProFormText
+                                    label="ID"
+                                    name="id"
+                                />
+                            </Form.Item>
+                        </div>
+                    case "miniprogram":
+                        return <div >
                             <ProFormText
-                                label="路径"
-                                name="miniprogramPage"
+                                label="标题"
+                                name="miniprogramTitle"
                                 rules={[
+                                    {
+                                        max: 21
+                                    },
                                     {
                                         required: true,
                                         message: '此项为必填项',
                                     }
                                 ]}
-                                fieldProps={{ placeholder: "pages/index/index" }}
                             />
                             <Form.Item label="图片" name="miniprogramPicurl" rules={[
                                 {
@@ -106,6 +162,14 @@ const MyForm = forwardRef((props: { index: any, arr: any[], appId: any, set: (v:
                                     isCropper
                                 />
                             </Form.Item>
+                            <Form.Item label="链接" name="miniprogramPage" rules={[
+                                {
+                                    required: true,
+                                    message: '此项为必填项',
+                                }
+                            ]}>
+                                <AddLink />
+                            </Form.Item>
                             <Form.Item hidden={true} >
                                 <ProFormText
                                     label="小程序ID"

+ 1 - 1
src/pages/MiniApp/EntWeChat/Welcome/index.tsx

@@ -129,7 +129,7 @@ const Page: React.FC = () => {
                 arr?.map((item, index) => {
                     return <div key={index} className={styles.box}>
                         <Card
-                            title={"消息" + (index + 1)}
+                            title={"消息" + (index + 1)}//+`${item.mediaType ? "-"+getEnum("MEDIA_TYPE", "map")?.get(item.mediaType):""}`
                             className={styles.card}
                             extra={item.mediaType === 'miniprogram' && <Tooltip
                                 title={

+ 46 - 8
src/pages/MiniApp/Extend/Book/index.tsx

@@ -1,20 +1,36 @@
-import { PageContainer, ProTable } from "@ant-design/pro-components"
+import { ActionType, PageContainer, ProTable } from "@ant-design/pro-components"
 import { columns } from "./tableConfig"
 import { useAjax } from "@/Hook/useAjax"
 import { history, useModel } from "@umijs/max"
 import { Button } from "antd"
 import { PlusCircleOutlined } from "@ant-design/icons"
 import { listOfPage } from "@/services/miniApp/extend"
-
-const Page: React.FC = () => {
+import { useEffect, useRef } from "react"
+type Props = {
+    isModal?: boolean
+    value?: any
+    onChange?: (v: any) => void
+    onClose?: (v: any) => void
+}
+const Page: React.FC<Props> = (props) => {
+    let { isModal, value } = props
     let { initialState } = useModel("@@initialState")
     let getList = useAjax((params) => listOfPage(params), { type: 'table' })
+    let form = useRef<any>()
+    // 当作为组件使用时首次打开存在链接名称以链接名称搜索
+    useEffect(() => {
+        if (value?.linkName && value?.bookName) {
+            form?.current?.setFieldValue("linkName",value?.linkName)
+            form?.current?.submit()
+        }
+    }, [])
     return <PageContainer title={false}
+        header={isModal ? { breadcrumb: {} } : {}}
     >
         <ProTable<any, any>
-            scroll={{ x: true}}
+            scroll={{ x: true }}
             toolBarRender={() => {
-                return [
+                return !isModal ? [
                     <Button
                         type="primary"
                         onClick={() => {
@@ -24,19 +40,41 @@ const Page: React.FC = () => {
                         <PlusCircleOutlined />
                         新建作品链接
                     </Button>,
-                ];
+                ] : [];
             }}
             params={{
                 appId: initialState?.selectApp?.id || "",
                 appType: initialState?.selectApp?.appType || "",
-                linkType:1,
+                linkType: 1,
             }}
             headerTitle={"作品推广列表"}
             rowKey={(r) => r.linkId}
+            //多选
+            rowSelection={!!props?.onChange ? {
+                hideSelectAll: true,
+                type: 'radio',
+                selectedRowKeys: [props?.value?.linkId || props?.value],
+                onSelect: (record, selected) => {
+                    props?.onChange?.(record)
+                    // props?.onClose?.(false)
+                },
+            } : false}
+            // 点击行
+            onRow={(record) => ({
+                onClick: () => {
+                    props?.onChange?.(record)
+                    props?.onClose?.(false)
+                }
+            })}
+            //多选展示按钮
+            tableAlertOptionRender={false}
+            //多选展示栏
+            tableAlertRender={false}
             search={{
                 labelWidth: 90,
-                span:4
+                span: 4,
             }}
+            formRef={form}
             request={async (params) => {
                 return await getList.run(params)
             }}

+ 44 - 7
src/pages/MiniApp/Extend/Page/index.tsx

@@ -3,33 +3,70 @@ import { columns } from "./tableConfig"
 import { useAjax } from "@/Hook/useAjax"
 import { useModel } from "@umijs/max"
 import PageExtend from "./extend"
-import { useRef } from "react"
+import { useEffect, useRef } from "react"
 import { listOfPage } from "@/services/miniApp/extend"
-
-const Page: React.FC = () => {
+type Props = {
+    isModal?: boolean
+    value?: any
+    onChange?: (v: any) => void
+    onClose?: (v: any) => void
+}
+const Page: React.FC<Props> = (props) => {
+    let { isModal, value } = props
     let { initialState } = useModel("@@initialState")
     const actionRef = useRef<ActionType>();
     let getList = useAjax((params) => listOfPage(params), { type: 'table' })
+    let form = useRef<any>()
+    // 当作为组件使用时首次打开存在链接名称以链接名称搜索
+    useEffect(() => {
+        if (value?.linkName && !value?.bookName) {
+            form?.current?.setFieldValue("linkName", value?.linkName)
+            form?.current?.submit()
+        }
+    }, [])
     return <PageContainer title={false}
+        header={isModal ? { breadcrumb: {} } : {}}
     >
         <ProTable<any, any>
             scroll={{ x: true }}
             actionRef={actionRef}
             toolBarRender={() => {
-                return [
+                return !isModal ? [
                     <PageExtend reload={actionRef?.current?.reload} />,
-                ];
+                ] : [];
             }}
             params={{
                 appId: initialState?.selectApp?.id || "",
                 appType: initialState?.selectApp?.appType || "",
-                linkType:2
+                linkType: 2
             }}
             headerTitle={"页面推广列表"}
             rowKey={(r) => r.linkId}
+            //多选
+            rowSelection={!!props?.onChange ? {
+                hideSelectAll: true,
+                type: 'radio',
+                selectedRowKeys: [props?.value?.linkId || props?.value],
+                onSelect: (record, selected) => {
+                    props?.onChange?.(record)
+                    // props?.onClose?.(false)
+                },
+            } : false}
+            // 点击行
+            onRow={(record) => ({
+                onClick: () => {
+                    props?.onChange?.(record)
+                    props?.onClose?.(false)
+                }
+            })}
+            //多选展示按钮
+            tableAlertOptionRender={false}
+            //多选展示栏
+            tableAlertRender={false}
+            formRef={form}
             search={{
                 labelWidth: 90,
-                span:4
+                span: 4
             }}
             request={async (params) => {
                 return await getList.run(params)

Nem az összes módosított fájl került megjelenítésre, mert túl sok fájl változott