Merge tag 'feat-RBAC-0.8.1' into feature/price_manager

# Conflicts:
#	src/main.jsx
feature/price_manager
Jimmy Liow 1 year ago
commit 16a21aae43

@ -8,5 +8,24 @@
"CurrentPassword": "请输入密码。",
"NewPassword": "请输入新密码。",
"ReenterPassword": "请重复输入密码。"
}
},
"createdOn": "创建时间",
"action": "操作",
"action.edit": "编辑",
"action.disable": "禁用",
"action.resetPassword": "重置密码",
"accountList": "管理账号",
"newAccount": "新增账号",
"detail": "详细信息",
"username": "用户名",
"realname": "姓名",
"travelAgencyName": "供应商名称",
"email": "邮箱地址",
"lastLogin": "最后登陆时间",
"roleList": "管理角色",
"newRole": "新增角色",
"roleName": "角色名称",
"permission": "权限"
}

@ -9,7 +9,6 @@ import App from "@/views/App";
import Standlone from "@/views/Standlone";
import Login from "@/views/Login";
import Logout from "@/views/Logout";
import Index from "@/views/index";
import ErrorPage from "@/components/ErrorPage";
import RequireAuth from '@/components/RequireAuth'
import ReservationNewest from "@/views/reservation/Newest";
@ -49,6 +48,8 @@ const initAppliction = async () => {
if (isNotEmpty(loginToken)) {
appendRequestParams('token', loginToken)
appendRequestParams('lmi_sn', userId)
}
if (isNotEmpty(userId)) {
@ -65,7 +66,7 @@ const router = createBrowserRouter([
element: <App />,
errorElement: <ErrorPage />,
children: [
{ index: true, element: <Index /> },
{ index: true, element: <NoticeIndex /> },
{ path: "account/change-password", element: <ChangePassword />},
{ path: "account/profile", element: <AccountProfile />},
{ path: "account/management", element: <RequireAuth subject={PERM_ACCOUNT_MANAGEMENT} result={true}><AccountManagement /></RequireAuth>},
@ -76,14 +77,14 @@ const router = createBrowserRouter([
{ path: "feedback/:GRI_SN/:CII_SN/:RefNo", element: <RequireAuth subject={PERM_OVERSEA} result={true}><FeedbackCustomerDetail /></RequireAuth>},
{ path: "feedback/:GRI_SN/:RefNo", element: <RequireAuth subject={PERM_OVERSEA} result={true}><FeedbackDetail /></RequireAuth>},
{ path: "report", element: <RequireAuth subject={PERM_OVERSEA} result={true}><ReportIndex /></RequireAuth>},
{ path: "notice", element: <RequireAuth subject={PERM_OVERSEA} result={true}><NoticeIndex /></RequireAuth>},
{ path: "notice/:CCP_BLID", element: <RequireAuth subject={PERM_OVERSEA} result={true}><NoticeDetail /></RequireAuth>},
{ path: "notice", element: <NoticeIndex />},
{ path: "notice/:CCP_BLID", element: <NoticeDetail />},
{ path: "invoice",element:<RequireAuth subject={PERM_OVERSEA} result={true}><InvoiceIndex /></RequireAuth>},
{ path: "invoice/detail/:GMDSN/:GSN",element:<RequireAuth subject={PERM_OVERSEA} result={true}><InvoiceDetail /></RequireAuth>},
{ path: "invoice/paid",element:<RequireAuth subject={PERM_OVERSEA} result={true}><InvoicePaid /></RequireAuth>},
{ path: "invoice/paid/detail/:flid", element: <RequireAuth subject={PERM_OVERSEA} result={true}><InvoicePaidDetail /></RequireAuth>},
{ path: "airticket",element: <RequireAuth subject={PERM_AIR_TICKET} result={true}><Airticket /></RequireAuth>},
{ path: "airticket/plan/:coli_sn",element:<AirticketPlan />},
{ path: "airticket/plan/:coli_sn",element:<RequireAuth subject={PERM_AIR_TICKET} result={true}><AirticketPlan /></RequireAuth>},
{ path: "products",element: <RequireAuth subject={PERM_PRODUCTS_MANAGEMENT} result={true}><ProductsManage /></RequireAuth>},
{ path: "products/:travel_agency_id/:use_year/:audit_state/audit",element:<RequireAuth subject={PERM_PRODUCTS_MANAGEMENT} result={true}><ProductsAudit /></RequireAuth>},
{ path: "products/:travel_agency_id/:use_year/:audit_state/edit",element:<ProductsDetail />},

@ -70,11 +70,11 @@ const useAccountStore = create((set, get) => ({
accountList: [],
disableAccount: async (accountId) => {
disableAccount: async (userId) => {
const formData = new FormData()
formData.append('wu_id', accountId)
// enable disable
formData.append('lmi_sn', userId)
// enable | disable
formData.append('account_status', 'disable')
const result = await postAccountStatus(formData)
@ -82,10 +82,10 @@ const useAccountStore = create((set, get) => ({
console.info(result)
},
resetAccountPassword: async (accountId, password) => {
resetAccountPassword: async (userId, password) => {
const formData = new FormData()
formData.append('wu_id', accountId)
formData.append('lmi_sn', userId)
formData.append('newPassword', password)
return postAccountPassword(formData)
@ -111,8 +111,8 @@ const useAccountStore = create((set, get) => ({
saveOrUpdateAccount: async (formValues) => {
const { userId } = usingStorage()
const formData = new FormData()
formData.append('wu_id', formValues.userId)
formData.append('lmi_sn', formValues.lmi_sn)
formData.append('wu_id', formValues.accountId)
formData.append('lmi_sn', formValues.userId)
formData.append('lmi2_sn', formValues.lmi2_sn)
formData.append('user_name', formValues.username)
formData.append('real_name', formValues.realname)
@ -138,8 +138,8 @@ const useAccountStore = create((set, get) => ({
const mapAccoutList = resultArray.map((r) => {
return {
userId: r.wu_id,
lmi_sn: r.lmi_sn,
accountId: r.wu_id,
userId: r.lmi_sn,
lmi2_sn: r.lmi2_sn,
username: r.user_name,
realname: r.real_name,

@ -36,7 +36,7 @@ export const fetchUserDetail = async (loginToken) => {
export const fetchPermissionListByUserId = async (userId) => {
const { errcode, result } = await fetchJSON(
`${HT_HOST}/service-CooperateSOA/get_account_permission_list`, { wu_id: userId})
`${HT_HOST}/service-CooperateSOA/get_account_permission_list`, { lmi_sn: userId})
return errcode !== 0 ? {} : result
}
@ -71,7 +71,7 @@ const useAuthStore = create(obervseLifecycle((set, get) => ({
// 以上是 Hardcode 判断
// 以下是权限列表从数据库读取后使用的方法
return permissionList.some((value) => {
if (value.indexOf(WILDCARD_TOKEN) > -1) {
if (value.indexOf(WILDCARD_TOKEN) == 0) {
return true
}
if (value === perm) {
@ -85,9 +85,9 @@ const useAuthStore = create(obervseLifecycle((set, get) => ({
const { startTokenInterval, loadUserPermission } = get()
const { setStorage } = usingStorage()
const { token: loginToken, WU_ID: userId } = await fetchLoginToken(usr, pwd)
const { token: loginToken } = await fetchLoginToken(usr, pwd)
const userDetail = await fetchUserDetail(loginToken)
await loadUserPermission(userId)
await loadUserPermission(userDetail.LMI_SN)
set(() => ({
tokenTimeout: false,
@ -95,7 +95,7 @@ const useAuthStore = create(obervseLifecycle((set, get) => ({
}))
setStorage(KEY_LOGIN_TOKEN, loginToken)
setStorage(KEY_USER_ID, userId)
setStorage(KEY_USER_ID, userDetail.LMI_SN)
setStorage(KEY_TRAVEL_AGENCY_ID, userDetail.LMI_VEI_SN)
appendRequestParams('token', loginToken)
appendRequestParams('wu_id', userDetail.LMI_SN)
@ -131,7 +131,7 @@ const useAuthStore = create(obervseLifecycle((set, get) => ({
const now = new Date()
const diffTime = now.getTime() - lastReqDate.getTime()
const diffHours = diffTime/1000/60/60
if (diffHours > 4) {
if (diffHours > 1) {
loginTimeout()
}
}
@ -144,7 +144,8 @@ const useAuthStore = create(obervseLifecycle((set, get) => ({
loginTimeout: () => {
const { tokenInterval } = get()
// TODO: 这里没有清理 token刷新后可以正常使用系统
const { clearStorage } = usingStorage()
clearStorage()
clearInterval(tokenInterval)
set(() => ({
tokenTimeout: true

@ -4,7 +4,7 @@ import { Layout, Menu, ConfigProvider, theme, Dropdown, Space, Row, Col, Badge,
import { DownOutlined } from '@ant-design/icons';
import 'antd/dist/reset.css';
import AppLogo from '@/assets/logo-gh.png';
import { isEmpty } from '@/utils/commons';
import { isEmpty, isNotEmpty } from '@/utils/commons';
import Language from '../i18n/LanguageSwitcher';
import { useTranslation } from 'react-i18next';
import zhLocale from 'antd/locale/zh_CN';
@ -43,13 +43,15 @@ function App() {
const needToLogin = href !== '/login' && isEmpty(loginToken)
useEffect(() => {
fetchUserDetail(loginToken)
.then(u => {
setUserDetail({
username: u.LoginName,
travelAgencyName: u.VName,
if (isNotEmpty(loginToken)) {
fetchUserDetail(loginToken)
.then(u => {
setUserDetail({
username: u.LoginName,
travelAgencyName: u.VName,
})
})
})
}
}, [loginToken])
useEffect(() => {
@ -58,10 +60,6 @@ function App() {
}
}, [href])
useEffect(() => {
window.gtag('event', 'page_view', { page_location: window.location.href });
}, [location])
const onSubmit = () => {
validateUserPassword(userDetail?.username, password)
.catch(ex => {
@ -69,23 +67,24 @@ function App() {
alert(t('Validation.LoginFailed'))
})
setPassword('')
};
}
const splitPath = href.split('/');
let defaultPath = 'reservation';
const splitPath = href.split('/')
let defaultPath = 'notice'
if (splitPath.length > 1) {
defaultPath = splitPath[1];
defaultPath = splitPath[1]
}
const {
token: { colorBgContainer },
} = theme.useToken();
} = theme.useToken()
const [antdLng, setAntdLng] = useState(enLocale);
useEffect(() => {
setAntdLng(i18n.language === 'en' ? enLocale : zhLocale);
}, [i18n.language]);
}, [i18n.language])
return (
<ConfigProvider locale={antdLng}
theme={{
@ -159,8 +158,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' },
isPermitted(PERM_ACCOUNT_MANAGEMENT) ? { label: <Link to='/account/management'>{t('account:management.tile')}</Link>, key: '3' } : null,
isPermitted(PERM_ROLE_NEW) ? { label: <Link to='/account/role-list'>{t('account:management.roleList')}</Link>, key: '4' } : null,
{ type: 'divider' },
isPermitted(PERM_ACCOUNT_MANAGEMENT) ? { label: <Link to='/account/management'>{t('account:accountList')}</Link>, key: '3' } : null,
isPermitted(PERM_ROLE_NEW) ? { label: <Link to='/account/role-list'>{t('account:roleList')}</Link>, key: '4' } : null,
{ type: 'divider' },
{ label: <Link to='/logout'>{t('Logout')}</Link>, key: '99' },
]
@ -195,7 +195,7 @@ function App() {
</ErrorBoundary>
</AntApp>
</ConfigProvider>
);
)
}
export default App

@ -1,13 +0,0 @@
export default function Index() {
return (
<p id="zero-state">
Global Highlights Hub
<br />
Check out{" "}
<a href="https://www.chinahighlights.com">
the docs at chinahighlights.com
</a>
.
</p>
);
}

@ -17,7 +17,7 @@ function Login() {
useEffect (() => {
if (loginStatus === 302) {
navigate('/reservation/newest')
navigate('/')
}
}, [loginStatus])

@ -1,14 +1,12 @@
import { useState, useEffect } from 'react'
import { Row, Col, Space, Button, Table, Select, TreeSelect, Typography, Modal, App, Form, Input } from 'antd'
import SearchForm from '@/components/SearchForm'
import useAccountStore, { fetchRoleList, fetchTravelAgencyByName } from '@/stores/Account'
import useFormStore from '@/stores/Form'
import { isEmpty } from '@/utils/commons'
import { ExclamationCircleFilled } from '@ant-design/icons'
import { useTranslation } from 'react-i18next'
import { fetchTravelAgencyByName } from '@/stores/Account'
import { App, Button, Col, Form, Input, Modal, Row, Select, Space, Table, Typography } from 'antd'
import dayjs from 'dayjs'
import { isEmpty } from '@/utils/commons'
import useAccountStore from '@/stores/Account'
import useFormStore from '@/stores/Form'
import { fetchRoleList } from '@/stores/Account'
import SearchForm from '@/components/SearchForm'
import { useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
const { Title } = Typography
@ -26,7 +24,7 @@ function Management() {
dataIndex: 'realname',
},
{
title: t('account:travelAgency'),
title: t('account:travelAgencyName'),
dataIndex: 'travelAgencyName',
},
{
@ -34,7 +32,7 @@ function Management() {
dataIndex: 'email',
},
{
title: t('account:role'),
title: t('account:roleName'),
dataIndex: 'role'
},
{
@ -43,7 +41,7 @@ function Management() {
render: (text) => (isEmpty(text) ? '' : dayjs(text).format('YYYY-MM-DD HH:mm:ss'))
},
{
title: t('account:action'),
title: t('account:action.edit'),
dataIndex: 'account:action',
render: actionRender
},
@ -78,7 +76,7 @@ function Management() {
const formValues = useFormStore(state => state.formValues)
const { notification, modal } = App.useApp()
useEffect (() => {
useEffect(() => {
fetchRoleList()
.then((roleList) => {
setRoleAllList(roleList.map(r => {
@ -108,7 +106,6 @@ function Management() {
}
const onAccountSeleted = async (account) => {
console.info(account)
setTravelAgencyList([{
label: account.travelAgencyName,
value: account.travelAgencyId
@ -156,7 +153,6 @@ function Management() {
}
const handleTravelAgencyChange = (newValue) => {
console.info(newValue)
setCurrentTravelAgency(newValue)
}
@ -189,7 +185,6 @@ function Management() {
duration: 60,
})
})
console.log('ResetPassword')
},
onCancel() {
},
@ -204,32 +199,32 @@ function Management() {
autoFocus: true,
htmlType: 'submit',
}}
title={t('account:management.newAccount')}
title={t('account:detail')}
open={isAccountModalOpen} onOk={() => setAccountModalOpen(false)} onCancel={() => setAccountModalOpen(false)}
destroyOnClose={true}
clearOnDestroy={true}
modalRender={(dom) => (
<Form
name='AccountForm'
form={accountForm}
layout='vertical'
size='large'
style={{
maxWidth: 600,
}}
onFinish={onAccountFinish}
onFinishFailed={onAccountFailed}
autoComplete='off'
>
name='AccountForm'
form={accountForm}
layout='vertical'
size='large'
style={{
maxWidth: 600,
}}
onFinish={onAccountFinish}
onFinishFailed={onAccountFailed}
autoComplete='off'
>
{dom}
</Form>
)}
>
<Form.Item name='userId' className='hidden' ><Input /></Form.Item>
<Form.Item name='lmi_sn' className='hidden' ><Input /></Form.Item>
<Form.Item name='lmi2_sn' className='hidden' ><Input /></Form.Item>
<Form.Item name='accountId' className='hidden' ><Input /></Form.Item>
<Form.Item name='userId' className='hidden' ><Input /></Form.Item>
<Form.Item name='lmi2_sn' className='hidden' ><Input /></Form.Item>
<Form.Item
label={t('account:management.username')}
label={t('account:username')}
name='username'
rules={[
{
@ -241,7 +236,7 @@ function Management() {
<Input />
</Form.Item>
<Form.Item
label={t('account:management.realname')}
label={t('account:realname')}
name='realname'
rules={[
{
@ -253,7 +248,7 @@ function Management() {
<Input />
</Form.Item>
<Form.Item
label={t('account:management.email')}
label={t('account:email')}
name='email'
rules={[
{
@ -265,7 +260,7 @@ function Management() {
<Input />
</Form.Item>
<Form.Item
label={t('account:management.travelAgency')}
label={t('account:travelAgencyName')}
name='travelAgencyId'
rules={[
{
@ -287,7 +282,7 @@ function Management() {
</Select>
</Form.Item>
<Form.Item
label={t('account:management.role')}
label={t('account:roleName')}
name='roleId'
rules={[
{
@ -296,17 +291,22 @@ function Management() {
},
]}
>
<Select options={roleAllList}>
<Select
options={roleAllList}
filterOption={false}
notFoundContent={null}
>
</Select>
</Form.Item>
</Modal>
<Space direction='vertical' style={{ width: '100%' }}>
<Title level={3}>{t('account:management.tile')}</Title>
<Title level={3}>{t('account:accountList')}</Title>
<SearchForm
fieldsConfig={{
shows: ['username', 'realname', 'dates'],
shows: ['username', 'realname'],
fieldProps: {
dates: { label: t('group:ArrivalDate') },
username: { label: t('account:username') },
realname: { label: t('account:realname') },
},
sort: { username: 1, realname: 2, dates: 3},
}}
@ -317,7 +317,7 @@ function Management() {
<Row>
<Col span={24}>
<Space>
<Button onClick={() => setAccountModalOpen(true)}>{t('account:management.newAccount')}</Button>
<Button onClick={() => setAccountModalOpen(true)}>{t('account:newAccount')}</Button>
</Space>
</Col>
</Row>

@ -1,12 +1,14 @@
import { useState, useEffect } from 'react'
import { Row, Col, Space, Button, Table, TreeSelect, Typography, Modal, App, Form, Input } from 'antd'
import { useTranslation } from 'react-i18next'
import useAccountStore from '@/stores/Account'
import { fetchRoleList, fetchPermissionList, fetchPermissionListByRoleId } from '@/stores/Account'
import dayjs from 'dayjs'
import { isEmpty } from '@/utils/commons'
import RequireAuth from '@/components/RequireAuth'
import { PERM_ROLE_NEW } from '@/config'
import useAccountStore, { fetchPermissionList, fetchPermissionListByRoleId, fetchRoleList } from '@/stores/Account'
import { isEmpty } from '@/utils/commons'
import {
SyncOutlined,
} from '@ant-design/icons'
import { App, Button, Col, Form, Input, Modal, Row, Space, Table, Tag, TreeSelect, Typography } from 'antd'
import dayjs from 'dayjs'
import { useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
const { Title } = Typography
@ -15,7 +17,7 @@ function RoleList() {
const roleListColumns = [
{
title: t('account:rolename'),
title: t('account:roleName'),
dataIndex: 'role_name',
},
{
@ -30,8 +32,10 @@ function RoleList() {
},
]
function actionRender(text, role) {
if (role.role_id > 1) {
function actionRender(_, role) {
if (role.role_id == 1) {
return (<Tag icon={<SyncOutlined spin />} color='warning'>不能修改</Tag>)
} else {
return (
<Button type='link' key='edit' onClick={() => onRoleSeleted(role)}>{t('account:action.edit')}</Button>
)
@ -49,7 +53,7 @@ function RoleList() {
}, {})
}
useEffect (() => {
useEffect(() => {
setDataLoading(true)
fetchRoleList()
.then(r => {
@ -110,7 +114,7 @@ function RoleList() {
const { notification, modal } = App.useApp()
const onRoleSeleted = (role) => {
fetchPermissionListByRoleId({role_id: role.role_id})
fetchPermissionListByRoleId({ role_id: role.role_id })
.then(result => {
role.res_array = result.map(r => r.res_id)
roleForm.setFieldsValue(role)
@ -154,71 +158,71 @@ function RoleList() {
autoFocus: true,
htmlType: 'submit',
}}
title={t('account:management.newRole')}
title={t('account:detail')}
open={isRoleModalOpen} onOk={() => setRoleModalOpen(false)} onCancel={() => setRoleModalOpen(false)}
destroyOnClose={true}
clearOnDestroy={true}
modalRender={(dom) => (
<Form
name='RoleForm'
form={roleForm}
layout='vertical'
size='large'
style={{
maxWidth: 600,
}}
onFinish={onRoleFinish}
onFinishFailed={onRoleFailed}
autoComplete='off'
>
name='RoleForm'
form={roleForm}
layout='vertical'
size='large'
style={{
maxWidth: 600,
}}
onFinish={onRoleFinish}
onFinishFailed={onRoleFailed}
autoComplete='off'
>
{dom}
</Form>
)}
>
<Form.Item name='role_id' className='hidden' ><Input /></Form.Item>
<Form.Item
label={t('account:management.roleName')}
name='role_name'
rules={[
{
required: true,
message: t('account:Validation.roleName'),
},
]}
>
<Input />
</Form.Item>
<Form.Item
label={t('account:management.permission')}
name='res_array'
>
<TreeSelect treeData={permissionTreeData} value={permissionValue}
dropdownStyle={{
maxHeight: 600,
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.Item name='role_id' className='hidden' ><Input /></Form.Item>
<Form.Item
label={t('account:roleName')}
name='role_name'
rules={[
{
required: true,
message: t('account:Validation.roleName'),
},
]}
>
<Input />
</Form.Item>
<Form.Item
label={t('account:permission')}
name='res_array'
>
<TreeSelect treeData={permissionTreeData} value={permissionValue}
dropdownStyle={{
maxHeight: 600,
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>
</Modal>
<Space direction='vertical' style={{ width: '100%' }}>
<Title level={3}>{t('account:management.roleList')}</Title>
<Title level={3}>{t('account:roleList')}</Title>
<Row>
<Col span={24}>
<Space>
<RequireAuth subject={PERM_ROLE_NEW}>
<Button onClick={() => onNewRole()}>{t('account:management.newRole')}</Button>
<Button onClick={() => onNewRole()}>{t('account:newRole')}</Button>
</RequireAuth>
</Space>
</Col>
@ -230,6 +234,7 @@ function RoleList() {
loading={dataLoading}
rowKey='role_id'
pagination={{
pageSize: 20,
showQuickJumper: true,
showLessItems: true,
showSizeChanger: true,

Loading…
Cancel
Save