Merge remote-tracking branch 'origin/feature/price_manager' into feature/price_manager

feature/price_manager
Lei OT 1 year ago
commit 545bee21cb

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

@ -68,6 +68,7 @@
"validityPeriod":"Validity period",
"operation": "Operation",
"price": "Price",
"weekends":"Weekends",
"Quotation": "Quotation",
"Offer": "Offer",

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

@ -31,6 +31,10 @@ 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 { fireAuth } from "./utils/lifecycle"
import ProductsManage from '@/views/products/Manage';
import ProductsDetail from '@/views/products/Detail';
@ -39,6 +43,22 @@ import { PERM_ACCOUNT_MANAGEMENT, PERM_ROLE_NEW, PERM_OVERSEA, PERM_AIR_TICKET,
import './i18n';
const { loginToken, userId } = usingStorage()
const initAppliction = async () => {
if (isNotEmpty(loginToken)) {
appendRequestParams('token', loginToken)
}
if (isNotEmpty(userId)) {
appendRequestParams('wu_id', userId)
await fireAuth()
}
}
await initAppliction()
const router = createBrowserRouter([
{
path: "/",
@ -76,7 +96,7 @@ const router = createBrowserRouter([
{ path: "/logout", element: <Logout /> },
]
}
]);
])
ReactDOM.createRoot(document.getElementById("root")).render(
@ -88,4 +108,4 @@ ReactDOM.createRoot(document.getElementById("root")).render(
/>
</ThemeContext.Provider>
//</React.StrictMode>
);
)

@ -45,6 +45,27 @@ 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
}
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: [],
@ -82,7 +103,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)
},
@ -124,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),

@ -3,12 +3,17 @@ 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'
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,12 +33,26 @@ 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
}
const useAuthStore = create((set, get) => ({
const useAuthStore = create(obervseLifecycle((set, get) => ({
onAuth: () => {
const { startTokenInterval, loadUserPermission } = get()
const { userId } = usingStorage()
loadUserPermission(userId)
startTokenInterval()
},
tokenInterval: null,
@ -41,66 +60,62 @@ const useAuthStore = create((set, get) => ({
loginStatus: 0,
loginUser: {
token: '',
telephone: '',
emailAddress: '',
cityId: 0,
permissionList: [],
},
isPermitted: (perm) => {
return true
const { permissionList } = get()
// 测试权限使用:
// if (perm === '/account/management') return false
// if (perm === '/account/role/new') return false
// 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) => {
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 { token: loginToken, WU_ID: userId } = await fetchLoginToken(usr, pwd)
const userDetail = await fetchUserDetail(loginToken)
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)
appendRequestParams('wu_id', userDetail.LMI_SN)
// 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
@ -153,6 +168,7 @@ const useAuthStore = create((set, get) => ({
}
});
},
}))
})))
export default useAuthStore

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

@ -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,20 +14,25 @@ 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'
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] = useAuthStore(
(state) => [state.validateUserPassword, state.tokenTimeout])
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()
@ -38,10 +42,15 @@ function App() {
// /p...
const needToLogin = href !== '/login' && isEmpty(loginToken)
if (!needToLogin) {
appendRequestParams('token', loginToken)
appendRequestParams('wu_id', userId)
}
useEffect(() => {
fetchUserDetail(loginToken)
.then(u => {
setUserDetail({
username: u.LoginName,
travelAgencyName: u.VName,
})
})
}, [loginToken])
useEffect(() => {
if (needToLogin) {
@ -51,7 +60,7 @@ function App() {
useEffect(() => {
window.gtag('event', 'page_view', { page_location: window.location.href });
}, [location]);
}, [location])
const onSubmit = () => {
validateUserPassword(userDetail?.username, password)
@ -121,11 +130,11 @@ function App() {
mode='horizontal'
selectedKeys={[defaultPath]}
items={[
{ key: 'reservation', label: <Link to='/reservation/newest'>{t('menu.Reservation')}</Link> },
{ key: 'invoice', label: <Link to='/invoice'>{t('menu.Invoice')}</Link> },
{ key: 'feedback', label: <Link to='/feedback'>{t('menu.Feedback')}</Link> },
{ key: 'report', label: <Link to='/report'>{t('menu.Report')}</Link> },
{ key: 'airticket', label: <Link to='/airticket'>{t('menu.Airticket')}</Link> },
isPermitted(PERM_OVERSEA) ? { key: 'reservation', label: <Link to='/reservation/newest'>{t('menu.Reservation')}</Link> } : null,
isPermitted(PERM_OVERSEA) ? { key: 'invoice', label: <Link to='/invoice'>{t('menu.Invoice')}</Link> } : null,
isPermitted(PERM_OVERSEA) ? { key: 'feedback', label: <Link to='/feedback'>{t('menu.Feedback')}</Link> } : null,
isPermitted(PERM_OVERSEA) ? { key: 'report', label: <Link to='/report'>{t('menu.Report')}</Link> } : null,
isPermitted(PERM_AIR_TICKET) ? { key: 'airticket', label: <Link to='/airticket'>{t('menu.Airticket')}</Link> } : null,
{ key: 'products', label: <Link to='/products'>{t('menu.Products')}</Link> },
{
key: 'notice',
@ -150,13 +159,11 @@ 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' },
{ label: <Link to='/account/role-list'>{t('account:management.roleList')}</Link>, key: '4' },
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' },
{ label: <Link to='/logout'>{t('Logout')}</Link>, key: '99' },
],
{ type: 'divider' },
{ label: <>v{BUILD_VERSION}</>, key: 'BUILD_VERSION' },
]
],
}}
trigger={['click']}
@ -183,7 +190,7 @@ function App() {
}}>
{needToLogin ? <>login...</> : <Outlet />}
</Content>
<Footer></Footer>
<Footer>China Highlights International Travel Service Co., LTD, Version: {BUILD_VERSION}</Footer>
</Layout>
</ErrorBoundary>
</AntApp>

@ -1,14 +1,14 @@
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 { 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
@ -27,7 +27,7 @@ function Management() {
},
{
title: t('account:travelAgency'),
dataIndex: 'travelAgency',
dataIndex: 'travelAgencyName',
},
{
title: t('account:email'),
@ -40,6 +40,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'),
@ -66,17 +67,20 @@ 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()
const onAccountSeleted = async (account) => {
accountForm.setFieldsValue(account)
const roleList = await fetchRoleList()
useEffect (() => {
fetchRoleList()
.then((roleList) => {
setRoleAllList(roleList.map(r => {
return {
value: r.role_id,
@ -84,12 +88,42 @@ function Management() {
disabled: r.role_id === 1
}
}))
})
}, [])
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',
@ -105,6 +139,27 @@ function Management() {
// form.resetFields()
}
const handleTravelAgencySearch = (newValue) => {
setDataLoading(true)
fetchTravelAgencyByName(newValue)
.then(result => {
setTravelAgencyList(result.map(r => {
return {
label: r.travel_agency_name,
value: r.travel_agency_id
}
}))
})
.finally(() => {
setDataLoading(false)
})
}
const handleTravelAgencyChange = (newValue) => {
console.info(newValue)
setCurrentTravelAgency(newValue)
}
const showDisableConfirm = (account) => {
modal.confirm({
title: 'Do you want to disable this account?',
@ -219,7 +274,17 @@ function Management() {
},
]}
>
<Select options={[{ value: 33032, label: 'test海外地接B' }]}></Select>
<Select
options={travelAgencyList}
value={currentTravelAgency}
onChange={handleTravelAgencyChange}
loading={dataLoading}
showSearch
filterOption={false}
onSearch={handleTravelAgencySearch}
notFoundContent={null}
>
</Select>
</Form.Item>
<Form.Item
label={t('account:management.role')}
@ -245,21 +310,8 @@ function Management() {
},
sort: { username: 1, realname: 2, dates: 3},
}}
onSubmit={(err, formValues, filedsVal) => {
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()
}}
/>
<Row>

@ -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,46 @@ function RoleList() {
.finally(() => {
setDataLoading(false)
})
const categoryMap = new Map([
['system', '系统管理'],
['oversea', '海外供应商'],
['domestic', '国内供应商'],
['air-ticket', '机票供应商'],
['products', '产品价格'],
]);
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: categoryMap.get(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([])
const [permissionTreeData, setPermissionTreeData] = useState([])
const [isRoleModalOpen, setRoleModalOpen] = useState(false)
const [dataLoading, setDataLoading] = useState(false)
const [roleAllList, setRoleAllList] = useState([])
@ -154,7 +110,11 @@ function RoleList() {
const { notification, modal } = App.useApp()
const onRoleSeleted = (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 +125,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 +143,6 @@ function RoleList() {
}
const onRoleFailed = (error) => {
console.log('Failed:', error)
// form.resetFields()
}
@ -225,8 +188,11 @@ function RoleList() {
>
<Input />
</Form.Item>
<Form.Item label={t('account:management.permission')}>
<TreeSelect treeData={permissionData} value={permissionValue}
<Form.Item
label={t('account:management.permission')}
name='res_array'
>
<TreeSelect treeData={permissionTreeData} value={permissionValue}
dropdownStyle={{
maxHeight: 600,
overflow: 'auto',

@ -4,6 +4,8 @@ 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';
import Extras from './Detail/Extras';
@ -11,17 +13,19 @@ import Extras from './Detail/Extras';
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') },
@ -32,88 +36,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);
@ -125,7 +150,46 @@ function Index() {
return (
<td {...restProps}>
{children}
<Button onClick={() => handleDateSelect(record.key)}>选择日期</Button>
<Button onClick={() => handleDateSelect(record.id)}>选择日期</Button>
</td>
);
}
if (dataIndex === 'age_type' && editing) {
inputNode = (
<Select>
<Select.Option value="每人">每人</Select.Option>
<Select.Option value="每团">每团</Select.Option>
</Select>
);
}
if (dataIndex === 'currency' && editing) {
inputNode = (
<Select>
<Select.Option value="每人">RMB</Select.Option>
<Select.Option value="每团">MY</Select.Option>
</Select>
);
}
if (dataIndex === 'group_size' && editing) {
const groupSizeValue = `${record.group_size_min}-${record.group_size_max}`;
return (
<td {...restProps} style={{ height: 80, display: 'flex', alignItems: 'center' }}>
<InputNumber
min={0}
value={record.group_size_min}
onChange={(value) => handleInputGroupSize('group_size_min', record.id, 'group_size', value)}
style={{ width: '50%', marginRight: '10px' }}
/>
<span>-</span>
<InputNumber
min={0}
value={record.group_size_max}
onChange={(value) => handleInputGroupSize('group_size_max', record.id, 'group_size', value)}
style={{ width: '50%', marginLeft: '10px' }}
/>
</td>
);
}
@ -147,13 +211,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',
@ -161,19 +265,20 @@ function Index() {
const editable = isEditing(record);
return editable ? (
<span>
<a href="#!" onClick={() => handleSave(record.key)} style={{ marginRight: 8 }}>{t('products:save')}</a>
<a href="#!" onClick={() => handleSave(record.id)} style={{ marginRight: 8 }}>{t('products:save')}</a>
<Popconfirm title={t('products:sureCancel')} onConfirm={cancel}><a>{t('products:cancel')}</a></Popconfirm>
</span>
) : (
<span>
<a disabled={editingKey !== ''} onClick={() => edit(record)} style={{ marginRight: 8 }}>{t('products:edit')}</a>
<Popconfirm title={t('products:sureCancel')} onConfirm={() => handleDelete(record.key)}>
<a disabled={editingid !== ''} onClick={() => edit(record)} style={{ marginRight: 8 }}>{t('products:edit')}</a>
<Popconfirm title={t('products:sureCancel')} onConfirm={() => handleDelete(record.id)}>
<a>{t('products:delete')}</a>
</Popconfirm>
</span>
);
},
},
];
@ -235,21 +340,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);
@ -262,7 +367,7 @@ function Index() {
const values = await form.validateFields();
const newFormState = {
...formState,
[selectedNodeKey]: {
[selectedNodeid]: {
...values,
quotation,
lgc_details: languageStatus,
@ -287,21 +392,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,
@ -313,7 +417,16 @@ function Index() {
children,
...restProps
}) => {
const inputNode = inputType === 'number' ? <InputNumber /> : <Input />;
let inputNode = inputType === 'number' ? <InputNumber /> : <Input />;
if (dataIndex === 'age_type' && editing) {
inputNode = (
<Select>
<Select.Option value="每人">每人</Select.Option>
<Select.Option value="每团">每团</Select.Option>
</Select>
);
}
return (
<td {...restProps}>
{editing ? (
@ -331,9 +444,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',
@ -341,13 +454,13 @@ function Index() {
const editable = isEditingBinding(record);
return editable ? (
<span>
<a href="#!" onClick={() => handleSaveBinding(record.key)} style={{ marginRight: 8 }}>{t('products:save')}</a>
<a href="#!" onClick={() => handleSaveBinding(record.id)} style={{ marginRight: 8 }}>{t('products:save')}</a>
<Popconfirm title={t('products:sureCancel')} onConfirm={cancelBinding}><a>{t('products:cancel')}</a></Popconfirm>
</span>
) : (
<span>
<a disabled={editingKeyBinding !== ''} onClick={() => editBinding(record)} style={{ marginRight: 8 }}>{t('products:edit')}</a>
<Popconfirm title={t('products:sureDelete')} onConfirm={() => handleDeleteBinding(record.key)}>
<a disabled={editingidBinding !== ''} onClick={() => editBinding(record)} style={{ marginRight: 8 }}>{t('products:edit')}</a>
<Popconfirm title={t('products:sureDelete')} onConfirm={() => handleDeleteBinding(record.id)}>
<a>{t('products:delete')}</a>
</Popconfirm>
</span>
@ -374,31 +487,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);
@ -406,9 +519,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);
};
@ -422,18 +535,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' }
]
}
]
@ -448,16 +561,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 (
@ -487,7 +607,7 @@ function Index() {
<h2>{t('products:productProject')}</h2>
<Row gutter={16}>
{productProject.map((item, index) => (
<Col span={8} key={index}>
<Col span={8} id={index}>
<Form.Item name={['info', item.code]} label={item.name}>
<Input />
</Form.Item>
@ -499,7 +619,7 @@ function Index() {
<div>
{tags.map(tag => (
<Tag
key={tag}
id={tag}
onClick={() => handleTagClick(tag)}
color={tag === selectedTag ? 'blue' : undefined}
style={{ cursor: 'pointer' }}
@ -583,6 +703,9 @@ function Index() {
<Button type="primary" htmlType="submit" style={{ marginTop: 16, float: "right", marginRight: "20%" }}>
{t('products:save')}
</Button>
<Button type="primary" htmlType="submit" style={{ marginTop: 16, float: "right", marginRight:"5%" }}>
提交审核
</Button>
</Form>
</Col>
</Row>

Loading…
Cancel
Save