Merge branch 'main' into dev/chat

dev/chat
Lei OT 2 years ago
commit ca20cfb1a2

@ -10,6 +10,7 @@
"preview": "vite preview"
},
"dependencies": {
"ahooks": "^3.7.8",
"antd": "^5.12.8",
"mobx": "^6.12.0",
"mobx-react": "^9.1.0",

@ -59,12 +59,12 @@ const router = createBrowserRouter([
const rootStore = new RootStore();
ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode>
// <React.StrictMode>
<StoreContext.Provider value={rootStore}>
<RouterProvider
router={router}
fallbackElement={() => <div>Loading...</div>}
/>
</StoreContext.Provider>
</React.StrictMode>
// </React.StrictMode>
)

@ -1,7 +1,6 @@
import { makeAutoObservable, runInAction } from 'mobx'
const KEY_USER_ID = 'KEY_USER_ID'
const KEY_WEBSITE = 'KEY_WEBSITE'
class Auth {
@ -9,22 +8,17 @@ class Auth {
makeAutoObservable(this, { rootStore: false })
this.root = root
this.userId = root.getSession(KEY_USER_ID)
this.website = root.getSession(KEY_WEBSITE)
}
setup(userId, website) {
setup(userId) {
runInAction(() => {
this.userId = userId
this.website = website
});
})
this.root.putSession(KEY_USER_ID, userId)
this.root.putSession(KEY_WEBSITE, website)
}
// HT 账号 SN
// HT 账号 OP_SN
userId = null
// 从哪个网站打开HTML 代码应用到哪个网站
website = ''
}
export default Auth

@ -73,13 +73,6 @@ class Order {
})
}
getKeywordHistory() {
const keywordListText = this.root.getLocal('KEYWORD_LIST')
const keywordList = isEmpty(keywordListText) ? [] : JSON.parse(keywordListText)
return keywordList.map(keyword => {
return {value: keyword}
})
}
fetchCityList(countryId) {
const fetchCityUrl =
@ -103,238 +96,9 @@ class Order {
})
}
fetchCropImageList(userId) {
const formData = new FormData()
formData.append('PII_SN', this.selectedImage.PII_SN)
formData.append('user_id', userId)
const postUrl = 'https://p9axztuwd7x8a7.mycht.cn/service-InfoSysSOA/search_cutimage'
return postForm(postUrl, formData)
.then(json => {
if (json.errcode == 0) {
runInAction(() => {
this.croppedImageList = json.result
})
} else {
throw new Error(json.errmsg + ': ' + json.errcode)
}
})
}
toggleFavorite(image, userId) {
runInAction(() => {
image.isFavorite = !image.isFavorite
})
const formData = new FormData()
formData.append('PII_SN', image.PII_SN)
formData.append('user_id', userId)
formData.append('addordelete', image.isFavorite ? 1: 0)
const postUrl = 'https://p9axztuwd7x8a7.mycht.cn/service-InfoSysSOA/favorite_image'
return postForm(postUrl, formData)
.then(json => {
if (json.errcode == 0) {
console.info(json)
// json.result.PII_SN
} else {
throw new Error(json.errmsg + ': ' + json.errcode)
}
})
}
fetchImageList(country, city, keyword, tags, star, type, userId) {
runInAction(() => {
this.imageSearchList = []
})
if (isNotEmpty(keyword)) {
const keywordListText = this.root.getLocal('KEYWORD_LIST')
const keywordList = isEmpty(keywordListText) ? [] : JSON.parse(keywordListText)
if (keywordList.indexOf(keyword) == -1) {
keywordList.unshift(keyword)
}
if (keywordList.length > 8) {
keywordList.splice(8, keywordList.length)
}
this.root.putLocal('KEYWORD_LIST', JSON.stringify(keywordList))
}
const formData = new FormData()
formData.append('CountrySN', country)
formData.append('citySN', city)
formData.append('keyword', keyword)
formData.append('tags', tags)
formData.append('star', star)
formData.append('searchType', type)
formData.append('user_id', userId)
const postUrl = 'https://p9axztuwd7x8a7.mycht.cn/service-InfoSysSOA/search_image'
return postForm(postUrl, formData)
.then(json => {
if (json.errcode == 0) {
runInAction(() => {
this.imageSearchList = json.result
})
} else {
throw new Error(json.errmsg + ': ' + json.errcode)
}
})
}
startUpload(userId) {
for (let index = 0; index < this.imageUploadList.length; index++) {
const element = this.imageUploadList[index];
this.requiredAlert =
(element.country === null) ||
(element.city === null) ||
(element.description_zh === '') ||
(element.description_en === '') ||
(element.photographer === '') ||
(element.copyright === -1) ||
(element.star === -1)
if (this.requiredAlert) break
}
if (this.requiredAlert) return
let successCount = 0
this.uploadPercent = 0
const imageCount = this.imageUploadList.length
const postUrl = 'https://p9axztuwd7x8a7.mycht.cn/service-InfoSysSOA/upload_image'
this.imageUploadList.forEach((element, index) => {
const formData = new FormData()
formData.append('image_file', element.image_file)
formData.append('image_uid', element.image_uid)
formData.append('country', element.country)
formData.append('city', element.city)
formData.append('description_zh', element.description_zh)
formData.append('description_en', element.description_en)
formData.append('photographer', element.photographer)
formData.append('copyright', element.copyright)
formData.append('labels', element.labelValues.join(','))
formData.append('user_id', userId)
formData.append('star', element.star)
return postForm(postUrl, formData)
.then(json => {
successCount++
runInAction(() => {
this.uploadPercent = parseInt(successCount / imageCount * 100)
})
if (json.errcode == 0) {
runInAction(() => {
for (var i = this.imageUploadList.length - 1; i >= 0; i--) {
const current = this.imageUploadList[i]
if (current.image_uid === json.result.image_uid) {
current.success = true
current.image_url = 'https://p9axztuwd7x8a7.mycht.cn/service-fileServer' + json.result.image_path
break
}
}
})
} else {
throw new Error(json.errmsg + ': ' + json.errcode)
}
})
})
}
startCrop(cropData, resizeWidth, userId, website) {
const formData = new FormData()
formData.append('filename', this.selectedImage.PII_Location + this.selectedImage.PII_FileName)
formData.append('x', cropData.x)
formData.append('y', cropData.y)
formData.append('width', Math.trunc(cropData.width))
formData.append('height', Math.trunc(cropData.height))
formData.append('resize_width', resizeWidth)
formData.append('user_id', userId)
formData.append('webcode', website)
const postUrl = 'https://p9axztuwd7x8a7.mycht.cn/service-InfoSysSOA/crop_image'
return postForm(postUrl, formData)
.then(json => {
if (json.errcode == 0) {
this.fetchCropImageList()
const imageHtml = this.generateHtml(json.result, website)
const cropResult = {
imageHtml: imageHtml
}
return cropResult
} else {
throw new Error(json.errmsg + ': ' + json.errcode)
}
})
}
getImageHost(website) {
let imageHost = ''
switch (website) {
case 'ch':
imageHost = 'https://images.chinahighlights.com'
break
case 'ah':
imageHost = 'https://images.asiahighlights.com'
break
case 'gh':
imageHost = 'https://images.globalhighlights.com'
break
case 'chinatravel':
imageHost = 'https://images.chinatravel.com'
break
case 'sht':
imageHost = 'https://images.shanghaihighlights.com'
break
case 'ts':
imageHost = 'https://images.trainspread.com'
break
case 'mbj':
imageHost = 'https://images.mybeijingchina.com'
break
default:
imageHost = 'https://images.chinahighlights.com'
break
}
return imageHost
}
generateHtml(imageObj, website) {
const imageUrl = this.getImageHost(website) + imageObj.PII_Location + imageObj.PII_FileName
let imageHtml = ''
switch (website) {
case 'ch':
imageHtml = '<div class="infoimage"><img alt="' + imageObj.PII2_Intro + '" class="img-responsive" '+ ' width="' + imageObj.PII_Width + '" height="' + imageObj.PII_Height + '" src="' + imageUrl + '"><span class="infoimagetitle">' + imageObj.PII2_Intro + '</span></div>'
break
case 'ah':
imageHtml = '<div class="infoimage"><img alt="' + imageObj.PII2_Intro + '" class="img-responsive" '+ ' width="' + imageObj.PII_Width + '" height="' + imageObj.PII_Height + '" src="' + imageUrl + '"><span class="photoTxt">' + imageObj.PII2_Intro + '</span></div>'
break
case 'gh':
imageHtml = '<div class="infoimage"><img alt="' + imageObj.PII2_Intro + '" class="img-responsive" '+ ' width="' + imageObj.PII_Width + '" height="' + imageObj.PII_Height + '" src="' + imageUrl + '"><span class="infoimagetitle">' + imageObj.PII2_Intro + '</span></div>'
break
case 'chinatravel':
case 'sht':
case 'ts':
case 'mbj':
imageHtml = '<figure><img alt="' + imageObj.PII2_Intro + '" class="img-responsive" '+ ' width="' + imageObj.PII_Width + '" height="' + imageObj.PII_Height + '" src="' + imageUrl + '"></figure>'
break
default:
imageHtml = '<img alt="' + imageObj.PII2_Intro + '" class="img-responsive" '+ ' width="' + imageObj.PII_Width + '" height="' + imageObj.PII_Height + '" src="' + imageUrl + '">'
break
}
return imageHtml
}
useImage(imageId, userId, website) {
const formData = new FormData()
formData.append('PII_SN', imageId)
formData.append('user_id', userId)
formData.append('Webcode', website)
const postUrl = 'https://p9axztuwd7x8a7.mycht.cn/service-InfoSysSOA/use_image'
return postForm(postUrl, formData)
@ -346,86 +110,6 @@ class Order {
}
})
}
continueUpload() {
runInAction(() => {
this.imageUploadList = []
this.uploadPercent = -1
})
}
initImageList(fileList) {
runInAction(() => {
this.imageUploadList = fileList.map(file => {
return {
image_file: file,
image_uid: file.uid,
country: null,
city: null,
description_zh: '',
description_en: '',
photographer: '',
copyright: -1,
labels: '',
labelValues: [],
user_id: -1,
star: -1,
success: false,
image_url: ''
}
})
})
}
removeUploadImage(fileUid) {
runInAction(() => {
this.imageUploadList = this.imageUploadList.filter((image, index) => {
return image.image_uid != fileUid
})
})
}
syncProperties() {
if (this.imageUploadList.length > 1) {
const source = this.imageUploadList[0]
runInAction(() => {
for (let index = 1; index < this.imageUploadList.length; index++) {
const current = this.imageUploadList[index]
current.country = source.country
current.city = source.city
current.description_zh = source.description_zh
current.description_en = source.description_en
current.photographer = source.photographer
current.copyright = source.copyright
current.labelValues = source.labelValues
current.user_id = source.user_id
current.star = source.star
}
})
}
}
updateImageProperty(uid, name, value) {
for (var i = this.imageUploadList.length - 1; i >= 0; i--) {
const current = this.imageUploadList[i]
if (current.image_file.uid === uid) {
current[name] = value
break
}
}
}
selectImage(image) {
this.selectedImage = image
}
imageUploadList = []
imageSearchList = []
uploadPercent = -1
selectedImage = null
croppedImageList = []
// 上传时数据验证是否通过
requiredAlert = false
}
export default Order

@ -2,6 +2,10 @@ import { Outlet, Link, useHref, NavLink } from 'react-router-dom'
import { useRef, useEffect, useState } from 'react'
import { Layout, Menu, ConfigProvider, theme, Empty, Row, Col, Dropdown, Space, Typography, Result, App as AntApp } from 'antd'
import { DownOutlined } from "@ant-design/icons";
import zhLocale from 'antd/locale/zh_CN';
import dayjs from 'dayjs';
import 'dayjs/locale/zh-cn';
import '@/assets/App.css'
import AppLogo from '@/assets/logo-gh.png'
import { useStore } from '@/stores/StoreContext.js'
@ -45,7 +49,7 @@ function App() {
function renderLayout() {
return (
<Layout>
<Header className='header' style={{ position: 'sticky', top: 0, zIndex: 1, width: '100%', background: 'white' }}>
<Header className='header' style={{ position: 'sticky', top: 0, zIndex: 2, width: '100%', background: 'white' }}>
<Row gutter={{ md: 24 }} align='middle'>
<Col flex="300px">
<NavLink to='/'>
@ -121,6 +125,7 @@ function App() {
},
algorithm: theme.defaultAlgorithm,
}}
locale={zhLocale}
renderEmpty={globalEmpty}
>
<AntApp>

@ -2,7 +2,7 @@ import { useNavigate } from 'react-router-dom'
import { useRef, useEffect, useState } from 'react'
import { observer } from 'mobx-react'
import { Row, Col, Divider, Table , Card, Button, Input,
Space, Empty, Radio, Select, DatePicker, Spin, List, Avatar
Space, Empty, Radio, AutoComplete, DatePicker, Spin, List, Avatar
} from 'antd'
import {
StarFilled, ZoomInOutlined, StarOutlined, SearchOutlined
@ -39,15 +39,30 @@ function ChatHistory() {
<Spin spinning={false} delay={500}>
<Space direction='vertical' style={{ width: '100%' }}>
<Row gutter={[16, 16]} justify='start' align='middle'>
<Col span={5}>
<AutoComplete
style={{ width: '100%' }}
placeholder='客人'
onChange={(value) => {
setKeyword(value)
}}
filterOption={(inputValue, option) =>
option.value.toUpperCase().indexOf(inputValue.toUpperCase()) !== -1
}
/>
</Col>
<Col span={6}>
<Input placeholder="关键词" allowClear />
</Col>
<Col span={4}>
<RangePicker />
</Col>
<Col span={6}>
<Search
placeholder="关键词"
allowClear
onSearch={()=>{}}
/>
<Col span={2}>
<Button icon={<SearchOutlined />} onClick={() => {
search()
}}>搜索</Button>
</Col>
</Row>
</Space>

@ -1,6 +1,7 @@
import React, { useEffect } from 'react'
import { useParams, useNavigate } from "react-router-dom"
import { Card, Typography, Flex, Button } from 'antd'
import { Card, Typography, Flex, Result, Button } from 'antd'
import { useRequest } from 'ahooks'
const { Title } = Typography
const { Meta } = Card
@ -13,13 +14,28 @@ function DingdingCallbak() {
console.info('code: ' + code)
console.info('state: ' + state)
const { data, error, loading } = useRequest(() => {
return new Promise((resolve) => {
setTimeout(() => {
resolve('use Request data...');
}, 1000);
});
});
useEffect(() => {
}, [])
return (
<Flex justify='center' align='center' gap="middle" vertical>
<Title level={4}>钉钉 Callbak</Title>
<p>{error}</p>
<p>{loading}</p>
<p>{data}</p>
<Title level={4}>正在获取你的权限请稍等</Title>
<Result
status="403"
title="403"
subTitle="你没有绑定钉钉账号,无法登陆。"
/>
</Flex>
)
}

@ -1,5 +1,5 @@
import { Card, Flex, Typography } from 'antd'
import React, { useEffect } from 'react'
import { Card, Typography, Flex, Button } from 'antd'
const { Title } = Typography
const { Meta } = Card
@ -12,7 +12,7 @@ function DingdingQRCode() {
import('https://g.alicdn.com/dingding/h5-dingtalk-login/0.21.0/ddlogin.js').then((module) => {
window.DTFrameLogin(
{
id: 'dingdingContainer',
id: 'qrCodeContainer',
width: 300,
height: 300,
},
@ -25,26 +25,26 @@ function DingdingQRCode() {
prompt: 'consent',
},
(loginResult) => {
const { redirectUrl, authCode, state } = loginResult;
const { redirectUrl, authCode, state } = loginResult
//
window.location.href = redirectUrl;
window.location.href = redirectUrl
// 使code
console.log(authCode);
console.log(authCode)
},
(errorMsg) => {
//
alert(`Login Error: ${errorMsg}`);
alert(`Login Error: ${errorMsg}`)
},
)
})
}, [])
return (
<Flex justify='center' align='center' gap="middle" vertical>
<Flex justify='center' align='center' gap='middle' vertical>
<Title level={4}>使用钉钉扫码</Title>
<div id="dingdingContainer" style={{ border: '1px solid rgba(5, 5, 5, 0.06)', borderRadius: '8px' }}></div>
<div id='qrCodeContainer' style={{ border: '1px solid rgba(5, 5, 5, 0.06)', borderRadius: '8px' }}></div>
</Flex>
)
}
export default DingdingQRCode;
export default DingdingQRCode

@ -1,16 +1,15 @@
import { useRouteError } from "react-router-dom";
import { Card, Typography, Flex, Result, Button } from 'antd'
export default function ErrorPage() {
const errorResponse = useRouteError();
console.error(errorResponse);
return (
<div id="error-page">
<h1>Oops!</h1>
<p>Sorry, an unexpected error has occurred.</p>
<p>
<i>{errorResponse.error.message}</i>
</p>
</div>
<Result
status="404"
title="Sorry, an unexpected error has occurred."
subTitle={errorResponse.error.message}
/>
);
}

@ -2,10 +2,10 @@ import { useNavigate } from 'react-router-dom'
import { useRef, useEffect, useState } from 'react'
import { observer } from 'mobx-react'
import { Row, Col, Divider, Table , Card, Button, Input,
Space, Empty, Radio, Select, AutoComplete, Spin, Typography, Flex
Space, Segmented, Radio, Select, AutoComplete, Spin, Typography, Flex
} from 'antd'
import {
StarFilled, ZoomInOutlined, StarOutlined, SearchOutlined
StarFilled, ZoomInOutlined, StarOutlined, BarsOutlined , AppstoreOutlined
} from '@ant-design/icons'
const dataSource = [
@ -41,7 +41,7 @@ const dataSource = [
orderNumber: 'LU231218115(3)',
fullname: 'Giacomo Guilizzoni(R1)',
orderStatus: '新订单',
trip: 'Itinerary2: Tkyoto tour',
trip: 'Itinerary2: Tkyoto tour 超级无敌长的报价标题',
lastMessage: '2024-03-25 16:02',
comment: '吃素、蜜月',
},
@ -79,6 +79,30 @@ const columns = [
title: '订单号',
dataIndex: 'orderNumber',
key: 'orderNumber',
width: 300,
render: (text, record, inde) => {
return (
<Space size="middle">
<a>{text}</a>
<Segmented
options={[
{
label: '潜力',
value: '潜力',
},
{
label: '重点',
value: '重点',
},
{
label: '休眠',
value: '休眠',
},
]}
/>
</Space>
)
}
},
{
title: '客人姓名',
@ -89,11 +113,49 @@ const columns = [
title: '订单状态',
dataIndex: 'orderStatus',
key: 'orderStatus',
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: '三催',
},
]}
/>
)
}
},
{
title: '报价title',
dataIndex: 'trip',
key: 'trip',
ellipsis: true,
},
{
title: '客人最后一次回复时间',

@ -5,7 +5,7 @@ import { Row, Col, Divider, Table , Card, Button, Input,
Space, Empty, Radio, Select, DatePicker, Spin, List, Avatar
} from 'antd'
import {
StarFilled, ZoomInOutlined, StarOutlined, SearchOutlined
StarFilled, ZoomInOutlined, StarOutlined, SearchOutlined, ImportOutlined
} from '@ant-design/icons'
const { Search } = Input;
@ -117,6 +117,9 @@ function SalesManagement() {
onSearch={()=>{}}
/>
</Col>
<Col span={6}>
<Button icon={<ImportOutlined />}>导入联系人</Button>
</Col>
</Row>
</Space>
<Divider plain orientation='left'></Divider>

Loading…
Cancel
Save