diff --git a/README.md b/README.md
index 6266731..5d5c5bc 100644
--- a/README.md
+++ b/README.md
@@ -33,6 +33,7 @@ antd https://ant-design.antgroup.com/components/upload-cn#uploadfile
wps的文档预览 https://wwo.wps.cn/docs/front-end/introduction/quick-start
pdf生成 https://github.com/ivmarcos/react-to-pdf
react-pdf https://react-pdf.org
+生成Docx文档 https://docx.js.org/#/?id=welcome
## 阿里云OSS
Bucket 名称:global-highlights-hub
diff --git a/doc/RBAC 权限.sql b/doc/RBAC 权限.sql
index 75bc702..489d8f4 100644
--- a/doc/RBAC 权限.sql
+++ b/doc/RBAC 权限.sql
@@ -1,3 +1,5 @@
+use Tourmanager
+
CREATE TABLE auth_role
(
[role_id] [int] IDENTITY(1,1) NOT NULL,
@@ -87,6 +89,8 @@ INSERT INTO [dbo].[auth_resource] ([res_name] ,[res_pattern], [res_category])
VALUES ('产品管理(客服)', 'route=/products', 'page')
INSERT INTO [dbo].[auth_resource] ([res_name] ,[res_pattern], [res_category])
VALUES ('产品管理(供应商)', 'route=/products/edit', 'page')
+INSERT INTO [dbo].[auth_resource] ([res_name] ,[res_pattern], [res_category])
+VALUES ('采购年份', 'route=/products/pick-year', 'page')
INSERT INTO [dbo].[auth_permission] ([role_id] ,[res_id])
VALUES (1, 1)
diff --git a/package.json b/package.json
index 23eb1d3..c84312b 100644
--- a/package.json
+++ b/package.json
@@ -22,8 +22,8 @@
"i18next-http-backend": "^2.5.2",
"react": "^18.3.1",
"react-dom": "^18.3.1",
- "react-router-dom": "^6.30.1",
"react-i18next": "^14.1.2",
+ "react-router-dom": "^6.30.1",
"react-to-pdf": "^1.0.1",
"xlsx": "https://cdn.sheetjs.com/xlsx-0.18.11/xlsx-0.18.11.tgz",
"zustand": "^4.5.7"
diff --git a/public/app-logo.jpg b/public/app-logo.jpg
deleted file mode 100644
index 0969a97..0000000
Binary files a/public/app-logo.jpg and /dev/null differ
diff --git a/public/locales/en/common.json b/public/locales/en/common.json
index 5fc92b0..4e8321a 100644
--- a/public/locales/en/common.json
+++ b/public/locales/en/common.json
@@ -36,6 +36,8 @@
"Table": {
"Total": "Total {{total}} items"
},
+ "operator": "Operator",
+ "time": "Time",
"Login": "Login",
"Username": "Username",
"Realname": "Realname",
@@ -105,4 +107,4 @@
"Finance_Dept_arrproved": "Finance Dept arrproved",
"Paid": "Paid"
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/en/products.json b/public/locales/en/products.json
index 7f4357f..20507cb 100644
--- a/public/locales/en/products.json
+++ b/public/locales/en/products.json
@@ -1,5 +1,9 @@
{
"ProductType": "Product Type",
+ "ProductName": "Product Name",
+ "ContractRemarks": "合同备注",
+ "versionHistory": "Version History",
+ "versionPublished": "Published",
"type": {
"Experience": "Experience",
"Car": "Transport Services",
@@ -49,6 +53,8 @@
"RecommendsRate": "Recommends Rate",
"OpenWeekdays": "Open Weekdays",
"DisplayToC": "Display To C",
+ "SortOrder": "Sort order",
+ "subTypeD": "Package Type",
"Dept": "Dept",
"Code": "Code",
"City": "City",
@@ -79,7 +85,8 @@
"withQuote": "Whether to copy the quotation",
"requiredVendor": "Please pick a target vendor",
"requiredTypes": "Please select product types",
- "requiredDept": "Please pick a owner department"
+ "requiredDept": "Please pick a owner department",
+ "copyTo": "Copy to"
},
"Validation": {
"adultPrice": "请输入成人价",
diff --git a/public/locales/zh/common.json b/public/locales/zh/common.json
index d749ecd..0a57df9 100644
--- a/public/locales/zh/common.json
+++ b/public/locales/zh/common.json
@@ -36,6 +36,8 @@
"Table": {
"Total": "共 {{total}} 条"
},
+ "operator": "操作",
+ "time": "时间",
"Login": "登录",
"Username": "账号",
"Realname": "姓名",
@@ -105,4 +107,4 @@
"Finance_Dept_arrproved": "财务已审核",
"Paid": "已打款"
}
-}
\ No newline at end of file
+}
diff --git a/public/locales/zh/products.json b/public/locales/zh/products.json
index a0ef6fa..7bfe0e1 100644
--- a/public/locales/zh/products.json
+++ b/public/locales/zh/products.json
@@ -1,6 +1,8 @@
{
"ProductType": "项目类型",
"ContractRemarks": "合同备注",
+ "versionHistory": "查看历史",
+ "versionPublished": "已发布的",
"type": {
"Experience": "综费",
"Car": "车费",
@@ -50,6 +52,8 @@
"RecommendsRate": "推荐指数",
"OpenWeekdays": "开放时间",
"DisplayToC": "报价信显示",
+ "SortOrder": "排序",
+ "subTypeD": "包价类型",
"Dept": "小组",
"Code": "简码",
"City": "城市",
diff --git a/src/components/LogUploader.jsx b/src/components/LogUploader.jsx
new file mode 100644
index 0000000..b9a40e0
--- /dev/null
+++ b/src/components/LogUploader.jsx
@@ -0,0 +1,76 @@
+import { useState } from "react";
+import { Popover, message, FloatButton, Button, Form, Input } from "antd";
+import { BugOutlined } from "@ant-design/icons";
+import useAuthStore from "@/stores/Auth";
+import { uploadPageSpyLog, sendNotify } from "@/pageSpy";
+
+function LogUploader() {
+ const [open, setOpen] = useState(false);
+ const hide = () => {
+ setOpen(false);
+ };
+ const handleOpenChange = (newOpen) => {
+ setOpen(newOpen);
+ };
+
+ const [currentUser] = useAuthStore((s) => [s.currentUser]);
+
+ const [messageApi, contextHolder] = message.useMessage();
+ const [formBug] = Form.useForm();
+
+ const popoverContent = (
+
+
+
+
+
+ );
+
+ return (
+ <>
+ {contextHolder}
+
+ } />
+
+ >
+ );
+}
+
+export default LogUploader;
diff --git a/src/components/SearchForm.jsx b/src/components/SearchForm.jsx
index cc182ae..7959904 100644
--- a/src/components/SearchForm.jsx
+++ b/src/components/SearchForm.jsx
@@ -271,7 +271,7 @@ function getFields(props) {
"agency", //地接社
99,
-
+
,
fieldProps?.agency?.col || 6
),
diff --git a/src/config.js b/src/config.js
index 25c1040..d714edd 100644
--- a/src/config.js
+++ b/src/config.js
@@ -42,7 +42,7 @@ export const PERM_TRAIN_TICKET = '/train-ticket/all'
// 价格管理
export const PERM_PRODUCTS_MANAGEMENT = '/products/*'; // 管理
export const PERM_PRODUCTS_NEW = '/products/new'; // 新增产品
-export const PERM_PRODUCTS_INFO_AUDIT = '/products/info/audit'; // 信息.审核
+export const PERM_PRODUCTS_INFO_AUDIT = '/products/info/audit'; // 信息.审核 @deprecated
export const PERM_PRODUCTS_INFO_PUT = '/products/info/put'; // 信息.录入
export const PERM_PRODUCTS_OFFER_AUDIT = '/products/offer/audit'; // 价格.审核
export const PERM_PRODUCTS_OFFER_PUT = '/products/offer/put'; // 价格.录入
diff --git a/src/hooks/useProductsSets.js b/src/hooks/useProductsSets.js
index 6a3d464..d960e91 100644
--- a/src/hooks/useProductsSets.js
+++ b/src/hooks/useProductsSets.js
@@ -96,20 +96,22 @@ export const useProductsAuditStatesMapVal = (value) => {
};
/**
- * @ignore
+ *
*/
export const useProductsTypesFieldsets = (type) => {
const [isPermitted] = useAuthStore((state) => [state.isPermitted]);
- const infoDefault = [['city'], ['title']];
+ const infoDefault = [['city', 'city_list'], ['title']];
const infoAdmin = ['title', 'product_title', 'code', 'remarks', 'dept']; // 'display_to_c'
const infoDisplay = isPermitted(PERM_PRODUCTS_MANAGEMENT) ? ['display_to_c'] : [];
const infoRecDisplay = isPermitted(PERM_PRODUCTS_MANAGEMENT) ? ['recommends_rate'] : [];
+ const subTypeD = isPermitted(PERM_PRODUCTS_MANAGEMENT) ? ['sub_type_D'] : [];
+ const sortOrder = isPermitted(PERM_PRODUCTS_MANAGEMENT) ? ['sort_order'] : [];
const infoTypesMap = {
'6': [[...infoDisplay], []],
'B': [['km', ...infoDisplay], []],
- 'J': [[...infoRecDisplay, 'duration', ...infoDisplay], ['description']],
- 'Q': [[...infoRecDisplay, 'duration', ...infoDisplay], ['description']],
- 'D': [[...infoRecDisplay, 'duration', ...infoDisplay], ['description']],
+ 'J': [[...infoRecDisplay, 'duration', ...infoDisplay, ...sortOrder], ['description']],
+ 'Q': [[...infoRecDisplay, 'duration', ...infoDisplay, ...sortOrder], ['description']],
+ 'D': [[...infoRecDisplay, 'duration', ...infoDisplay, ...subTypeD, ...sortOrder], ['description']],
'7': [[...infoRecDisplay, 'duration', 'open_weekdays', ...infoDisplay], ['description']],
'R': [[...infoDisplay], ['description']],
'8': [[...infoDisplay], []],
@@ -152,6 +154,10 @@ export const useNewProductRecord = () => {
'create_date': '',
'created_by': '',
'edit_status': 2,
+ 'sort_order': '',
+ 'sub_type_D': '', // 包价类型, 值保存在`item_type`字段中
+ 'item_type': '', // 产品子类型的值
+ 'city_list': [],
},
lgc_details: [
{
@@ -182,3 +188,24 @@ export const useNewProductRecord = () => {
],
};
};
+
+export const PackageTypes = [
+ { key: '35001', value: '35001', label: '飞机接送' },
+ { key: '35002', value: '35002', label: '车站接送' },
+ { key: '35003', value: '35003', label: '码头接送' },
+ { key: '35004', value: '35004', label: '一天游' },
+ { key: '35005', value: '35005', label: '半天游' },
+ { key: '35006', value: '35006', label: '夜间活动' },
+ { key: '35007', value: '35007', label: '大车游' },
+ { key: '35008', value: '35008', label: '单车单导' },
+ { key: '35009', value: '35009', label: '单租车' },
+ { key: '35010', value: '35010', label: '单导游' },
+ { key: '35011', value: '35011', label: '火车站接送' },
+ { key: '35012', value: '35012', label: '门票预定' },
+ { key: '35013', value: '35013', label: '车导费' },
+ { key: '35014', value: '35014', label: '其它(餐补等)' },
+];
+
+export const formatGroupSize = (min, max) => {
+ return max === 1000 ? min === 0 ? '不分人等' : `${min}人以上` : `${min}-${max}`;
+};
diff --git a/src/main.jsx b/src/main.jsx
index 626f7ae..c383813 100644
--- a/src/main.jsx
+++ b/src/main.jsx
@@ -46,6 +46,8 @@ import ProductsManage from '@/views/products/Manage';
import ProductsDetail from '@/views/products/Detail';
import ProductsAudit from '@/views/products/Audit';
import ImageViewer from '@/views/ImageViewer';
+import PickYear from './views/products/PickYear'
+
import { PERM_ACCOUNT_MANAGEMENT, PERM_ROLE_NEW, PERM_OVERSEA,PERM_TRAIN_TICKET, PERM_AIR_TICKET, PERM_PRODUCTS_MANAGEMENT, PERM_PRODUCTS_OFFER_PUT } from '@/config'
import './i18n'
@@ -95,6 +97,7 @@ const initRouter = async () => {
{ path: "products/:travel_agency_id/:use_year/:audit_state/edit",element:},
{ path: "products/audit",element:},
{ path: "products/edit",element:},
+ { path: "products/pick-year",element: },
//
]
},
diff --git a/src/pageSpy/index.jsx b/src/pageSpy/index.jsx
index a0918e5..0bf2433 100644
--- a/src/pageSpy/index.jsx
+++ b/src/pageSpy/index.jsx
@@ -1,5 +1,27 @@
import { loadScript } from '@/utils/commons';
import { PROJECT_NAME, BUILD_VERSION } from '@/config';
+import { fetchJSON } from '@/utils/request'
+import { usingStorage } from "@/hooks/usingStorage";
+
+export const sendNotify = async (message) => {
+
+ const { userId, travelAgencyId } = usingStorage();
+ const notifyUrl = 'https://p9axztuwd7x8a7.mycht.cn/dingtalk/dingtalkwork/SendMDMsgByDingRobotToGroup';
+
+ const params = {
+ groupid: 'cidFtzcIzNwNoiaGU9Q795CIg==',
+ msgTitle: '有人求助',
+ msgText: `${message}\\n\\nID: ${userId}, ${travelAgencyId} | ${PROJECT_NAME} (${BUILD_VERSION})`,
+ };
+
+ return fetchJSON(notifyUrl, params).then((json) => {
+ if (json.errcode === 0) {
+ console.info('发送通知成功');
+ } else {
+ throw new Error(json?.errmsg + ': ' + json.errcode);
+ }
+ });
+};
export const loadPageSpy = (title) => {
@@ -20,19 +42,45 @@ export const loadPageSpy = (title) => {
PageSpy.registerPlugin(p)
})
window.$pageSpy = new PageSpy(PageSpyConfig);
+
+ window.onerror = async function (msg, url, lineNo, columnNo, error) {
+ // 上传最近 3 分钟的日志
+ const now = Date.now()
+ await window.$harbor.uploadPeriods({
+ startTime: now - 3 * 60000,
+ endTime: now,
+ remark: `\`onerror\`自动上传. ${msg}`,
+ })
+ }
});
};
export const uploadPageSpyLog = async () => {
- // window.$pageSpy.triggerPlugins('onOfflineLog', 'upload');
+
+ if (import.meta.env.DEV) return true;
+
if (window.$pageSpy) {
- await window.$harbor.upload() // 上传日志 { clearCache: true, remark: '' }
- alert('Success')
+ try {
+ // await window.$harbor.upload() // 上传日志 { clearCache: true, remark: '' }
+ // 上传最近 1 小时的日志, 直接upload 所有日志: 413 Payload Too Large
+ const now = Date.now();
+ await window.$harbor.uploadPeriods({
+ startTime: now - 60 * 60000,
+ endTime: now,
+ });
+ return true;
+ } catch (error) {
+ return false;
+ }
} else {
- alert('Failure')
+ return false;
}
}
+/**
+ * @deprecated
+ * @outdated
+ */
export const PageSpyLog = () => {
return (
<>
diff --git a/src/stores/Auth.js b/src/stores/Auth.js
index 8828884..34768fe 100644
--- a/src/stores/Auth.js
+++ b/src/stores/Auth.js
@@ -37,14 +37,7 @@ export const fetchPermissionListByUserId = async (userId) => {
return errcode !== 0 ? {} : result
}
-// 取消令牌时间过期检测,待删除
-async function fetchLastRequet() {
- const { errcode, result } = await fetchJSON(`${HT_HOST}/service-CooperateSOA/GetLastReqDate`)
- return errcode !== 0 ? {} : result
-}
-
const initialState = {
- tokenInterval: null,
loginStatus: 0,
defaltRoute: '',
currentUser: {
@@ -125,10 +118,9 @@ const useAuthStore = create(devtools((set, get) => ({
},
logout: () => {
- const { tokenInterval, currentUser } = get()
+ const { currentUser } = get()
const { clearStorage } = usingStorage()
clearStorage()
- clearInterval(tokenInterval)
set(() => ({
...initialState,
currentUser: {
@@ -175,6 +167,16 @@ const useAuthStore = create(devtools((set, get) => ({
})
},
+ // 根据某项数据来判断是否有权限
+ //
+ // INSERT INTO [dbo].[auth_resource] ([res_name] ,[res_pattern], [res_category])
+ // VALUES ('审核CH直销产品', '[125, 375]', 'data')
+ //
+ // const PERM_PRODUCTS_AUDIT_CH = '[125, 375]'
+ isAllowed: (perm, data) => {
+ return true
+ },
+
}), { name: 'authStore' }))
export default useAuthStore
diff --git a/src/stores/Products/Index.js b/src/stores/Products/Index.js
index 850e8c4..7f173c6 100644
--- a/src/stores/Products/Index.js
+++ b/src/stores/Products/Index.js
@@ -146,13 +146,29 @@ export const fetchRemarkList = async (params) => {
}
/**
- * 获取合同备注
+ * 保存合同备注
*/
export const postRemarkList = async (params) => {
const { errcode, result } = await postJSON(`${HT_HOST}/Service_BaseInfoWeb/agency_product_memo_add`, params)
return { errcode, result, success: errcode === 0 }
}
+/**
+ * 产品价格快照
+ */
+export const getPPSnapshotAction = async (params) => {
+ const { errcode, result } = await fetchJSON(`${HT_HOST}/Service_BaseInfoWeb/agency_product_price_snapshot`, params)
+ return errcode !== 0 ? [] : result;
+}
+
+/**
+ * 修改产品的类型
+ */
+export const moveProductTypeAction = async (params) => {
+ const { errcode, result } = await postJSON(`${HT_HOST}/Service_BaseInfoWeb/agency_product_move`, params)
+ return errcode !== 0 ? [] : result;
+};
+
const defaultRemarkList = [
{id: 0, "product_type_id": "6","Memo": ""},
{id: 0, "product_type_id": "B","Memo": ""},
@@ -259,7 +275,7 @@ export const useProductsStore = create(
}
},
- newEmptyQuotation: () => ({
+ newEmptyQuotation: (useDates) => ({
id: null,
adult_cost: 0,
child_cost: 0,
@@ -267,10 +283,7 @@ export const useProductsStore = create(
unit_id: '0',
group_size_min: 1,
group_size_max: 10,
- use_dates: [
- dayjs().startOf('M'),
- dayjs().endOf('M')
- ],
+ use_dates: useDates,
weekdayList: [],
fresh: true // 标识是否是新记录,新记录才用添加列表
}),
@@ -297,7 +310,7 @@ export const useProductsStore = create(
weekdays: definition.weekend.join(','),
WPI_SN: editingProduct.info.id,
WPP_VEI_SN: activeAgency.travel_agency_id,
- lastedit_changed: '',
+ lastedit_changed: {},
audit_state_id: -1,
key: generateId(),
fresh: false
@@ -328,24 +341,23 @@ export const useProductsStore = create(
if (formValues.fresh) {
formValues.key = generateId()
- formValues.lastedit_changed = ''
+ formValues.lastedit_changed = {}
formValues.audit_state_id = -1 // 新增,
formValues.fresh = false // 添加到列表后就不是新纪录,保存要修改原来记录
mergedList = [...quotationList,...[formValues]]
} else {
mergedList = quotationList.map(prevQuotation => {
if (prevQuotation.key === formValues.key) {
- const changedList = []
+ const changedObject = {}
for (const [key, value] of Object.entries(formValues)) {
- if (key === 'use_dates' || key === 'id' || key === 'key') continue
+ if (key === 'use_dates' || key === 'id' || key === 'key' || key === 'weekdayList'
+ || key === 'WPI_SN' || key === 'WPP_VEI_SN') continue
const preValue = prevQuotation[key]
const hasChanged = preValue !== value
if (hasChanged) {
- changedList.push({
- [key]: preValue,
- })
+ changedObject[key] = preValue
}
}
@@ -361,7 +373,7 @@ export const useProductsStore = create(
use_dates_start: formValues.use_dates_start,
use_dates_end: formValues.use_dates_end,
weekdays: formValues.weekdays,
- lastedit_changed: JSON.stringify(changedList, null, 2)
+ lastedit_changed: changedObject
}
} else {
return prevQuotation
diff --git a/src/views/App.jsx b/src/views/App.jsx
index 63d27fb..94a4eb6 100644
--- a/src/views/App.jsx
+++ b/src/views/App.jsx
@@ -1,6 +1,9 @@
import { Outlet, Link, useHref, useNavigate, NavLink } from 'react-router-dom'
import { useEffect, useState } from 'react'
-import { Layout, Menu, ConfigProvider, theme, Dropdown, message, FloatButton, Space, Row, Col, Badge, App as AntApp } from 'antd'
+import {
+ Popover, Layout, Menu, ConfigProvider, theme, Dropdown, message, FloatButton, Space, Row, Col, Badge, App as AntApp,
+ Button, Form, Input
+} from 'antd'
import { DownOutlined } from '@ant-design/icons'
import 'antd/dist/reset.css'
import AppLogo from '@/assets/highlights_travel_600_550.png'
@@ -9,7 +12,6 @@ import { useTranslation } from 'react-i18next'
import zhLocale from 'antd/locale/zh_CN'
import enLocale from 'antd/locale/en_US'
import 'dayjs/locale/zh-cn'
-import { BugOutlined } from "@ant-design/icons"
import ErrorBoundary from '@/components/ErrorBoundary'
import { BUILD_VERSION, PERM_PRODUCTS_OFFER_PUT, PERM_PRODUCTS_INFO_PUT } from '@/config'
import useNoticeStore from '@/stores/Notice'
@@ -18,7 +20,7 @@ import { useThemeContext } from '@/stores/ThemeContext'
import { usingStorage } from '@/hooks/usingStorage'
import { useDefaultLgc } from '@/i18n/LanguageSwitcher'
import { appendRequestParams } from '@/utils/request'
-import { uploadPageSpyLog } from '@/pageSpy';
+import LogUploader from '@/components/LogUploader'
import { PERM_ACCOUNT_MANAGEMENT, PERM_ROLE_NEW, PERM_OVERSEA, PERM_AIR_TICKET, PERM_PRODUCTS_MANAGEMENT,PERM_TRAIN_TICKET } from '@/config'
@@ -63,26 +65,15 @@ function App() {
appendRequestParams('lgc', language)
}, [i18n.language])
- const uploadLog = () => {
- if (window.$pageSpy) {
- window.$pageSpy.triggerPlugins('onOfflineLog', 'upload')
- messageApi.info('Success')
- } else {
- messageApi.error('Failure')
- }
- }
-
// 地接和客服权限不同,产品管理页面也不同
const isProductPermitted = isPermitted(PERM_PRODUCTS_MANAGEMENT) || isPermitted(PERM_PRODUCTS_INFO_PUT)
- const productLink = isPermitted(PERM_PRODUCTS_MANAGEMENT) ? '/products' : '/products/edit'
+ const productLink = isPermitted(PERM_PRODUCTS_MANAGEMENT) ? '/products' : '/products/pick-year'
return (
@@ -93,7 +84,7 @@ function App() {
insetInlineEnd: 94,
}}
>
- } onClick={() => uploadPageSpyLog()} />
+
{contextHolder}
diff --git a/src/views/account/Management.jsx b/src/views/account/Management.jsx
index 7e51825..e6030c4 100644
--- a/src/views/account/Management.jsx
+++ b/src/views/account/Management.jsx
@@ -231,7 +231,7 @@ function Management() {
}}
title={t('account:detail')}
open={isAccountModalOpen} onCancel={() => setAccountModalOpen(false)}
- destroyOnClose
+ destroyOnHidden
forceRender
modalRender={(dom) => (