Merge remote-tracking branch 'origin/main' into dev/chat

# Conflicts:
#	package.json
#	src/main.jsx
#	src/stores/ConversationContext.js
#	src/views/Conversations/ChatWindow.jsx
#	src/views/Conversations/Components/ConversationsList.jsx
#	src/views/Conversations/Components/CustomerProfile.jsx
#	src/views/Conversations/Components/InputBox.jsx
#	src/views/Conversations/Components/Messages.jsx
#	src/views/Conversations/ConversationProvider.jsx
#	src/views/Conversations/Conversations.css
#	tailwind.config.js
#	vite.config.js
dev/chat
Lei OT 2 years ago
commit 7c23a75c26

@ -2,23 +2,22 @@ import React from 'react';
import ReactDOM from 'react-dom/client'; import ReactDOM from 'react-dom/client';
import { configure } from 'mobx'; import { configure } from 'mobx';
import { createBrowserRouter, RouterProvider } from 'react-router-dom'; import { createBrowserRouter, RouterProvider } from 'react-router-dom';
import { AuthContext } from '@/stores/AuthContext'; import { AuthContext } from '@/stores/AuthContext'
import { ThemeContext } from '@/stores/ThemeContext'; import { ThemeContext } from '@/stores/ThemeContext'
import ConversationProvider from '@/views/Conversations/ConversationProvider'; import ConversationProvider from '@/views/Conversations/ConversationProvider'
import Auth from '@/stores/Auth'; import Auth from '@/stores/Auth'
import App from '@/views/App'; import App from '@/views/App'
import Standlone from '@/views/Standlone'; import Standlone from '@/views/Standlone'
import OrderFollow from '@/views/OrderFollow'; import OrderFollow from '@/views/OrderFollow'
import ChatHistory from '@/views/ChatHistory'; import ChatHistory from '@/views/ChatHistory'
import SalesManagement from '@/views/SalesManagement'; import SalesManagement from '@/views/SalesManagement'
import DingdingQRCode from '@/views/DingdingQRCode'; import DingdingQRCode from '@/views/DingdingQRCode'
import DingdingCallbak from '@/views/DingdingCallbak'; import AccountProfile from '@/views/AccountProfile'
import AccountProfile from '@/views/AccountProfile'; import ErrorPage from '@/views/ErrorPage'
import ErrorPage from '@/views/ErrorPage';
import Conversations from '@/views/Conversations/ChatWindow'; import Conversations from '@/views/Conversations/ChatWindow'
// import Conversations from '@/views/Conversations/ChatApp'; // import Conversations from '@/views/Conversations/ChatApp'
import '@/assets/index.css'; import '@/assets/index.css'
configure({ configure({
useProxies: 'ifavailable', useProxies: 'ifavailable',
@ -48,7 +47,6 @@ const router = createBrowserRouter([
element: <Standlone />, element: <Standlone />,
children: [ children: [
{ path: 'dingding/qrcode', element: <DingdingQRCode /> }, { path: 'dingding/qrcode', element: <DingdingQRCode /> },
{ path: 'dingding/callback', element: <DingdingCallbak /> },
], ],
}, },
]); ]);

@ -22,10 +22,10 @@ function AccountProfile() {
<Row> <Row>
<Col span={12} offset={6}> <Col span={12} offset={6}>
<Descriptions title='个人信息' layout='vertical' column={2}> <Descriptions title='个人信息' layout='vertical' column={2}>
<Descriptions.Item label='名字'><Space size='middle'><Avatar src={`https://api.dicebear.com/7.x/miniavs/svg?seed=${new Date().toString()}`} />骆梅玉</Space></Descriptions.Item> <Descriptions.Item label='名字'><Space size='middle'><Avatar src={loginUser.avatarUrl} />{loginUser.username}</Space></Descriptions.Item>
<Descriptions.Item label='手机'>+86-15878301602</Descriptions.Item> <Descriptions.Item label='HT 账号'>{loginUser.accountName}</Descriptions.Item>
<Descriptions.Item label='邮件'>christyluo@hainatravel.com</Descriptions.Item> <Descriptions.Item label='手机'>{loginUser.stateCode} {loginUser.mobile}</Descriptions.Item>
<Descriptions.Item label='部门'>海纳业务组-GH事业部群-GH销售</Descriptions.Item> <Descriptions.Item label='邮件'>{loginUser.email}</Descriptions.Item>
</Descriptions> </Descriptions>
</Col> </Col>
</Row> </Row>

@ -15,7 +15,7 @@ import { isEmpty } from '@/utils/commons';
const { Header, Footer, Content } = Layout const { Header, Footer, Content } = Layout
const { Title } = Typography const { Title } = Typography
// SecurityApp // AuthApp
function App() { function App() {
const { colorPrimary, borderRadius } = useThemeContext() const { colorPrimary, borderRadius } = useThemeContext()

@ -1,56 +0,0 @@
import React, { useEffect } from 'react'
import { useParams, useNavigate } from 'react-router-dom'
import { Card, Typography, Flex, Result, Button } from 'antd'
import { useRequest } from 'ahooks'
const { Title } = Typography
const { Meta } = Card
import { useAuthContext } from '@/stores/AuthContext'
function DingdingCallbak() {
const navigate = useNavigate()
const params = new URLSearchParams(window.location.search)
const code = params.get('code')
const state = params.get('state')
console.info('code: ' + code)
console.info('state: ' + state)
const { loginUser, permissionList } = useAuthContext()
console.info('loginUser: ')
console.info(loginUser)
console.info('permissionList: ')
console.info(permissionList)
// const { data, error, loading } = useRequest(() => {
// return new Promise((resolve) => {
// setTimeout(() => {
// resolve('use Request data...');
// }, 1000);
// });
// });
setTimeout(() => {
loginUser.userId = 999
loginUser.openId = '987654321'
navigate('/s/account/profile')
}, 1000);
useEffect(() => {
}, [])
return (
<Flex justify='center' align='center' gap='middle' vertical>
<Title level={4}>正在获取你的权限请稍等</Title>
<Result
status='403'
title='403'
subTitle='你没有绑定钉钉账号,无法登陆。'
/>
</Flex>
)
}
export default DingdingCallbak;

@ -1,15 +1,21 @@
import { Card, Flex, Typography } from 'antd' import { Result, Spin, Flex, Typography } from 'antd'
import React, { useEffect } from 'react' import React, { useEffect, useState } from 'react'
import { useNavigate } from 'react-router-dom'
import { useAuthContext } from '@/stores/AuthContext'
const { Title } = Typography const { Title } = Typography
const { Meta } = Card
// https://open.dingtalk.com/document/orgapp/tutorial-obtaining-user-personal-information#title-qpi-0qv-anm // https://open.dingtalk.com/document/orgapp/tutorial-obtaining-user-personal-information#title-qpi-0qv-anm
function DingdingQRCode() { function DingdingQRCode() {
const navigate = useNavigate()
const { loginUser } = useAuthContext()
const [loginStatus, setLoginStatus] = useState(0)
useEffect(() => { useEffect(() => {
import('https://g.alicdn.com/dingding/h5-dingtalk-login/0.21.0/ddlogin.js').then((module) => { import('https://g.alicdn.com/dingding/h5-dingtalk-login/0.21.0/ddlogin.js').then(() => {
window.DTFrameLogin( window.DTFrameLogin(
{ {
id: 'qrCodeContainer', id: 'qrCodeContainer',
@ -17,34 +23,76 @@ function DingdingQRCode() {
height: 300, height: 300,
}, },
{ {
redirect_uri: encodeURIComponent('https://sales.mycht.cn/dingding/callback'), redirect_uri: encodeURIComponent('https://sales.mycht.cn/p/dingding/callback'),
client_id: 'dingwgdx6emlxr3fcrg8', client_id: 'dingwgdx6emlxr3fcrg8',
scope: 'openid', scope: 'openid',
response_type: 'code', response_type: 'code',
state: 'state1111', state: 'global-sales',
prompt: 'consent', prompt: 'consent',
}, },
(loginResult) => { (loginResult) => {
const { redirectUrl, authCode, state } = loginResult const { authCode } = loginResult
// console.log(loginResult)
window.location.href = redirectUrl setLoginStatus(200)
// 使code
console.log(authCode) fetch(`https://p9axztuwd7x8a7.mycht.cn/dingtalk/dingtalkwork/WhatsAppAuth?authCode=${authCode}`)
.then(response => response.json())
.then(json => {
// {"errcode":0,"errmsg":"ok","result":{"nick":"","unionId":"j7cuUCplZe1ZiiqjQNUUmFwiEiE","avatarUrl":"https://static-legacy.dingtalk.com/media/lALPBDDrhXr716HNAoDNAoA_640_640.png","openId":"iioljiPmZ4RPoOYpkFiSn7IKAiEiE","mobile":"18777396951","stateCode":"86","email":"lyj@hainatravel.com","opisn":"383"}}
if (json.errcode === 0) {
loginUser.userId = json.result.opisn
loginUser.accountName = json.result.opisn
loginUser.username = json.result.nick
loginUser.avatarUrl = json.result.avatarUrl
loginUser.stateCode = json.result.stateCode
loginUser.mobile = json.result.mobile
loginUser.email = json.result.email
loginUser.openId = json.result.openId
navigate('/account/profile')
} else {
setLoginStatus(403)
}
})
}, },
(errorMsg) => { (errorMsg) => {
// setLoginStatus(403)
alert(`Login Error: ${errorMsg}`) console.error(`Login Error: ${errorMsg}`)
}, },
) )
}) })
}, []) }, [])
return ( if (loginStatus === 200) {
<Flex justify='center' align='center' gap='middle' vertical> return (
<Title level={4}>使用钉钉扫码</Title> <Flex justify='center' align='center' gap='middle' vertical>
<div id='qrCodeContainer' style={{ border: '1px solid rgba(5, 5, 5, 0.06)', borderRadius: '8px' }}></div> <Result
</Flex> status='success'
) title='扫码成功'
subTitle='正在获取你的权限'
extra={[
<Spin size='small' />
]}
/>
</Flex>
)
} else if (loginStatus === 403) {
return (
<Flex justify='center' align='center' gap='middle' vertical>
<Result
status='403'
title='403'
subTitle='你没有绑定钉钉账号,无法登陆。'
/>
</Flex>
)
} else {
return (
<Flex justify='center' align='center' gap='middle' vertical>
<Title level={4}>使用钉钉扫码</Title>
<div id='qrCodeContainer' style={{ border: '12px solid rgba(5, 5, 5, 0.06)', borderRadius: '8px' }}></div>
</Flex>
)
}
} }
export default DingdingQRCode export default DingdingQRCode

@ -1,8 +1,8 @@
import { useNavigate } from 'react-router-dom' import { useNavigate } from 'react-router-dom'
import { memo, useMemo, useCallback, useEffect, useState } from 'react' import { memo, useMemo, useCallback, useEffect, useState } from 'react'
import { import {
Row, Col, Divider, Table, Card, Button, Input, Row, Badge, Divider, Table, Card, Button, Input,
Space, Segmented, Radio, Select, Flex, Spin, Form, Switch, DatePicker, List, Avatar Space, Tag, Radio, Select, Flex, Spin, Form, Switch, DatePicker, List, Avatar
} from 'antd' } from 'antd'
import { import {
StarFilled, ZoomInOutlined, StarOutlined, BarsOutlined, AppstoreOutlined, SearchOutlined StarFilled, ZoomInOutlined, StarOutlined, BarsOutlined, AppstoreOutlined, SearchOutlined
@ -83,29 +83,11 @@ const columns = [
title: '订单号', title: '订单号',
dataIndex: 'orderNumber', dataIndex: 'orderNumber',
key: 'orderNumber', key: 'orderNumber',
width: 300, width: 222,
render: (text, record, inde) => { render: (text, record, index) => {
return ( if (index === 1) return <Space size='middle'>{text}<Tag color='red'>重点</Tag></Space>
<Space size='middle'> else if (index === 2) return <Space size='middle'>{text}<Tag color='green'>潜力</Tag></Space>
{text} else return <Space size='middle'>{text}<Tag color='blue'>休眠</Tag></Space>
<Segmented
options={[
{
label: '潜力',
value: '潜力',
},
{
label: '重点',
value: '重点',
},
{
label: '休眠',
value: '休眠',
},
]}
/>
</Space>
)
} }
}, },
{ {
@ -116,6 +98,13 @@ const columns = [
return ( return (
<Space size='middle'> <Space size='middle'>
<a>{text}</a> <a>{text}</a>
<Badge
className="site-badge-count-109"
count={Math.floor(Math.random() * (100 - 2 + 1) + 2)}
style={{
backgroundColor: '#52c41a',
}}
/>
</Space> </Space>
) )
} }
@ -125,42 +114,11 @@ const columns = [
dataIndex: 'orderStatus', dataIndex: 'orderStatus',
key: 'orderStatus', key: 'orderStatus',
width: 120, width: 120,
render: (text, record, inde) => { render: (text, record, index) => {
return ( if (index === 1) return text + '(一催)'
<Select else if (index === 2) return text + '(二催)'
defaultValue='新订单' else if (index === 3) return text + '(三催)'
style={{ else return text
width: 100,
}}
bordered={false}
options={[
{
value: '新订单',
label: '新订单',
},
{
value: '报价中',
label: '报价中',
},
{
value: '丢失',
label: '丢失',
},
{
value: '一催',
label: '一催',
},
{
value: '二催',
label: '二催',
},
{
value: '三催',
label: '三催',
},
]}
/>
)
} }
}, },
{ {
@ -181,11 +139,10 @@ const columns = [
}, },
] ]
const AdvanceSearchForm = memo(function AdvanceSearch({ onSubmit }) { const AdvanceSearchForm = memo(function ({ onSubmit }) {
const [form] = Form.useForm() const [form] = Form.useForm()
console.info('AdvanceSearch: ')
function handleSubmit(values) { function handleSubmit(values) {
onSubmit(values); onSubmit?.(values)
} }
return ( return (
<Form <Form
@ -278,8 +235,6 @@ function OrderFollow() {
const orderListMemo = useMemo(() => <OrderList searchCriteria={searchCriteria} />, [searchCriteria]) const orderListMemo = useMemo(() => <OrderList searchCriteria={searchCriteria} />, [searchCriteria])
const handleSubmit = useCallback((criteria) => { const handleSubmit = useCallback((criteria) => {
console.info('onSubmit.searchCriteria: ')
console.info(criteria)
setSearchCriteria({ setSearchCriteria({
...criteria, ...criteria,
type: 'advance' type: 'advance'
@ -292,7 +247,7 @@ function OrderFollow() {
return ( return (
<> <>
<Space direction='vertical' size='large' style={{ width: '100%' }}> <Space direction='vertical' size='large' style={{ width: '100%' }}>
<Flex gap='large' justify='start' align='center' horizontal> <Flex gap='large' justify='start' align='center' horizontal='true'>
<Radio.Group <Radio.Group
options={[ options={[
{ label: '今日任务', value: 'today' }, { label: '今日任务', value: 'today' },
@ -327,13 +282,11 @@ function OrderFollow() {
) )
function OrderList({ searchCriteria }) { function OrderList({ searchCriteria }) {
const countryList = useGetJson(`https://p9axztuwd7x8a7.mycht.cn/service-InfoSys/InfoSys/GetCountryList`)
console.info(countryList)
console.info('OrderList.searchCriteria: ')
console.info(searchCriteria) console.info(searchCriteria)
const countryList = useGetJson(`https://p9axztuwd7x8a7.mycht.cn/service-InfoSys/InfoSys/GetCountryList`)
return ( return (
<Table loading={countryList === null} dataSource={dataSource} columns={columns} /> <Table loading={countryList === null} dataSource={countryList === null ? null : dataSource} columns={columns} />
) )
} }
} }

@ -1,17 +1,16 @@
import { useNavigate } from 'react-router-dom' import { useNavigate } from 'react-router-dom'
import { useRef, useEffect, useState } from 'react' import { memo, useRef, useCallback, useEffect, useState } from 'react'
import { Row, Col, Divider, Table , Card, Button, Input, import {
Space, Empty, Radio, Select, DatePicker, Spin, List, Avatar Row, Col, Divider, Table, Card, Button, Input,
Space, Segmented, Radio, Select, Flex, Spin, Form, Switch, DatePicker, List, Avatar
} from 'antd' } from 'antd'
import { import {
StarFilled, ZoomInOutlined, StarOutlined, SearchOutlined, ImportOutlined StarFilled, ZoomInOutlined, StarOutlined, SearchOutlined, ImportOutlined
} from '@ant-design/icons' } from '@ant-design/icons'
import {useFormInput} from '@/hooks/useFormInput'
import {useGetJson} from '@/hooks/userFetch' import {useGetJson} from '@/hooks/userFetch'
const { Search } = Input; const { RangePicker } = DatePicker
const { RangePicker } = DatePicker;
const dataSource = [ const dataSource = [
{ {
@ -72,70 +71,155 @@ const dataSource = [
}, },
]; ];
const columns = [ const AdvanceSearchForm = memo(function ({ onSubmit }) {
{ console.info('AdvanceSearchForm')
title: '顾问', const [form] = Form.useForm()
dataIndex: 'travelAdvisor', function handleSubmit(values) {
key: 'travelAdvisor', onSubmit?.(values)
}, }
{ return (
title: '客人名单', <Form
dataIndex: 'contactList', layout={'inline'}
key: 'contactList', form={form}
}, initialValues={{ orderLabel: '全部' }}
{ onFinish={handleSubmit}
title: '重点订单', style={{
dataIndex: 'important', maxWidth: 'none',
key: 'important', }}
}, >
{ <Form.Item label='标签' name='orderLabel'>
title: '潜力客户', <Select
dataIndex: 'star', style={{
key: 'star', width: 100,
}, }}
{ options={[
title: '成型', {
dataIndex: 'ing', value: '潜力',
key: 'ing', label: '潜力',
}, },
]; {
value: '重点',
label: '重点',
},
{
value: '休眠',
label: '休眠',
}
]}
/>
</Form.Item>
<Form.Item label='状态' name='orderStatus'>
<Select
style={{
width: 100,
}}
options={[
{
value: '新订单',
label: '新订单',
},
{
value: '报价中',
label: '报价中',
},
{
value: '丢失',
label: '丢失',
},
{
value: '一催',
label: '一催',
},
{
value: '二催',
label: '二催',
},
{
value: '三催',
label: '三催',
},
]}
/>
</Form.Item>
<Form.Item label='订单号' name='orderNumber'>
<Input placeholder='订单号' allowClear />
</Form.Item>
<Form.Item label='出发日期' name='startDate'>
<RangePicker />
</Form.Item>
<Form.Item >
<Button type='primary' htmlType='submit'>搜索</Button>
</Form.Item>
</Form>
)
})
function SalesManagement() { const SalesTable = function({formValues}) {
console.info('SalesTable')
console.info(formValues)
const keywordProps = useFormInput('') const isMounted = useRef(false)
const countryList = useGetJson(`https://p9axztuwd7x8a7.mycht.cn/service-InfoSys/InfoSys/GetCountryList`)
console.info(countryList) const [salesData, setSalesData] = useState([])
const [loading, setLoading] = useState(false)
useEffect(() => { useEffect(() => {
console.info('SalesTable.useEffect')
console.info(isMounted)
setLoading(true)
// setSalesData([]) //
setTimeout(() => {
const randomData = dataSource.map(data => {
data.important = Math.floor(Math.random() * (100 - 2 + 1) + 2)
return data
})
setSalesData([...randomData])
setLoading(false)
}, 1000)
isMounted.current = true
}, [formValues])
const columns = [
{
title: '顾问',
dataIndex: 'travelAdvisor',
key: 'travelAdvisor',
},
{
title: '重点订单',
dataIndex: 'important',
key: 'important',
},
{
title: '潜力客户',
dataIndex: 'star',
key: 'star',
},
{
title: '成型',
dataIndex: 'ing',
key: 'ing',
},
]
return (
<Table dataSource={salesData} loading={loading} columns={columns} />
)
}
function SalesManagement() {
const [formValues, setFormValues] = useState({})
const handleSubmit = useCallback((values) => {
setFormValues({...values})
}, []) }, [])
return ( return (
<Spin spinning={false} delay={500}> <>
<Space direction='vertical' style={{ width: '100%' }}> <AdvanceSearchForm onSubmit={handleSubmit} />
<Row gutter={[16, 16]} justify='start' align='middle'>
<Col span={6}>
<RangePicker />
</Col>
<Col span={6}>
<Input placeholder='关键词' {...keywordProps} />
</Col>
<Col span={6}>
<Button icon={<ImportOutlined />} onClick={() => {console.info(keywordProps.value)}}>OK</Button>
<Button icon={<ImportOutlined />}>导入联系人</Button>
</Col>
</Row>
</Space>
<Divider plain orientation='left'></Divider> <Divider plain orientation='left'></Divider>
<Space <SalesTable formValues={formValues} />
direction='vertical' </>
size='middle'
style={{
display: 'flex',
}}
>
<Table dataSource={dataSource} columns={columns} />
</Space>
</Spin>
) )
} }

@ -17,29 +17,18 @@ export default defineConfig({
sourcemap: true, sourcemap: true,
manifest: true, manifest: true,
chunkSizeWarningLimit: 555, chunkSizeWarningLimit: 555,
rollupOptions: { // rollupOptions: {
output: { // output: {
manualChunks(id) { // manualChunks(id) {
// if (id.includes('antd')) { // if (id.includes('node_modules')) {
// console.info('chunk: ' + id) // return id.toString().split('node_modules/')[1].split('/')[0].toString();
// return 'antd-component' // }
// } else if (id.includes('rc-')) { // },
// return 'rc-component' // chunkFileNames: (chunkInfo) => {
// } else if (id.includes('ant-design')) { // const facadeModuleId = chunkInfo.facadeModuleId ? chunkInfo.facadeModuleId.split('/') : [];
// return 'ant-design' // return `assets/[name].[hash].js`;
// } else { // }
// // console.info('chunk: ' + id) // }
// } // }
if (id.includes('node_modules')) {
return id.toString().split('node_modules/')[1].split('/')[0].toString();
}
},
chunkFileNames: (chunkInfo) => {
const facadeModuleId = chunkInfo.facadeModuleId ? chunkInfo.facadeModuleId.split('/') : [];
// const fileName = facadeModuleId[facadeModuleId.length - 2] || '[name]';
return `assets/[name].[hash].js`;
}
}
}
} }
}) })

Loading…
Cancel
Save