Merge remote-tracking branch 'origin/main'

feature/price_manager
Lei OT 1 year ago
commit 61076ed908

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

@ -1,7 +1,7 @@
{
"name": "global.highlights.hub",
"private": true,
"version": "0.1.0",
"version": "2.0.0",
"type": "module",
"scripts": {
"dev": "vite",

@ -18,6 +18,7 @@ import ReservationNewest from "@/views/reservation/Newest";
import ReservationDetail from "@/views/reservation/Detail";
import ChangePassword from "@/views/account/ChangePassword";
import AccountProfile from "@/views/account/Profile";
import AccountManagement from "@/views/account/Management";
import FeedbackIndex from "@/views/feedback/Index";
import FeedbackDetail from "@/views/feedback/Detail";
import FeedbackCustomerDetail from "@/views/feedback/CustomerDetail";
@ -51,6 +52,7 @@ const router = createBrowserRouter([
{ path: "reservation/:reservationId", element: <ReservationDetail />},
{ path: "account/change-password", element: <ChangePassword />},
{ path: "account/profile", element: <AccountProfile />},
{ path: "account/management", element: <AccountManagement />},
{ path: "feedback", element: <FeedbackIndex />},
{ path: "feedback/:GRI_SN/:CII_SN/:RefNo", element: <FeedbackCustomerDetail />},
{ path: "feedback/:GRI_SN/:RefNo", element: <FeedbackDetail />},

@ -19,7 +19,7 @@ export const fetchPlanDetail = async (travelAgencyId, reservationId) => {
if (json.errcode == 0) {
return {
planDetail: json.PlanDetail[0],
planDetail: json.PlanDetail == null ? {} : json.PlanDetail[0],
planChangeList: json.PlanChange??[]
}
} else {
@ -139,7 +139,7 @@ const useReservationStore = create((set, get) => ({
},
fetchAllGuideList: () => {
const { userId, travelAgencyId } = usingStorage()
const { travelAgencyId } = usingStorage()
const fetchUrl = prepareUrl(HT_HOST + '/service-cusservice/PTGetGuideList')
.append('VEI_SN', travelAgencyId)
.build();
@ -161,7 +161,8 @@ const useReservationStore = create((set, get) => ({
});
},
getReservationDetail: async (travelAgencyId, reservationId) => {
getReservationDetail: async (reservationId) => {
const { travelAgencyId } = usingStorage()
const { planDetail, planChangeList } = await fetchPlanDetail(travelAgencyId, reservationId)
const attachListJson = await fetchAttachList(reservationId)
@ -192,7 +193,8 @@ const useReservationStore = create((set, get) => ({
}))
},
submitConfirmation: (userId, travelAgencyId, confirmText) => {
submitConfirmation: (confirmText) => {
const { userId, travelAgencyId } = usingStorage()
const { selectedConfirmation, getReservationDetail, reservationDetail } = get()
const formData = new FormData()
formData.append('PCI_SN', selectedConfirmation.key)
@ -205,7 +207,6 @@ const useReservationStore = create((set, get) => ({
return postForm(postUrl, formData)
.then(json => {
if (json.errcode == 0 && json.Result.length == 1) {
// this.fetchReservation(this.reservationDetail.reservationId);
getReservationDetail(travelAgencyId, reservationDetail.reservationId)
return json
}

@ -0,0 +1,416 @@
import { useState } 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 useReservationStore from '@/stores/Reservation'
import SearchForm from '@/components/SearchForm'
const { Title } = Typography
const permissionData = [
{
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: '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-0',
key: '2-1-0',
},
{
title: '禁用账号',
value: '2-1-1',
key: '2-1-1',
},
{
title: '分配账号角色',
value: '2-1-2',
key: '2-1-2',
},
],
},
];
function Management() {
const { t } = useTranslation()
const accountListColumns = [
{
title: t('account:username'),
dataIndex: 'username',
render: accountRender
},
{
title: t('account:realname'),
dataIndex: 'realname',
},
{
title: t('account:email'),
dataIndex: 'email',
},
{
title: t('account:role'),
dataIndex: 'role',
render: roleRender
},
{
title: t('account:lastLogin'),
dataIndex: 'lastLogin',
},
{
title: t('account:action'),
dataIndex: 'account:action',
render: actionRender
},
]
function accountRender(text) {
return (
<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={() => showDisableConfirm()}>{t('account:action.disable')}</Button>
<Button type='link' key='resetPassword' onClick={() => showResetPasswordConfirm()}>{t('account:action.resetPassword')}</Button>
</Space>
)
}
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)
const [dataLoading, setDataLoading] = useState(false)
const [accountList, setaccountList] = useState([
{
key: 1,
username: 'bjyiran',
realname: '怡小芳',
email: 'xiaofang@yiran.com',
role: '国内供应商',
lastLogin: '2024-06-12 13:53'
},
{
key: 2,
username: 'int-robin',
realname: 'Robin',
email: 'robin@int.com',
role: '海外供应商',
lastLogin: '2024-06-12 13:53'
},
{
key: 3,
username: 'betty-wu',
realname: '吴雪',
email: 'betty@hainatravel.com',
role: '客服组',
lastLogin: '2024-06-12 13:53'
},
{
key: 4,
username: 'lancy',
realname: '吴金倩',
email: 'lancy@hainatravel.com',
role: '产品组',
lastLogin: '2024-06-12 13:53'
},
{
key: 5,
username: 'LYJ',
realname: '廖一军',
email: 'lyj@hainatravel.com',
role: 'Web 开发组,海外测试供应商',
lastLogin: '2024-06-12 13:53'
}
])
const formValuesToSub = useFormStore((state) => state.formValuesToSub)
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, modal } = App.useApp()
const handleAccountOk = () => {
}
const handleAccountCancel = () => {
setAccountModalOpen(false)
}
const handleRoleOk = () => {
}
const handleRoleCancel = () => {
setRoleModalOpen(false)
}
const onFinish = (values) => {
console.log(values)
}
const onFinishFailed = (error) => {
console.log('Failed:', error)
// form.resetFields()
}
//
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 (
<>
<Modal
centered
open={isAccountModalOpen} onOk={handleAccountOk} onCancel={handleAccountCancel}
>
<Form
name='basic'
form={editAccountForm}
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'),
},
]}
>
<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
centered
open={isRoleModalOpen} onOk={handleRoleOk} onCancel={handleRoleCancel}
>
<Form
name='basic'
form={editRoleForm}
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'),
},
]}
>
<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>
<SearchForm
// initialValue={
// {
// }
// }
fieldsConfig={{
shows: ['username', 'referenceNo', 'dates'],
fieldProps: {
dates: { label: t('group:ArrivalDate') },
},
}}
onSubmit={(err, formVal, filedsVal) => {
setDataLoading(true)
fetchReservationList(formVal)
.catch(ex => {
notification.error({
message: 'Notification',
description: ex.message,
placement: 'top',
duration: 4,
})
})
.finally(() => {
setDataLoading(false)
})
}}
/>
<Row>
<Col span={24}>
<Space>
<Button onClick={() => setAccountModalOpen(true)}>{t('account:management.newAccount')}</Button>
<Button onClick={() => setRoleModalOpen(true)}>{t('account:management.newRole')}</Button>
</Space>
</Col>
</Row>
<Row>
<Col span={24}>
<Table
bordered
loading={dataLoading}
pagination={{
showQuickJumper: true,
showLessItems: true,
showSizeChanger: true,
showTotal: (total) => { return `总数:${total}` }
}}
onChange={(pagination) => {onSearchClick(pagination.current)}}
columns={accountListColumns} dataSource={accountList}
/>
</Col>
</Row>
</Space>
</>
)
}
export default Management

@ -1,10 +1,10 @@
import { useParams, useNavigate } from "react-router-dom"
import { useParams } from "react-router-dom"
import { useEffect, useState } from 'react'
import { Row, Col, Space, Button, Table, Input, Typography, Modal, Tag, App } from 'antd'
import {
FileOutlined
} from '@ant-design/icons';
import useAuthStore from '@/stores/Auth'
import { usingStorage } from '@/hooks/usingStorage'
import useReservationStore from '@/stores/Reservation'
import { useTranslation } from 'react-i18next'
import BackBtn from '@/components/BackBtn'
@ -69,7 +69,6 @@ function Detail() {
);
}
const navigate = useNavigate();
const [isModalOpen, setIsModalOpen] = useState(false);
const [confirmLoading, setConfirmLoading] = useState(false);
const [confirmText, setConfirmText] = useState('');
@ -78,7 +77,8 @@ function Detail() {
const { notification } = App.useApp();
const { reservationId } = useParams();
const loginUser = useAuthStore((state) => state.loginUser)
const { travelAgencyId, loginToken } = usingStorage()
const [getReservationDetail, reservationDetail, confirmationList, selectConfirmation, submitConfirmation] =
useReservationStore((state) =>
[state.getReservationDetail, state.reservationDetail, state.confirmationList, state.selectConfirmation, state.submitConfirmation])
@ -87,9 +87,9 @@ function Detail() {
'https://view.officeapps.live.com/op/embed.aspx?wdPrint=1&wdHideGridlines=0&wdHideComments=1&wdEmbedCode=0&src=';
// https://www.chinahighlights.com/public/reservationW220420009.doc
const reservationUrl =
`https://p9axztuwd7x8a7.mycht.cn/service-fileServer/DownloadPlanDoc?GRI_SN=${reservationId}&VEI_SN=${loginUser.travelAgencyId}&token=${loginUser.token}&FileType=1`;
`https://p9axztuwd7x8a7.mycht.cn/service-fileServer/DownloadPlanDoc?GRI_SN=${reservationId}&VEI_SN=${travelAgencyId}&token=${loginToken}&FileType=1`;
const nameCardUrl =
`https://p9axztuwd7x8a7.mycht.cn/service-fileServer/DownloadPlanDoc?GRI_SN=${reservationId}&VEI_SN=${loginUser.travelAgencyId}&token=${loginUser.token}&FileType=2`;
`https://p9axztuwd7x8a7.mycht.cn/service-fileServer/DownloadPlanDoc?GRI_SN=${reservationId}&VEI_SN=${travelAgencyId}&token=${loginToken}&FileType=2`;
const reservationPreviewUrl = officeWebViewerUrl + encodeURIComponent(reservationUrl);
const nameCardPreviewUrl = officeWebViewerUrl + encodeURIComponent(nameCardUrl);
@ -103,7 +103,7 @@ function Detail() {
const handleOk = () => {
setConfirmLoading(true);
submitConfirmation(loginUser.userId, loginUser.travelAgencyId, confirmText + '\n——————————————————————\n' +newConfirmText)
submitConfirmation(confirmText + '\n——————————————————————\n' +newConfirmText)
.finally(() => {
setNewConfirmText('');
setIsModalOpen(false);
@ -116,7 +116,7 @@ function Detail() {
useEffect(() => {
setDataLoading(true);
getReservationDetail(loginUser.travelAgencyId, reservationId)
getReservationDetail(reservationId)
.catch(ex => {
notification.error({
message: `Notification`,

@ -16,7 +16,6 @@ function Newest() {
{
title: t('group:RefNo'),
dataIndex: 'referenceNumber',
key: 'Reference number',
render: (text, record) => {
const after3Dayjs = dayjs().add(3, 'day');
const lastDayjs = dayjs().subtract(1, 'day');
@ -31,28 +30,23 @@ function Newest() {
{
title: t('group:ArrivalDate'),
dataIndex: 'arrivalDate',
key: 'Arrival date',
render: (text, record) => (isEmpty(text) ? '' : dayjs(text).format('YYYY-MM-DD')),
},
{
title: t('group:Pax'),
key: 'Pax',
dataIndex: 'pax'
},
{
title: t('group:Status'),
key: 'Status',
dataIndex: 'status'
},
{
title: t('group:ResSendingDate'),
key: 'Reservation date',
dataIndex: 'reservationDate',
render: (text, record) => (isEmpty(text) ? '' : dayjs(text).format('YYYY-MM-DD')),
},
{
title: t('group:Guide'),
key: 'Guide',
dataIndex: 'guide',
render: guideRender
},
@ -82,7 +76,7 @@ function Newest() {
style={{
width: 280,
}}
bordered={false}
variant='borderless'
allowClear
placeholder='Select a guide'
optionFilterProp='children'

Loading…
Cancel
Save