diff --git a/public/locales/en/common.json b/public/locales/en/common.json index 8ed1854..c87b7cd 100644 --- a/public/locales/en/common.json +++ b/public/locales/en/common.json @@ -26,8 +26,9 @@ "Export": "Export", "Copy": "Copy", - "sureCancel": "Sure you want to cancel?", - "sureDelete":"Sure you want to delete?", + "sureCancel": "Are you sure to cancel?", + "sureDelete":"Are you sure to delete?", + "Yes": "Yes", "Success": "Success", "Failed": "Failed", diff --git a/public/locales/en/products.json b/public/locales/en/products.json index b170ecf..0e90423 100644 --- a/public/locales/en/products.json +++ b/public/locales/en/products.json @@ -1,4 +1,5 @@ { + "ProductType": "Product Type", "type": { "Experience": "Experience", "Car": "Transport Services", @@ -7,8 +8,7 @@ "Attractions": "Attractions", "Meals": "Meals", "Extras": "Extras", - "Overtravel": "超公里", - "Special": "Special" + "UltraService": "Ultra Service" }, "EditComponents": { "info": "Product Information", @@ -29,6 +29,11 @@ "Rejected": "Reject", "Published": "Publish" }, + "PriceUnit": { + "0": "Person", + "1": "Group", + "title": "Price Unit" + }, "Status": "Status", "State": "State", @@ -39,6 +44,13 @@ "CreateDate": "Create Date", "AuditedBy": "Audited By", "AuditDate": "Audit Date", + "OpenHours": "Open Hours", + "Duration": "Duration", + "KM": "KM", + "RecommendsRate": "RecommendsRate", + "OpenWeekdays": "Open Weekdays", + "DisplayToC": "DisplayToC", + "Dept": "Dept", "productProject": "Product project", "Code": "Code", diff --git a/public/locales/zh/account.json b/public/locales/zh/account.json index a7caf37..35c0dc0 100644 --- a/public/locales/zh/account.json +++ b/public/locales/zh/account.json @@ -8,5 +8,24 @@ "CurrentPassword": "请输入密码。", "NewPassword": "请输入新密码。", "ReenterPassword": "请重复输入密码。" - } + }, + "createdOn": "创建时间", + "action": "操作", + "action.edit": "编辑", + "action.disable": "禁用", + "action.resetPassword": "重置密码", + + "accountList": "管理账号", + "newAccount": "新增账号", + "detail": "详细信息", + "username": "用户名", + "realname": "姓名", + "travelAgencyName": "供应商名称", + "email": "邮箱地址", + "lastLogin": "最后登陆时间", + + "roleList": "管理角色", + "newRole": "新增角色", + "roleName": "角色名称", + "permission": "权限" } \ No newline at end of file diff --git a/public/locales/zh/common.json b/public/locales/zh/common.json index 82b43f9..459ff35 100644 --- a/public/locales/zh/common.json +++ b/public/locales/zh/common.json @@ -28,6 +28,7 @@ "sureCancel": "确定取消?", "sureDelete":"确定删除?", + "Yes": "是", "Success": "成功", "Failed": "失败", @@ -62,13 +63,13 @@ "thisYear": "今年" }, "weekdays": { - "1": "一", - "2": "二", - "3": "三", - "4": "四", - "5": "五", - "6": "六", - "7": "日" + "1": "周一", + "2": "周二", + "3": "周三", + "4": "周四", + "5": "周五", + "6": "周六", + "7": "周日" }, "weekdaysShort": { "1": "一", diff --git a/public/locales/zh/products.json b/public/locales/zh/products.json index 1d7ebff..3fe9c47 100644 --- a/public/locales/zh/products.json +++ b/public/locales/zh/products.json @@ -1,14 +1,14 @@ { + "ProductType": "项目类型", "type": { "Experience": "综费", "Car": "车费", - "Guide": "导服", + "Guide": "导游", "Package": "包价线路", "Attractions": "景点", "Meals": "餐费", "Extras": "附加项目", - "Overtravel": "超公里", - "Special": "特殊项目" + "UltraService": "超公里" }, "EditComponents": { "info": "产品信息", @@ -29,6 +29,11 @@ "Rejected": "审核拒绝", "Published": "审核发布" }, + "PriceUnit": { + "0": "每人", + "1": "每团", + "title": "报价单位" + }, "Status": "状态", "State": "状态", @@ -37,11 +42,18 @@ "AuState": "审核状态", "CreatedBy": "提交人员", "CreateDate": "提交时间", - - "AuditedBy": "审核人员", "AuditDate": "审核时间", + "OpenHours": "游览时间", + "Duration": "游览时长", + "KM": "公里数", + "RecommendsRate": "推荐指数", + "OpenWeekdays": "周开放日", + "DisplayToC": "报价信显示", + "Dept": "小组", + + "productProject": "产品项目", "Code": "简码", "City": "城市", @@ -71,7 +83,7 @@ "GroupSize": "人等", "UseDates": "使用日期", - "Weekdays": "有效日/周X", + "Weekdays": "周末", "OnWeekdays": "周: ", "Unlimited": "不限", diff --git a/src/components/SearchForm.jsx b/src/components/SearchForm.jsx index f6bad2c..f896792 100644 --- a/src/components/SearchForm.jsx +++ b/src/components/SearchForm.jsx @@ -3,7 +3,7 @@ import { Form, Input, Row, Col, Select, DatePicker, Space, Button } from 'antd'; import { objectMapper, at } from '@/utils/commons'; import { DATE_FORMAT, SMALL_DATETIME_FORMAT } from '@/config'; import useFormStore from '@/stores/Form'; -import useDatePresets from '@/hooks/useDatePresets'; +import {useDatePresets} from '@/hooks/useDatePresets'; import { useTranslation } from 'react-i18next'; import { fetchJSON } from '@/utils/request'; @@ -209,7 +209,7 @@ function getFields(props) { ), item( 'username', - 3, + 99, , @@ -217,7 +217,7 @@ function getFields(props) { ), item( 'realname', - 4, + 99, , diff --git a/src/hooks/useDatePresets.js b/src/hooks/useDatePresets.js index 87397b9..2eb4c7d 100644 --- a/src/hooks/useDatePresets.js +++ b/src/hooks/useDatePresets.js @@ -1,8 +1,9 @@ import { useEffect, useState } from 'react'; import dayjs from "dayjs"; import { useTranslation } from 'react-i18next'; +import i18n from '@/i18n'; -const useDatePresets = () => { +export const useDatePresets = () => { const [presets, setPresets] = useState([]); const { t, i18n } = useTranslation(); @@ -39,4 +40,21 @@ const useDatePresets = () => { return presets; } -export default useDatePresets; +export const useWeekdays = () => { + const [data, setData] = useState([]); + const { t, i18n } = useTranslation(); + useEffect(() => { + const newData = [ + { value: '1', label: t('weekdays.1') }, + { value: '2', label: t('weekdays.2') }, + { value: '3', label: t('weekdays.3') }, + { value: '4', label: t('weekdays.4') }, + { value: '5', label: t('weekdays.5') }, + { value: '6', label: t('weekdays.6') }, + { value: '7', label: t('weekdays.7') }, + ]; + setData(newData); + return () => {}; + }, [i18n.language]); + return data; +}; diff --git a/src/hooks/useProductsSets.js b/src/hooks/useProductsSets.js index 0dd63f7..015fa8a 100644 --- a/src/hooks/useProductsSets.js +++ b/src/hooks/useProductsSets.js @@ -1,42 +1,44 @@ import { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; +import useAuthStore from '@/stores/Auth'; +import { PERM_OVERSEA, PERM_AIR_TICKET, PERM_PRODUCTS_MANAGEMENT } from '@/config'; /** * 产品管理 相关的预设数据 * 项目类型 -酒店预定 1 -火车 2 -飞机票务 3 -游船 4 -快巴 5 -旅行社(综费) 6 -景点 7 -特殊项目 8 -其他 9 -酒店 A -超公里 B -餐费 C -小包价 D -站 X -购物 S -餐 R -娱乐 E -精华线路 T -客人testimonial F -线路订单 O -省 P -信息 I -国家 G -城市 K -图片 H -地图 M -包价线路 L -节日节庆 V -火车站 N -手机租赁 Z - * * webht 类型, 20240624 新增HT类型 -Q 导游 -J 车费 + * * 酒店预定 1 + * * 火车 2 + * * 飞机票务 3 + * * 游船 4 + * * 快巴 5 + * * 旅行社(综费) 6 + * * 景点 7 + * * 特殊项目 8 + * * 其他 9 + * * 酒店 A + * * 超公里 B + * * 餐费 C + * * 小包价 D + * * 站 X + * * 购物 S + * * 餐 R (餐厅) + * * 娱乐 E + * * 精华线路 T + * * 客人testimonial F + * * 线路订单 O + * * 省 P + * * 信息 I + * * 国家 G + * * 城市 K + * * 图片 H + * * 地图 M + * * 包价线路 L (已废弃) + * * 节日节庆 V + * * 火车站 N + * * 手机租赁 Z + * * ---- webht 类型, 20240624 新增HT类型 ---- + * * 导游 Q + * * 车费 J */ export const useProductsTypes = () => { @@ -46,20 +48,24 @@ export const useProductsTypes = () => { useEffect(() => { const newData = [ { label: t('products:type.Experience'), value: '6', key: '6' }, - { label: t('products:type.Overtravel'), value: 'B', key: 'B' }, + { label: t('products:type.UltraService'), value: 'B', key: 'B' }, { label: t('products:type.Car'), value: 'J', key: 'J' }, { label: t('products:type.Guide'), value: 'Q', key: 'Q' }, { label: t('products:type.Package'), value: 'D', key: 'D' }, // 包价线路 { label: t('products:type.Attractions'), value: '7', key: '7' }, - { label: t('products:type.Meals'), value: 'C', key: 'C' }, + { label: t('products:type.Meals'), value: 'R', key: 'R' }, { label: t('products:type.Extras'), value: '8', key: '8' }, - // { label: t('products:type.Special'), value: 'Special', key: 'Special' }, ]; setTypes(newData); }, [i18n.language]); return types; }; +export const useProductsTypesMapVal = (value) => { + const stateSets = useProductsTypes(); + const stateMapVal = stateSets.reduce((r, c) => ({ ...r, [`${c.value}`]: c }), {}); + return stateMapVal; +}; export const useProductsAuditStates = () => { const [types, setTypes] = useState([]); @@ -89,17 +95,26 @@ export const useProductsAuditStatesMapVal = (value) => { /** * @ignore */ -export const useProductsTypesFieldsets = (type, role) => { - const infoDefault = ['code', 'title']; +export const useProductsTypesFieldsets = (type) => { + const [isPermitted] = useAuthStore((state) => [state.isPermitted]); + const infoDefault = [['code'], ['title']]; const infoAdmin = ['remarks', 'dept', 'display_to_c']; const infoTypesMap = { - '6': [], - 'B': ['city_id', 'km'], - 'J': ['description', 'city_id', 'recommends_rate', 'duration', 'display_to_c'], - 'Q': ['description', 'city_id', 'duration', ], - 'D': ['description', 'city_id', 'recommends_rate','duration',], - '7': ['description', 'city_id', 'recommends_rate', 'duration', 'display_to_c', 'open_weekdays'], // todo: 怎么是2个图 - 'C': ['description', 'city_id',], - '8': [], // todo: ? + '6': [[],[]], + 'B': [['city_id', 'km'], []], + 'J': [['city_id', 'recommends_rate', 'duration', 'display_to_c'], ['description',]], + 'Q': [['city_id', 'duration', ], ['description',]], + 'D': [['city_id', 'recommends_rate','duration',], ['description',]], + '7': [['city_id', 'recommends_rate', 'duration', 'display_to_c', 'open_weekdays'], ['description',]], // todo: 怎么是2个图 + 'R': [['city_id',], ['description',]], + '8': [[],[]], // todo: ? }; -}; + const thisTypeFieldset = (_type) => { + const adminSet = isPermitted(PERM_PRODUCTS_MANAGEMENT) ? infoAdmin : []; + return [ + [...infoDefault[0], ...infoTypesMap[_type][0], ...adminSet], + [...infoDefault[1], ...infoTypesMap[_type][1]] + ]; + }; + return thisTypeFieldset(type); +} diff --git a/src/i18n/LanguageSwitcher.jsx b/src/i18n/LanguageSwitcher.jsx index dd1a743..f09a07e 100644 --- a/src/i18n/LanguageSwitcher.jsx +++ b/src/i18n/LanguageSwitcher.jsx @@ -8,16 +8,15 @@ const i18n_to_htcode = { 'en': 1, }; -export const useLanguage = () => { +export const useDefaultLgc = () => { const { i18n } = useTranslation(); - return { language: i18n.language, }; + return { language: i18n_to_htcode[i18n.language], }; }; - /** * 语言选择组件 */ const Language = () => { - const { t, i18n } = useTranslation(); +const { t, i18n } = useTranslation(); const [selectedKeys, setSelectedKeys] = useState([i18n.language]); useEffect(() => { diff --git a/src/main.jsx b/src/main.jsx index eb70c92..02f2244 100644 --- a/src/main.jsx +++ b/src/main.jsx @@ -9,7 +9,6 @@ import App from "@/views/App"; import Standlone from "@/views/Standlone"; import Login from "@/views/Login"; import Logout from "@/views/Logout"; -import Index from "@/views/index"; import ErrorPage from "@/components/ErrorPage"; import RequireAuth from '@/components/RequireAuth' import ReservationNewest from "@/views/reservation/Newest"; @@ -49,10 +48,12 @@ const initAppliction = async () => { if (isNotEmpty(loginToken)) { appendRequestParams('token', loginToken) + appendRequestParams('lmi_sn', userId) + } if (isNotEmpty(userId)) { - appendRequestParams('wu_id', userId) + appendRequestParams('lmi_sn', userId) await fireAuth() } } @@ -65,7 +66,7 @@ const router = createBrowserRouter([ element: , errorElement: , children: [ - { index: true, element: }, + { index: true, element: }, { path: "account/change-password", element: }, { path: "account/profile", element: }, { path: "account/management", element: }, @@ -76,14 +77,14 @@ const router = createBrowserRouter([ { path: "feedback/:GRI_SN/:CII_SN/:RefNo", element: }, { path: "feedback/:GRI_SN/:RefNo", element: }, { path: "report", element: }, - { path: "notice", element: }, - { path: "notice/:CCP_BLID", element: }, + { path: "notice", element: }, + { path: "notice/:CCP_BLID", element: }, { path: "invoice",element:}, { path: "invoice/detail/:GMDSN/:GSN",element:}, { path: "invoice/paid",element:}, { path: "invoice/paid/detail/:flid", element: }, { path: "airticket",element: }, - { path: "airticket/plan/:coli_sn",element:}, + { path: "airticket/plan/:coli_sn",element:}, { path: "products",element: }, { path: "products/:travel_agency_id/:use_year/:audit_state/audit",element:}, { path: "products/:travel_agency_id/:use_year/:audit_state/edit",element:}, diff --git a/src/stores/Account.js b/src/stores/Account.js index 804299a..01010d8 100644 --- a/src/stores/Account.js +++ b/src/stores/Account.js @@ -70,11 +70,11 @@ const useAccountStore = create((set, get) => ({ accountList: [], - disableAccount: async (accountId) => { + disableAccount: async (userId) => { const formData = new FormData() - formData.append('wu_id', accountId) - // enable disable + formData.append('lmi_sn', userId) + // enable | disable formData.append('account_status', 'disable') const result = await postAccountStatus(formData) @@ -82,10 +82,10 @@ const useAccountStore = create((set, get) => ({ console.info(result) }, - resetAccountPassword: async (accountId, password) => { + resetAccountPassword: async (userId, password) => { const formData = new FormData() - formData.append('wu_id', accountId) + formData.append('lmi_sn', userId) formData.append('newPassword', password) return postAccountPassword(formData) @@ -111,8 +111,8 @@ const useAccountStore = create((set, get) => ({ saveOrUpdateAccount: async (formValues) => { const { userId } = usingStorage() const formData = new FormData() - formData.append('wu_id', formValues.userId) - formData.append('lmi_sn', formValues.lmi_sn) + formData.append('wu_id', formValues.accountId) + formData.append('lmi_sn', formValues.userId) formData.append('lmi2_sn', formValues.lmi2_sn) formData.append('user_name', formValues.username) formData.append('real_name', formValues.realname) @@ -138,8 +138,8 @@ const useAccountStore = create((set, get) => ({ const mapAccoutList = resultArray.map((r) => { return { - userId: r.wu_id, - lmi_sn: r.lmi_sn, + accountId: r.wu_id, + userId: r.lmi_sn, lmi2_sn: r.lmi2_sn, username: r.user_name, realname: r.real_name, diff --git a/src/stores/Auth.js b/src/stores/Auth.js index c413edb..344e147 100644 --- a/src/stores/Auth.js +++ b/src/stores/Auth.js @@ -36,7 +36,7 @@ export const fetchUserDetail = async (loginToken) => { export const fetchPermissionListByUserId = async (userId) => { const { errcode, result } = await fetchJSON( - `${HT_HOST}/service-CooperateSOA/get_account_permission_list`, { wu_id: userId}) + `${HT_HOST}/service-CooperateSOA/get_account_permission_list`, { lmi_sn: userId}) return errcode !== 0 ? {} : result } @@ -71,7 +71,7 @@ const useAuthStore = create(obervseLifecycle((set, get) => ({ // 以上是 Hardcode 判断 // 以下是权限列表从数据库读取后使用的方法 return permissionList.some((value) => { - if (value.indexOf(WILDCARD_TOKEN) > -1) { + if (value.indexOf(WILDCARD_TOKEN) == 0) { return true } if (value === perm) { @@ -85,9 +85,9 @@ const useAuthStore = create(obervseLifecycle((set, get) => ({ const { startTokenInterval, loadUserPermission } = get() const { setStorage } = usingStorage() - const { token: loginToken, WU_ID: userId } = await fetchLoginToken(usr, pwd) + const { token: loginToken } = await fetchLoginToken(usr, pwd) const userDetail = await fetchUserDetail(loginToken) - await loadUserPermission(userId) + await loadUserPermission(userDetail.LMI_SN) set(() => ({ tokenTimeout: false, @@ -95,10 +95,10 @@ const useAuthStore = create(obervseLifecycle((set, get) => ({ })) setStorage(KEY_LOGIN_TOKEN, loginToken) - setStorage(KEY_USER_ID, userId) + setStorage(KEY_USER_ID, userDetail.LMI_SN) setStorage(KEY_TRAVEL_AGENCY_ID, userDetail.LMI_VEI_SN) appendRequestParams('token', loginToken) - appendRequestParams('wu_id', userDetail.LMI_SN) + appendRequestParams('lmi_sn', userDetail.LMI_SN) // loadPageSpy(`${json.Result.VName}-${json.Result.LoginName}`) startTokenInterval() }, @@ -131,7 +131,7 @@ const useAuthStore = create(obervseLifecycle((set, get) => ({ const now = new Date() const diffTime = now.getTime() - lastReqDate.getTime() const diffHours = diffTime/1000/60/60 - if (diffHours > 4) { + if (diffHours > 1) { loginTimeout() } } @@ -144,7 +144,8 @@ const useAuthStore = create(obervseLifecycle((set, get) => ({ loginTimeout: () => { const { tokenInterval } = get() - // TODO: 这里没有清理 token,刷新后可以正常使用系统 + const { clearStorage } = usingStorage() + clearStorage() clearInterval(tokenInterval) set(() => ({ tokenTimeout: true @@ -171,4 +172,4 @@ const useAuthStore = create(obervseLifecycle((set, get) => ({ }))) -export default useAuthStore \ No newline at end of file +export default useAuthStore diff --git a/src/views/App.jsx b/src/views/App.jsx index 0ebcff4..3541d9b 100644 --- a/src/views/App.jsx +++ b/src/views/App.jsx @@ -4,7 +4,7 @@ import { Layout, Menu, ConfigProvider, theme, Dropdown, Space, Row, Col, Badge, import { DownOutlined } from '@ant-design/icons'; import 'antd/dist/reset.css'; import AppLogo from '@/assets/logo-gh.png'; -import { isEmpty } from '@/utils/commons'; +import { isEmpty, isNotEmpty } from '@/utils/commons'; import Language from '../i18n/LanguageSwitcher'; import { useTranslation } from 'react-i18next'; import zhLocale from 'antd/locale/zh_CN'; @@ -43,13 +43,15 @@ function App() { const needToLogin = href !== '/login' && isEmpty(loginToken) useEffect(() => { - fetchUserDetail(loginToken) - .then(u => { - setUserDetail({ - username: u.LoginName, - travelAgencyName: u.VName, + if (isNotEmpty(loginToken)) { + fetchUserDetail(loginToken) + .then(u => { + setUserDetail({ + username: u.LoginName, + travelAgencyName: u.VName, + }) }) - }) + } }, [loginToken]) useEffect(() => { @@ -58,10 +60,6 @@ function App() { } }, [href]) - useEffect(() => { - window.gtag('event', 'page_view', { page_location: window.location.href }); - }, [location]) - const onSubmit = () => { validateUserPassword(userDetail?.username, password) .catch(ex => { @@ -69,23 +67,24 @@ function App() { alert(t('Validation.LoginFailed')) }) setPassword('') - }; + } - const splitPath = href.split('/'); - let defaultPath = 'reservation'; + const splitPath = href.split('/') + let defaultPath = 'notice' if (splitPath.length > 1) { - defaultPath = splitPath[1]; + defaultPath = splitPath[1] } const { token: { colorBgContainer }, - } = theme.useToken(); + } = theme.useToken() const [antdLng, setAntdLng] = useState(enLocale); useEffect(() => { setAntdLng(i18n.language === 'en' ? enLocale : zhLocale); - }, [i18n.language]); + }, [i18n.language]) + return ( {t('ChangePassword')}, key: '0' }, { label: {t('Profile')}, key: '1' }, - isPermitted(PERM_ACCOUNT_MANAGEMENT) ? { label: {t('account:management.tile')}, key: '3' } : null, - isPermitted(PERM_ROLE_NEW) ? { label: {t('account:management.roleList')}, key: '4' } : null, + { type: 'divider' }, + isPermitted(PERM_ACCOUNT_MANAGEMENT) ? { label: {t('account:accountList')}, key: '3' } : null, + isPermitted(PERM_ROLE_NEW) ? { label: {t('account:roleList')}, key: '4' } : null, { type: 'divider' }, { label: {t('Logout')}, key: '99' }, ] @@ -195,7 +195,7 @@ function App() { - ); + ) } export default App diff --git a/src/views/Index.jsx b/src/views/Index.jsx deleted file mode 100644 index 9034490..0000000 --- a/src/views/Index.jsx +++ /dev/null @@ -1,13 +0,0 @@ -export default function Index() { - return ( -

- Global Highlights Hub -
- Check out{" "} - - the docs at chinahighlights.com - - . -

- ); -} \ No newline at end of file diff --git a/src/views/Login.jsx b/src/views/Login.jsx index b973b96..f9c3008 100644 --- a/src/views/Login.jsx +++ b/src/views/Login.jsx @@ -17,7 +17,7 @@ function Login() { useEffect (() => { if (loginStatus === 302) { - navigate('/reservation/newest') + navigate('/') } }, [loginStatus]) diff --git a/src/views/account/Management.jsx b/src/views/account/Management.jsx index 517f03f..d3e3d2d 100644 --- a/src/views/account/Management.jsx +++ b/src/views/account/Management.jsx @@ -1,14 +1,12 @@ -import { useState, useEffect } from 'react' -import { Row, Col, Space, Button, Table, Select, TreeSelect, Typography, Modal, App, Form, Input } from 'antd' +import SearchForm from '@/components/SearchForm' +import useAccountStore, { fetchRoleList, fetchTravelAgencyByName } from '@/stores/Account' +import useFormStore from '@/stores/Form' +import { isEmpty } from '@/utils/commons' import { ExclamationCircleFilled } from '@ant-design/icons' -import { useTranslation } from 'react-i18next' -import { fetchTravelAgencyByName } from '@/stores/Account' +import { App, Button, Col, Form, Input, Modal, Row, Select, Space, Table, Typography } from 'antd' import dayjs from 'dayjs' -import { isEmpty } from '@/utils/commons' -import useAccountStore from '@/stores/Account' -import useFormStore from '@/stores/Form' -import { fetchRoleList } from '@/stores/Account' -import SearchForm from '@/components/SearchForm' +import { useEffect, useState } from 'react' +import { useTranslation } from 'react-i18next' const { Title } = Typography @@ -26,7 +24,7 @@ function Management() { dataIndex: 'realname', }, { - title: t('account:travelAgency'), + title: t('account:travelAgencyName'), dataIndex: 'travelAgencyName', }, { @@ -34,7 +32,7 @@ function Management() { dataIndex: 'email', }, { - title: t('account:role'), + title: t('account:roleName'), dataIndex: 'role' }, { @@ -43,7 +41,7 @@ function Management() { render: (text) => (isEmpty(text) ? '' : dayjs(text).format('YYYY-MM-DD HH:mm:ss')) }, { - title: t('account:action'), + title: t('account:action.edit'), dataIndex: 'account:action', render: actionRender }, @@ -78,7 +76,7 @@ function Management() { const formValues = useFormStore(state => state.formValues) const { notification, modal } = App.useApp() - useEffect (() => { + useEffect(() => { fetchRoleList() .then((roleList) => { setRoleAllList(roleList.map(r => { @@ -108,7 +106,6 @@ function Management() { } const onAccountSeleted = async (account) => { - console.info(account) setTravelAgencyList([{ label: account.travelAgencyName, value: account.travelAgencyId @@ -156,7 +153,6 @@ function Management() { } const handleTravelAgencyChange = (newValue) => { - console.info(newValue) setCurrentTravelAgency(newValue) } @@ -189,7 +185,6 @@ function Management() { duration: 60, }) }) - console.log('ResetPassword') }, onCancel() { }, @@ -204,32 +199,32 @@ function Management() { autoFocus: true, htmlType: 'submit', }} - title={t('account:management.newAccount')} + title={t('account:detail')} open={isAccountModalOpen} onOk={() => setAccountModalOpen(false)} onCancel={() => setAccountModalOpen(false)} destroyOnClose={true} clearOnDestroy={true} modalRender={(dom) => (
+ name='AccountForm' + form={accountForm} + layout='vertical' + size='large' + style={{ + maxWidth: 600, + }} + onFinish={onAccountFinish} + onFinishFailed={onAccountFailed} + autoComplete='off' + > {dom}
)} > - - - + + + - - {t('account:management.tile')} + {t('account:accountList')} { handelAccountSearch() @@ -316,7 +317,7 @@ function Management() { - + diff --git a/src/views/account/RoleList.jsx b/src/views/account/RoleList.jsx index 13a1501..71909f1 100644 --- a/src/views/account/RoleList.jsx +++ b/src/views/account/RoleList.jsx @@ -1,12 +1,14 @@ -import { useState, useEffect } from 'react' -import { Row, Col, Space, Button, Table, TreeSelect, Typography, Modal, App, Form, Input } from 'antd' -import { useTranslation } from 'react-i18next' -import useAccountStore from '@/stores/Account' -import { fetchRoleList, fetchPermissionList, fetchPermissionListByRoleId } from '@/stores/Account' -import dayjs from 'dayjs' -import { isEmpty } from '@/utils/commons' import RequireAuth from '@/components/RequireAuth' import { PERM_ROLE_NEW } from '@/config' +import useAccountStore, { fetchPermissionList, fetchPermissionListByRoleId, fetchRoleList } from '@/stores/Account' +import { isEmpty } from '@/utils/commons' +import { + SyncOutlined, +} from '@ant-design/icons' +import { App, Button, Col, Form, Input, Modal, Row, Space, Table, Tag, TreeSelect, Typography } from 'antd' +import dayjs from 'dayjs' +import { useEffect, useState } from 'react' +import { useTranslation } from 'react-i18next' const { Title } = Typography @@ -15,7 +17,7 @@ function RoleList() { const roleListColumns = [ { - title: t('account:rolename'), + title: t('account:roleName'), dataIndex: 'role_name', }, { @@ -30,8 +32,10 @@ function RoleList() { }, ] - function actionRender(text, role) { - if (role.role_id > 1) { + function actionRender(_, role) { + if (role.role_id == 1) { + return (} color='warning'>不能修改) + } else { return ( ) @@ -49,7 +53,7 @@ function RoleList() { }, {}) } - useEffect (() => { + useEffect(() => { setDataLoading(true) fetchRoleList() .then(r => { @@ -110,7 +114,7 @@ function RoleList() { const { notification, modal } = App.useApp() const onRoleSeleted = (role) => { - fetchPermissionListByRoleId({role_id: role.role_id}) + fetchPermissionListByRoleId({ role_id: role.role_id }) .then(result => { role.res_array = result.map(r => r.res_id) roleForm.setFieldsValue(role) @@ -154,71 +158,71 @@ function RoleList() { autoFocus: true, htmlType: 'submit', }} - title={t('account:management.newRole')} + title={t('account:detail')} open={isRoleModalOpen} onOk={() => setRoleModalOpen(false)} onCancel={() => setRoleModalOpen(false)} destroyOnClose={true} clearOnDestroy={true} modalRender={(dom) => (
+ name='RoleForm' + form={roleForm} + layout='vertical' + size='large' + style={{ + maxWidth: 600, + }} + onFinish={onRoleFinish} + onFinishFailed={onRoleFailed} + autoComplete='off' + > {dom}
)} > - - - - - - - + + + + + + + - {t('account:management.roleList')} + {t('account:roleList')} - + @@ -230,6 +234,7 @@ function RoleList() { loading={dataLoading} rowKey='role_id' pagination={{ + pageSize: 20, showQuickJumper: true, showLessItems: true, showSizeChanger: true, diff --git a/src/views/products/Audit.jsx b/src/views/products/Audit.jsx index 9646fd7..f654d20 100644 --- a/src/views/products/Audit.jsx +++ b/src/views/products/Audit.jsx @@ -62,6 +62,8 @@ const PriceTable = ({ dataSource, refresh }) => { const { message, notification } = App.useApp(); const stateMapVal = useProductsAuditStatesMapVal(); + // console.log(dataSource); + const handleAuditPriceItem = (state, row) => { postProductsQuoteAuditAction(state, { id: row.id, travel_agency_id: activeAgency.travel_agency_id }) .then((json) => { @@ -83,11 +85,9 @@ const PriceTable = ({ dataSource, refresh }) => { }; const columns = [ - { key: 'title', dataIndex: ['info', 'title'], width: '16rem', title: t('Title'), onCell: (r, index) => ({ rowSpan: r.rowSpan }) }, - { key: 'adult', title: t('AgeType.Adult'), render: (_, { value, currency, unit_name }) => `${value} ${currency} / ${unit_name}` }, - { key: 'child', title: t('AgeType.Child'), render: (_, { value, currency, unit_name }) => `${value} ${currency} / ${unit_name}` }, - // {key: 'price', title: t('Currency'), }, - // {key: 'currency', title: t('Currency'), }, + { key: 'title', dataIndex: ['info', 'title'], width: '16rem', title: t('Title'), onCell: (r, index) => ({ rowSpan: r.rowSpan }), render: (text, r) => text || r.lgc_details?.['2']?.title || r.lgc_details?.['1']?.title || '' }, + { key: 'adult', title: t('AgeType.Adult'), render: (_, { adult_cost, currency, unit_id, unit_name }) => `${adult_cost} ${currency} / ${t(`PriceUnit.${unit_id}`)}` }, + { key: 'child', title: t('AgeType.Child'), render: (_, { child_cost, currency, unit_id, unit_name }) => `${child_cost} ${currency} / ${t(`PriceUnit.${unit_id}`)}` }, // {key: 'unit', title: t('Unit'), }, { key: 'groupSize', @@ -135,16 +135,33 @@ const TypesPanels = (props) => { const [activeKey, setActiveKey] = useState([]); const [showTypes, setShowTypes] = useState([]); useEffect(() => { - // 只显示有产品的类型; 展开产品的价格表, 合并名称列 + // 只显示有产品的类型; 展开产品的价格表, 合并名称列; 转化为价格主表, 携带产品属性信息 const hasDataTypes = Object.keys(agencyProducts); const _show = productsTypes .filter((kk) => hasDataTypes.includes(kk.value)) .map((ele) => ({ ...ele, + extra: t('Table.Total', { total: agencyProducts[ele.value].length }), children: ( r.concat(c.quotation.map((q, i) => ({ ...q, weekdays: q.weekdays.split(',').filter(Boolean).map(w => t(`weekdaysShort.${w}`)).join(', '), info: c.info, rowSpan: i === 0 ? c.quotation.length : 0 }))), [])} + dataSource={agencyProducts[ele.value].reduce( + (r, c) => + r.concat( + c.quotation.map((q, i) => ({ + ...q, + weekdays: q.weekdays + .split(',') + .filter(Boolean) + .map((w) => t(`weekdaysShort.${w}`)) + .join(', '), + info: c.info, + lgc_details: c.lgc_details.reduce((rlgc, clgc) => ({...r, [clgc.lgc]: clgc}), {}), + rowSpan: i === 0 ? c.quotation.length : 0, + })) + ), + [] + )} refresh={props.refresh} /> ), diff --git a/src/views/products/Detail/Extras.jsx b/src/views/products/Detail/Extras.jsx index b5e0f1b..2ee8587 100644 --- a/src/views/products/Detail/Extras.jsx +++ b/src/views/products/Detail/Extras.jsx @@ -1,19 +1,22 @@ -import { createContext, useContext, useEffect, useState } from 'react'; +import { useEffect, useState } from 'react'; import { useParams } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; import { App, Table, Button, Modal, Popconfirm } from 'antd'; -import useProductsStore, { getAgencyProductExtrasAction, getAgencyProductsAction, addProductExtraAction, delProductExtrasAction } from '@/stores/Products/Index'; -import { isEmpty, cloneDeep } from '@/utils/commons'; +import { getAgencyProductExtrasAction, getAgencyProductsAction, addProductExtraAction, delProductExtrasAction } from '@/stores/Products/Index'; +import { cloneDeep } from '@/utils/commons'; import SearchForm from '@/components/SearchForm'; import RequireAuth from '@/components/RequireAuth'; import { PERM_PRODUCTS_MANAGEMENT } from '@/config'; +import { useProductsTypesMapVal } from '@/hooks/useProductsSets'; const NewAddonModal = ({ onPick, ...props }) => { const { travel_agency_id, use_year } = useParams(); const { t } = useTranslation(); const { notification, message } = App.useApp(); + const productsTypesMapVal = useProductsTypesMapVal(); + const [open, setOpen] = useState(false); const [loading, setLoading] = useState(false); // bind loading const [searchLoading, setSearchLoading] = useState(false); @@ -24,27 +27,26 @@ const NewAddonModal = ({ onPick, ...props }) => { const { starttime, endtime, ...param } = copyObject; setSearchLoading(true); setSearchResult([]); - const result = await getAgencyProductsAction({ ...param, audit_state: '1', travel_agency_id, use_year }); + // debug: audit_state: '1', + const result = await getAgencyProductsAction({ ...param, audit_state: '0', travel_agency_id, use_year }); setSearchResult(result?.products || []); setSearchLoading(false); }; const handleAddExtras = async (item) => { - // const success = await fetchBindOrder({ coli_sn, conversationid: currentConversationID }); - // success ? message.success('绑定成功') : message.error('绑定失败'); - // setOpen(false); if (typeof onPick === 'function') { onPick(item); } }; - // todo: + // todo: 如何显示价格表 const searchResultColumns = [ + { key: 'ptype', dataIndex: ['info', 'product_type_id'], width: '6rem', title: t('products:ProductType'), render: (text, r) => productsTypesMapVal[text].label }, { key: 'title', dataIndex: ['info', 'title'], width: '16rem', title: t('products:Title') }, { title: t('products:price'), - dataIndex: ['quotation', '0', 'value'], + dataIndex: ['quotation', '0', 'adult_cost'], width: '10rem', - render: (_, { quotation }) => `${quotation[0].value} ${quotation[0].currency} / ${quotation[0].unit_name}`, // todo: 成人 儿童 + render: (_, { quotation }) => `${quotation[0].adult_cost} ${quotation[0].currency} / ${quotation[0].unit_name}`, // todo: 成人 儿童 }, { key: 'action', @@ -63,7 +65,7 @@ const NewAddonModal = ({ onPick, ...props }) => { return ( <> setOpen(false)} destroyOnClose> @@ -119,14 +121,14 @@ const Extras = ({ productId, onChange, ...props }) => { setExtrasData(prev => [].concat(prev, [item])); // todo: 提交后端; 重复绑定同一个 const newSuccess = await addProductExtraAction({ travel_agency_id, id: productId, extras: [2] }); - newSuccess ? message.success(t('Success')) : message.error(t('Failed')); + newSuccess ? message.success(t('Action')+t('Success')) : message.error(t('Action')+t('Failed')); await handleGetAgencyProductExtras(); } const handleDelAddon = async (item) => { // todo: 提交后端 const delSuccess = await delProductExtrasAction({ travel_agency_id, id: productId, extras: [2] }); - delSuccess ? message.success(t('Success')) : message.error(t('Failed')); + delSuccess ? message.success(t('Action')+t('Success')) : message.error(t('Action')+t('Failed')); await handleGetAgencyProductExtras(); }; @@ -143,7 +145,7 @@ const Extras = ({ productId, onChange, ...props }) => { dataIndex: ['quotation', '0', 'value'], width: '10rem', - render: (_, { quotation }) => `${quotation[0].value} ${quotation[0].currency} / ${quotation[0].unit_name}`, // todo: 成人 儿童 + render: (_, { quotation }) => `${quotation[0].adult_cost} ${quotation[0].currency} / ${quotation[0].unit_name}`, // todo: 成人 儿童 }, // { title: t('products:Types'), dataIndex: 'age_type', width: '40%', }, { @@ -151,7 +153,7 @@ const Extras = ({ productId, onChange, ...props }) => { dataIndex: 'operation', width: '4rem', render: (_, r) => ( - handleDelAddon(r)} > + handleDelAddon(r)} okText={t('Yes')} > diff --git a/src/views/products/Manage.jsx b/src/views/products/Manage.jsx index 3e5d3b6..65cfc6b 100644 --- a/src/views/products/Manage.jsx +++ b/src/views/products/Manage.jsx @@ -49,10 +49,10 @@ function Index() { const columns = [ { title: t('products:Vendor'), key: 'vendor', dataIndex: 'travel_agency_name' }, - { title: t('products:CreatedBy'), key: 'created_by', dataIndex: 'created_by' }, + { title: t('products:CreatedBy'), key: 'created_by', dataIndex: 'created_by_name' }, { title: t('products:CreateDate'), key: 'create_date', dataIndex: 'create_date' }, { title: t('products:AuState'), key: 'audit_state', dataIndex: 'audit_state' }, - { title: t('products:AuditedBy'), key: 'audited_by', dataIndex: 'audited_by' }, + { title: t('products:AuditedBy'), key: 'audited_by', dataIndex: 'audited_by_name' }, { title: t('products:AuditDate'), key: 'audit_date', dataIndex: 'audit_date' }, { title: '',