From 3ec9a438339eb0d7a8ae07ecbbd91c84bf3dd719 Mon Sep 17 00:00:00 2001 From: Jimmy Liow Date: Mon, 24 Jun 2024 14:13:27 +0800 Subject: [PATCH 01/10] =?UTF-8?q?=E5=AE=8C=E6=88=90=E6=9D=83=E9=99=90?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=E3=80=81=E7=BC=96=E8=BE=91=E3=80=81=E5=88=86?= =?UTF-8?q?=E9=85=8D=E8=A7=92=E8=89=B2=E6=9D=83=E9=99=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/stores/Account.js | 16 +++- src/views/account/Management.jsx | 26 +++-- src/views/account/RoleList.jsx | 158 ++++++++++++------------------- 3 files changed, 90 insertions(+), 110 deletions(-) diff --git a/src/stores/Account.js b/src/stores/Account.js index 6c6410e..f6cfa4f 100644 --- a/src/stores/Account.js +++ b/src/stores/Account.js @@ -45,6 +45,20 @@ export const fetchRoleList = async () => { return errcode !== 0 ? {} : result } +export const fetchPermissionList = async () => { + + const { errcode, result } = await fetchJSON( + `${HT_HOST}/service-CooperateSOA/get_all_permission_list`) + return errcode !== 0 ? {} : result +} + +export const fetchPermissionListByRoleId = async (params) => { + + const { errcode, result } = await fetchJSON( + `${HT_HOST}/service-CooperateSOA/get_role_permission_list`, params) + return errcode !== 0 ? {} : result +} + const useAccountStore = create((set, get) => ({ accountList: [], @@ -82,7 +96,7 @@ const useAccountStore = create((set, get) => ({ const formData = new FormData() formData.append('role_id', formValues.role_id) formData.append('role_name', formValues.role_name) - formData.append('res_ids', '2,3') + formData.append('res_ids', formValues.res_array.join(',')) return postRoleForm(formData) }, diff --git a/src/views/account/Management.jsx b/src/views/account/Management.jsx index 6c2c323..e895ba4 100644 --- a/src/views/account/Management.jsx +++ b/src/views/account/Management.jsx @@ -1,9 +1,11 @@ -import { useState } from 'react' +import { useState, useEffect } from 'react' import { Row, Col, Space, Button, Table, Select, TreeSelect, Typography, Modal, App, Form, Input } from 'antd' import { ExclamationCircleFilled } from '@ant-design/icons' import { useTranslation } from 'react-i18next' import useFormStore from '@/stores/Form' import useAuthStore from '@/stores/Auth' +import dayjs from 'dayjs' +import { isEmpty } from '@/utils/commons' import useAccountStore from '@/stores/Account' import { fetchRoleList } from '@/stores/Account' import SearchForm from '@/components/SearchForm' @@ -40,6 +42,7 @@ function Management() { { title: t('account:lastLogin'), dataIndex: 'lastLogin', + render: (text) => (isEmpty(text) ? '' : dayjs(text).format('YYYY-MM-DD HH:mm:ss')) }, { title: t('account:action'), @@ -74,16 +77,21 @@ function Management() { const { notification, modal } = App.useApp() + useEffect (() => { + fetchRoleList() + .then((roleList) => { + setRoleAllList(roleList.map(r => { + return { + value: r.role_id, + label: r.role_name, + disabled: r.role_id === 1 + } + })) + }) + }, []) + const onAccountSeleted = async (account) => { accountForm.setFieldsValue(account) - const roleList = await fetchRoleList() - setRoleAllList(roleList.map(r => { - return { - value: r.role_id, - label: r.role_name, - disabled: r.role_id === 1 - } - })) setAccountModalOpen(true) } diff --git a/src/views/account/RoleList.jsx b/src/views/account/RoleList.jsx index 9686304..db2e6ae 100644 --- a/src/views/account/RoleList.jsx +++ b/src/views/account/RoleList.jsx @@ -1,103 +1,15 @@ import { useState, useEffect } from 'react' -import { Row, Col, Space, Button, Table, Select, TreeSelect, Typography, Modal, App, Form, Input } from 'antd' -import { ExclamationCircleFilled } from '@ant-design/icons' +import { Row, Col, Space, Button, Table, TreeSelect, Typography, Modal, App, Form, Input } from 'antd' import { useTranslation } from 'react-i18next' -import useFormStore from '@/stores/Form' -import useAuthStore from '@/stores/Auth' import useAccountStore from '@/stores/Account' -import { fetchRoleList } from '@/stores/Account' -import SearchForm from '@/components/SearchForm' +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' const { Title } = Typography -const permissionData = [ - { - title: '海外供应商', - value: 'oversea-0', - key: 'oversea-0', - children: [ - { - title: '所有海外功能', - value: 'oversea-0-0', - key: 'oversea-0-0', - }, - ], - }, - { - title: '机票管理', - value: '0-0', - key: '0-0', - children: [ - { - title: '录入机票价格', - value: '0-0-0', - key: '0-0-0', - }, - ], - }, - { - title: '产品管理', - value: '0-1', - key: '0-1', - children: [ - { - title: '搜索供应商产品', - value: 'B-1-0', - key: 'B-1-0', - }, - { - title: '录入产品价格', - value: '0-1-0', - key: '0-1-0', - }, - { - title: '新增产品描述', - value: '0-1-1', - key: '0-1-1', - }, - { - title: '复制供应商产品信息', - value: '0-1-2', - key: '0-1-2', - }, - ], - }, - { - title: '账号管理', - value: '2-1', - key: '2-1', - children: [ - { - title: '搜索账号', - value: '2-1-01', - key: '2-1-01', - }, - { - title: '新增账号', - value: '2-1-11', - key: '2-1-11', - }, - { - title: '禁用账号', - value: '2-1-21', - key: '2-1-21', - }, - { - title: '重置账号密码', - value: '2-1-31', - key: '2-1-31', - }, - { - title: '新增角色', - value: '2-1-41', - key: '2-1-41', - }, - ], - }, -] - function RoleList() { const { t } = useTranslation() @@ -109,6 +21,7 @@ function RoleList() { { title: t('account:createdOn'), dataIndex: 'created_on', + render: (text) => (isEmpty(text) ? '' : dayjs(text).format('YYYY-MM-DD HH:mm:ss')) }, { title: t('account:action'), @@ -126,10 +39,16 @@ function RoleList() { } const onPermissionChange = (newValue) => { - console.log('onChange ', newValue) setPermissionValue(newValue) } + function groupByParam(array, param) { + return array.reduce((result, item) => { + (result[item[param]] = result[item[param]] || []).push(item) + return result + }, {}) + } + useEffect (() => { setDataLoading(true) fetchRoleList() @@ -139,9 +58,38 @@ function RoleList() { .finally(() => { setDataLoading(false) }) + + const permissionTree = [] + fetchPermissionList() + .then(r => { + + const groupPermissionData = groupByParam(r, 'res_category') + const categoryKeys = Object.keys(groupPermissionData) + + categoryKeys.forEach((categoryName) => { + const permissisonList = groupPermissionData[categoryName] + const categoryGroup = { + title: categoryName, + value: categoryName, + key: categoryName, + children: permissisonList.map(p => { + return { + disableCheckbox: p.res_id == 1, + title: p.res_name, + value: p.res_id, + key: p.res_id, + } + }) + } + permissionTree.push(categoryGroup) + }) + + setPermissionTreeData(permissionTree) + }) }, []) - const [permissionValue, setPermissionValue] = useState(['0-0-0']) + const [permissionValue, setPermissionValue] = useState(['4','5']) + const [permissionTreeData, setPermissionTreeData] = useState(['0-0-0']) const [isRoleModalOpen, setRoleModalOpen] = useState(false) const [dataLoading, setDataLoading] = useState(false) const [roleAllList, setRoleAllList] = useState([]) @@ -154,7 +102,11 @@ function RoleList() { const { notification, modal } = App.useApp() const onRoleSeleted = (role) => { - roleForm.setFieldsValue(role) + fetchPermissionListByRoleId({role_id: role.role_id}) + .then(result => { + role.res_array = result.map(r => r.res_id) + roleForm.setFieldsValue(role) + }) setRoleModalOpen(true) } @@ -165,10 +117,14 @@ function RoleList() { } const onRoleFinish = (values) => { - console.log(values) saveOrUpdateRole(values) + .then(() => { + fetchRoleList() + .then(r => { + setRoleAllList(r) + }) + }) .catch(ex => { - console.info(ex.message) notification.error({ message: 'Notification', description: ex.message, @@ -179,7 +135,6 @@ function RoleList() { } const onRoleFailed = (error) => { - console.log('Failed:', error) // form.resetFields() } @@ -225,8 +180,11 @@ function RoleList() { > - - + Date: Mon, 24 Jun 2024 14:31:13 +0800 Subject: [PATCH 02/10] =?UTF-8?q?=E5=AF=BC=E8=88=AA=E5=92=8C=E8=8F=9C?= =?UTF-8?q?=E5=8D=95=E5=A2=9E=E5=8A=A0=E6=9D=83=E9=99=90=E5=88=A4=E6=96=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/stores/Auth.js | 3 +++ src/views/App.jsx | 20 +++++++++++--------- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/src/stores/Auth.js b/src/stores/Auth.js index fe0900e..d2371d7 100644 --- a/src/stores/Auth.js +++ b/src/stores/Auth.js @@ -50,6 +50,9 @@ const useAuthStore = create((set, get) => ({ }, isPermitted: (perm) => { + // 测试权限使用: + // if (perm === '/account/management') return false + // if (perm === '/account/role/new') return false return true // 以上是 Hardcode 判断 // 以下是权限列表从数据库读取后使用的方法 diff --git a/src/views/App.jsx b/src/views/App.jsx index 779993c..607dfe3 100644 --- a/src/views/App.jsx +++ b/src/views/App.jsx @@ -17,6 +17,8 @@ import useNoticeStore from '@/stores/Notice'; import useAuthStore from '@/stores/Auth' import { usingStorage } from '@/hooks/usingStorage' +import { PERM_ACCOUNT_MANAGEMENT, PERM_ROLE_NEW, PERM_OVERSEA, PERM_AIR_TICKET } from '@/config' + const { Header, Content, Footer } = Layout; const { Title } = Typography; @@ -25,8 +27,8 @@ function App() { const [password, setPassword] = useState('') - const [validateUserPassword, tokenTimeout] = useAuthStore( - (state) => [state.validateUserPassword, state.tokenTimeout]) + const [validateUserPassword, tokenTimeout, isPermitted] = useAuthStore( + (state) => [state.validateUserPassword, state.tokenTimeout, state.isPermitted]) const { loginToken, userDetail } = usingStorage() @@ -120,11 +122,11 @@ function App() { mode='horizontal' selectedKeys={[defaultPath]} items={[ - { key: 'reservation', label: {t('menu.Reservation')} }, - { key: 'invoice', label: {t('menu.Invoice')} }, - { key: 'feedback', label: {t('menu.Feedback')} }, - { key: 'report', label: {t('menu.Report')} }, - { key: 'airticket', label: {t('menu.Airticket')} }, + isPermitted(PERM_OVERSEA) ? { key: 'reservation', label: {t('menu.Reservation')} } : null, + isPermitted(PERM_OVERSEA) ? { key: 'invoice', label: {t('menu.Invoice')} } : null, + isPermitted(PERM_OVERSEA) ? { key: 'feedback', label: {t('menu.Feedback')} } : null, + isPermitted(PERM_OVERSEA) ? { key: 'report', label: {t('menu.Report')} } : null, + isPermitted(PERM_AIR_TICKET) ? { key: 'airticket', label: {t('menu.Airticket')} } : null, { key: 'notice', label: ( @@ -148,8 +150,8 @@ function App() { items: [...[ { label: {t('ChangePassword')}, key: '0' }, { label: {t('Profile')}, key: '1' }, - { label: {t('account:management.tile')}, key: '3' }, - { label: {t('account:management.roleList')}, key: '4' }, + 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' }, { label: {t('Logout')}, key: '99' }, ], From 4afb1d4371082a5967d86c21f648480d513e6795 Mon Sep 17 00:00:00 2001 From: Jimmy Liow Date: Mon, 24 Jun 2024 15:11:57 +0800 Subject: [PATCH 03/10] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E6=9D=83=E9=99=90?= =?UTF-8?q?=E7=B1=BB=E5=88=AB=E5=90=8D=E7=A7=B0=EF=BC=8C=E5=88=9D=E5=A7=8B?= =?UTF-8?q?=E5=8C=96=E6=95=B0=E6=8D=AE=20SQL?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- doc/RBAC 权限.sql | 28 ++++++++++++++++++++++++---- src/config.js | 2 +- src/views/account/RoleList.jsx | 12 ++++++++++-- 3 files changed, 35 insertions(+), 7 deletions(-) diff --git a/doc/RBAC 权限.sql b/doc/RBAC 权限.sql index 6e70db9..b463af5 100644 --- a/doc/RBAC 权限.sql +++ b/doc/RBAC 权限.sql @@ -45,14 +45,34 @@ VALUES ('技术研发部') INSERT INTO [dbo].[auth_resource] ([res_name] ,[res_pattern], [res_category]) VALUES ('所有权限', '*', 'system') INSERT INTO [dbo].[auth_resource] ([res_name] ,[res_pattern], [res_category]) -VALUES ('最新团计划', '/reservation/newest', 'oversea') +VALUES ('管理账号', '/account/management', 'system') INSERT INTO [dbo].[auth_resource] ([res_name] ,[res_pattern], [res_category]) -VALUES ('账单', '/invoice', 'oversea') +VALUES ('新增账号', '/account/new', 'system') +INSERT INTO [dbo].[auth_resource] ([res_name] ,[res_pattern], [res_category]) +VALUES ('禁用账号', '/account/disable', 'system') +INSERT INTO [dbo].[auth_resource] ([res_name] ,[res_pattern], [res_category]) +VALUES ('重置密码', '/account/reset-password', 'system') +INSERT INTO [dbo].[auth_resource] ([res_name] ,[res_pattern], [res_category]) +VALUES ('管理角色', '/account/role-new', 'system') + +INSERT INTO [dbo].[auth_resource] ([res_name] ,[res_pattern], [res_category]) +VALUES ('所有海外功能', '/oversea/all', 'oversea') +INSERT INTO [dbo].[auth_resource] ([res_name] ,[res_pattern], [res_category]) +VALUES ('所有国内功能', '/domestic/all', 'domestic') +INSERT INTO [dbo].[auth_resource] ([res_name] ,[res_pattern], [res_category]) +VALUES ('所有机票功能', '/air-ticket/all', 'air-ticket') +-- 价格管理 +INSERT INTO [dbo].[auth_resource] ([res_name] ,[res_pattern], [res_category]) +VALUES ('管理产品', '/products/*', 'products') +INSERT INTO [dbo].[auth_resource] ([res_name] ,[res_pattern], [res_category]) +VALUES ('审核信息', '/products/info/audit', 'products') +INSERT INTO [dbo].[auth_resource] ([res_name] ,[res_pattern], [res_category]) +VALUES ('录入信息', '/products/info/put', 'products') INSERT INTO [dbo].[auth_resource] ([res_name] ,[res_pattern], [res_category]) -VALUES ('账号权限管理', '/account/management', 'system') +VALUES ('审核价格', '/products/offer/audit', 'products') INSERT INTO [dbo].[auth_resource] ([res_name] ,[res_pattern], [res_category]) -VALUES ('新增角色', '/account/new-role', 'system') +VALUES ('录入价格', '/products/offer/put', 'products') INSERT INTO [dbo].[auth_permission] ([role_id] ,[res_id]) VALUES (1, 1) diff --git a/src/config.js b/src/config.js index fc69f82..1d39e0e 100644 --- a/src/config.js +++ b/src/config.js @@ -18,7 +18,7 @@ export const PERM_ACCOUNT_MANAGEMENT = '/account/management' export const PERM_ACCOUNT_NEW = '/account/new' export const PERM_ACCOUNT_DISABLE = '/account/disable' export const PERM_ACCOUNT_RESET_PASSWORD = '/account/reset-password' -export const PERM_ROLE_NEW = '/account/role/new' +export const PERM_ROLE_NEW = '/account/role-new' // 海外供应商 // category: oversea diff --git a/src/views/account/RoleList.jsx b/src/views/account/RoleList.jsx index db2e6ae..feb2383 100644 --- a/src/views/account/RoleList.jsx +++ b/src/views/account/RoleList.jsx @@ -59,6 +59,14 @@ function RoleList() { setDataLoading(false) }) + const categoryMap = new Map([ + ['system', '系统管理'], + ['oversea', '海外供应商'], + ['domestic', '国内供应商'], + ['air-ticket', '机票供应商'], + ['products', '产品价格'], + ]); + const permissionTree = [] fetchPermissionList() .then(r => { @@ -69,7 +77,7 @@ function RoleList() { categoryKeys.forEach((categoryName) => { const permissisonList = groupPermissionData[categoryName] const categoryGroup = { - title: categoryName, + title: categoryMap.get(categoryName), value: categoryName, key: categoryName, children: permissisonList.map(p => { @@ -77,7 +85,7 @@ function RoleList() { disableCheckbox: p.res_id == 1, title: p.res_name, value: p.res_id, - key: p.res_id, + key: p.res_name, } }) } From efad9d8697963402549a91b6a9b319d74d98bcd3 Mon Sep 17 00:00:00 2001 From: Jimmy Liow Date: Tue, 25 Jun 2024 09:17:46 +0800 Subject: [PATCH 04/10] =?UTF-8?q?feat:=20=E5=AE=8C=E6=88=90=E6=96=B0?= =?UTF-8?q?=E5=A2=9E/=E7=BC=96=E8=BE=91=E8=B4=A6=E5=8F=B7=E9=80=89?= =?UTF-8?q?=E6=8B=A9=E4=BE=9B=E5=BA=94=E5=95=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/stores/Account.js | 9 +++- src/views/account/Management.jsx | 82 ++++++++++++++++++++++++-------- src/views/account/RoleList.jsx | 4 +- 3 files changed, 71 insertions(+), 24 deletions(-) diff --git a/src/stores/Account.js b/src/stores/Account.js index f6cfa4f..804299a 100644 --- a/src/stores/Account.js +++ b/src/stores/Account.js @@ -59,6 +59,13 @@ export const fetchPermissionListByRoleId = async (params) => { return errcode !== 0 ? {} : result } +export const fetchTravelAgencyByName = async (name) => { + + const { errcode, result } = await fetchJSON( + `${HT_HOST}/Service_BaseInfoWeb/VendorList`, {q: name}) + return errcode !== 0 ? {} : result +} + const useAccountStore = create((set, get) => ({ accountList: [], @@ -138,7 +145,7 @@ const useAccountStore = create((set, get) => ({ realname: r.real_name, email: r.email, lastLogin: r.wu_lastlogindate, - travelAgency: r.travel_agency_name, + travelAgencyName: r.travel_agency_name, travelAgencyId: r.travel_agency_id, // 数据库支持逗号分隔多角色(5,6,7),目前界面只需单个。 roleId: parseInt(r.roles), diff --git a/src/views/account/Management.jsx b/src/views/account/Management.jsx index e895ba4..d3d181c 100644 --- a/src/views/account/Management.jsx +++ b/src/views/account/Management.jsx @@ -2,15 +2,13 @@ import { useState, useEffect } from 'react' import { Row, Col, Space, Button, Table, Select, TreeSelect, Typography, Modal, App, Form, Input } from 'antd' import { ExclamationCircleFilled } from '@ant-design/icons' import { useTranslation } from 'react-i18next' -import useFormStore from '@/stores/Form' -import useAuthStore from '@/stores/Auth' +import { fetchTravelAgencyByName } from '@/stores/Account' 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 RequireAuth from '@/components/RequireAuth' -import { PERM_ROLE_NEW } from '@/config' const { Title } = Typography @@ -29,7 +27,7 @@ function Management() { }, { title: t('account:travelAgency'), - dataIndex: 'travelAgency', + dataIndex: 'travelAgencyName', }, { title: t('account:email'), @@ -69,12 +67,15 @@ function Management() { const [isAccountModalOpen, setAccountModalOpen] = useState(false) const [dataLoading, setDataLoading] = useState(false) const [roleAllList, setRoleAllList] = useState([]) + const [travelAgencyList, setTravelAgencyList] = useState([]) + const [currentTravelAgency, setCurrentTravelAgency] = useState(null) const [accountForm] = Form.useForm() const [searchAccountByCriteria, accountList, disableAccount, saveOrUpdateAccount, resetAccountPassword] = useAccountStore((state) => [state.searchAccountByCriteria, state.accountList, state.disableAccount, state.saveOrUpdateAccount, state.resetAccountPassword]) + const formValues = useFormStore(state => state.formValues) const { notification, modal } = App.useApp() useEffect (() => { @@ -90,14 +91,39 @@ function Management() { }) }, []) + const handelAccountSearch = () => { + setDataLoading(true) + searchAccountByCriteria(formValues) + .catch(ex => { + notification.error({ + message: 'Notification', + description: ex.message, + placement: 'top', + duration: 4, + }) + }) + .finally(() => { + setDataLoading(false) + }) + } + const onAccountSeleted = async (account) => { + console.info(account) + setTravelAgencyList([{ + label: account.travelAgencyName, + value: account.travelAgencyId + }]) accountForm.setFieldsValue(account) + setCurrentTravelAgency(account.travelAgencyId) setAccountModalOpen(true) } const onAccountFinish = (values) => { console.log(values) saveOrUpdateAccount(values) + .then(() => { + handelAccountSearch() + }) .catch(ex => { notification.error({ message: 'Notification', @@ -113,6 +139,24 @@ function Management() { // form.resetFields() } + // [{ value: 33032, label: 'test海外地接B' }] + const handleTravelAgencySearch = (newValue) => { + fetchTravelAgencyByName(newValue) + .then(result => { + setTravelAgencyList(result.map(r => { + return { + label: r.travel_agency_name, + value: r.travel_agency_id + } + })) + }) + } + + const handleTravelAgencyChange = (newValue) => { + console.info(newValue) + setCurrentTravelAgency(newValue) + } + const showDisableConfirm = (account) => { modal.confirm({ title: 'Do you want to disable this account?', @@ -227,7 +271,16 @@ function Management() { }, ]} > - + { - console.info(formValues) - setDataLoading(true) - searchAccountByCriteria(formValues) - .catch(ex => { - notification.error({ - message: 'Notification', - description: ex.message, - placement: 'top', - duration: 4, - }) - }) - .finally(() => { - setDataLoading(false) - }) + onSubmit={() => { + handelAccountSearch() }} /> diff --git a/src/views/account/RoleList.jsx b/src/views/account/RoleList.jsx index feb2383..3e57a52 100644 --- a/src/views/account/RoleList.jsx +++ b/src/views/account/RoleList.jsx @@ -96,8 +96,8 @@ function RoleList() { }) }, []) - const [permissionValue, setPermissionValue] = useState(['4','5']) - const [permissionTreeData, setPermissionTreeData] = useState(['0-0-0']) + const [permissionValue, setPermissionValue] = useState([]) + const [permissionTreeData, setPermissionTreeData] = useState([]) const [isRoleModalOpen, setRoleModalOpen] = useState(false) const [dataLoading, setDataLoading] = useState(false) const [roleAllList, setRoleAllList] = useState([]) From 702d7876c07d76c86e317f3d54f48eb60790eef4 Mon Sep 17 00:00:00 2001 From: Jimmy Liow Date: Tue, 25 Jun 2024 15:15:52 +0800 Subject: [PATCH 05/10] =?UTF-8?q?feat:=20=E5=88=9D=E5=A7=8B=E5=8C=96?= =?UTF-8?q?=E8=B4=A6=E5=8F=B7=E6=9D=83=E9=99=90=E3=80=81=E7=B3=BB=E7=BB=9F?= =?UTF-8?q?=E5=BC=80=E5=A7=8B=E5=88=A4=E6=96=AD=E6=9D=83=E9=99=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main.jsx | 34 +++++++++++------ src/stores/Auth.js | 63 ++++++++++++++++---------------- src/views/App.jsx | 21 ++++++++--- src/views/account/Management.jsx | 6 ++- src/views/account/RoleList.jsx | 2 +- 5 files changed, 76 insertions(+), 50 deletions(-) diff --git a/src/main.jsx b/src/main.jsx index 9114460..b1eca60 100644 --- a/src/main.jsx +++ b/src/main.jsx @@ -1,5 +1,4 @@ import React from "react"; -import { configure } from "mobx"; import ReactDOM from "react-dom/client"; import { createBrowserRouter, @@ -32,19 +31,32 @@ import InvoicePaidDetail from "@/views/invoice/PaidDetail"; import Airticket from "@/views/airticket/Index"; import AirticketPlan from "@/views/airticket/Plan"; import { ThemeContext } from '@/stores/ThemeContext' +import { usingStorage } from '@/hooks/usingStorage' +import { isNotEmpty } from '@/utils/commons' +import { appendRequestParams } from '@/utils/request' +import useAuthStore from '@/stores/Auth' import { PERM_ACCOUNT_MANAGEMENT, PERM_ROLE_NEW, PERM_OVERSEA, PERM_AIR_TICKET } from '@/config' import './i18n'; -configure({ - useProxies: "ifavailable", - enforceActions: "observed", - computedRequiresReaction: true, - observableRequiresReaction: false, - reactionRequiresObservable: true, - disableErrorBoundaries: process.env.NODE_ENV == "production" -}); +onInit(() => { + + const { loginToken, userId } = usingStorage() + + if (isNotEmpty(loginToken)) { + appendRequestParams('token', loginToken) + + console.info('loginToken') + } + + if (isNotEmpty(userId)) { + useAuthStore.getState().loadUserPermission(userId) + console.info('loadUserPermission') + } +}) + +onInit() const router = createBrowserRouter([ { @@ -80,7 +92,7 @@ const router = createBrowserRouter([ { path: "/logout", element: }, ] } -]); +]) ReactDOM.createRoot(document.getElementById("root")).render( @@ -92,4 +104,4 @@ ReactDOM.createRoot(document.getElementById("root")).render( /> // -); +) diff --git a/src/stores/Auth.js b/src/stores/Auth.js index d2371d7..e7fc4bb 100644 --- a/src/stores/Auth.js +++ b/src/stores/Auth.js @@ -9,6 +9,8 @@ const KEY_TRAVEL_AGENCY_ID = 'G-INT:TRAVEL_AGENCY_ID' const KEY_USER_ID = 'G-INT:USER_ID' const KEY_USER_DETAIL = 'G-JSON:USER_DETAIL' +const WILDCARD_TOKEN = '*' + export const fetchLoginToken = async (username, password) => { const formData = new FormData() @@ -28,6 +30,13 @@ export const fetchUserDetail = async (loginToken) => { return errcode !== 0 ? {} : Result } +export const fetchPermissionListByUserId = async (userId) => { + + const { errcode, result } = await fetchJSON( + `${HT_HOST}/service-CooperateSOA/get_account_permission_list`, { wu_id: userId}) + return errcode !== 0 ? {} : result +} + async function fetchLastRequet() { const { errcode, result } = await fetchJSON(`${HT_HOST}/service-CooperateSOA/GetLastReqDate`) return errcode !== 0 ? {} : result @@ -41,68 +50,60 @@ const useAuthStore = create((set, get) => ({ loginStatus: 0, - loginUser: { - token: '', - telephone: '', - emailAddress: '', - cityId: 0, - permissionList: [], - }, + permissionList: [], isPermitted: (perm) => { + const { permissionList } = get() // 测试权限使用: // if (perm === '/account/management') return false // if (perm === '/account/role/new') return false - return true + // return true // 以上是 Hardcode 判断 // 以下是权限列表从数据库读取后使用的方法 - // return this.permissionList.some((value, key, arry) => { - // if (value.indexOf(WILDCARD_TOKEN) > -1) { - // return true - // } - // if (value === perm) { - // return true - // } - // return false - // }) - + return permissionList.some((value, key, arry) => { + if (value.indexOf(WILDCARD_TOKEN) > -1) { + return true + } + if (value === perm) { + return true + } + return false + }) }, validateUserPassword: async (usr, pwd) => { - const { startTokenInterval } = get() + const { startTokenInterval, loadUserPermission } = get() const { setStorage } = usingStorage() - const { token: loginToken } = await fetchLoginToken(usr, pwd) - - const userDetail = await fetchUserDetail(loginToken) + const { token: loginToken, WU_ID: userId } = await fetchLoginToken(usr, pwd) + await loadUserPermission(userId) set(() => ({ - loginUser: { - telephone: userDetail.LkPhone, - emailAddress: userDetail.LMI_listmail, - cityId: userDetail.citysn, - }, tokenTimeout: false, loginStatus: 302 })) setStorage(KEY_LOGIN_TOKEN, loginToken) - setStorage(KEY_USER_ID, userDetail.LMI_SN) + setStorage(KEY_USER_ID, userId) setStorage(KEY_TRAVEL_AGENCY_ID, userDetail.LMI_VEI_SN) - setStorage(KEY_USER_DETAIL, {username: userDetail.LoginName, travelAgencyName: userDetail.VName}) appendRequestParams('token', loginToken) // loadPageSpy(`${json.Result.VName}-${json.Result.LoginName}`) startTokenInterval() }, + loadUserPermission: async(userId) => { + const permissionResult = await fetchPermissionListByUserId(userId) + set(() => ({ + permissionList: permissionResult.map(p => p.res_pattern) + })) + }, + logout: () => { const { tokenInterval } = get() const { clearStorage } = usingStorage() clearStorage() clearInterval(tokenInterval) set(() => ({ - loginUser: { - }, loginStatus: 0, tokenInterval: null, tokenTimeout: true diff --git a/src/views/App.jsx b/src/views/App.jsx index 607dfe3..1882319 100644 --- a/src/views/App.jsx +++ b/src/views/App.jsx @@ -5,7 +5,6 @@ import { DownOutlined } from '@ant-design/icons'; import 'antd/dist/reset.css'; import AppLogo from '@/assets/logo-gh.png'; import { isEmpty } from '@/utils/commons'; -import { appendRequestParams } from '@/utils/request' import Language from '../i18n/LanguageSwitcher'; import { useTranslation } from 'react-i18next'; import zhLocale from 'antd/locale/zh_CN'; @@ -15,6 +14,7 @@ import ErrorBoundary from '@/components/ErrorBoundary' import { BUILD_VERSION, } from '@/config'; import useNoticeStore from '@/stores/Notice'; import useAuthStore from '@/stores/Auth' +import { fetchUserDetail} from '@/stores/Auth' import { usingStorage } from '@/hooks/usingStorage' import { PERM_ACCOUNT_MANAGEMENT, PERM_ROLE_NEW, PERM_OVERSEA, PERM_AIR_TICKET } from '@/config' @@ -23,14 +23,16 @@ const { Header, Content, Footer } = Layout; const { Title } = Typography; function App() { + const { t, i18n } = useTranslation(); const [password, setPassword] = useState('') + const [userDetail, setUserDetail] = useState({}) const [validateUserPassword, tokenTimeout, isPermitted] = useAuthStore( (state) => [state.validateUserPassword, state.tokenTimeout, state.isPermitted]) - const { loginToken, userDetail } = usingStorage() + const { loginToken } = usingStorage() const noticeUnRead = useNoticeStore((state) => state.noticeUnRead) const href = useHref() @@ -40,9 +42,16 @@ function App() { // 除了路由 /p...以外都需要登陆系统 const needToLogin = href !== '/login' && isEmpty(loginToken) - if (!needToLogin) { - appendRequestParams('token', loginToken) - } + useEffect(() => { + console.info('app.useEffect.loginToken: ' + loginToken) + fetchUserDetail(loginToken) + .then(u => { + setUserDetail({ + username: u.LoginName, + travelAgencyName: u.VName, + }) + }) + }, [loginToken]) useEffect(() => { if (needToLogin) { @@ -52,7 +61,7 @@ function App() { useEffect(() => { window.gtag('event', 'page_view', { page_location: window.location.href }); - }, [location]); + }, [location]) const onSubmit = () => { validateUserPassword(userDetail?.username, password) diff --git a/src/views/account/Management.jsx b/src/views/account/Management.jsx index d3d181c..517f03f 100644 --- a/src/views/account/Management.jsx +++ b/src/views/account/Management.jsx @@ -139,8 +139,8 @@ function Management() { // form.resetFields() } - // [{ value: 33032, label: 'test海外地接B' }] const handleTravelAgencySearch = (newValue) => { + setDataLoading(true) fetchTravelAgencyByName(newValue) .then(result => { setTravelAgencyList(result.map(r => { @@ -150,6 +150,9 @@ function Management() { } })) }) + .finally(() => { + setDataLoading(false) + }) } const handleTravelAgencyChange = (newValue) => { @@ -275,6 +278,7 @@ function Management() { options={travelAgencyList} value={currentTravelAgency} onChange={handleTravelAgencyChange} + loading={dataLoading} showSearch filterOption={false} onSearch={handleTravelAgencySearch} diff --git a/src/views/account/RoleList.jsx b/src/views/account/RoleList.jsx index 3e57a52..13a1501 100644 --- a/src/views/account/RoleList.jsx +++ b/src/views/account/RoleList.jsx @@ -85,7 +85,7 @@ function RoleList() { disableCheckbox: p.res_id == 1, title: p.res_name, value: p.res_id, - key: p.res_name, + key: p.res_id, } }) } From 752bd729eb3a613c8d876f3532adaa05319ced3c Mon Sep 17 00:00:00 2001 From: Jimmy Liow Date: Tue, 25 Jun 2024 15:17:01 +0800 Subject: [PATCH 06/10] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E5=88=9D?= =?UTF-8?q?=E5=A7=8B=E5=8C=96=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main.jsx | 22 +++++++--------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/src/main.jsx b/src/main.jsx index b1eca60..5c6a620 100644 --- a/src/main.jsx +++ b/src/main.jsx @@ -40,23 +40,15 @@ import { PERM_ACCOUNT_MANAGEMENT, PERM_ROLE_NEW, PERM_OVERSEA, PERM_AIR_TICKET } import './i18n'; -onInit(() => { +const { loginToken, userId } = usingStorage() - const { loginToken, userId } = usingStorage() +if (isNotEmpty(loginToken)) { + appendRequestParams('token', loginToken) +} - if (isNotEmpty(loginToken)) { - appendRequestParams('token', loginToken) - - console.info('loginToken') - } - - if (isNotEmpty(userId)) { - useAuthStore.getState().loadUserPermission(userId) - console.info('loadUserPermission') - } -}) - -onInit() +if (isNotEmpty(userId)) { + useAuthStore.getState().loadUserPermission(userId) +} const router = createBrowserRouter([ { From f2177a9e8e8ec3551173936c3a049835942a669e Mon Sep 17 00:00:00 2001 From: Jimmy Liow Date: Tue, 25 Jun 2024 15:34:41 +0800 Subject: [PATCH 07/10] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E4=BE=9B?= =?UTF-8?q?=E5=BA=94=E5=95=86ID=E9=94=99=E8=AF=AF=20feat:=20=E5=BA=95?= =?UTF-8?q?=E9=83=A8=E5=A2=9E=E5=8A=A0=E5=85=AC=E5=8F=B8=E5=90=8D=E5=AD=97?= =?UTF-8?q?=E5=92=8C=E7=89=88=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/stores/Auth.js | 3 ++- src/views/App.jsx | 7 ++----- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/stores/Auth.js b/src/stores/Auth.js index e7fc4bb..55ab140 100644 --- a/src/stores/Auth.js +++ b/src/stores/Auth.js @@ -60,7 +60,7 @@ const useAuthStore = create((set, get) => ({ // return true // 以上是 Hardcode 判断 // 以下是权限列表从数据库读取后使用的方法 - return permissionList.some((value, key, arry) => { + return permissionList.some((value) => { if (value.indexOf(WILDCARD_TOKEN) > -1) { return true } @@ -76,6 +76,7 @@ const useAuthStore = create((set, get) => ({ const { setStorage } = usingStorage() const { token: loginToken, WU_ID: userId } = await fetchLoginToken(usr, pwd) + const userDetail = await fetchUserDetail(loginToken) await loadUserPermission(userId) set(() => ({ diff --git a/src/views/App.jsx b/src/views/App.jsx index 1882319..22d0737 100644 --- a/src/views/App.jsx +++ b/src/views/App.jsx @@ -43,7 +43,6 @@ function App() { const needToLogin = href !== '/login' && isEmpty(loginToken) useEffect(() => { - console.info('app.useEffect.loginToken: ' + loginToken) fetchUserDetail(loginToken) .then(u => { setUserDetail({ @@ -163,9 +162,7 @@ function App() { isPermitted(PERM_ROLE_NEW) ? { label: {t('account:management.roleList')}, key: '4' } : null, { type: 'divider' }, { label: {t('Logout')}, key: '99' }, - ], - { type: 'divider' }, - { label: <>v{BUILD_VERSION}, key: 'BUILD_VERSION' }, + ] ], }} trigger={['click']} @@ -192,7 +189,7 @@ function App() { }}> {needToLogin ? <>login... : } -
+
China Highlights International Travel Service Co., LTD, Version: {BUILD_VERSION}
From 51834fbc97cc9bd5bedd8fa7678f4aec968cba5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E6=96=87=E5=BC=BA=40HWQ-PC?= Date: Wed, 26 Jun 2024 11:40:31 +0800 Subject: [PATCH 08/10] =?UTF-8?q?1.=E5=AE=8C=E5=96=84=E7=95=8C=E9=9D=A2?= =?UTF-8?q?=E6=98=BE=E7=A4=BA=E6=95=88=E6=9E=9C=202.=E4=BF=AE=E6=94=B9?= =?UTF-8?q?=E8=A1=A8=E5=8D=95=E5=AF=B9=E5=BA=94=E7=9A=84=E6=8E=A5=E5=8F=A3?= =?UTF-8?q?=E5=AD=97=E6=AE=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/locales/en/products.json | 2 +- public/locales/zh/products.json | 4 +- src/views/products/Detail.jsx | 281 +++++++++++++++++++++++--------- 3 files changed, 203 insertions(+), 84 deletions(-) diff --git a/public/locales/en/products.json b/public/locales/en/products.json index 0ccfecf..b0363b7 100644 --- a/public/locales/en/products.json +++ b/public/locales/en/products.json @@ -56,7 +56,7 @@ "validityPeriod":"Validity period", "operation": "Operation", "price": "Price", - + "weekends":"Weekends", "save":"save", "edit":"edit", diff --git a/public/locales/zh/products.json b/public/locales/zh/products.json index 3abea44..17ec532 100644 --- a/public/locales/zh/products.json +++ b/public/locales/zh/products.json @@ -52,9 +52,7 @@ "Child": "儿童" }, - "#": "#" - "Auditors": "审核人员", - "AuditDate": "审核时间", + "#": "#", "productProject": "产品项目", "Code": "代码", diff --git a/src/views/products/Detail.jsx b/src/views/products/Detail.jsx index 7188431..8ce7648 100644 --- a/src/views/products/Detail.jsx +++ b/src/views/products/Detail.jsx @@ -4,23 +4,25 @@ import { Link } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; import DateComponent from '@/components/date'; import { fetchJSON } from "@/utils/request"; - - +import { type } from 'windicss/utils'; +import { searchAgencyAction, getAgencyProductsAction } from '@/stores/Products/Index'; function Index() { const { t } = useTranslation(); const [form] = Form.useForm(); - const [editingKey, setEditingKey] = useState(''); + const [editingid, setEditingid] = useState(''); const [tags, setTags] = useState(['中文', 'English']); const [isModalVisible, setIsModalVisible] = useState(false); const [selectedTag, setSelectedTag] = useState('中文'); const [saveData, setSaveData] = useState(null); const [datePickerVisible, setDatePickerVisible] = useState(false); - const [currentKey, setCurrentKey] = useState(null); + const [currentid, setCurrentid] = useState(null); const [languageStatus, setLanguageStatus] = useState([{ "中文": { title: "", description: "" } }, { "English": { title: "", description: "" } }]); const [formState, setFormState] = useState({}); - const [selectedNodeKey, setSelectedNodeKey] = useState(null); + const [selectedNodeid, setSelectedNodeid] = useState(null); const [selectedDateData, setSelectedDateData] = useState({ dateRange: null, selectedDays: [] }); + const [startValue,setStartValue] = useState(null); + const [endValue,setEndValue] = useState(null); const productProject = [ { code: "code", name: t('products:Code') }, @@ -31,88 +33,109 @@ function Index() { ]; const initialData = [ - { key: '1', adult_cost: '15', child_cost: "美元", currency: 'aa', age_type: 'as', group_size: 'dawd', validityPeriod: 'dawda' }, - { key: '2', adult_cost: '15', child_cost: "美元", currency: 'aa', age_type: 'as', group_size: 'dawd', validityPeriod: 'dawda' }, - { key: '3', adult_cost: '15', child_cost: "美元", currency: 'aa', age_type: 'as', group_size: 'dawd', validityPeriod: 'dawda' }, - { key: '4', adult_cost: '15', child_cost: "美元", currency: 'aa', age_type: 'as', group_size: 'dawd', validityPeriod: 'dawda' }, + { id: '1', adult_cost: '1000', child_cost: "800", currency: 'RMB', age_type: '每人', group_size_min: 4, group_size_max: 5, use_dates_start: '2024/06/12',use_dates_end: '2024/07/22', weekdays: '' }, + { id: '2', adult_cost: '1200', child_cost: "900", currency: 'RMB', age_type: '每人', group_size_min: 3, group_size_max: 5, use_dates_start: '2024/06/12',use_dates_end: '2024/07/22', weekdays: '' }, + { id: '3', adult_cost: '1500', child_cost: "1200", currency: 'RMB', age_type: '每人', group_size_min: 2, group_size_max: 5, use_dates_start: '2024/06/12',use_dates_end: '2024/07/22', weekdays: '' }, + { id: '4', adult_cost: '1100', child_cost: "700", currency: 'RMB', age_type: '每人', group_size_min: 1, group_size_max: 5, use_dates_start: '2024/06/12',use_dates_end: '2024/07/22', weekdays: '' }, ]; const [quotation, setQuotation] = useState(initialData); - const isEditing = (record) => record.key === editingKey; + const isEditing = (record) => record.id === editingid; const edit = (record) => { form.setFieldsValue({ ...record }); - setEditingKey(record.key); + setEditingid(record.id); }; const cancel = () => { - setEditingKey(''); + setEditingid(''); }; - const handleSave = async (key) => { + const handleSave = async (id) => { try { const { info, ...restRow } = await form.validateFields(); const newData = [...quotation]; - const index = newData.findIndex((item) => key === item.key); + const index = newData.findIndex((item) => id === item.id); if (index > -1) { const item = newData[index]; newData.splice(index, 1, { ...item, ...restRow }); delete newData[index].quotation delete newData[index].extras setQuotation(newData); - setEditingKey(''); + setEditingid(''); } else { newData.push(restRow); setQuotation(newData); - setEditingKey(''); + setEditingid(''); } } catch (errInfo) { console.log('Validate Failed:', errInfo); } }; - const handleDelete = (key) => { + const handleDelete = (id) => { const newData = [...quotation]; - const index = newData.findIndex((item) => key === item.key); + const index = newData.findIndex((item) => id === item.id); newData.splice(index, 1); setQuotation(newData); }; const handleAdd = () => { const newData = { - key: `${quotation.length + 1}`, + id: `${quotation.length + 1}`, value: '', currency: '', age_type: '', weekdays: '', - group_size: '', - validityPeriod: '', + use_dates_start: '', + use_dates_end: '', + group_size_min:'', + group_size_max:'' }; setQuotation([...quotation, newData]); }; - const handleDateSelect = (key) => { - setCurrentKey(key); + const handleDateSelect = (id) => { + setCurrentid(id); setDatePickerVisible(true); }; const handleDateChange = ({ dateRange, selectedDays }) => { - console.log('Date Range:', dateRange); - console.log('Selected Days:', selectedDays); + + // 计算周末 + const weekdays = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday','Sunday']; + let weekDayCount = selectedDays.map(day => weekdays.indexOf(day) + 1).sort().join(','); + console.log('Weekday Count:', weekDayCount); + if (!weekDayCount || weekDayCount.length === 0) { + weekDayCount = "全年"; + } + const newData = [...quotation]; + const index = newData.findIndex((item) => currentid === item.id); + if (index > -1) { + newData[index].weekdays = weekDayCount; + setQuotation(newData); + } + setSelectedDateData({ dateRange, selectedDays }) }; const handleDateOk = () => { const { dateRange } = selectedDateData; + const dateRangeList = dateRange.split('-'); + console.log("dateRangeList",dateRangeList); + const use_dates_start = dateRangeList[0]; + const use_dates_end = dateRangeList[1]; - if (currentKey !== null) { + console.log("dateRange",dateRange) + + if (currentid !== null) { const newData = [...quotation]; - const index = newData.findIndex((item) => currentKey === item.key); + console.log("newData",newData) + const index = newData.findIndex((item) => currentid === item.id); if (index > -1) { - newData[index].validityPeriod = dateRange; - - console.log() + newData[index].use_dates_start = use_dates_start; + newData[index].use_dates_end = use_dates_end; setQuotation(newData); - setCurrentKey(null); + setCurrentid(null); } } setDatePickerVisible(false); @@ -124,11 +147,50 @@ function Index() { return ( {children} - + ); } + if (dataIndex === 'age_type' && editing) { + inputNode = ( + + ); + } + + if (dataIndex === 'currency' && editing) { + inputNode = ( + + ); + } + + if (dataIndex === 'group_size' && editing) { + const groupSizeValue = `${record.group_size_min}-${record.group_size_max}`; + return ( + + handleInputGroupSize('group_size_min', record.id, 'group_size', value)} + style={{ width: '50%', marginRight: '10px' }} + /> + - + handleInputGroupSize('group_size_max', record.id, 'group_size', value)} + style={{ width: '50%', marginLeft: '10px' }} + /> + + ); + } + return ( {editing ? ( @@ -146,13 +208,53 @@ function Index() { ); }; + const handleInputGroupSize = (name,id, dataIndex, value ) => { + + const newData = [...quotation]; + console.log("newData",newData); + console.log("value",value); + const index = newData.findIndex((item) => id === item.id); + if (index > -1) { + const item = newData[index]; + console.log("item",item) + newData[index] = {...item,} + if(name==='group_size_min'){ + newData[index] = { ...item, group_size_min: value }; + console.log("newData[index]",newData[index]) + }else{ + newData[index] = { ...item, group_size_max: value }; + console.log("newData[index]",newData[index]) + } + // const [groupSizeMin, groupSizeMax] = value.split('-').map(val => parseInt(val.trim())); + + setQuotation(newData); + console.log("setQuotation",newData) + } + + } + const columns = [ { title: t('products:adultPrice'), dataIndex: 'adult_cost', width: '10%', editable: true }, { title: t('products:childrenPrice'), dataIndex: 'child_cost', width: '10%', editable: true }, { title: t('products:currency'), dataIndex: 'currency', width: '10%', editable: true }, { title: t('products:Types'), dataIndex: 'age_type', width: '10%', editable: true }, - { title: t('products:number'), dataIndex: 'group_size', width: '10%', editable: true }, - { title: t('products:validityPeriod'), dataIndex: 'validityPeriod', width: '30%', editable: true }, + { + title: t('products:number'), + dataIndex: 'group_size', + width: '20%', + editable: true, + render: (_, record) => `${record.group_size_min}-${record.group_size_max}` + }, + + { title: t('products:validityPeriod'), + dataIndex: 'validityPeriod', + width: '20%', + editable: true, + render: (_, record) => `${record.use_dates_start}-${record.use_dates_end}` + }, + + { title: t('products:weekend'), dataIndex: 'weekdays', width: '10%' }, + { title: t('products:operation'), dataIndex: 'operation', @@ -160,19 +262,20 @@ function Index() { const editable = isEditing(record); return editable ? ( - handleSave(record.key)} style={{ marginRight: 8 }}>{t('products:save')} + handleSave(record.id)} style={{ marginRight: 8 }}>{t('products:save')} {t('products:cancel')} ) : ( - edit(record)} style={{ marginRight: 8 }}>{t('products:edit')} - handleDelete(record.key)}> + edit(record)} style={{ marginRight: 8 }}>{t('products:edit')} + handleDelete(record.id)}> {t('products:delete')} ); }, }, + ]; @@ -234,21 +337,21 @@ function Index() { const handleNodeSelect = async (_, { node }) => { // 如果点击的是同一个节点,不做任何操作 - if (selectedNodeKey === node.key) return; + if (selectedNodeid === node.id) return; // 保存当前表单数据 - if (selectedNodeKey) { + if (selectedNodeid) { await handleSaveForm(); } // 更新选中的节点 - setSelectedNodeKey(node.key); + setSelectedNodeid(node.id); // 加载新节点的表单数据 - if (formState[node.key]) { - form.setFieldsValue(formState[node.key]); - setQuotation(formState[node.key].quotation || []); - setLanguageStatus(formState[node.key].lgc_details || languageStatus); + if (formState[node.id]) { + form.setFieldsValue(formState[node.id]); + setQuotation(formState[node.id].quotation || []); + setLanguageStatus(formState[node.id].lgc_details || languageStatus); } else { form.resetFields(); setQuotation(initialData); @@ -261,7 +364,7 @@ function Index() { const values = await form.validateFields(); const newFormState = { ...formState, - [selectedNodeKey]: { + [selectedNodeid]: { ...values, quotation, lgc_details: languageStatus, @@ -286,21 +389,20 @@ function Index() { //绑定产品 const initBindingData = [ - { key: '1', title: '15', value: "美元", age_type: 'aa' }, - { key: '2', title: '15', value: "美元", age_type: 'aa' }, - { key: '3', title: '15', value: "美元", age_type: 'aa' }, - { key: '4', title: '15', value: "美元", age_type: 'aa' }, + { id: '1', title: '英文导游', value: "100", age_type: '每人' }, + { id: '2', title: '中文导游', value: "200", age_type: '每团' }, + { id: '3', title: '可陪餐费', value: "400", age_type: '每人' }, ] const [bindingData, setBindingData] = useState(initBindingData); - const isEditingBinding = (record) => record.key === editingKeyBinding; - const [editingKeyBinding, setEditingKeyBinding] = useState(''); + const isEditingBinding = (record) => record.id === editingidBinding; + const [editingidBinding, setEditingidBinding] = useState(''); const editBinding = (record) => { form.setFieldsValue({ ...record }); - setEditingKeyBinding(record.key); + setEditingidBinding(record.id); }; const cancelBinding = () => { - setEditingKeyBinding(''); + setEditingidBinding(''); }; const EditableCellBinding = ({ editing, @@ -312,7 +414,16 @@ function Index() { children, ...restProps }) => { - const inputNode = inputType === 'number' ? : ; + let inputNode = inputType === 'number' ? : ; + + if (dataIndex === 'age_type' && editing) { + inputNode = ( + + ); + } return ( {editing ? ( @@ -330,9 +441,9 @@ function Index() { ); }; const bindingColums = [ - { title: t('products:Name'), dataIndex: 'title', width: '15%', editable: true }, - { title: t('products:price'), dataIndex: 'value', width: '15%', editable: true }, - { title: t('products:Types'), dataIndex: 'age_type', width: '40%', editable: true }, + { title: t('products:Name'), dataIndex: 'title', width: '25%', editable: true }, + { title: t('products:price'), dataIndex: 'value', width: '25%', editable: true }, + { title: t('products:Types'), dataIndex: 'age_type', width: '25%', editable: true }, { title: t('products:operation'), dataIndex: 'operation', @@ -340,13 +451,13 @@ function Index() { const editable = isEditingBinding(record); return editable ? ( - handleSaveBinding(record.key)} style={{ marginRight: 8 }}>{t('products:save')} + handleSaveBinding(record.id)} style={{ marginRight: 8 }}>{t('products:save')} {t('products:cancel')} ) : ( - editBinding(record)} style={{ marginRight: 8 }}>{t('products:edit')} - handleDeleteBinding(record.key)}> + editBinding(record)} style={{ marginRight: 8 }}>{t('products:edit')} + handleDeleteBinding(record.id)}> {t('products:delete')} @@ -373,31 +484,31 @@ function Index() { const handleAddBinding = () => { const newData = { - key: `${bindingData.length + 1}`, + id: `${bindingData.length + 1}`, title: '', value: '', age_type: '', }; setBindingData([...bindingData, newData]); - setEditingKeyBinding(''); // 添加这一行 + setEditingidBinding(''); // 添加这一行 }; - const handleSaveBinding = async (key) => { + const handleSaveBinding = async (id) => { try { const row = await form.validateFields(); const newData = [...bindingData]; const { value, title, age_type } = row - const index = newData.findIndex((item) => key === item.key); + const index = newData.findIndex((item) => id === item.id); if (index > -1) { const item = newData[index]; newData.splice(index, 1, { ...item, value, title, age_type }); setBindingData(newData); - setEditingKeyBinding(''); + setEditingidBinding(''); } else { newData.push(row); setBindingData(newData); - setEditingKeyBinding(''); + setEditingidBinding(''); } } catch (errInfo) { console.log('Validate Failed:', errInfo); @@ -405,9 +516,9 @@ function Index() { }; - const handleDeleteBinding = (key) => { + const handleDeleteBinding = (id) => { const newData = [...bindingData]; - const index = newData.findIndex((item) => key === item.key); + const index = newData.findIndex((item) => id === item.id); newData.splice(index, 1); setBindingData(newData); }; @@ -421,18 +532,18 @@ function Index() { const treeData = [ { title: '综费', - key: 'zf', + // id: 'zf', selectable: false, - children: [{ title: '北京怡然假日', key: 'bjyrjr' }] + children: [{ title: '北京怡然假日', id: 'bjyrjr' }] }, { title: '车费', - key: 'cf', + // id: 'cf', selectable: false, children: [ - { title: '北京', key: 'bj' }, - { title: '天津', key: 'tj' }, - { title: '北京-天津', key: 'bj-tj-3-5' } + { title: '北京', id: 'bj' }, + { title: '天津', id: 'tj' }, + { title: '北京-天津', id: 'bj-tj-3-5' } ] } ] @@ -447,16 +558,23 @@ function Index() { }, [saveData]); useEffect(() => { - // const { errcode, result } = fetchJSON('http://127.0.0.1:4523/m1/2602949-1933890-default/Service_BaseInfoWeb/travel_agency_products'); - console.log("get请求") + searchAgencyAction('54,55').then((res)=>{ + console.log("res",res) + }) + const a = { + travel_agency_id:'1511' + } + getAgencyProductsAction(a).then((res1)=>{ + console.log("res1",res1) + }) }, []) useEffect(() => { - if (selectedNodeKey) { + if (selectedNodeid) { handleSaveForm(); } - }, [selectedNodeKey]); + }, [selectedNodeid]); return ( @@ -486,7 +604,7 @@ function Index() {

{t('products:productProject')}

{productProject.map((item, index) => ( - + @@ -498,7 +616,7 @@ function Index() {
{tags.map(tag => ( handleTagClick(tag)} color={tag === selectedTag ? 'blue' : undefined} style={{ cursor: 'pointer' }} @@ -581,6 +699,9 @@ function Index() { + From 37c3b5aa45a91a14013530f1efef7cad723cef11 Mon Sep 17 00:00:00 2001 From: Jimmy Liow Date: Wed, 26 Jun 2024 11:44:53 +0800 Subject: [PATCH 09/10] =?UTF-8?q?=E4=BD=BF=E7=94=A8Lifecycle=E5=9B=9E?= =?UTF-8?q?=E8=B0=83=E6=9D=83=E9=99=90=E5=88=9D=E5=A7=8B=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main.jsx | 16 ++++++++++------ src/stores/Auth.js | 15 +++++++++++++-- src/utils/lifecycle.js | 42 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 65 insertions(+), 8 deletions(-) create mode 100644 src/utils/lifecycle.js diff --git a/src/main.jsx b/src/main.jsx index 5c6a620..c6764aa 100644 --- a/src/main.jsx +++ b/src/main.jsx @@ -34,7 +34,7 @@ import { ThemeContext } from '@/stores/ThemeContext' import { usingStorage } from '@/hooks/usingStorage' import { isNotEmpty } from '@/utils/commons' import { appendRequestParams } from '@/utils/request' -import useAuthStore from '@/stores/Auth' +import { fireAuth } from "./utils/lifecycle" import { PERM_ACCOUNT_MANAGEMENT, PERM_ROLE_NEW, PERM_OVERSEA, PERM_AIR_TICKET } from '@/config' @@ -42,14 +42,18 @@ import './i18n'; const { loginToken, userId } = usingStorage() -if (isNotEmpty(loginToken)) { - appendRequestParams('token', loginToken) -} +const initAppliction = async () => { + if (isNotEmpty(loginToken)) { + appendRequestParams('token', loginToken) + } -if (isNotEmpty(userId)) { - useAuthStore.getState().loadUserPermission(userId) + if (isNotEmpty(userId)) { + await fireAuth() + } } +await initAppliction() + const router = createBrowserRouter([ { path: "/", diff --git a/src/stores/Auth.js b/src/stores/Auth.js index 55ab140..b462a1d 100644 --- a/src/stores/Auth.js +++ b/src/stores/Auth.js @@ -3,6 +3,9 @@ import { appendRequestParams, fetchJSON, postForm } from '@/utils/request' import { HT_HOST } from "@/config" import { loadPageSpy } from '@/pageSpy' import { usingStorage } from '@/hooks/usingStorage' +import { devtools } from 'zustand/middleware' + +import { obervseLifecycle } from '@/utils/lifecycle' const KEY_LOGIN_TOKEN = 'G-STR:LOGIN_TOKEN' const KEY_TRAVEL_AGENCY_ID = 'G-INT:TRAVEL_AGENCY_ID' @@ -42,7 +45,14 @@ async function fetchLastRequet() { return errcode !== 0 ? {} : result } -const useAuthStore = create((set, get) => ({ +const useAuthStore = create(obervseLifecycle((set, get) => ({ + + onAuth: () => { + const { startTokenInterval, loadUserPermission } = get() + const { userId } = usingStorage() + loadUserPermission(userId) + startTokenInterval() + }, tokenInterval: null, @@ -157,6 +167,7 @@ const useAuthStore = create((set, get) => ({ } }); }, -})) + +}))) export default useAuthStore \ No newline at end of file diff --git a/src/utils/lifecycle.js b/src/utils/lifecycle.js new file mode 100644 index 0000000..a2de514 --- /dev/null +++ b/src/utils/lifecycle.js @@ -0,0 +1,42 @@ +const initListener = [] +const authListener = [] + +export const onInit = (fn) => { + initListener.push(fn) +} + +export const onAuth = (fn) => { + authListener.push(fn) +} + +export const fireInit = async () => { + initListener.forEach(async (fn) => { + await fn() + }) +} + +export const fireAuth = (obj) => { + authListener.forEach(fn => fn(obj)) +} + +// Zustand 中间件,用于订阅前端应用的生命周期,实验阶段 +export const obervseLifecycle = (fn) => (set, get, store) => { + + onInit(() => { + if (store.getState().hasOwnProperty('onInit')) { + store.getState().onInit() + } else { + console.info('store has no function: onInit.') + } + }) + + onAuth(() => { + if (store.getState().hasOwnProperty('onAuth')) { + store.getState().onAuth() + } else { + console.info('store has no function: onAuth.') + } + }) + + return fn(set, get, store) +} \ No newline at end of file From a3d02f4caf28999337aa4c430046b65374e9efda Mon Sep 17 00:00:00 2001 From: Jimmy Liow Date: Wed, 26 Jun 2024 11:58:18 +0800 Subject: [PATCH 10/10] =?UTF-8?q?=E7=BB=9F=E4=B8=80=E5=BA=94=E7=94=A8?= =?UTF-8?q?=E5=88=9D=E5=A7=8B=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main.jsx | 2 ++ src/views/App.jsx | 6 +----- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/main.jsx b/src/main.jsx index 6e84eb9..eb70c92 100644 --- a/src/main.jsx +++ b/src/main.jsx @@ -46,11 +46,13 @@ import './i18n'; const { loginToken, userId } = usingStorage() const initAppliction = async () => { + if (isNotEmpty(loginToken)) { appendRequestParams('token', loginToken) } if (isNotEmpty(userId)) { + appendRequestParams('wu_id', userId) await fireAuth() } } diff --git a/src/views/App.jsx b/src/views/App.jsx index f9a37ad..0ebcff4 100644 --- a/src/views/App.jsx +++ b/src/views/App.jsx @@ -32,7 +32,7 @@ function App() { const [validateUserPassword, tokenTimeout, isPermitted] = useAuthStore( (state) => [state.validateUserPassword, state.tokenTimeout, state.isPermitted]) - const { loginToken, userDetail, userId } = usingStorage() + const { loginToken } = usingStorage() const noticeUnRead = useNoticeStore((state) => state.noticeUnRead) const href = useHref() @@ -42,10 +42,6 @@ function App() { // 除了路由 /p...以外都需要登陆系统 const needToLogin = href !== '/login' && isEmpty(loginToken) - if (!needToLogin) { - appendRequestParams('token', loginToken) - appendRequestParams('wu_id', userId) - } useEffect(() => { fetchUserDetail(loginToken) .then(u => {