Merge branch 'main' of github.com:hainatravel/GHHub

feature/price_manager
YCC 2 years ago
commit b62bba8587

@ -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)

Binary file not shown.

@ -18,6 +18,7 @@
"Download": "Download",
"Upload": "Upload",
"preview": "Preview",
"Total": "Total",
"Login": "Login",
"Username": "Username",

@ -18,6 +18,7 @@
"Download": "下载",
"Upload": "上传",
"preview": "预览",
"Total": "总数",
"Login": "登录",
"Username": "账号",

@ -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 = [
];
}
export default useReservationStore

@ -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);

@ -148,8 +148,9 @@ function App() {
items: [...[
{ label: <Link to='/account/change-password'>{t('ChangePassword')}</Link>, key: '0' },
{ label: <Link to='/account/profile'>{t('Profile')}</Link>, key: '1' },
{ label: <Link to='/account/management'>{t('account:management.tile')}</Link>, key: '3' },
{ type: 'divider' },
{ label: <Link to='/logout'>{t('Logout')}</Link>, key: '3' },
{ label: <Link to='/logout'>{t('Logout')}</Link>, key: '4' },
],
{ type: 'divider' },
{ label: <>v{BUILD_VERSION}</>, key: 'BUILD_VERSION' },

@ -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 (
<ConfigProvider
theme={{
token: {
colorPrimary: "#00b96b",
colorPrimary: '#00b96b',
},
algorithm: theme.defaultAlgorithm,
}}>
<AntApp>
<Layout
style={{
minHeight: "100vh",
minHeight: '100vh',
}}>
<Header className="header" style={{ position: "sticky", top: 0, zIndex: 1, width: "100%" }}>
<Row gutter={{ md: 24 }} justify="center">
<Header className='header' style={{ position: 'sticky', top: 0, zIndex: 1, width: '100%' }}>
<Row gutter={{ md: 24 }} justify='center'>
<Col span={4}>
<img src={AppLogo} className="logo" alt="App logo" />
<img src={AppLogo} className='logo' alt='App logo' />
</Col>
<Col span={18}><Title style={{ color: "white", marginTop: "3.5px" }}>Global Highlights Hub</Title></Col>
<Col span={18}><Title style={{ color: 'white', marginTop: '3.5px' }}>Global Highlights Hub</Title></Col>
<Col span={2}>
<Language />
</Col>
@ -55,7 +50,7 @@ function Standlone() {
</Layout>
</AntApp>
</ConfigProvider>
);
)
}
export default observer(Standlone);
export default Standlone

@ -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 (
<Button type='link' onClick={() => console.info('account:action.assignRole')}>{text}</Button>
<Button type='link' onClick={() => setAccountModalOpen(true)}>{text}</Button>
)
}
function roleRender(text) {
return (
<Button type='link' onClick={() => setRoleModalOpen(true)}>{text}</Button>
)
}
function actionRender() {
return (
<Space key='actionRenderSpace' size='middle'>
<Button type='link' key='disable' onClick={() => console.info('account:action.disable')}>{t('account:action.disable')}</Button>
<Button type='link' key='resetPassword' onClick={() => console.info('account:action.resetPassword')}>{t('account:action.resetPassword')}</Button>
<Button type='link' key='disable' onClick={() => showDisableConfirm()}>{t('account:action.disable')}</Button>
<Button type='link' key='resetPassword' onClick={() => showResetPasswordConfirm()}>{t('account:action.resetPassword')}</Button>
</Space>
)
}
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: <ExclamationCircleFilled />,
content: 'Username: Ivy, Realname: 怡小芳',
onOk() {
console.log('OK')
},
onCancel() {
console.log('Cancel')
},
})
}
const showResetPasswordConfirm = () => {
modal.confirm({
title: 'Do you want to reset password?',
icon: <ExclamationCircleFilled />,
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}
>
<Form
name='basic'
form={form}
layout='vertical'
size='large'
style={{
maxWidth: 600,
}}
onFinish={onFinish}
onFinishFailed={onFinishFailed}
autoComplete='off'
>
<Form.Item><Title level={2}>{t('account:management.newAccount')}</Title></Form.Item>
<Form.Item
label={t('account:management.username')}
name='username'
rules={[
{
required: true,
message: t('account:Validation.username'),
},
]}
>
<Input />
</Form.Item>
<Form.Item
label={t('account:management.realname')}
name='realname'
rules={[
{
required: true,
message: t('account:Validation.realname'),
},
]}
>
<Input />
</Form.Item>
<Form.Item
label={t('account:management.email')}
name='email'
rules={[
{
required: true,
message: t('account:Validation.email'),
},
]}
name='basic'
form={editAccountForm}
layout='vertical'
size='large'
style={{
maxWidth: 600,
}}
onFinish={onFinish}
onFinishFailed={onFinishFailed}
autoComplete='off'
>
<Input />
</Form.Item>
<Form.Item label={t('account:management.role')}>
<Select>
<Select.Option value='1'>客服组</Select.Option>
<Select.Option value='2'>产品组</Select.Option>
<Select.Option value='3'>国内供应商</Select.Option>
<Select.Option value='4'>海外供应商</Select.Option>
<Select.Option value='5'>技术研发部</Select.Option>
<Select.Option value='0' disabled>系统管理员</Select.Option>
</Select>
</Form.Item>
</Form>
<Form.Item><Title level={2}>{t('account:management.newAccount')}</Title></Form.Item>
<Form.Item
label={t('account:management.username')}
name='username'
rules={[
{
required: true,
message: t('account:Validation.username'),
},
]}
>
<Input />
</Form.Item>
<Form.Item
label={t('account:management.realname')}
name='realname'
rules={[
{
required: true,
message: t('account:Validation.realname'),
},
]}
>
<Input />
</Form.Item>
<Form.Item
label={t('account:management.email')}
name='email'
rules={[
{
required: true,
message: t('account:Validation.email'),
},
]}
>
<Input />
</Form.Item>
<Form.Item label={t('account:management.role')}>
<Select>
<Select.Option value='1'>客服组</Select.Option>
<Select.Option value='2'>产品组</Select.Option>
<Select.Option value='3'>国内供应商</Select.Option>
<Select.Option value='4'>海外供应商</Select.Option>
<Select.Option value='5'>技术研发部</Select.Option>
<Select.Option value='0' disabled>系统管理员</Select.Option>
</Select>
</Form.Item>
</Form>
</Modal>
{/* Role Edit */}
<Modal
@ -275,51 +309,51 @@ function Management() {
open={isRoleModalOpen} onOk={handleRoleOk} onCancel={handleRoleCancel}
>
<Form
name='basic'
form={form}
layout='vertical'
size='large'
style={{
maxWidth: 600,
}}
onFinish={onFinish}
onFinishFailed={onFinishFailed}
autoComplete='off'
>
<Form.Item><Title level={2}>{t('account:management.newRole')}</Title></Form.Item>
<Form.Item
label={t('account:management.roleName')}
name='roleName'
rules={[
{
required: true,
message: t('account:Validation.roleName'),
},
]}
name='basic'
form={editRoleForm}
layout='vertical'
size='large'
style={{
maxWidth: 600,
}}
onFinish={onFinish}
onFinishFailed={onFinishFailed}
autoComplete='off'
>
<Input />
</Form.Item>
<Form.Item label={t('account:management.permission')}>
<TreeSelect treeData={permissionData} value={permissionValue}
dropdownStyle={{
maxHeight: 500,
overflow: 'auto',
}}
placement='bottomLeft'
showSearch
allowClear
multiple
treeDefaultExpandAll
treeLine={true}
onChange={onPermissionChange}
treeCheckable={true}
showCheckedStrategy={TreeSelect.SHOW_CHILD}
placeholder={'Please select'}
style={{
width: '100%',
}} />
</Form.Item>
</Form>
<Form.Item><Title level={2}>{t('account:management.newRole')}</Title></Form.Item>
<Form.Item
label={t('account:management.roleName')}
name='roleName'
rules={[
{
required: true,
message: t('account:Validation.roleName'),
},
]}
>
<Input />
</Form.Item>
<Form.Item label={t('account:management.permission')}>
<TreeSelect treeData={permissionData} value={permissionValue}
dropdownStyle={{
maxHeight: 500,
overflow: 'auto',
}}
placement='bottomLeft'
showSearch
allowClear
multiple
treeDefaultExpandAll
treeLine={true}
onChange={onPermissionChange}
treeCheckable={true}
showCheckedStrategy={TreeSelect.SHOW_CHILD}
placeholder={'Please select'}
style={{
width: '100%',
}} />
</Form.Item>
</Form>
</Modal>
<Space direction='vertical' style={{ width: '100%' }}>
<Title level={3}>{t('account:management.tile')}</Title>
@ -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}
/>
</Col>

@ -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);

Loading…
Cancel
Save