React + Antd 项目配置

dev/mobile
Jimmy Liow 1 year ago
parent 8b89757b25
commit 72634ebb91

@ -0,0 +1,20 @@
module.exports = {
root: true,
env: { browser: true, es2020: true },
extends: [
'eslint:recommended',
'plugin:react/recommended',
'plugin:react/jsx-runtime',
'plugin:react-hooks/recommended',
],
ignorePatterns: ['dist', '.eslintrc.cjs'],
parserOptions: { ecmaVersion: 'latest', sourceType: 'module' },
settings: { react: { version: '18.2' } },
plugins: ['react-refresh'],
rules: {
'react-refresh/only-export-components': [
'warn',
{ allowConstantExport: true },
],
},
}

26
.gitignore vendored

@ -0,0 +1,26 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
/package-lock.json

@ -1,2 +1,14 @@
# global-sales
聊天销售平台
# Global sales
聊天式销售平台
## 开发设置
1. 安装组件npm install
2. 运行开发环境npm run dev 或者 start.bat
3. 打包代码npm run build 或者 build.bat
## 相关文档
[聊天式销售平台需求文档](https://www.kdocs.cn/l/calaUjgmCmDA?from=docs&reqtype=kdocs&startTime=1703645330177&createDirect=true&newFile=true)

@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>聊天式销售平台需求文档</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>
</body>
</html>

@ -0,0 +1,30 @@
{
"name": "global-sales",
"private": true,
"version": "1.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0",
"preview": "vite preview"
},
"dependencies": {
"antd": "^5.12.8",
"mobx": "^6.12.0",
"mobx-react": "^9.1.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.21.1"
},
"devDependencies": {
"@types/react": "^18.2.15",
"@types/react-dom": "^18.2.7",
"@vitejs/plugin-react": "^4.2.1",
"eslint": "^8.45.0",
"eslint-plugin-react": "^7.32.2",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.3",
"vite": "^4.5.1"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

@ -0,0 +1,6 @@
.logo {
float: left;
height: 36px;
margin: 16px 24px 16px 0;
background: rgba(255, 255, 255, 0.3);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

@ -0,0 +1,43 @@
import React from 'react'
import ReactDOM from 'react-dom/client'
import { configure } from 'mobx'
import {
createBrowserRouter,
RouterProvider,
} from 'react-router-dom'
import RootStore from '@/stores/Root'
import { StoreContext } from '@/stores/StoreContext'
import App from '@/views/App'
import OrderFollow from '@/views/OrderFollow'
configure({
useProxies: 'ifavailable',
enforceActions: 'observed',
computedRequiresReaction: true,
observableRequiresReaction: false,
reactionRequiresObservable: true,
disableErrorBoundaries: process.env.NODE_ENV == 'production'
})
const router = createBrowserRouter([
{
path: '/',
element: <App />,
children: [
{ index: true, element: <OrderFollow /> }
]
}
])
const rootStore = new RootStore();
ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode>
<StoreContext.Provider value={rootStore}>
<RouterProvider
router={router}
fallbackElement={() => <div>Loading...</div>}
/>
</StoreContext.Provider>
</React.StrictMode>
)

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

@ -0,0 +1,431 @@
import { makeAutoObservable, runInAction } from 'mobx'
import { fetchJSON, postForm } from '@/utils/request'
import { prepareUrl, isNotEmpty, isEmpty } from '@/utils/commons'
class Order {
constructor(root) {
makeAutoObservable(this, { rootStore: false })
this.root = root
}
fetchOptionList() {
const fetchCountryUrl = 'https://p9axztuwd7x8a7.mycht.cn/service-InfoSys/InfoSys/GetCountryList'
const fetchPhotographerUrl = 'https://p9axztuwd7x8a7.mycht.cn/service-InfoSys/InfoSys/GetphotographerList'
const fetchTagUrl = 'https://p9axztuwd7x8a7.mycht.cn/service-InfoSys/InfoSys/GetPhotoTagList'
const countryPromise = fetchJSON(fetchCountryUrl)
.then(json => {
if (json.errcode == 0) {
const countryOptionList = (json?.result ?? []).map((data, index) => {
return {
value: data.COI_SN,
label: data.COI_Name
}
})
countryOptionList.unshift({value:-1, label: '未知'})
return countryOptionList
} else {
throw new Error(json.errmsg + ': ' + json.errcode)
}
})
const cityPromise = this.fetchCityList('')
const photographerPromise = fetchJSON(fetchPhotographerUrl)
.then(json => {
if (json.errcode == 0) {
const photographerOptionList = (json?.result ?? []).map((data, index) => {
return {
value: data.U_Name,
label: data.U_Name
}
})
return photographerOptionList
} else {
throw new Error(json.errmsg + ': ' + json.errcode)
}
})
const tagPromise = fetchJSON(fetchTagUrl)
.then(json => {
if (json.errcode == 0) {
const tagOptionList = (json?.result ?? []).map((data, index) => {
return {
value: data.Tag_SN,
label: data.Tag_Name
}
})
return tagOptionList
} else {
throw new Error(json.errmsg + ': ' + json.errcode)
}
})
return Promise.all([countryPromise, cityPromise, photographerPromise, tagPromise])
.then(results => {
return {
countryOptionList: results[0],
cityOptionList: results[1],
photographerOptionList: results[2],
tagOptionList: results[3]
}
})
}
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 =
prepareUrl('https://p9axztuwd7x8a7.mycht.cn/service-InfoSys/InfoSys/GetCityList')
.append('coi_sn', countryId)
.build()
return fetchJSON(fetchCityUrl)
.then(json => {
if (json.errcode == 0) {
const cityOptionList = (json?.result ?? []).map((data, index) => {
return {
value: data.CII_SN,
label: data.CII_Name
}
})
cityOptionList.unshift({value:-1, label: '未知'})
return cityOptionList
} else {
throw new Error(json.errmsg + ': ' + json.errcode)
}
})
}
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)
.then(json => {
if (json.errcode == 0) {
console.info(json)
} else {
throw new Error(json.errmsg + ': ' + json.errcode)
}
})
}
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

@ -0,0 +1,60 @@
import { makeAutoObservable } from 'mobx'
import Auth from './Auth'
import Order from './Order'
class Root {
constructor() {
this.orderStore = new Order(this)
this.authStore = new Auth(this)
makeAutoObservable(this)
}
clearSession() {
if (window.sessionStorage) {
const sessionStorage = window.sessionStorage
sessionStorage.clear()
} else {
console.error('browser not support sessionStorage!')
}
}
getSession(key) {
if (window.sessionStorage) {
const sessionStorage = window.sessionStorage
return sessionStorage.getItem(key)
} else {
console.error('browser not support sessionStorage!')
return null
}
}
putSession(key, value) {
if (window.sessionStorage) {
const sessionStorage = window.sessionStorage
return sessionStorage.setItem(key, value)
} else {
console.error('browser not support sessionStorage!')
}
}
getLocal(key) {
if (window.localStorage) {
const localStorage = window.localStorage
return localStorage.getItem(key)
} else {
console.error('browser not support localStorage!')
return null
}
}
putLocal(key, value) {
if (window.localStorage) {
const localStorage = window.localStorage
return localStorage.setItem(key, value)
} else {
console.error('browser not support localStorage!')
}
}
}
export default Root

@ -0,0 +1,7 @@
import { createContext, useContext } from 'react'
export const StoreContext = createContext()
export function useStore() {
return useContext(StoreContext)
}

@ -0,0 +1,94 @@
export function copy(obj) {
return JSON.parse(JSON.stringify(obj))
}
export function splitArray2Parts(arr, size) {
const result = []
for (let i = 0; i < arr.length; i += size) {
result.push(arr.slice(i, i + size))
}
return result
}
export function camelCase(name) {
return name.substr(0, 1).toLowerCase() + name.substr(1)
}
export class UrlBuilder {
constructor(url) {
this.url = url
this.paramList = []
}
append(name, value) {
if (isNotEmpty(value)) {
this.paramList.push({ name: name, value: value })
}
return this
}
build() {
this.paramList.forEach((e, i, a) => {
if (i === 0) {
this.url += "?"
} else {
this.url += "&"
}
this.url += e.name + "=" + e.value
})
return this.url
}
}
export function isNotEmpty(val) {
return val !== undefined && val !== null && val !== ""
}
export function isEmpty(val) {
return val === undefined || val === null || val === ""
}
export function prepareUrl(url) {
return new UrlBuilder(url)
}
export function debounce(fn, delay = 500) {
let timer
return e => {
e.persist()
clearTimeout(timer)
timer = setTimeout(() => {
fn(e)
}, delay)
}
}
export function throttle(fn, delay, atleast) {
let timeout = null,
startTime = new Date()
return function () {
let curTime = new Date()
clearTimeout(timeout)
if (curTime - startTime >= atleast) {
fn()
startTime = curTime
} else {
timeout = setTimeout(fn, delay)
}
}
}
export function clickUrl(url) {
const httpLink = document.createElement("a")
httpLink.href = url
httpLink.target = "_blank"
httpLink.click()
}
export function escape2Html(str) {
var temp = document.createElement("div")
temp.innerHTML = str
var output = temp.innerText || temp.textContent
temp = null
return output
}

@ -0,0 +1,69 @@
function checkStatus(response) {
if (response.status >= 200 && response.status < 300) {
return response
} else {
const message =
'Fetch error: ' + response.url + ' ' + response.status + ' (' +
response.statusText + ')'
const error = new Error(message)
error.response = response
throw error
}
}
export function fetchText(url) {
return fetch(url)
.then(checkStatus)
.then(response => response.text())
.catch(error => {
throw error
})
}
export function fetchJSON(url) {
return fetch(url)
.then(checkStatus)
.then(response => response.json())
.catch(error => {
throw error
})
}
export function postForm(url, data) {
return fetch(url, {
method: 'POST',
body: data
}).then(checkStatus)
.then(response => response.json())
.catch(error => {
throw error
})
}
export function postJSON(url, obj) {
return fetch(url, {
method: 'POST',
body: JSON.stringify(obj),
headers: {
'Content-type': 'application/json; charset=UTF-8'
}
}).then(checkStatus)
.then(response => response.json())
.catch(error => {
throw error
})
}
export function postStream(url, obj) {
return fetch(url, {
method: 'POST',
body: JSON.stringify(obj),
headers: {
'Content-type': 'application/octet-stream'
}
}).then(checkStatus)
.then(response => response.json())
.catch(error => {
throw error
})
}

@ -0,0 +1,153 @@
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 '@/assets/App.css'
import AppLogo from '@/assets/logo-gh.png'
import { useStore } from '@/stores/StoreContext.js'
import { isEmpty } from '@/utils/commons'
const { Header, Footer, Content } = Layout
const { Title } = Typography
const items = [
{
label: <Link to="/account/change-password">Change password</Link>,
key: "0",
},
{
label: <Link to="/account/profile">Profile</Link>,
key: "1",
},
{
type: "divider",
},
{
label: <Link to="/login?out">Logout</Link>,
key: "3",
},
];
function App() {
const href = useHref()
const { authStore } = useStore()
const { userId, website } = authStore
const shouldBeLogin = (isEmpty(userId) || isEmpty(website)) && (href.indexOf('/authorise/') == -1)
let defaultPath = 'follow'
if (href !== '/') {
const splitPath = href.split('/')
if (splitPath.length > 1) {
defaultPath = splitPath[2]
}
}
const {
token: { colorBgContainer },
} = theme.useToken()
function globalEmpty() {
return (
<Empty description={false} />
)
}
function renderLogin() {
return (
<Result
status='403'
title='授权失败'
subTitle='请登陆信息平台,通过指定链接打开。'
/>
)
}
function renderLayout() {
return (
<Layout>
<Header className='header' style={{ position: 'sticky', top: 0, zIndex: 1, width: '100%', background: 'white' }}>
<Row gutter={{ md: 24 }} align='middle'>
<Col span={5}>
<NavLink to='/'>
<img src={AppLogo} className='logo' alt='App logo' />
</NavLink>
<Title level={3}>
聊天式销售平台
</Title>
</Col>
<Col span={10}>
<Menu
mode='horizontal'
selectedKeys={[defaultPath]}
items={[
{ key: 'follow', label: <Link to='/order/follow'>订单跟踪</Link> },
{ key: 'chat', label: <Link to='/order/chat'>销售聊天</Link> },
{ key: 'history', label: <Link to='/chat/history'>聊天历史</Link> },
{ key: 'management', label: <Link to='/sales/management'>销售管理</Link> }
]}
/>
</Col>
<Col span={9} style={{ color: "white", marginBottom: "0", display: "flex", justifyContent: "end" }}>
<Dropdown
menu={{
items: [
{
label: <Link to="/account/profile">个人资料</Link>,
key: "1",
},
{
type: "divider",
},
{
label: <Link to="/login?out">退出</Link>,
key: "3",
},
]
}}
trigger={['click']}
>
<a onClick={(e) => e.preventDefault()}>
<Space>
廖一军
<DownOutlined />
</Space>
</a>
</Dropdown>
</Col>
</Row>
</Header>
<Layout>
<Content
style={{
padding: 24,
margin: 0,
minHeight: 280,
background: colorBgContainer,
}}>
<Outlet />
</Content>
</Layout>
<Footer>桂林海纳国际旅行社有限公司</Footer>
</Layout>
)
}
return (
<ConfigProvider
theme={{
token: {
colorPrimary: '#645822',
borderRadius: 4
},
algorithm: theme.defaultAlgorithm,
}}
renderEmpty={globalEmpty}
>
<AntApp>
{renderLayout()}
</AntApp>
</ConfigProvider>
)
}
export default App

@ -0,0 +1,154 @@
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
} from 'antd'
import {
StarFilled, ZoomInOutlined, StarOutlined, SearchOutlined
} from '@ant-design/icons'
const dataSource = [
{
key: '1',
orderNumber: 'LU231218115(3)',
fullname: 'Giacomo Guilizzoni(R1)',
orderStatus: '新订单',
trip: 'Itinerary2: Tkyoto tour',
lastMessage: '2024-03-25 16:02',
comment: '吃素、蜜月',
},
{
key: '1',
orderNumber: 'LU231218115(3)',
fullname: 'Giacomo Guilizzoni(R1)',
orderStatus: '新订单',
trip: 'Itinerary2: Tkyoto tour',
lastMessage: '2024-03-25 16:02',
comment: '吃素、蜜月',
},
{
key: '1',
orderNumber: 'LU231218115(3)',
fullname: 'Giacomo Guilizzoni(R1)',
orderStatus: '新订单',
trip: 'Itinerary2: Tkyoto tour',
lastMessage: '2024-03-25 16:02',
comment: '吃素、蜜月',
},
{
key: '1',
orderNumber: 'LU231218115(3)',
fullname: 'Giacomo Guilizzoni(R1)',
orderStatus: '新订单',
trip: 'Itinerary2: Tkyoto tour',
lastMessage: '2024-03-25 16:02',
comment: '吃素、蜜月',
},
{
key: '1',
orderNumber: 'LU231218115(3)',
fullname: 'Giacomo Guilizzoni(R1)',
orderStatus: '新订单',
trip: 'Itinerary2: Tkyoto tour',
lastMessage: '2024-03-25 16:02',
comment: '吃素、蜜月',
},
{
key: '1',
orderNumber: 'LU231218115(3)',
fullname: 'Giacomo Guilizzoni(R1)',
orderStatus: '新订单',
trip: 'Itinerary2: Tkyoto tour',
lastMessage: '2024-03-25 16:02',
comment: '吃素、蜜月',
},
{
key: '1',
orderNumber: 'LU231218115(3)',
fullname: 'Giacomo Guilizzoni(R1)',
orderStatus: '新订单',
trip: 'Itinerary2: Tkyoto tour',
lastMessage: '2024-03-25 16:02',
comment: '吃素、蜜月',
},
];
const columns = [
{
title: '订单号',
dataIndex: 'orderNumber',
key: 'orderNumber',
},
{
title: '客人姓名',
dataIndex: 'fullname',
key: 'fullname',
},
{
title: '订单状态',
dataIndex: 'orderStatus',
key: 'orderStatus',
},
{
title: '报价title',
dataIndex: 'trip',
key: 'trip',
},
{
title: '客人最后一次回复时间',
dataIndex: 'lastMessage',
key: 'lastMessage',
},
{
title: '附加信息',
dataIndex: 'comment',
key: 'comment',
},
];
function OrderFollow() {
useEffect(() => {
}, [])
return (
<Spin spinning={false} delay={500}>
<Space direction='vertical' style={{ width: '100%' }}>
<Row gutter={[16, 16]} justify='start' align='middle'>
<Col span={24}>
<Radio.Group
options={[
{ label: '今日任务', value: 'today' },
{ label: '潜力客户', value: 'myused' },
{ label: '重点订单', value: 'myupload' },
{ label: '成行', value: 'myfavorites' },
{ label: '走团中', value: 'unCheck' },
{ label: '走团后一月', value: 'unCheck' },
{ label: '高级查询', value: 'unCheck' }
]}
value={'today'}
onChange={({ target: { value } }) => {
setSearchType(value)
}}
optionType='button'
buttonStyle='solid'
/>
</Col>
</Row>
</Space>
<Divider plain orientation='left'></Divider>
<Space
direction='vertical'
size='middle'
style={{
display: 'flex',
}}
>
<Table dataSource={dataSource} columns={columns} />
</Space>
</Spin>
)
}
export default observer(OrderFollow)

@ -0,0 +1,44 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
server: {
host: "0.0.0.0",
},
resolve: {
alias: {
"@": "/src",
},
},
build: {
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)
// }
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