diff --git a/AGENTS.md b/AGENTS.md index fb8442a..4990725 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -8,6 +8,7 @@ Vue 3 SPA with custom composables for data fetching. Element Plus for UI. ## Structure + ``` cloud-ui/src/ ├── api/ # API modules (one per entity) @@ -23,15 +24,16 @@ cloud-ui/src/ ## Key Hooks -| Hook | Purpose | -|------|---------| -| `useDict(type1, type2)` | Dictionary data with caching | -| `useTable({ pageList, queryForm })` | Table + pagination | -| `useMessage()` | Toast notifications | -| `useMessageBox()` | Confirm dialogs | -| `useParam(type)` | System parameters | +| Hook | Purpose | +| ----------------------------------- | ---------------------------- | +| `useDict(type1, type2)` | Dictionary data with caching | +| `useTable({ pageList, queryForm })` | Table + pagination | +| `useMessage()` | Toast notifications | +| `useMessageBox()` | Confirm dialogs | +| `useParam(type)` | System parameters | ## API Pattern + ```typescript import request from '/@/utils/request'; @@ -43,6 +45,7 @@ export const delObj = (id) => request.post('/module/entity/delete', id); ``` ## Hooks Usage + ```typescript // Dictionary const { dict } = useDict('sex', 'status'); @@ -50,8 +53,8 @@ const { dict } = useDict('sex', 'status'); // Table const { tableData, loading, getData, pagination } = useTable({ - pageList: fetchList, - queryForm: reactive({ name: '' }) + pageList: fetchList, + queryForm: reactive({ name: '' }), }); // Message @@ -65,16 +68,17 @@ await msgBoxConfirm('确定删除?'); ## Constraints -| Rule | Details | -|------|---------| -| **Hardcoding** | NO hardcoded strings. Define constants first. | -| **Line width** | 150 chars (Prettier) | -| **Indentation** | Tabs (Prettier) | -| **Quotes** | Single quotes | -| **API updates** | POST /edit (NOT PUT) | -| **API deletes** | POST /delete (NOT DELETE) | +| Rule | Details | +| --------------- | --------------------------------------------- | +| **Hardcoding** | NO hardcoded strings. Define constants first. | +| **Line width** | 150 chars (Prettier) | +| **Indentation** | Tabs (Prettier) | +| **Quotes** | Single quotes | +| **API updates** | POST /edit (NOT PUT) | +| **API deletes** | POST /delete (NOT DELETE) | ## Commands + ```bash npm run dev # Development server npm run build # Production build @@ -84,17 +88,18 @@ npm run prettier # Format code ## Non-Standard -| Location | Issue | -|----------|-------| -| `src/composables/` + `src/hooks/` | Both exist (prefer hooks/) | -| `src/directive/` + `src/directives/` | Both exist | -| `src/const/` | Named "const" not "constants" | +| Location | Issue | +| ------------------------------------ | ----------------------------- | +| `src/composables/` + `src/hooks/` | Both exist (prefer hooks/) | +| `src/directive/` + `src/directives/` | Both exist | +| `src/const/` | Named "const" not "constants" | ## ESLint Relaxed Rules Many TypeScript rules are OFF: + - `@typescript-eslint/no-explicit-any`: OFF - `@typescript-eslint/explicit-function-return-type`: OFF - `vue/require-default-prop`: OFF -Follow existing patterns in codebase. \ No newline at end of file +Follow existing patterns in codebase. diff --git a/COLUMN_VISIBILITY_SOLUTIONS.md b/COLUMN_VISIBILITY_SOLUTIONS.md index f122150..c3ac964 100644 --- a/COLUMN_VISIBILITY_SOLUTIONS.md +++ b/COLUMN_VISIBILITY_SOLUTIONS.md @@ -1,14 +1,16 @@ # 列显示/隐藏方案对比 -## 方案1:TableColumn 包装组件 ⭐推荐 +## 方案 1:TableColumn 包装组件 ⭐ 推荐 ### 优点 + - ✅ 使用简单,只需替换组件名 - ✅ 完全兼容 `el-table-column` 的所有属性和插槽 - ✅ 代码清晰,易于维护 - ✅ 无需修改现有代码结构 ### 缺点 + - ❌ 需要创建一个新组件 - ❌ 需要在使用的地方 provide `isColumnVisible` 函数 @@ -16,43 +18,45 @@ ```vue ``` --- -## 方案2:自定义指令 v-column-visible +## 方案 2:自定义指令 v-column-visible ### 优点 + - ✅ 可以保留 `el-table-column` - ✅ 使用相对简单 ### 缺点 + - ❌ 仍然需要在每个列上添加指令 - ❌ 实现较复杂,需要操作 DOM - ❌ 可能不够优雅 @@ -61,26 +65,23 @@ const isColumnVisible = (propOrLabel) => { ```vue ``` --- -## 方案3:使用 computed 动态生成列配置 +## 方案 3:使用 computed 动态生成列配置 ### 优点 + - ✅ 最彻底,完全控制列的渲染 - ✅ 性能最好(只渲染可见的列) ### 缺点 + - ❌ 需要重构现有代码,改动较大 - ❌ 需要将列配置抽离出来 - ❌ 插槽处理较复杂 @@ -89,40 +90,38 @@ const isColumnVisible = (propOrLabel) => { ```vue ``` --- -## 方案4:在 el-table 层面使用 provide + 自动处理 +## 方案 4:在 el-table 层面使用 provide + 自动处理 ### 优点 + - ✅ 在表格层面统一处理 - ✅ 子组件自动继承 ### 缺点 + - ❌ 实现最复杂 - ❌ 需要修改 Element Plus 的渲染逻辑 - ❌ 可能影响性能 @@ -131,21 +130,23 @@ const visibleColumns = computed(() => { ## 推荐方案 -**推荐使用方案1(TableColumn 包装组件)**,因为: +**推荐使用方案 1(TableColumn 包装组件)**,因为: + 1. 使用最简单,只需替换组件名 2. 完全兼容现有代码 3. 易于维护和理解 4. 性能良好 -## 迁移步骤(方案1) +## 迁移步骤(方案 1) 1. 导入组件: + ```vue -import TableColumnProvider from '/@/components/TableColumn/Provider.vue' -import TableColumn from '/@/components/TableColumn/index.vue' +import TableColumnProvider from '/@/components/TableColumn/Provider.vue' import TableColumn from '/@/components/TableColumn/index.vue' ``` 2. 用 `TableColumnProvider` 包裹所有列,并传入 `isColumnVisible`: + ```vue @@ -153,6 +154,7 @@ import TableColumn from '/@/components/TableColumn/index.vue' ``` 3. 将所有 `el-table-column` 替换为 `TableColumn`,并移除 `v-if`: + ```vue @@ -160,4 +162,3 @@ import TableColumn from '/@/components/TableColumn/index.vue' ``` - diff --git a/auto-imports.d.ts b/auto-imports.d.ts index 2b4704b..e2b3e27 100644 --- a/auto-imports.d.ts +++ b/auto-imports.d.ts @@ -1,73 +1,73 @@ // Generated by 'unplugin-auto-import' -export {} +export {}; declare global { - const EffectScope: typeof import('vue')['EffectScope'] - const acceptHMRUpdate: typeof import('pinia')['acceptHMRUpdate'] - const computed: typeof import('vue')['computed'] - const createApp: typeof import('vue')['createApp'] - const createPinia: typeof import('pinia')['createPinia'] - const customRef: typeof import('vue')['customRef'] - const defineAsyncComponent: typeof import('vue')['defineAsyncComponent'] - const defineComponent: typeof import('vue')['defineComponent'] - const defineStore: typeof import('pinia')['defineStore'] - const effectScope: typeof import('vue')['effectScope'] - const getActivePinia: typeof import('pinia')['getActivePinia'] - const getCurrentInstance: typeof import('vue')['getCurrentInstance'] - const getCurrentScope: typeof import('vue')['getCurrentScope'] - const h: typeof import('vue')['h'] - const inject: typeof import('vue')['inject'] - const isProxy: typeof import('vue')['isProxy'] - const isReactive: typeof import('vue')['isReactive'] - const isReadonly: typeof import('vue')['isReadonly'] - const isRef: typeof import('vue')['isRef'] - const mapActions: typeof import('pinia')['mapActions'] - const mapGetters: typeof import('pinia')['mapGetters'] - const mapState: typeof import('pinia')['mapState'] - const mapStores: typeof import('pinia')['mapStores'] - const mapWritableState: typeof import('pinia')['mapWritableState'] - const markRaw: typeof import('vue')['markRaw'] - const nextTick: typeof import('vue')['nextTick'] - const onActivated: typeof import('vue')['onActivated'] - const onBeforeMount: typeof import('vue')['onBeforeMount'] - const onBeforeRouteLeave: typeof import('vue-router')['onBeforeRouteLeave'] - const onBeforeRouteUpdate: typeof import('vue-router')['onBeforeRouteUpdate'] - const onBeforeUnmount: typeof import('vue')['onBeforeUnmount'] - const onBeforeUpdate: typeof import('vue')['onBeforeUpdate'] - const onDeactivated: typeof import('vue')['onDeactivated'] - const onErrorCaptured: typeof import('vue')['onErrorCaptured'] - const onMounted: typeof import('vue')['onMounted'] - const onRenderTracked: typeof import('vue')['onRenderTracked'] - const onRenderTriggered: typeof import('vue')['onRenderTriggered'] - const onScopeDispose: typeof import('vue')['onScopeDispose'] - const onServerPrefetch: typeof import('vue')['onServerPrefetch'] - const onUnmounted: typeof import('vue')['onUnmounted'] - const onUpdated: typeof import('vue')['onUpdated'] - const provide: typeof import('vue')['provide'] - const reactive: typeof import('vue')['reactive'] - const readonly: typeof import('vue')['readonly'] - const ref: typeof import('vue')['ref'] - const resolveComponent: typeof import('vue')['resolveComponent'] - const resolveDirective: typeof import('vue')['resolveDirective'] - const setActivePinia: typeof import('pinia')['setActivePinia'] - const setMapStoreSuffix: typeof import('pinia')['setMapStoreSuffix'] - const shallowReactive: typeof import('vue')['shallowReactive'] - const shallowReadonly: typeof import('vue')['shallowReadonly'] - const shallowRef: typeof import('vue')['shallowRef'] - const storeToRefs: typeof import('pinia')['storeToRefs'] - const toRaw: typeof import('vue')['toRaw'] - const toRef: typeof import('vue')['toRef'] - const toRefs: typeof import('vue')['toRefs'] - const triggerRef: typeof import('vue')['triggerRef'] - const unref: typeof import('vue')['unref'] - const useAttrs: typeof import('vue')['useAttrs'] - const useCssModule: typeof import('vue')['useCssModule'] - const useCssVars: typeof import('vue')['useCssVars'] - const useLink: typeof import('vue-router')['useLink'] - const useRoute: typeof import('vue-router')['useRoute'] - const useRouter: typeof import('vue-router')['useRouter'] - const useSlots: typeof import('vue')['useSlots'] - const watch: typeof import('vue')['watch'] - const watchEffect: typeof import('vue')['watchEffect'] - const watchPostEffect: typeof import('vue')['watchPostEffect'] - const watchSyncEffect: typeof import('vue')['watchSyncEffect'] + const EffectScope: typeof import('vue')['EffectScope']; + const acceptHMRUpdate: typeof import('pinia')['acceptHMRUpdate']; + const computed: typeof import('vue')['computed']; + const createApp: typeof import('vue')['createApp']; + const createPinia: typeof import('pinia')['createPinia']; + const customRef: typeof import('vue')['customRef']; + const defineAsyncComponent: typeof import('vue')['defineAsyncComponent']; + const defineComponent: typeof import('vue')['defineComponent']; + const defineStore: typeof import('pinia')['defineStore']; + const effectScope: typeof import('vue')['effectScope']; + const getActivePinia: typeof import('pinia')['getActivePinia']; + const getCurrentInstance: typeof import('vue')['getCurrentInstance']; + const getCurrentScope: typeof import('vue')['getCurrentScope']; + const h: typeof import('vue')['h']; + const inject: typeof import('vue')['inject']; + const isProxy: typeof import('vue')['isProxy']; + const isReactive: typeof import('vue')['isReactive']; + const isReadonly: typeof import('vue')['isReadonly']; + const isRef: typeof import('vue')['isRef']; + const mapActions: typeof import('pinia')['mapActions']; + const mapGetters: typeof import('pinia')['mapGetters']; + const mapState: typeof import('pinia')['mapState']; + const mapStores: typeof import('pinia')['mapStores']; + const mapWritableState: typeof import('pinia')['mapWritableState']; + const markRaw: typeof import('vue')['markRaw']; + const nextTick: typeof import('vue')['nextTick']; + const onActivated: typeof import('vue')['onActivated']; + const onBeforeMount: typeof import('vue')['onBeforeMount']; + const onBeforeRouteLeave: typeof import('vue-router')['onBeforeRouteLeave']; + const onBeforeRouteUpdate: typeof import('vue-router')['onBeforeRouteUpdate']; + const onBeforeUnmount: typeof import('vue')['onBeforeUnmount']; + const onBeforeUpdate: typeof import('vue')['onBeforeUpdate']; + const onDeactivated: typeof import('vue')['onDeactivated']; + const onErrorCaptured: typeof import('vue')['onErrorCaptured']; + const onMounted: typeof import('vue')['onMounted']; + const onRenderTracked: typeof import('vue')['onRenderTracked']; + const onRenderTriggered: typeof import('vue')['onRenderTriggered']; + const onScopeDispose: typeof import('vue')['onScopeDispose']; + const onServerPrefetch: typeof import('vue')['onServerPrefetch']; + const onUnmounted: typeof import('vue')['onUnmounted']; + const onUpdated: typeof import('vue')['onUpdated']; + const provide: typeof import('vue')['provide']; + const reactive: typeof import('vue')['reactive']; + const readonly: typeof import('vue')['readonly']; + const ref: typeof import('vue')['ref']; + const resolveComponent: typeof import('vue')['resolveComponent']; + const resolveDirective: typeof import('vue')['resolveDirective']; + const setActivePinia: typeof import('pinia')['setActivePinia']; + const setMapStoreSuffix: typeof import('pinia')['setMapStoreSuffix']; + const shallowReactive: typeof import('vue')['shallowReactive']; + const shallowReadonly: typeof import('vue')['shallowReadonly']; + const shallowRef: typeof import('vue')['shallowRef']; + const storeToRefs: typeof import('pinia')['storeToRefs']; + const toRaw: typeof import('vue')['toRaw']; + const toRef: typeof import('vue')['toRef']; + const toRefs: typeof import('vue')['toRefs']; + const triggerRef: typeof import('vue')['triggerRef']; + const unref: typeof import('vue')['unref']; + const useAttrs: typeof import('vue')['useAttrs']; + const useCssModule: typeof import('vue')['useCssModule']; + const useCssVars: typeof import('vue')['useCssVars']; + const useLink: typeof import('vue-router')['useLink']; + const useRoute: typeof import('vue-router')['useRoute']; + const useRouter: typeof import('vue-router')['useRouter']; + const useSlots: typeof import('vue')['useSlots']; + const watch: typeof import('vue')['watch']; + const watchEffect: typeof import('vue')['watchEffect']; + const watchPostEffect: typeof import('vue')['watchPostEffect']; + const watchSyncEffect: typeof import('vue')['watchSyncEffect']; } diff --git a/deploy-tools/email-notice-false.html b/deploy-tools/email-notice-false.html index a699f66..f4e82bf 100644 --- a/deploy-tools/email-notice-false.html +++ b/deploy-tools/email-notice-false.html @@ -1,77 +1,89 @@ - - -${ENV, var="JOB_NAME"}-第${BUILD_NUMBER}次构建日志 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
(本邮件是程序自动下发的,请勿回复!)

- 构建结果 - ${BUILD_STATUS} -


- 构建信息 -
- -
Changes Since Last - Successful Build: -
- ${CHANGES_SINCE_LAST_SUCCESS,reverse=true, format="Changes for Build #%n:
%c
",showPaths=true,changesFormat="
[%a]
%m
",pathFormat="    %p"} -
Failed Test Results -
$FAILED_TESTS
-
构建日志 (最后 100行): -
Test Logs (if test has ran): - ${PROJECT_URL}/ws/TestResult/archive_logs/Log-Build-${BUILD_NUMBER}.zip -
-
-
-
- - \ No newline at end of file + + + ${ENV, var="JOB_NAME"}-第${BUILD_NUMBER}次构建日志 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
(本邮件是程序自动下发的,请勿回复!)
+

+ 构建结果 - ${BUILD_STATUS} +

+
+
+ 构建信息 +
+
+ +
+ Changes Since Last Successful Build: +
+
+ + ${CHANGES_SINCE_LAST_SUCCESS,reverse=true, format="Changes for Build #%n:
%c
",showPaths=true,changesFormat=" +
[%a]
%m
+ ",pathFormat="    %p"} +
+ Failed Test Results +
+
+
$FAILED_TESTS
+
+
+ 构建日志 (最后 100行): +
+
+ Test Logs (if test has ran): + ${PROJECT_URL}/ws/TestResult/archive_logs/Log-Build-${BUILD_NUMBER}.zip +
+
+
+ + diff --git a/deploy-tools/email-notice-success.html b/deploy-tools/email-notice-success.html index 36bb1f2..65210e0 100644 --- a/deploy-tools/email-notice-success.html +++ b/deploy-tools/email-notice-success.html @@ -1,77 +1,89 @@ - - -${ENV, var="JOB_NAME"}-第${BUILD_NUMBER}次构建日志 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
(本邮件是程序自动下发的,请勿回复!)

- 构建结果 - ${BUILD_STATUS} -


- 构建信息 -
- -
Changes Since Last - Successful Build: -
- ${CHANGES_SINCE_LAST_SUCCESS,reverse=true, format="Changes for Build #%n:
%c
",showPaths=true,changesFormat="
[%a]
%m
",pathFormat="    %p"} -
Failed Test Results -
$FAILED_TESTS
-
构建日志 (最后 100行): -
Test Logs (if test has ran): - ${PROJECT_URL}/ws/TestResult/archive_logs/Log-Build-${BUILD_NUMBER}.zip -
-
-
-
- - \ No newline at end of file + + + ${ENV, var="JOB_NAME"}-第${BUILD_NUMBER}次构建日志 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
(本邮件是程序自动下发的,请勿回复!)
+

+ 构建结果 - ${BUILD_STATUS} +

+
+
+ 构建信息 +
+
+ +
+ Changes Since Last Successful Build: +
+
+ + ${CHANGES_SINCE_LAST_SUCCESS,reverse=true, format="Changes for Build #%n:
%c
",showPaths=true,changesFormat=" +
[%a]
%m
+ ",pathFormat="    %p"} +
+ Failed Test Results +
+
+
$FAILED_TESTS
+
+
+ 构建日志 (最后 100行): +
+
+ Test Logs (if test has ran): + ${PROJECT_URL}/ws/TestResult/archive_logs/Log-Build-${BUILD_NUMBER}.zip +
+
+
+ + diff --git a/src/views/purchase/purchasingrequisition/accept/AcceptBatchForm.vue b/src/views/purchase/purchasingrequisition/accept/AcceptBatchForm.vue index a3a2f06..9e00e4b 100644 --- a/src/views/purchase/purchasingrequisition/accept/AcceptBatchForm.vue +++ b/src/views/purchase/purchasingrequisition/accept/AcceptBatchForm.vue @@ -154,7 +154,18 @@ watch(templateFiles, (files) => { }, { deep: true }) const rules: FormRules = { - acceptDate: [{ required: true, message: '请选择验收日期', trigger: 'change' }], + templateFileIds: [ + { + validator: (_rule: any, value: any, callback: (e?: Error) => void) => { + if (!value || (Array.isArray(value) && value.length === 0) || (typeof value === 'string' && !value.trim())) { + callback(new Error('请上传履约验收文件')) + return + } + callback() + }, + trigger: 'change' + }, + ], } const validate = () => formRef.value?.validate() diff --git a/src/views/purchase/purchasingrequisition/accept/AcceptCommonForm.vue b/src/views/purchase/purchasingrequisition/accept/AcceptCommonForm.vue index 5454eb0..58deca6 100644 --- a/src/views/purchase/purchasingrequisition/accept/AcceptCommonForm.vue +++ b/src/views/purchase/purchasingrequisition/accept/AcceptCommonForm.vue @@ -58,8 +58,30 @@ - - + + + + {{ item.commonDeptName ? item.commonDeptName + ' - ' : '' }}{{ item.realName || item.name }} + {{ item.teacherNo }} + + +
如入固定资产,必填
@@ -167,7 +189,6 @@ const form = reactive({ projectName: '', deptName: '', supplierName: '', - supplierContact: '', purchaserId: '', purchaserName: '', assetAdminId: '', @@ -336,4 +357,9 @@ defineExpose({ validate, form }) .mb20 { margin-bottom: 20px; } +.field-note { + font-size: 12px; + color: #999; + margin-top: 4px; +} diff --git a/src/views/purchase/purchasingrequisition/accept/PurchasingAcceptModal.vue b/src/views/purchase/purchasingrequisition/accept/PurchasingAcceptModal.vue index a869e2a..665133f 100644 --- a/src/views/purchase/purchasingrequisition/accept/PurchasingAcceptModal.vue +++ b/src/views/purchase/purchasingrequisition/accept/PurchasingAcceptModal.vue @@ -192,7 +192,6 @@ const loadData = async () => { isInstallment: config.common.isInstallment || '0', totalPhases: config.common.totalPhases || 1, supplierName: config.common.supplierName || '', - supplierContact: config.common.supplierContact || '', transactionAmount: config.common.transactionAmount || null, }) } @@ -278,7 +277,6 @@ const saveCommonConfig = async () => { isInstallment: form.isInstallment ?? '0', totalPhases: isInstallment ? (Number(form.totalPhases) || 1) : 1, supplierName: String(form.supplierName ?? ''), - supplierContact: String(form.supplierContact ?? ''), purchaserId: String(form.purchaserId ?? ''), purchaserName: String(form.purchaserName ?? ''), assetAdminId: String(form.assetAdminId ?? ''), @@ -307,10 +305,7 @@ const saveCurrentBatch = async () => { const formData = batchFormRef?.getFormData?.() || batchFormRef?.form if (!formData) return - if (!formData.acceptDate) { - useMessage().error('请选择验收日期') - return - } + // acceptDate is now optional - removed the validation check // templateFileIds: 提取ID数组 let fileIds: string[] = [] @@ -357,7 +352,6 @@ const DEFAULT_COMMON_FORM = { isInstallment: '0', totalPhases: 1, supplierName: '', - supplierContact: '', purchaserId: '', purchaserName: '', assetAdminId: '', diff --git a/src/views/purchase/purchasingrequisition/add.vue b/src/views/purchase/purchasingrequisition/add.vue index fc948e4..3bf6e8e 100644 --- a/src/views/purchase/purchasingrequisition/add.vue +++ b/src/views/purchase/purchasingrequisition/add.vue @@ -205,7 +205,7 @@ - - - - - + { // 申请阶段:始终可选(根据默认值自动选中后,允许用户自行修改) // 流程嵌入:采购中心节点可编辑,其他节点只读 const schoolUnifiedPurchaseFormDisabled = computed(() => { - if (!isFlowEmbed.value) { - return false; - } + // if (!isFlowEmbed.value) { + // return false; + // } // 流程嵌入且为采购中心:放开编辑 if (isPurchaseCenter.value) { return false; @@ -1610,6 +1597,7 @@ const dataRules = reactive({ ], purchaseChannel: [ { + required: true, validator: (_rule: any, value: string, callback: (e?: Error) => void) => { if (!isDeptPurchase.value) { callback();