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

@ -22,10 +22,10 @@ function AccountProfile() {
<Row>
<Col span={12} offset={6}>
<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='手机'>+86-15878301602</Descriptions.Item>
<Descriptions.Item label='邮件'>christyluo@hainatravel.com</Descriptions.Item>
<Descriptions.Item label='部门'>海纳业务组-GH事业部群-GH销售</Descriptions.Item>
<Descriptions.Item label='名字'><Space size='middle'><Avatar src={loginUser.avatarUrl} />{loginUser.username}</Space></Descriptions.Item>
<Descriptions.Item label='HT 账号'>{loginUser.accountName}</Descriptions.Item>
<Descriptions.Item label='手机'>{loginUser.stateCode} {loginUser.mobile}</Descriptions.Item>
<Descriptions.Item label='邮件'>{loginUser.email}</Descriptions.Item>
</Descriptions>
</Col>
</Row>

@ -15,7 +15,7 @@ import { isEmpty } from '@/utils/commons';
const { Header, Footer, Content } = Layout
const { Title } = Typography
// SecurityApp
// AuthApp
function App() {
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 React, { useEffect } from 'react'
import { Result, Spin, Flex, Typography } from 'antd'
import React, { useEffect, useState } from 'react'
import { useNavigate } from 'react-router-dom'
import { useAuthContext } from '@/stores/AuthContext'
const { Title } = Typography
const { Meta } = Card
// https://open.dingtalk.com/document/orgapp/tutorial-obtaining-user-personal-information#title-qpi-0qv-anm
function DingdingQRCode() {
const navigate = useNavigate()
const { loginUser } = useAuthContext()
const [loginStatus, setLoginStatus] = useState(0)
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(
{
id: 'qrCodeContainer',
@ -17,34 +23,76 @@ function DingdingQRCode() {
height: 300,
},
{
redirect_uri: encodeURIComponent('https://sales.mycht.cn/dingding/callback'),
redirect_uri: encodeURIComponent('https://sales.mycht.cn/p/dingding/callback'),
client_id: 'dingwgdx6emlxr3fcrg8',
scope: 'openid',
response_type: 'code',
state: 'state1111',
state: 'global-sales',
prompt: 'consent',
},
(loginResult) => {
const { redirectUrl, authCode, state } = loginResult
//
window.location.href = redirectUrl
// 使code
console.log(authCode)
const { authCode } = loginResult
console.log(loginResult)
setLoginStatus(200)
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) => {
//
alert(`Login Error: ${errorMsg}`)
setLoginStatus(403)
console.error(`Login Error: ${errorMsg}`)
},
)
})
}, [])
if (loginStatus === 200) {
return (
<Flex justify='center' align='center' gap='middle' vertical>
<Result
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: '1px solid rgba(5, 5, 5, 0.06)', borderRadius: '8px' }}></div>
<div id='qrCodeContainer' style={{ border: '12px solid rgba(5, 5, 5, 0.06)', borderRadius: '8px' }}></div>
</Flex>
)
}
}
export default DingdingQRCode

@ -1,8 +1,8 @@
import { useNavigate } from 'react-router-dom'
import { memo, useMemo, useCallback, useEffect, useState } from 'react'
import {
Row, Col, Divider, Table, Card, Button, Input,
Space, Segmented, Radio, Select, Flex, Spin, Form, Switch, DatePicker, List, Avatar
Row, Badge, Divider, Table, Card, Button, Input,
Space, Tag, Radio, Select, Flex, Spin, Form, Switch, DatePicker, List, Avatar
} from 'antd'
import {
StarFilled, ZoomInOutlined, StarOutlined, BarsOutlined, AppstoreOutlined, SearchOutlined
@ -83,29 +83,11 @@ const columns = [
title: '订单号',
dataIndex: 'orderNumber',
key: 'orderNumber',
width: 300,
render: (text, record, inde) => {
return (
<Space size='middle'>
{text}
<Segmented
options={[
{
label: '潜力',
value: '潜力',
},
{
label: '重点',
value: '重点',
},
{
label: '休眠',
value: '休眠',
},
]}
/>
</Space>
)
width: 222,
render: (text, record, index) => {
if (index === 1) return <Space size='middle'>{text}<Tag color='red'>重点</Tag></Space>
else if (index === 2) return <Space size='middle'>{text}<Tag color='green'>潜力</Tag></Space>
else return <Space size='middle'>{text}<Tag color='blue'>休眠</Tag></Space>
}
},
{
@ -116,6 +98,13 @@ const columns = [
return (
<Space size='middle'>
<a>{text}</a>
<Badge
className="site-badge-count-109"
count={Math.floor(Math.random() * (100 - 2 + 1) + 2)}
style={{
backgroundColor: '#52c41a',
}}
/>
</Space>
)
}
@ -125,42 +114,11 @@ const columns = [
dataIndex: 'orderStatus',
key: 'orderStatus',
width: 120,
render: (text, record, inde) => {
return (
<Select
defaultValue='新订单'
style={{
width: 100,
}}
bordered={false}
options={[
{
value: '新订单',
label: '新订单',
},
{
value: '报价中',
label: '报价中',
},
{
value: '丢失',
label: '丢失',
},
{
value: '一催',
label: '一催',
},
{
value: '二催',
label: '二催',
},
{
value: '三催',
label: '三催',
},
]}
/>
)
render: (text, record, index) => {
if (index === 1) return text + '(一催)'
else if (index === 2) return text + '(二催)'
else if (index === 3) return text + '(三催)'
else return text
}
},
{
@ -181,11 +139,10 @@ const columns = [
},
]
const AdvanceSearchForm = memo(function AdvanceSearch({ onSubmit }) {
const AdvanceSearchForm = memo(function ({ onSubmit }) {
const [form] = Form.useForm()
console.info('AdvanceSearch: ')
function handleSubmit(values) {
onSubmit(values);
onSubmit?.(values)
}
return (
<Form
@ -278,8 +235,6 @@ function OrderFollow() {
const orderListMemo = useMemo(() => <OrderList searchCriteria={searchCriteria} />, [searchCriteria])
const handleSubmit = useCallback((criteria) => {
console.info('onSubmit.searchCriteria: ')
console.info(criteria)
setSearchCriteria({
...criteria,
type: 'advance'
@ -292,7 +247,7 @@ function OrderFollow() {
return (
<>
<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
options={[
{ label: '今日任务', value: 'today' },
@ -327,13 +282,11 @@ function OrderFollow() {
)
function OrderList({ searchCriteria }) {
const countryList = useGetJson(`https://p9axztuwd7x8a7.mycht.cn/service-InfoSys/InfoSys/GetCountryList`)
console.info(countryList)
console.info('OrderList.searchCriteria: ')
console.info(searchCriteria)
const countryList = useGetJson(`https://p9axztuwd7x8a7.mycht.cn/service-InfoSys/InfoSys/GetCountryList`)
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 { useRef, useEffect, useState } from 'react'
import { Row, Col, Divider, Table , Card, Button, Input,
Space, Empty, Radio, Select, DatePicker, Spin, List, Avatar
import { memo, useRef, useCallback, useEffect, useState } from 'react'
import {
Row, Col, Divider, Table, Card, Button, Input,
Space, Segmented, Radio, Select, Flex, Spin, Form, Switch, DatePicker, List, Avatar
} from 'antd'
import {
StarFilled, ZoomInOutlined, StarOutlined, SearchOutlined, ImportOutlined
} from '@ant-design/icons'
import {useFormInput} from '@/hooks/useFormInput'
import {useGetJson} from '@/hooks/userFetch'
const { Search } = Input;
const { RangePicker } = DatePicker;
const { RangePicker } = DatePicker
const dataSource = [
{
@ -72,17 +71,120 @@ const dataSource = [
},
];
const columns = [
const AdvanceSearchForm = memo(function ({ onSubmit }) {
console.info('AdvanceSearchForm')
const [form] = Form.useForm()
function handleSubmit(values) {
onSubmit?.(values)
}
return (
<Form
layout={'inline'}
form={form}
initialValues={{ orderLabel: '全部' }}
onFinish={handleSubmit}
style={{
maxWidth: 'none',
}}
>
<Form.Item label='标签' name='orderLabel'>
<Select
style={{
width: 100,
}}
options={[
{
value: '潜力',
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>
)
})
const SalesTable = function({formValues}) {
console.info('SalesTable')
console.info(formValues)
const isMounted = useRef(false)
const [salesData, setSalesData] = useState([])
const [loading, setLoading] = useState(false)
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: 'contactList',
key: 'contactList',
},
{
title: '重点订单',
dataIndex: 'important',
@ -98,44 +200,26 @@ const columns = [
dataIndex: 'ing',
key: 'ing',
},
];
]
return (
<Table dataSource={salesData} loading={loading} columns={columns} />
)
}
function SalesManagement() {
const keywordProps = useFormInput('')
const countryList = useGetJson(`https://p9axztuwd7x8a7.mycht.cn/service-InfoSys/InfoSys/GetCountryList`)
console.info(countryList)
const [formValues, setFormValues] = useState({})
useEffect(() => {
const handleSubmit = useCallback((values) => {
setFormValues({...values})
}, [])
return (
<Spin spinning={false} delay={500}>
<Space direction='vertical' style={{ width: '100%' }}>
<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>
<>
<AdvanceSearchForm onSubmit={handleSubmit} />
<Divider plain orientation='left'></Divider>
<Space
direction='vertical'
size='middle'
style={{
display: 'flex',
}}
>
<Table dataSource={dataSource} columns={columns} />
</Space>
</Spin>
<SalesTable formValues={formValues} />
</>
)
}

@ -17,29 +17,18 @@ export default defineConfig({
sourcemap: true,
manifest: true,
chunkSizeWarningLimit: 555,
rollupOptions: {
output: {
manualChunks(id) {
// if (id.includes('antd')) {
// console.info('chunk: ' + id)
// return 'antd-component'
// } else if (id.includes('rc-')) {
// return 'rc-component'
// } else if (id.includes('ant-design')) {
// return 'ant-design'
// } else {
// // console.info('chunk: ' + id)
// rollupOptions: {
// output: {
// manualChunks(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('/') : [];
// return `assets/[name].[hash].js`;
// }
// }
// }
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