diff --git a/doc/RBAC 权限.sql b/doc/RBAC 权限.sql new file mode 100644 index 0000000..6e70db9 --- /dev/null +++ b/doc/RBAC 权限.sql @@ -0,0 +1,66 @@ +CREATE TABLE auth_role +( + [role_id] [int] IDENTITY(1,1) NOT NULL, + [role_name] [nvarchar](255) NOT NULL, + [created_on] [datetime] NOT NULL, + CONSTRAINT [PK_auth_role] PRIMARY KEY CLUSTERED +( + [role_id] ASC +)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] +) ON [PRIMARY] + +ALTER TABLE auth_role ADD CONSTRAINT [DF_auth_role_created_on] DEFAULT (getdate()) FOR [created_on] + +CREATE TABLE auth_permission +( + [role_id] [int] NOT NULL, + [res_id] [int] NOT NULL +) ON [PRIMARY] + +CREATE TABLE auth_resource +( + [res_id] [int] IDENTITY(1,1) NOT NULL, + [res_name] [nvarchar](255) NOT NULL, + [res_pattern] [nvarchar](255) NOT NULL, + [res_category] [nvarchar](255) NOT NULL, + CONSTRAINT [PK_auth_resource] PRIMARY KEY CLUSTERED +( + [res_id] ASC +)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] +) ON [PRIMARY] + +INSERT INTO [dbo].[auth_role] ([role_name]) +VALUES ('系统管理员') +INSERT INTO [dbo].[auth_role] ([role_name]) +VALUES ('国内供应商') +INSERT INTO [dbo].[auth_role] ([role_name]) +VALUES ('海外供应商') +INSERT INTO [dbo].[auth_role] ([role_name]) +VALUES ('客服组') +INSERT INTO [dbo].[auth_role] ([role_name]) +VALUES ('产品组') +INSERT INTO [dbo].[auth_role] ([role_name]) +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') +INSERT INTO [dbo].[auth_resource] ([res_name] ,[res_pattern], [res_category]) +VALUES ('账单', '/invoice', 'oversea') + +INSERT INTO [dbo].[auth_resource] ([res_name] ,[res_pattern], [res_category]) +VALUES ('账号权限管理', '/account/management', 'system') +INSERT INTO [dbo].[auth_resource] ([res_name] ,[res_pattern], [res_category]) +VALUES ('新增角色', '/account/new-role', 'system') + +INSERT INTO [dbo].[auth_permission] ([role_id] ,[res_id]) +VALUES (1, 1) +INSERT INTO [dbo].[auth_permission] ([role_id] ,[res_id]) +VALUES (6, 2) +INSERT INTO [dbo].[auth_permission] ([role_id] ,[res_id]) +VALUES (6, 3) +INSERT INTO [dbo].[auth_permission] ([role_id] ,[res_id]) +VALUES (6, 4) +INSERT INTO [dbo].[auth_permission] ([role_id] ,[res_id]) +VALUES (6, 5) diff --git a/doc/价格管理平台.bmpr b/doc/价格管理平台.bmpr index f82ae80..2e3bd02 100644 Binary files a/doc/价格管理平台.bmpr and b/doc/价格管理平台.bmpr differ diff --git a/public/locales/en/common.json b/public/locales/en/common.json index 4ad6ddc..a513e2a 100644 --- a/public/locales/en/common.json +++ b/public/locales/en/common.json @@ -18,6 +18,7 @@ "Download": "Download", "Upload": "Upload", "preview": "Preview", + "Total": "Total", "Login": "Login", "Username": "Username", diff --git a/public/locales/zh/common.json b/public/locales/zh/common.json index f6c273c..59c417e 100644 --- a/public/locales/zh/common.json +++ b/public/locales/zh/common.json @@ -18,6 +18,7 @@ "Download": "下载", "Upload": "上传", "preview": "预览", + "Total": "总数", "Login": "登录", "Username": "账号", diff --git a/src/stores/Reservation.js b/src/stores/Reservation.js index 686d191..8990ae7 100644 --- a/src/stores/Reservation.js +++ b/src/stores/Reservation.js @@ -1,4 +1,3 @@ -import { makeAutoObservable, runInAction } from "mobx" import { create } from 'zustand' import { fetchJSON, postForm } from '@/utils/request' import { HT_HOST } from "@/config" @@ -211,88 +210,65 @@ const useReservationStore = create((set, get) => ({ return json } }); - } -})) + }, + + setupCityGuide: (cityId, guideId) => { + const { selectedReservation } = get() + const { userId, travelAgencyId } = usingStorage() + const formData = new FormData() + formData.append('GRI_SN', selectedReservation.reservationId) + formData.append('VEI_SN', travelAgencyId) + formData.append('TGI_SN', guideId) + formData.append('CII_SN', cityId) + formData.append('GetDate', selectedReservation.reservationDate) + formData.append('LMI_SN', userId) + const postUrl = HT_HOST + '/service-cusservice/PTAddGuide' -export default useReservationStore + return postForm(postUrl, formData) + .then(json => { + if (json.errcode != 0) { + throw new Error(json.errmsg + ': ' + json.errcode) + } + }); + }, -export class Reservation { - constructor(root) { - makeAutoObservable(this, { rootStore: false }); - this.root = root; - } - updateReservationGuide() { + updateReservationGuide: () => { + const { selectedReservation } = get() + const { travelAgencyId } = usingStorage() const fetchUrl = prepareUrl(HT_HOST + '/service-cusservice/PTGetCityGuide') - .append('VEI_SN', this.root.authStore.login.travelAgencyId) - .append('GRI_SN', this.selectedReservation.reservationId) + .append('VEI_SN', travelAgencyId) + .append('GRI_SN', selectedReservation.reservationId) .append('LGC', 1) - .append("token", this.root.authStore.login.token) .build(); return fetchJSON(fetchUrl) .then(json => { if (json.errcode == 0) { const reservationGuide = (json?.Result??[]).filter((data) => { - return data.TGI_SN != 0; + return data.TGI_SN != 0 }).map((data) => { - return data.GuideName; - }).join(','); + return data.GuideName + }).join(',') + runInAction(() => { - this.selectedReservation.guide = reservationGuide; - }); - return reservationGuide; - } else { - throw new Error(json.errmsg + ': ' + json.errcode); - } - }); - } + selectedReservation.guide = reservationGuide + }) - setupCityGuide(cityId, guideId) { - let formData = new FormData(); - formData.append('GRI_SN', this.selectedReservation.reservationId); - formData.append('VEI_SN', this.root.authStore.login.travelAgencyId); - formData.append('TGI_SN', guideId); - formData.append('CII_SN', cityId); - formData.append('GetDate', this.selectedReservation.reservationDate); - formData.append('LMI_SN', this.root.authStore.login.userId); - formData.append("token", this.root.authStore.login.token); - const postUrl = HT_HOST + '/service-cusservice/PTAddGuide'; + set((state) => ({ + selectedReservation: { + ...state.selectedReservation, + guide: reservationGuide, + }, + })) - return postForm(postUrl, formData) - .then(json => { - if (json.errcode != 0) { - throw new Error(json.errmsg + ': ' + json.errcode); + return reservationGuide + } else { + throw new Error(json.errmsg + ': ' + json.errcode) } - }); - } - - updatePropertyValue(name, value) { - runInAction(() => { - this[name] = value; - }); - } - - cityList = []; - - selectedReservation = null; - selectedConfirmation = null; - arrivalDateRange = []; - referenceNo = ''; - - reservationList = []; - - reservationDetail = { - referenceNumber: '', arrivalDate: '', tourGuide: '' - }; - - reservationPage = { - current: 1, - size: 10, - total: 0 + }) } +})) - confirmationList = [ - ]; -} \ No newline at end of file +export default useReservationStore \ No newline at end of file diff --git a/src/stores/Root.js b/src/stores/Root.js index 57645f4..d8369f5 100644 --- a/src/stores/Root.js +++ b/src/stores/Root.js @@ -1,11 +1,9 @@ import { makeAutoObservable } from "mobx"; -import { Reservation } from "./Reservation"; import { Auth } from "./Auth"; import {Invoice} from "./Invoice"; class Root { constructor() { - this.reservationStore = new Reservation(this); this.authStore = new Auth(this); this.invoiceStore = new Invoice(this); makeAutoObservable(this); diff --git a/src/views/App.jsx b/src/views/App.jsx index 0d11cd0..dd2dec6 100644 --- a/src/views/App.jsx +++ b/src/views/App.jsx @@ -148,8 +148,9 @@ function App() { items: [...[ { label: {t('ChangePassword')}, key: '0' }, { label: {t('Profile')}, key: '1' }, + { label: {t('account:management.tile')}, key: '3' }, { type: 'divider' }, - { label: {t('Logout')}, key: '3' }, + { label: {t('Logout')}, key: '4' }, ], { type: 'divider' }, { label: <>v{BUILD_VERSION}, key: 'BUILD_VERSION' }, diff --git a/src/views/Standlone.jsx b/src/views/Standlone.jsx index e13fd85..24acad3 100644 --- a/src/views/Standlone.jsx +++ b/src/views/Standlone.jsx @@ -1,42 +1,37 @@ -import { Outlet, Link, useHref, useLocation } from "react-router-dom"; -import { useEffect } from "react"; -import { observer } from "mobx-react"; -import { Layout, Menu, ConfigProvider, theme, Typography, Space, Row, Col, Alert, App as AntApp } from "antd"; -import { DownOutlined } from "@ant-design/icons"; -import "antd/dist/reset.css"; -import AppLogo from "@/assets/logo-gh.png"; -import { useStore } from "@/stores/StoreContext.js"; -import Language from "../i18n/LanguageSwitcher"; +import { Outlet } from 'react-router-dom' +import { Layout, ConfigProvider, theme, Typography, Row, Col, App as AntApp } from 'antd' +import 'antd/dist/reset.css' +import AppLogo from '@/assets/logo-gh.png' +import Language from '../i18n/LanguageSwitcher' -const { Title } = Typography; -const { Header, Content, Footer } = Layout; +const { Title } = Typography +const { Header, Content, Footer } = Layout function Standlone() { - const { authStore } = useStore(); const { token: { colorBgContainer }, - } = theme.useToken(); + } = theme.useToken() return ( -
- +
+ - App logo + App logo - Global Highlights Hub + Global Highlights Hub @@ -55,7 +50,7 @@ function Standlone() { - ); + ) } -export default observer(Standlone); +export default Standlone diff --git a/src/views/account/Management.jsx b/src/views/account/Management.jsx index 32092c6..4f028ea 100644 --- a/src/views/account/Management.jsx +++ b/src/views/account/Management.jsx @@ -1,8 +1,6 @@ -import { NavLink, useLocation } from 'react-router-dom' -import { useState, useEffect } from 'react' +import { useState } from 'react' import { Row, Col, Space, Button, Table, Select, TreeSelect, Typography, Modal, App, Form, Input } from 'antd' -import dayjs from 'dayjs' -import { isEmpty } from '@/utils/commons' +import { ExclamationCircleFilled } from '@ant-design/icons' import { useTranslation } from 'react-i18next' import useFormStore from '@/stores/Form' import useReservationStore from '@/stores/Reservation' @@ -88,6 +86,7 @@ function Management() { { title: t('account:role'), dataIndex: 'role', + render: roleRender }, { title: t('account:lastLogin'), @@ -102,23 +101,30 @@ function Management() { function accountRender(text) { return ( - + + ) + } + + function roleRender(text) { + return ( + ) } function actionRender() { return ( - - + + ) } - const onPermissionChange = (newValue) => { + const onPermissionChange = (newValue) => { console.log('onChange ', newValue); setPermissionValue(newValue); } + const [permissionValue, setPermissionValue] = useState(['0-0-0']) const [isAccountModalOpen, setAccountModalOpen] = useState(false) const [isRoleModalOpen, setRoleModalOpen] = useState(false) @@ -168,12 +174,12 @@ function Management() { const formValuesToSub = useFormStore((state) => state.formValuesToSub) - const [form] = Form.useForm() + const [editAccountForm, editRoleForm] = Form.useForm() const [fetchReservationList] = useReservationStore((state) => [state.fetchAllGuideList, state.fetchReservationList, state.reservationList, state.reservationPage, state.cityList, state.selectReservation, state.getCityListByReservationId]) - const { notification } = App.useApp() + const { notification, modal } = App.useApp() const handleAccountOk = () => { } @@ -199,7 +205,35 @@ function Management() { } // 默认重新搜索第一页,所有状态的计划 - const onSearchClick = (current=1, status=null) => { + const onSearchClick = (current = 1, status = null) => { + } + + const showDisableConfirm = () => { + modal.confirm({ + title: 'Do you want to disable this account?', + icon: , + content: 'Username: Ivy, Realname: 怡小芳', + onOk() { + console.log('OK') + }, + onCancel() { + console.log('Cancel') + }, + }) + } + + const showResetPasswordConfirm = () => { + modal.confirm({ + title: 'Do you want to reset password?', + icon: , + content: 'Username: Ivy, Realname: 怡小芳', + onOk() { + console.log('OK') + }, + onCancel() { + console.log('Cancel') + }, + }) } return ( @@ -209,65 +243,65 @@ function Management() { open={isAccountModalOpen} onOk={handleAccountOk} onCancel={handleAccountCancel} >
- {t('account:management.newAccount')} - - - - - - - - - - - - -
+ {t('account:management.newAccount')} + + + + + + + + + + + + + {/* Role Edit */}
- {t('account:management.newRole')} - - - - - - -
+ {t('account:management.newRole')} + + + + + + +
{t('account:management.tile')} @@ -367,9 +401,9 @@ function Management() { showQuickJumper: true, showLessItems: true, showSizeChanger: true, - showTotal: (total) => { return `总数:${total}` } + showTotal: (total) => { return t('Total') + `:${total}` } }} - onChange={(pagination) => {onSearchClick(pagination.current)}} + onChange={(pagination) => { onSearchClick(pagination.current) }} columns={accountListColumns} dataSource={accountList} /> diff --git a/src/views/reservation/Newest.jsx b/src/views/reservation/Newest.jsx index 99d8b70..d9f3420 100644 --- a/src/views/reservation/Newest.jsx +++ b/src/views/reservation/Newest.jsx @@ -82,7 +82,7 @@ function Newest() { optionFilterProp='children' defaultValue={(guideSelectOptions.length == 0 || city.tourGuideId == 0) ? null : city.tourGuideId} onChange={(guideId) => { - reservationStore.setupCityGuide(city.cityId, guideId); + setupCityGuide(city.cityId, guideId); }} onSearch={(value) => { // console.log('search:', value); @@ -102,9 +102,9 @@ function Newest() { const formValuesToSub = useFormStore((state) => state.formValuesToSub) - const [fetchAllGuideList, fetchReservationList, reservationList, reservationPage, cityList, selectReservation, getCityListByReservationId] = + const [fetchAllGuideList, fetchReservationList, reservationList, reservationPage, cityList, selectReservation, getCityListByReservationId, setupCityGuide, updateReservationGuide] = useReservationStore((state) => - [state.fetchAllGuideList, state.fetchReservationList, state.reservationList, state.reservationPage, state.cityList, state.selectReservation, state.getCityListByReservationId]) + [state.fetchAllGuideList, state.fetchReservationList, state.reservationList, state.reservationPage, state.cityList, state.selectReservation, state.getCityListByReservationId, state.setupCityGuide, state.updateReservationGuide]) const { notification } = App.useApp() @@ -143,7 +143,7 @@ function Newest() { }) } const handleOk = () => { - reservationStore.updateReservationGuide() + updateReservationGuide() .finally(() => { setIsModalOpen(false); setDataLoading(false);