feat: 使用window.getSelection().addRange(range) 方式复制 HTML;测试设置商业号身份

2.0/email-builder
Jimmy Liow 8 months ago committed by Lei OT
parent 52b62f5b1f
commit 7a0bcf8f81

@ -1,6 +1,6 @@
# Global sales # Global sales
销售平台 2.0 销售平台通过邮件、WhatsApp Business Account、WhatsApp 和客人沟通。
## 开发设置 ## 开发设置
@ -10,8 +10,15 @@
2. 运行开发环境dev.bat 2. 运行开发环境dev.bat
3. 打包代码build.bat 3. 打包代码build.bat
## 版本设置 ## 版本设置
遵循 [Semantic Versioning 2.0.0](http://semver.org/lang/zh-CN/) 语义化版本规范。
修订版本号:日常 bugfix 更新。(如果有紧急的 bugfix则任何时候都可发布
次版本号:有新特性的向下兼容的版本。
主版本号:含有破坏性更新和新特性。
npm version [<newversion> | major | minor | patch | premajor | preminor | prepatch | prerelease | from-git] npm version [<newversion> | major | minor | patch | premajor | preminor | prepatch | prerelease | from-git]
npm version premajor --no-git-tag-version npm version premajor --no-git-tag-version

@ -1,6 +1,7 @@
import { create } from 'zustand' import { create } from 'zustand'
import { fetchJSON } from '@/utils/request' import { fetchJSON, postForm } from '@/utils/request'
import { isEmpty, isNotEmpty } from '@/utils/commons' import { isEmpty, isNotEmpty } from '@/utils/commons'
import { API_HOST } from '@/config'
export const PERM_MERGE_CONVERSATION = 'merge-conversation' export const PERM_MERGE_CONVERSATION = 'merge-conversation'
export const PERM_ASSIGN_NEW_CONVERSATION = 'assign-new-conversation' export const PERM_ASSIGN_NEW_CONVERSATION = 'assign-new-conversation'
@ -142,6 +143,21 @@ const useAuthStore = create((set, get) => ({
navigator.clipboard.writeText(sessionData) navigator.clipboard.writeText(sessionData)
} }
}, },
setWhatsAppBusiness: async (userId, whatsAppBusiness) => {
const postWABAUrl = `${API_HOST}/v2/set_whatsapp_info`
const params = {opi_sn: userId, whatsapp_waba: whatsAppBusiness};
return fetchJSON(postWABAUrl, params)
.then(json => {
if (json.errcode === 0) {
console.info(json)
} else {
throw new Error(json?.errmsg + ': ' + json.errcode)
}
})
},
})) }))
export default useAuthStore export default useAuthStore

@ -3,7 +3,7 @@ import { Conditional } from '@/components/Conditional'
import { isNotEmpty } from '@/utils/commons' import { isNotEmpty } from '@/utils/commons'
const HtmlPreview = (props) => { const HtmlPreview = (props) => {
const { loading = false, value, onEdit, onCopy, onDelete } = props const { loading = false, value, onEdit, onCopied, onDelete } = props
if (loading) { if (loading) {
return <Skeleton className='p-6' active /> return <Skeleton className='p-6' active />
@ -14,7 +14,7 @@ const HtmlPreview = (props) => {
<Conditional <Conditional
condition={isNotEmpty(value)} condition={isNotEmpty(value)}
whenTrue={ whenTrue={
<pre className='whitespace-pre-wrap break-words ps-6 pe-6' dangerouslySetInnerHTML={{__html:value}}> <pre id='__preHtml__' className='whitespace-pre-wrap break-words ps-6 pe-6' dangerouslySetInnerHTML={{__html: value}}>
</pre> </pre>
} }
whenFalse={<Empty className='p-6' description={false} />} whenFalse={<Empty className='p-6' description={false} />}
@ -27,7 +27,13 @@ const HtmlPreview = (props) => {
</Button> </Button>
<Button <Button
onClick={() => { onClick={() => {
navigator.clipboard.writeText(value) const range = document.createRange()
range.selectNode(document.getElementById('__preHtml__'))
window.getSelection().removeAllRanges()
window.getSelection().addRange(range)
document.execCommand('copy')
// window.getSelection().removeAllRanges()
onCopied()
}}> }}>
复制 复制
</Button> </Button>

@ -1,33 +1,10 @@
import { useEffect } from 'react' import { useEffect } from 'react'
import { import { Row, Col, Space, Descriptions, Avatar, Tag, Divider, List, Alert, Button, Flex, Select, Spin, Form, Typography, QRCode, Tooltip } from 'antd'
Row, import { UserOutlined, InfoCircleOutlined, CloseCircleFilled, ReloadOutlined, CheckCircleFilled } from '@ant-design/icons'
Col,
Space,
Descriptions,
Avatar,
Tag,
Divider,
List,
Alert,
Button,
Flex,
Select,
Spin,
Form,
Typography,
QRCode,
} from 'antd'
import {
UserOutlined,
InfoCircleOutlined,
CloseCircleFilled,
ReloadOutlined,
CheckCircleFilled,
} from '@ant-design/icons'
import useAuthStore from '@/stores/AuthStore' import useAuthStore from '@/stores/AuthStore'
function Profile() { function Profile() {
const loginUser = useAuthStore((state) => state.loginUser) const [loginUser, setWhatsAppBusiness] = useAuthStore((state) => [state.loginUser, state.setWhatsAppBusiness])
useEffect(() => { useEffect(() => {
console.info(loginUser) console.info(loginUser)
@ -41,12 +18,12 @@ function Profile() {
case 'expired': case 'expired':
return ( return (
<div> <div>
<CloseCircleFilled <CheckCircleFilled
style={{ style={{
color: 'red', color: '#52c41a',
}} }}
/>{' '} />{' '}
{info.locale?.expired} 8618754124786 已登录
<p> <p>
<Button type='link' onClick={info.onRefresh}> <Button type='link' onClick={info.onRefresh}>
<ReloadOutlined /> {info.locale?.refresh} <ReloadOutlined /> {info.locale?.refresh}
@ -84,35 +61,34 @@ function Profile() {
<Descriptions title='个人资料' layout='vertical' column={1}> <Descriptions title='个人资料' layout='vertical' column={1}>
<Descriptions.Item label='名字'> <Descriptions.Item label='名字'>
<Space size='middle'> <Space size='middle'>
<Avatar src={loginUser.avatarUrl}> <Avatar src={loginUser.avatarUrl}>{loginUser.username.substring(1)}</Avatar>
{loginUser.username.substring(1)}
</Avatar>
{loginUser.username} {loginUser.username}
</Space> </Space>
</Descriptions.Item> </Descriptions.Item>
<Descriptions.Item label='HT 账号'> <Descriptions.Item label='HT 账号'>
{loginUser.accountList?.map((a) => { {loginUser.accountList?.map((a) => {
return ( return (
<Tag <Tag key={a.OPI_Code} icon={<UserOutlined />} bordered={false}>
key={a.OPI_Code}
icon={<UserOutlined />}
bordered={false}>
{a.OPI_Code} {a.OPI_Code}
</Tag> </Tag>
) )
})} })}
</Descriptions.Item> </Descriptions.Item>
<Descriptions.Item label='商业号身份'> <Descriptions.Item
label={
<Space>
<Tooltip title='你所属的业务部门'>
<InfoCircleOutlined />
</Tooltip>
<span>商业号身份</span>
</Space>
}>
<Form <Form
layout='vertical' layout='vertical'
// form={form} // form={form}
> >
<Form.Item <Form.Item>
tooltip={{
title: '你所属的业务部门',
icon: <InfoCircleOutlined />,
}}>
<Select <Select
defaultValue='8617607730395' defaultValue='8617607730395'
options={[ options={[
@ -147,7 +123,13 @@ function Profile() {
/> />
</Form.Item> </Form.Item>
<Form.Item> <Form.Item>
<Button type='primary'>保存</Button> <Button
type='primary'
onClick={() => {
setWhatsAppBusiness(383, 8617607730395)
}}>
保存
</Button>
</Form.Item> </Form.Item>
</Form> </Form>
</Descriptions.Item> </Descriptions.Item>
@ -158,9 +140,7 @@ function Profile() {
header={<div>邮箱</div>} header={<div>邮箱</div>}
dataSource={loginUser.emailList} dataSource={loginUser.emailList}
renderItem={(item) => { renderItem={(item) => {
const isDefault = item.default ? ( const isDefault = item.default ? <Tag color='green'>默认</Tag> : null
<Tag color='green'>默认</Tag>
) : null
const isBackup = item.backup ? <Tag color='cyan'>备用</Tag> : null const isBackup = item.backup ? <Tag color='cyan'>备用</Tag> : null
return ( return (
<List.Item> <List.Item>
@ -180,22 +160,15 @@ function Profile() {
<Typography.Paragraph> <Typography.Paragraph>
<ul> <ul>
<li>在手机上打开 WhatsApp</li> <li>在手机上打开 WhatsApp</li>
<li>点击已关联的设备然后点击关联新设备</li> <li>点击已关联的设备点击关联新设备</li>
<li>将手机对准屏幕扫描二维码</li> <li>将手机对准屏幕扫描二维码</li>
</ul> </ul>
</Typography.Paragraph> </Typography.Paragraph>
</Typography> </Typography>
</Col> </Col>
<Col span={6}> <Col span={6}>
<Flex gap='middle' vertical justify={'center'} align={'center'}> <Flex gap='middle' vertical justify={'center'} align={'center'}>
<QRCode <QRCode size={264} value={'https://web.whatsapp.com/'} status='expired' statusRender={customStatusRender} />
size={264}
value={'https://web.whatsapp.com/'}
status='scanned'
statusRender={customStatusRender}
/>
<Alert message='WhatsApp8618754124786' type='success' showIcon />
<Button type='primary'>退出</Button>
</Flex> </Flex>
</Col> </Col>
</Row> </Row>

@ -11,15 +11,19 @@ import {
Modal, Modal,
Select, Select,
Divider, Divider,
Empty, message,
} from 'antd' } from 'antd'
import { useState, useRef, useEffect } from 'react' import { useState, useRef, useEffect } from 'react'
import LexicalEditorInput from './LexicalEditorInput' import LexicalEditorInput from './LexicalEditorInput'
import HtmlPreview from './HtmlPreview' import HtmlPreview from './HtmlPreview'
import useSnippetStore from '@/stores/SnippetStore' import useSnippetStore from '@/stores/SnippetStore'
import LexicalEditor from '@/components/LexicalEditor'
import { isEmpty, isNotEmpty } from '@/utils/commons' import { isEmpty, isNotEmpty } from '@/utils/commons'
function SnippetList() { function SnippetList() {
const [messageApi, contextHolder] = message.useMessage()
const [searchform] = Form.useForm() const [searchform] = Form.useForm()
const [snippetForm] = Form.useForm() const [snippetForm] = Form.useForm()
@ -203,11 +207,12 @@ function SnippetList() {
<Col span={16}> <Col span={16}>
<HtmlPreview value={editorContent} loading={isHtmlLoading} <HtmlPreview value={editorContent} loading={isHtmlLoading}
onEdit={() => console.info('onEdit')} onEdit={() => console.info('onEdit')}
onCopy={() => console.info('onCopy')} onCopied={() => messageApi.success('已复制')}
onDelete={() => console.info('onDelete')} /> onDelete={() => console.info('onDelete')} />
</Col> </Col>
</Row> </Row>
</Space> </Space>
{contextHolder}
</> </>
) )
} }

Loading…
Cancel
Save