From 9a36b1954f4cd8f7cd72bd64f253ad8ad7eb87d4 Mon Sep 17 00:00:00 2001 From: liuyuanjin <1107290929@qq.com> Date: Fri, 7 Aug 2026 16:00:18 +0800 Subject: [PATCH] =?UTF-8?q?feat:=E5=BD=93=E5=BC=80=E6=94=BE=E9=9B=86?= =?UTF-8?q?=E5=90=88=E6=9C=89=E7=AC=A6=E5=90=88=E6=9C=AC=E5=8D=95=E4=BD=8D?= =?UTF-8?q?=E7=9A=84=E6=95=B0=E6=8D=AE=E6=97=B6=EF=BC=8C=E5=BC=B9=E7=AA=97?= =?UTF-8?q?=E6=8F=90=E9=86=92?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app.tsx | 31 +- src/components/Common/MonacoEditor/editor.tsx | 2 +- .../session/setting/targetSetting/page.tsx | 287 ++++++++++++++++-- src/components/DynamicDataNotify/index.tsx | 271 +++++++++++++++++ 4 files changed, 558 insertions(+), 33 deletions(-) create mode 100644 src/components/DynamicDataNotify/index.tsx diff --git a/src/app.tsx b/src/app.tsx index c53f2482e..f33bb547b 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -17,6 +17,7 @@ import zhMessage from 'devextreme/localization/messages/zh.json'; import { LoggerLevel, generateUuid, logger } from '@/ts/base/common'; import { useAudio } from 'react-use'; import ModalLogin from '@/components/ModalLogin'; +import DynamicDataNotify from '@/components/DynamicDataNotify'; moment.locale('zh-cn'); config({ defaultCurrency: 'zh' }); @@ -26,7 +27,7 @@ locale('zh'); // 副作用依赖引入 import './components/Common/MonacoEditor'; import useAsyncLoad from './hooks/useAsyncLoad'; -import { kernel } from './ts/base'; +import { kernel, command } from './ts/base'; ConfigProvider.config({ prefixCls: 'ogo', @@ -90,6 +91,7 @@ const App = () => { webSetting(); break; case LoggerLevel.inited: + command.emitterFlag('inited'); // checkPlatformAuth(); break; } @@ -102,21 +104,22 @@ const App = () => { {renderRoutes(routes)} + {loginVisible && ( + { + if (cancel) { + orgCtrl.exit(); + window.location.reload(); + } else { + setLoginVisible(false); + } + }} + /> + )} + {audio} - {loginVisible && ( - { - if (cancel) { - orgCtrl.exit(); - window.location.reload(); - } else { - setLoginVisible(false); - } - }} - /> - )} ); }; diff --git a/src/components/Common/MonacoEditor/editor.tsx b/src/components/Common/MonacoEditor/editor.tsx index f9531455b..53fecb8f1 100644 --- a/src/components/Common/MonacoEditor/editor.tsx +++ b/src/components/Common/MonacoEditor/editor.tsx @@ -87,7 +87,7 @@ export const MonacoEditor = (props: MonacoEditorProps) => { if (code != props.value) { instance.current.setValue(props.value || ''); } - }, [props.value]); + }, [props.value, ready]); /** * 编辑器内容变化事件 diff --git a/src/components/DataPreview/session/setting/targetSetting/page.tsx b/src/components/DataPreview/session/setting/targetSetting/page.tsx index f551ed08a..c8961942a 100644 --- a/src/components/DataPreview/session/setting/targetSetting/page.tsx +++ b/src/components/DataPreview/session/setting/targetSetting/page.tsx @@ -1,10 +1,11 @@ import React, { useEffect, useMemo } from 'react'; -import { Card, Switch } from 'antd'; +import { Card, Switch, Input, message, Button } from 'antd'; import { ISession, ITarget } from '@/ts/core'; import orgCtrl from '@/ts/controller'; import './index.less'; import OrgIcons from '@/components/Common/GlobalComps/orgIcons'; import EntityIcon from '@/components/Common/GlobalComps/entityIcon'; +import { MonacoEditor } from '@/components/Common/MonacoEditor'; interface IProps { target: ITarget; session: ISession; @@ -24,6 +25,15 @@ const defaultActions = [ const WebPage: React.FC = ({ target, id }) => { const [commons, setCommons] = React.useState([]); const [actionStatus, setActionStatus] = React.useState([]); + const [notifySetting, setNotifySetting] = React.useState({ + enable: false, + collName: '', + belongId: '', + fields: [], + matchOpts: {}, + }); + const [fieldsStr, setFieldsStr] = React.useState('[]'); + const [matchOptsStr, setMatchOptsStr] = React.useState('{}'); const hasRelationAuth = target.hasRelationAuth(); useEffect(() => { orgCtrl.home.loadCommons(target as any).then((res) => { @@ -32,6 +42,29 @@ const WebPage: React.FC = ({ target, id }) => { target.webSiteProvider.loadConfig().then((res) => { setActionStatus([...res]); }); + target.cacheObj.get('dynamicDataNotifySetting').then((res: any) => { + if (res) { + let loadedFields = res.fields; + if (typeof res.fields === 'string') { + try { + loadedFields = JSON.parse(res.fields); + } catch (e) {} + } + let loadedMatchOpts = res.matchOpts; + if (typeof res.matchOpts === 'string') { + try { + loadedMatchOpts = JSON.parse(res.matchOpts); + } catch (e) {} + } + setNotifySetting({ ...res, fields: loadedFields, matchOpts: loadedMatchOpts }); + if (loadedFields) { + setFieldsStr(JSON.stringify(loadedFields, null, 2)); + } + if (loadedMatchOpts) { + setMatchOptsStr(JSON.stringify(loadedMatchOpts, null, 2)); + } + } + }); }, [target]); // 序号 栏目名称 展示模板 页面模板 备注 const actionsPages = useMemo(() => { @@ -71,24 +104,242 @@ const WebPage: React.FC = ({ target, id }) => { const handleSwitch = (item: any, checked: boolean) => { target.webSiteProvider.changeCommonTabStatus(item, checked); }; + + const updateNotifySetting = (key: string, value: any) => { + setNotifySetting((prev: any) => { + const newSetting = { ...prev, [key]: value }; + const cacheSetting = { ...newSetting }; + if (cacheSetting.fields && typeof cacheSetting.fields !== 'string') { + cacheSetting.fields = JSON.stringify(cacheSetting.fields); + } + if (cacheSetting.matchOpts && typeof cacheSetting.matchOpts !== 'string') { + cacheSetting.matchOpts = JSON.stringify(cacheSetting.matchOpts); + } + target.cacheObj.set('dynamicDataNotifySetting', cacheSetting); + return newSetting; + }); + }; + + const handleJsonEditorChange = ( + value: string, + key: 'fields' | 'matchOpts', + setStringState: React.Dispatch>, + validator: (parsed: any) => boolean, + ) => { + setStringState(value); + try { + const parsed = JSON.parse(value); + if (validator(parsed)) { + setNotifySetting((prev: any) => ({ ...prev, [key]: parsed })); + } + } catch (e) { + // Ignore parse error during typing + } + }; + + const handleSaveNotifySetting = () => { + try { + const parsedFields = JSON.parse(fieldsStr); + if (!Array.isArray(parsedFields)) { + message.error('字段配置必须是 JSON 数组格式!'); + return; + } + + let parsedMatchOpts = {}; + if (matchOptsStr && matchOptsStr.trim() !== '') { + parsedMatchOpts = JSON.parse(matchOptsStr); + if ( + typeof parsedMatchOpts !== 'object' || + Array.isArray(parsedMatchOpts) || + parsedMatchOpts === null + ) { + message.error('查询条件必须是 JSON 对象格式!'); + return; + } + } + + const newSetting = { + ...notifySetting, + fields: parsedFields, + matchOpts: parsedMatchOpts, + }; + setNotifySetting(newSetting); + target.cacheObj.set('dynamicDataNotifySetting', { + ...newSetting, + fields: JSON.stringify(parsedFields), + matchOpts: + Object.keys(parsedMatchOpts).length > 0 + ? JSON.stringify(parsedMatchOpts) + : undefined, + }); + message.success('保存成功!'); + } catch (e) { + message.error('JSON 格式有误,请检查配置!'); + } + }; + return ( - 可在下方编辑是否展示}> -
    -
  • 默认页面
  • -
  • - {actionsPages.map((item) => renderPageItem(item))} -
  • -
-
    -
  • 常用页面
  • -
  • {commons.map((item) => renderSystemItem(item))}
  • -
-
+ <> + 可在下方编辑是否展示}> +
    +
  • 默认页面
  • +
  • + {actionsPages.map((item) => renderPageItem(item))} +
  • +
+
    +
  • 常用页面
  • +
  • + {commons.map((item) => renderSystemItem(item))} +
  • +
+
+ + 保存设置 + + } + style={{ marginTop: 16 }}> +
    +
  • +
    + + 是否接收通知: + + updateNotifySetting('enable', checked)} + /> +
    +
    +
    + + 通知集合名称: + + updateNotifySetting('collName', e.target.value)} + bordered={true} + /> +
    +
    + + 归属ID: + + updateNotifySetting('belongId', e.target.value)} + bordered={true} + /> +
    +
    + {/* 过滤条件设置 */} +
    + + 查询条件(JSON): + +
    + + handleJsonEditorChange( + value, + 'matchOpts', + setMatchOptsStr, + (parsed) => + typeof parsed === 'object' && + parsed !== null && + !Array.isArray(parsed), + ) + } + style={{ height: '100%' }} + /> +
    +
    + {/* 字段设置 */} +
    + + 字段配置(JSON): + +
    + + handleJsonEditorChange(value, 'fields', setFieldsStr, (parsed) => + Array.isArray(parsed), + ) + } + style={{ height: '100%' }} + /> +
    +
    +
  • +
+
+ ); }; diff --git a/src/components/DynamicDataNotify/index.tsx b/src/components/DynamicDataNotify/index.tsx new file mode 100644 index 000000000..5df4aa817 --- /dev/null +++ b/src/components/DynamicDataNotify/index.tsx @@ -0,0 +1,271 @@ +import React, { useEffect, useState } from 'react'; +import { Modal, Table, Button, message } from 'antd'; +import { ExclamationCircleFilled } from '@ant-design/icons'; +import orgCtrl from '@/ts/controller'; +import { kernel, command } from '@/ts/base'; + +const DynamicDataNotify: React.FC = () => { + const [visible, setVisible] = useState(false); + const [data, setData] = useState([]); + const [loading, setLoading] = useState(false); + const [fieldsConfig, setFieldsConfig] = useState([]); + + useEffect(() => { + const checkUserConfigs = async () => { + if (!orgCtrl.user || !orgCtrl.user.companys) { + return; + } + const ignoreDate = localStorage.getItem('IGNORE_DYNAMIC_NOTIFY_DATE'); + if (ignoreDate === new Date().toDateString()) { + return; + } + let allData: any[] = []; + let foundFields: any[] = []; + setLoading(true); + + for (const company of orgCtrl.user.companys) { + try { + const setting: any = await company.cacheObj.get('dynamicDataNotifySetting'); + if (setting && setting.enable && setting.collName) { + let loadedFields = setting.fields; + if (typeof setting.fields === 'string') { + try { + loadedFields = JSON.parse(setting.fields); + } catch (e) {} + } + if (foundFields.length === 0 && loadedFields && loadedFields.length > 0) { + foundFields = loadedFields; + } + const resData = await fetchCompanyData( + company.id, + setting.collName, + setting.matchOpts, + setting.belongId, + ); + if (resData && resData.length > 0) { + const mappedData = resData.map((item: any) => ({ + ...item, + __companyId: company.id, + __collName: setting.collName, + __belongId: setting.belongId, + })); + allData = [...allData, ...mappedData]; + } + } + } catch (e) { + console.error('Failed to get notify setting', e); + } + } + if (allData.length > 0) { + setData(allData); + setFieldsConfig(foundFields); + setVisible(true); + } + setLoading(false); + }; + + const id = command.subscribeByFlag('inited', () => { + checkUserConfigs(); + }); + + return () => { + command.unsubscribeByFlag(id); + }; + }, []); + + const fetchCompanyData = async ( + companyId: string, + collName: string, + customMatchOpts?: any, + customBelongId?: string, + ) => { + try { + let result: any[] = []; + let hasMore = true; + let lastId = ''; + + let baseMatch: any = { isDeleted: false }; + if (customMatchOpts) { + if (typeof customMatchOpts === 'string') { + try { + baseMatch = JSON.parse(customMatchOpts); + } catch (e) { + console.error('Failed to parse custom match options', e); + } + } else { + baseMatch = customMatchOpts; + } + } + + const loadSpaceId = customBelongId || companyId; + + while (hasMore) { + const matchOpts: any = { + belongId: companyId, + ...baseMatch, + }; + if (lastId) { + matchOpts.id = { _gt_: lastId }; + } + + const res: any = await kernel.collectionLoad( + loadSpaceId, + loadSpaceId, + [], + collName, + { + take: 500, + options: { + match: matchOpts, + sort: { + id: 1, + }, + }, + }, + ); + console.log('DynamicDataNotify-res', res); + + if (res.success && res.data && res.data.length > 0) { + result.push(...res.data); + const newLastId = res.data.at(-1)?.id; + if (!newLastId || newLastId === lastId) { + hasMore = false; + } else { + lastId = newLastId; + } + } else { + hasMore = false; + } + } + return result; + } catch (e) { + console.error('Failed to load check data', e); + return []; + } + }; + + const handleCustomScript = async (scriptStr: string, record: any) => { + console.log('handleCustomScript', scriptStr, record); + try { + const removeRecord = (id: string) => { + setData((prevData) => { + const newData = prevData.filter((d) => d.id !== id); + if (newData.length === 0) { + setVisible(false); + } + return newData; + }); + }; + + const AsyncFunction = Object.getPrototypeOf(async function () {}).constructor; + const func = new AsyncFunction( + 'record', + 'kernel', + 'message', + 'orgCtrl', + 'removeRecord', + scriptStr, + ); + await func(record, kernel, message, orgCtrl, removeRecord); + } catch (e) { + console.error('Custom script execution failed', e); + message.error('脚本执行异常'); + } + }; + + const columns: any[] = fieldsConfig.map((col: any) => { + if (col.script) { + return { + title: col.title, + key: col.code, + dataIndex: col.code, + width: col.width, + fixed: col.fixed, + render: (_: any, record: any) => { + return ( + + ); + }, + }; + } + return { + title: col.title, + dataIndex: col.code, + key: col.code, + width: col.width, + fixed: col.fixed, + render: (text: any) => { + let content = text; + if (col.valueType === '选择型' && col.lookups) { + const match = col.lookups.find((l: any) => l.value === text); + content = match ? match.text : text; + } + + if (col.isWarning && content) { + return {content}; + } + return content; + }, + }; + }); + + return ( + + + + 动态数据提醒 + + + + } + open={visible} + footer={null} + bodyStyle={{ overflow: 'hidden', padding: '16px 24px' }} + width={1000} + onCancel={() => setVisible(false)} + maskClosable={false} + destroyOnClose> + `共 ${total} 条`, + }} + scroll={{ x: 'max-content', y: 'calc(100vh - 400px)' }} + /> + + ); +}; + +export default DynamicDataNotify; -- Gitee