This commit is contained in:
吴红兵
2026-03-05 15:00:19 +08:00
parent bff8907f88
commit 2317909261
9 changed files with 397 additions and 348 deletions

View File

@@ -8,6 +8,7 @@
Vue 3 SPA with custom composables for data fetching. Element Plus for UI. Vue 3 SPA with custom composables for data fetching. Element Plus for UI.
## Structure ## Structure
``` ```
cloud-ui/src/ cloud-ui/src/
├── api/ # API modules (one per entity) ├── api/ # API modules (one per entity)
@@ -23,15 +24,16 @@ cloud-ui/src/
## Key Hooks ## Key Hooks
| Hook | Purpose | | Hook | Purpose |
|------|---------| | ----------------------------------- | ---------------------------- |
| `useDict(type1, type2)` | Dictionary data with caching | | `useDict(type1, type2)` | Dictionary data with caching |
| `useTable({ pageList, queryForm })` | Table + pagination | | `useTable({ pageList, queryForm })` | Table + pagination |
| `useMessage()` | Toast notifications | | `useMessage()` | Toast notifications |
| `useMessageBox()` | Confirm dialogs | | `useMessageBox()` | Confirm dialogs |
| `useParam(type)` | System parameters | | `useParam(type)` | System parameters |
## API Pattern ## API Pattern
```typescript ```typescript
import request from '/@/utils/request'; import request from '/@/utils/request';
@@ -43,6 +45,7 @@ export const delObj = (id) => request.post('/module/entity/delete', id);
``` ```
## Hooks Usage ## Hooks Usage
```typescript ```typescript
// Dictionary // Dictionary
const { dict } = useDict('sex', 'status'); const { dict } = useDict('sex', 'status');
@@ -50,8 +53,8 @@ const { dict } = useDict('sex', 'status');
// Table // Table
const { tableData, loading, getData, pagination } = useTable({ const { tableData, loading, getData, pagination } = useTable({
pageList: fetchList, pageList: fetchList,
queryForm: reactive({ name: '' }) queryForm: reactive({ name: '' }),
}); });
// Message // Message
@@ -65,16 +68,17 @@ await msgBoxConfirm('确定删除?');
## Constraints ## Constraints
| Rule | Details | | Rule | Details |
|------|---------| | --------------- | --------------------------------------------- |
| **Hardcoding** | NO hardcoded strings. Define constants first. | | **Hardcoding** | NO hardcoded strings. Define constants first. |
| **Line width** | 150 chars (Prettier) | | **Line width** | 150 chars (Prettier) |
| **Indentation** | Tabs (Prettier) | | **Indentation** | Tabs (Prettier) |
| **Quotes** | Single quotes | | **Quotes** | Single quotes |
| **API updates** | POST /edit (NOT PUT) | | **API updates** | POST /edit (NOT PUT) |
| **API deletes** | POST /delete (NOT DELETE) | | **API deletes** | POST /delete (NOT DELETE) |
## Commands ## Commands
```bash ```bash
npm run dev # Development server npm run dev # Development server
npm run build # Production build npm run build # Production build
@@ -84,15 +88,16 @@ npm run prettier # Format code
## Non-Standard ## Non-Standard
| Location | Issue | | Location | Issue |
|----------|-------| | ------------------------------------ | ----------------------------- |
| `src/composables/` + `src/hooks/` | Both exist (prefer hooks/) | | `src/composables/` + `src/hooks/` | Both exist (prefer hooks/) |
| `src/directive/` + `src/directives/` | Both exist | | `src/directive/` + `src/directives/` | Both exist |
| `src/const/` | Named "const" not "constants" | | `src/const/` | Named "const" not "constants" |
## ESLint Relaxed Rules ## ESLint Relaxed Rules
Many TypeScript rules are OFF: Many TypeScript rules are OFF:
- `@typescript-eslint/no-explicit-any`: OFF - `@typescript-eslint/no-explicit-any`: OFF
- `@typescript-eslint/explicit-function-return-type`: OFF - `@typescript-eslint/explicit-function-return-type`: OFF
- `vue/require-default-prop`: OFF - `vue/require-default-prop`: OFF

View File

@@ -1,14 +1,16 @@
# 列显示/隐藏方案对比 # 列显示/隐藏方案对比
## 方案1TableColumn 包装组件 ⭐推荐 ## 方案 1TableColumn 包装组件 ⭐ 推荐
### 优点 ### 优点
- ✅ 使用简单,只需替换组件名 - ✅ 使用简单,只需替换组件名
- ✅ 完全兼容 `el-table-column` 的所有属性和插槽 - ✅ 完全兼容 `el-table-column` 的所有属性和插槽
- ✅ 代码清晰,易于维护 - ✅ 代码清晰,易于维护
- ✅ 无需修改现有代码结构 - ✅ 无需修改现有代码结构
### 缺点 ### 缺点
- ❌ 需要创建一个新组件 - ❌ 需要创建一个新组件
- ❌ 需要在使用的地方 provide `isColumnVisible` 函数 - ❌ 需要在使用的地方 provide `isColumnVisible` 函数
@@ -16,43 +18,45 @@
```vue ```vue
<template> <template>
<el-table ref="tableRef"> <el-table ref="tableRef">
<TableColumnProvider :is-column-visible="isColumnVisible"> <TableColumnProvider :is-column-visible="isColumnVisible">
<!-- 替换 el-table-column TableColumn移除 v-if --> <!-- 替换 el-table-column TableColumn移除 v-if -->
<TableColumn prop="tied" label="是否退休" width="100" align="center"> <TableColumn prop="tied" label="是否退休" width="100" align="center">
<template #default="scope"> <template #default="scope">
<StatusTag :value="scope.row.tied" :options="YES_OR_NO" /> <StatusTag :value="scope.row.tied" :options="YES_OR_NO" />
</template> </template>
</TableColumn> </TableColumn>
<TableColumn prop="nameNo" label="姓名/工号" min-width="150" align="center" fixed> <TableColumn prop="nameNo" label="姓名/工号" min-width="150" align="center" fixed>
<template #default="scope"> <template #default="scope">
<TeacherNameNo :name="scope.row.realName" :no="scope.row.teacherNo" /> <TeacherNameNo :name="scope.row.realName" :no="scope.row.teacherNo" />
</template> </template>
</TableColumn> </TableColumn>
</TableColumnProvider> </TableColumnProvider>
</el-table> </el-table>
</template> </template>
<script setup> <script setup>
import TableColumnProvider from '/@/components/TableColumn/Provider.vue' import TableColumnProvider from '/@/components/TableColumn/Provider.vue';
import TableColumn from '/@/components/TableColumn/index.vue' import TableColumn from '/@/components/TableColumn/index.vue';
const isColumnVisible = (propOrLabel) => { const isColumnVisible = (propOrLabel) => {
return tableColumnControlRef.value?.isColumnVisible?.(propOrLabel) ?? true return tableColumnControlRef.value?.isColumnVisible?.(propOrLabel) ?? true;
} };
</script> </script>
``` ```
--- ---
## 方案2自定义指令 v-column-visible ## 方案 2自定义指令 v-column-visible
### 优点 ### 优点
- ✅ 可以保留 `el-table-column` - ✅ 可以保留 `el-table-column`
- ✅ 使用相对简单 - ✅ 使用相对简单
### 缺点 ### 缺点
- ❌ 仍然需要在每个列上添加指令 - ❌ 仍然需要在每个列上添加指令
- ❌ 实现较复杂,需要操作 DOM - ❌ 实现较复杂,需要操作 DOM
- ❌ 可能不够优雅 - ❌ 可能不够优雅
@@ -61,26 +65,23 @@ const isColumnVisible = (propOrLabel) => {
```vue ```vue
<template> <template>
<el-table> <el-table>
<el-table-column <el-table-column v-column-visible="'tied'" prop="tied" label="是否退休" width="100" />
v-column-visible="'tied'" </el-table>
prop="tied"
label="是否退休"
width="100"
/>
</el-table>
</template> </template>
``` ```
--- ---
## 方案3使用 computed 动态生成列配置 ## 方案 3使用 computed 动态生成列配置
### 优点 ### 优点
- ✅ 最彻底,完全控制列的渲染 - ✅ 最彻底,完全控制列的渲染
- ✅ 性能最好(只渲染可见的列) - ✅ 性能最好(只渲染可见的列)
### 缺点 ### 缺点
- ❌ 需要重构现有代码,改动较大 - ❌ 需要重构现有代码,改动较大
- ❌ 需要将列配置抽离出来 - ❌ 需要将列配置抽离出来
- ❌ 插槽处理较复杂 - ❌ 插槽处理较复杂
@@ -89,40 +90,38 @@ const isColumnVisible = (propOrLabel) => {
```vue ```vue
<template> <template>
<el-table> <el-table>
<component <component v-for="col in visibleColumns" :is="col.component" v-bind="col.props">
v-for="col in visibleColumns" <template v-if="col.slots" #[slotName]="slotProps" v-for="(slot, slotName) in col.slots">
:is="col.component" <component :is="slot" v-bind="slotProps" />
v-bind="col.props" </template>
> </component>
<template v-if="col.slots" #[slotName]="slotProps" v-for="(slot, slotName) in col.slots"> </el-table>
<component :is="slot" v-bind="slotProps" />
</template>
</component>
</el-table>
</template> </template>
<script setup> <script setup>
const columns = [ const columns = [
{ prop: 'tied', label: '是否退休', component: 'el-table-column', slots: { default: StatusTag } }, { prop: 'tied', label: '是否退休', component: 'el-table-column', slots: { default: StatusTag } },
// ... // ...
] ];
const visibleColumns = computed(() => { const visibleColumns = computed(() => {
return columns.filter(col => isColumnVisible(col.prop || col.label)) return columns.filter((col) => isColumnVisible(col.prop || col.label));
}) });
</script> </script>
``` ```
--- ---
## 方案4在 el-table 层面使用 provide + 自动处理 ## 方案 4在 el-table 层面使用 provide + 自动处理
### 优点 ### 优点
- ✅ 在表格层面统一处理 - ✅ 在表格层面统一处理
- ✅ 子组件自动继承 - ✅ 子组件自动继承
### 缺点 ### 缺点
- ❌ 实现最复杂 - ❌ 实现最复杂
- ❌ 需要修改 Element Plus 的渲染逻辑 - ❌ 需要修改 Element Plus 的渲染逻辑
- ❌ 可能影响性能 - ❌ 可能影响性能
@@ -131,21 +130,23 @@ const visibleColumns = computed(() => {
## 推荐方案 ## 推荐方案
**推荐使用方案1TableColumn 包装组件)**,因为: **推荐使用方案 1TableColumn 包装组件)**,因为:
1. 使用最简单,只需替换组件名 1. 使用最简单,只需替换组件名
2. 完全兼容现有代码 2. 完全兼容现有代码
3. 易于维护和理解 3. 易于维护和理解
4. 性能良好 4. 性能良好
## 迁移步骤方案1 ## 迁移步骤(方案 1
1. 导入组件: 1. 导入组件:
```vue ```vue
import TableColumnProvider from '/@/components/TableColumn/Provider.vue' import TableColumnProvider from '/@/components/TableColumn/Provider.vue' import TableColumn from '/@/components/TableColumn/index.vue'
import TableColumn from '/@/components/TableColumn/index.vue'
``` ```
2.`TableColumnProvider` 包裹所有列,并传入 `isColumnVisible` 2.`TableColumnProvider` 包裹所有列,并传入 `isColumnVisible`
```vue ```vue
<TableColumnProvider :is-column-visible="isColumnVisible"> <TableColumnProvider :is-column-visible="isColumnVisible">
<!-- 所有列 --> <!-- 所有列 -->
@@ -153,6 +154,7 @@ import TableColumn from '/@/components/TableColumn/index.vue'
``` ```
3. 将所有 `el-table-column` 替换为 `TableColumn`,并移除 `v-if` 3. 将所有 `el-table-column` 替换为 `TableColumn`,并移除 `v-if`
```vue ```vue
<!-- 之前 --> <!-- 之前 -->
<el-table-column v-if="isColumnVisible('tied')" prop="tied" label="是否退休" /> <el-table-column v-if="isColumnVisible('tied')" prop="tied" label="是否退休" />
@@ -160,4 +162,3 @@ import TableColumn from '/@/components/TableColumn/index.vue'
<!-- 之后 --> <!-- 之后 -->
<TableColumn prop="tied" label="是否退休" /> <TableColumn prop="tied" label="是否退休" />
``` ```

140
auto-imports.d.ts vendored
View File

@@ -1,73 +1,73 @@
// Generated by 'unplugin-auto-import' // Generated by 'unplugin-auto-import'
export {} export {};
declare global { declare global {
const EffectScope: typeof import('vue')['EffectScope'] const EffectScope: typeof import('vue')['EffectScope'];
const acceptHMRUpdate: typeof import('pinia')['acceptHMRUpdate'] const acceptHMRUpdate: typeof import('pinia')['acceptHMRUpdate'];
const computed: typeof import('vue')['computed'] const computed: typeof import('vue')['computed'];
const createApp: typeof import('vue')['createApp'] const createApp: typeof import('vue')['createApp'];
const createPinia: typeof import('pinia')['createPinia'] const createPinia: typeof import('pinia')['createPinia'];
const customRef: typeof import('vue')['customRef'] const customRef: typeof import('vue')['customRef'];
const defineAsyncComponent: typeof import('vue')['defineAsyncComponent'] const defineAsyncComponent: typeof import('vue')['defineAsyncComponent'];
const defineComponent: typeof import('vue')['defineComponent'] const defineComponent: typeof import('vue')['defineComponent'];
const defineStore: typeof import('pinia')['defineStore'] const defineStore: typeof import('pinia')['defineStore'];
const effectScope: typeof import('vue')['effectScope'] const effectScope: typeof import('vue')['effectScope'];
const getActivePinia: typeof import('pinia')['getActivePinia'] const getActivePinia: typeof import('pinia')['getActivePinia'];
const getCurrentInstance: typeof import('vue')['getCurrentInstance'] const getCurrentInstance: typeof import('vue')['getCurrentInstance'];
const getCurrentScope: typeof import('vue')['getCurrentScope'] const getCurrentScope: typeof import('vue')['getCurrentScope'];
const h: typeof import('vue')['h'] const h: typeof import('vue')['h'];
const inject: typeof import('vue')['inject'] const inject: typeof import('vue')['inject'];
const isProxy: typeof import('vue')['isProxy'] const isProxy: typeof import('vue')['isProxy'];
const isReactive: typeof import('vue')['isReactive'] const isReactive: typeof import('vue')['isReactive'];
const isReadonly: typeof import('vue')['isReadonly'] const isReadonly: typeof import('vue')['isReadonly'];
const isRef: typeof import('vue')['isRef'] const isRef: typeof import('vue')['isRef'];
const mapActions: typeof import('pinia')['mapActions'] const mapActions: typeof import('pinia')['mapActions'];
const mapGetters: typeof import('pinia')['mapGetters'] const mapGetters: typeof import('pinia')['mapGetters'];
const mapState: typeof import('pinia')['mapState'] const mapState: typeof import('pinia')['mapState'];
const mapStores: typeof import('pinia')['mapStores'] const mapStores: typeof import('pinia')['mapStores'];
const mapWritableState: typeof import('pinia')['mapWritableState'] const mapWritableState: typeof import('pinia')['mapWritableState'];
const markRaw: typeof import('vue')['markRaw'] const markRaw: typeof import('vue')['markRaw'];
const nextTick: typeof import('vue')['nextTick'] const nextTick: typeof import('vue')['nextTick'];
const onActivated: typeof import('vue')['onActivated'] const onActivated: typeof import('vue')['onActivated'];
const onBeforeMount: typeof import('vue')['onBeforeMount'] const onBeforeMount: typeof import('vue')['onBeforeMount'];
const onBeforeRouteLeave: typeof import('vue-router')['onBeforeRouteLeave'] const onBeforeRouteLeave: typeof import('vue-router')['onBeforeRouteLeave'];
const onBeforeRouteUpdate: typeof import('vue-router')['onBeforeRouteUpdate'] const onBeforeRouteUpdate: typeof import('vue-router')['onBeforeRouteUpdate'];
const onBeforeUnmount: typeof import('vue')['onBeforeUnmount'] const onBeforeUnmount: typeof import('vue')['onBeforeUnmount'];
const onBeforeUpdate: typeof import('vue')['onBeforeUpdate'] const onBeforeUpdate: typeof import('vue')['onBeforeUpdate'];
const onDeactivated: typeof import('vue')['onDeactivated'] const onDeactivated: typeof import('vue')['onDeactivated'];
const onErrorCaptured: typeof import('vue')['onErrorCaptured'] const onErrorCaptured: typeof import('vue')['onErrorCaptured'];
const onMounted: typeof import('vue')['onMounted'] const onMounted: typeof import('vue')['onMounted'];
const onRenderTracked: typeof import('vue')['onRenderTracked'] const onRenderTracked: typeof import('vue')['onRenderTracked'];
const onRenderTriggered: typeof import('vue')['onRenderTriggered'] const onRenderTriggered: typeof import('vue')['onRenderTriggered'];
const onScopeDispose: typeof import('vue')['onScopeDispose'] const onScopeDispose: typeof import('vue')['onScopeDispose'];
const onServerPrefetch: typeof import('vue')['onServerPrefetch'] const onServerPrefetch: typeof import('vue')['onServerPrefetch'];
const onUnmounted: typeof import('vue')['onUnmounted'] const onUnmounted: typeof import('vue')['onUnmounted'];
const onUpdated: typeof import('vue')['onUpdated'] const onUpdated: typeof import('vue')['onUpdated'];
const provide: typeof import('vue')['provide'] const provide: typeof import('vue')['provide'];
const reactive: typeof import('vue')['reactive'] const reactive: typeof import('vue')['reactive'];
const readonly: typeof import('vue')['readonly'] const readonly: typeof import('vue')['readonly'];
const ref: typeof import('vue')['ref'] const ref: typeof import('vue')['ref'];
const resolveComponent: typeof import('vue')['resolveComponent'] const resolveComponent: typeof import('vue')['resolveComponent'];
const resolveDirective: typeof import('vue')['resolveDirective'] const resolveDirective: typeof import('vue')['resolveDirective'];
const setActivePinia: typeof import('pinia')['setActivePinia'] const setActivePinia: typeof import('pinia')['setActivePinia'];
const setMapStoreSuffix: typeof import('pinia')['setMapStoreSuffix'] const setMapStoreSuffix: typeof import('pinia')['setMapStoreSuffix'];
const shallowReactive: typeof import('vue')['shallowReactive'] const shallowReactive: typeof import('vue')['shallowReactive'];
const shallowReadonly: typeof import('vue')['shallowReadonly'] const shallowReadonly: typeof import('vue')['shallowReadonly'];
const shallowRef: typeof import('vue')['shallowRef'] const shallowRef: typeof import('vue')['shallowRef'];
const storeToRefs: typeof import('pinia')['storeToRefs'] const storeToRefs: typeof import('pinia')['storeToRefs'];
const toRaw: typeof import('vue')['toRaw'] const toRaw: typeof import('vue')['toRaw'];
const toRef: typeof import('vue')['toRef'] const toRef: typeof import('vue')['toRef'];
const toRefs: typeof import('vue')['toRefs'] const toRefs: typeof import('vue')['toRefs'];
const triggerRef: typeof import('vue')['triggerRef'] const triggerRef: typeof import('vue')['triggerRef'];
const unref: typeof import('vue')['unref'] const unref: typeof import('vue')['unref'];
const useAttrs: typeof import('vue')['useAttrs'] const useAttrs: typeof import('vue')['useAttrs'];
const useCssModule: typeof import('vue')['useCssModule'] const useCssModule: typeof import('vue')['useCssModule'];
const useCssVars: typeof import('vue')['useCssVars'] const useCssVars: typeof import('vue')['useCssVars'];
const useLink: typeof import('vue-router')['useLink'] const useLink: typeof import('vue-router')['useLink'];
const useRoute: typeof import('vue-router')['useRoute'] const useRoute: typeof import('vue-router')['useRoute'];
const useRouter: typeof import('vue-router')['useRouter'] const useRouter: typeof import('vue-router')['useRouter'];
const useSlots: typeof import('vue')['useSlots'] const useSlots: typeof import('vue')['useSlots'];
const watch: typeof import('vue')['watch'] const watch: typeof import('vue')['watch'];
const watchEffect: typeof import('vue')['watchEffect'] const watchEffect: typeof import('vue')['watchEffect'];
const watchPostEffect: typeof import('vue')['watchPostEffect'] const watchPostEffect: typeof import('vue')['watchPostEffect'];
const watchSyncEffect: typeof import('vue')['watchSyncEffect'] const watchSyncEffect: typeof import('vue')['watchSyncEffect'];
} }

View File

@@ -1,77 +1,89 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8" />
<title>${ENV, var="JOB_NAME"}-第${BUILD_NUMBER}次构建日志</title> <title>${ENV, var="JOB_NAME"}-第${BUILD_NUMBER}次构建日志</title>
</head> </head>
<body leftmargin="8" marginwidth="0" topmargin="8" marginheight="4" <body leftmargin="8" marginwidth="0" topmargin="8" marginheight="4" offset="0">
offset="0"> <table width="95%" cellpadding="0" cellspacing="0" style="font-size: 11pt; font-family: Tahoma, Arial, Helvetica, sans-serif">
<table width="95%" cellpadding="0" cellspacing="0" <tr>
style="font-size: 11pt; font-family: Tahoma, Arial, Helvetica, sans-serif"> <td>(本邮件是程序自动下发的,请勿回复!)</td>
<tr> </tr>
<td>(本邮件是程序自动下发的,请勿回复!)</td> <tr>
</tr> <td>
<tr> <h2>
<td><h2> <font color="#FF0000">构建结果 - ${BUILD_STATUS}</font>
<font color="#FF0000">构建结果 - ${BUILD_STATUS}</font> </h2>
</h2></td> </td>
</tr> </tr>
<tr> <tr>
<td><br /> <td>
<b><font color="#0B610B">构建信息</font></b> <br />
<hr size="2" width="100%" align="center" /></td> <b><font color="#0B610B">构建信息</font></b>
</tr> <hr size="2" width="100%" align="center" />
<tr> </td>
<td> </tr>
<ul> <tr>
<li>项目名称&nbsp;&nbsp;${PROJECT_NAME}</li> <td>
<li>构建编号&nbsp;&nbsp;第${BUILD_NUMBER}次构建</li> <ul>
<li>触发原因:&nbsp;${CAUSE}</li> <li>项目名称&nbsp;&nbsp;${PROJECT_NAME}</li>
<li>构建日志:&nbsp;<a href="${BUILD_URL}console">${BUILD_URL}console</a></li> <li>构建编号&nbsp;&nbsp;第${BUILD_NUMBER}次构建</li>
<li>构建&nbsp;&nbsp;Url&nbsp;&nbsp;<a href="${BUILD_URL}">${BUILD_URL}</a></li> <li>触发原因:&nbsp;${CAUSE}</li>
<li>工作目录&nbsp;&nbsp;<a href="${PROJECT_URL}ws">${PROJECT_URL}</a></li> <li>构建日志:&nbsp;<a href="${BUILD_URL}console">${BUILD_URL}console</a></li>
<li>项目&nbsp;&nbsp;Url&nbsp;&nbsp;<a href="${PROJECT_URL}">${PROJECT_URL}</a></li> <li>构建&nbsp;&nbsp;Url&nbsp;&nbsp;<a href="${BUILD_URL}">${BUILD_URL}</a></li>
</ul> <li>工作目录&nbsp;&nbsp;<a href="${PROJECT_URL}ws">${PROJECT_URL}</a></li>
</td> <li>项目&nbsp;&nbsp;Url&nbsp;&nbsp;<a href="${PROJECT_URL}">${PROJECT_URL}</a></li>
</tr> </ul>
<tr> </td>
<td><b><font color="#0B610B">Changes Since Last </tr>
Successful Build:</font></b> <tr>
<hr size="2" width="100%" align="center" /></td> <td>
</tr> <b><font color="#0B610B">Changes Since Last Successful Build:</font></b>
<tr> <hr size="2" width="100%" align="center" />
<td> </td>
<ul> </tr>
<li>历史变更记录 : <a href="${PROJECT_URL}changes">${PROJECT_URL}changes</a></li> <tr>
</ul> ${CHANGES_SINCE_LAST_SUCCESS,reverse=true, format="Changes for Build #%n:<br />%c<br />",showPaths=true,changesFormat="<pre>[%a]<br />%m</pre>",pathFormat="&nbsp;&nbsp;&nbsp;&nbsp;%p"} <td>
</td> <ul>
</tr> <li>历史变更记录 : <a href="${PROJECT_URL}changes">${PROJECT_URL}changes</a></li>
<tr> </ul>
<td><b>Failed Test Results</b> ${CHANGES_SINCE_LAST_SUCCESS,reverse=true, format="Changes for Build #%n:<br />%c<br />",showPaths=true,changesFormat="
<hr size="2" width="100%" align="center" /></td> <pre>[%a]<br />%m</pre>
</tr> ",pathFormat="&nbsp;&nbsp;&nbsp;&nbsp;%p"}
<tr> </td>
<td><pre </tr>
style="font-size: 11pt; font-family: Tahoma, Arial, Helvetica, sans-serif">$FAILED_TESTS</pre> <tr>
<br /></td> <td>
</tr> <b>Failed Test Results</b>
<tr> <hr size="2" width="100%" align="center" />
<td><b><font color="#0B610B">构建日志 (最后 100行):</font></b> </td>
<hr size="2" width="100%" align="center" /></td> </tr>
</tr> <tr>
<tr> <td>
<td>Test Logs (if test has ran): <pre style="font-size: 11pt; font-family: Tahoma, Arial, Helvetica, sans-serif">$FAILED_TESTS</pre>
<a href="${PROJECT_URL}ws/TestResult/archive_logs/Log-Build-${BUILD_NUMBER}.zip">${PROJECT_URL}/ws/TestResult/archive_logs/Log-Build-${BUILD_NUMBER}.zip</a> <br />
<br /> </td>
<br /> </tr>
</td> <tr>
</tr> <td>
<tr> <b><font color="#0B610B">构建日志 (最后 100行):</font></b>
<td><textarea cols="80" rows="30" readonly="readonly" <hr size="2" width="100%" align="center" />
style="font-family: Courier New">${BUILD_LOG}</textarea> </td>
</td> </tr>
</tr> <tr>
</table> <td>
</body> Test Logs (if test has ran):
<a href="${PROJECT_URL}ws/TestResult/archive_logs/Log-Build-${BUILD_NUMBER}.zip"
>${PROJECT_URL}/ws/TestResult/archive_logs/Log-Build-${BUILD_NUMBER}.zip</a
>
<br />
<br />
</td>
</tr>
<tr>
<td><textarea cols="80" rows="30" readonly="readonly" style="font-family: Courier New">${BUILD_LOG}</textarea></td>
</tr>
</table>
</body>
</html> </html>

View File

@@ -1,77 +1,89 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8" />
<title>${ENV, var="JOB_NAME"}-第${BUILD_NUMBER}次构建日志</title> <title>${ENV, var="JOB_NAME"}-第${BUILD_NUMBER}次构建日志</title>
</head> </head>
<body leftmargin="8" marginwidth="0" topmargin="8" marginheight="4" <body leftmargin="8" marginwidth="0" topmargin="8" marginheight="4" offset="0">
offset="0"> <table width="95%" cellpadding="0" cellspacing="0" style="font-size: 11pt; font-family: Tahoma, Arial, Helvetica, sans-serif">
<table width="95%" cellpadding="0" cellspacing="0" <tr>
style="font-size: 11pt; font-family: Tahoma, Arial, Helvetica, sans-serif"> <td>(本邮件是程序自动下发的,请勿回复!)</td>
<tr> </tr>
<td>(本邮件是程序自动下发的,请勿回复!)</td> <tr>
</tr> <td>
<tr> <h2>
<td><h2> <font color="#228B22">构建结果 - ${BUILD_STATUS}</font>
<font color="#228B22">构建结果 - ${BUILD_STATUS}</font> </h2>
</h2></td> </td>
</tr> </tr>
<tr> <tr>
<td><br /> <td>
<b><font color="#0B610B">构建信息</font></b> <br />
<hr size="2" width="100%" align="center" /></td> <b><font color="#0B610B">构建信息</font></b>
</tr> <hr size="2" width="100%" align="center" />
<tr> </td>
<td> </tr>
<ul> <tr>
<li>项目名称&nbsp;&nbsp;${PROJECT_NAME}</li> <td>
<li>构建编号&nbsp;&nbsp;第${BUILD_NUMBER}次构建</li> <ul>
<li>触发原因:&nbsp;${CAUSE}</li> <li>项目名称&nbsp;&nbsp;${PROJECT_NAME}</li>
<li>构建日志:&nbsp;<a href="${BUILD_URL}console">${BUILD_URL}console</a></li> <li>构建编号&nbsp;&nbsp;第${BUILD_NUMBER}次构建</li>
<li>构建&nbsp;&nbsp;Url&nbsp;&nbsp;<a href="${BUILD_URL}">${BUILD_URL}</a></li> <li>触发原因:&nbsp;${CAUSE}</li>
<li>工作目录&nbsp;&nbsp;<a href="${PROJECT_URL}ws">${PROJECT_URL}</a></li> <li>构建日志:&nbsp;<a href="${BUILD_URL}console">${BUILD_URL}console</a></li>
<li>项目&nbsp;&nbsp;Url&nbsp;&nbsp;<a href="${PROJECT_URL}">${PROJECT_URL}</a></li> <li>构建&nbsp;&nbsp;Url&nbsp;&nbsp;<a href="${BUILD_URL}">${BUILD_URL}</a></li>
</ul> <li>工作目录&nbsp;&nbsp;<a href="${PROJECT_URL}ws">${PROJECT_URL}</a></li>
</td> <li>项目&nbsp;&nbsp;Url&nbsp;&nbsp;<a href="${PROJECT_URL}">${PROJECT_URL}</a></li>
</tr> </ul>
<tr> </td>
<td><b><font color="#0B610B">Changes Since Last </tr>
Successful Build:</font></b> <tr>
<hr size="2" width="100%" align="center" /></td> <td>
</tr> <b><font color="#0B610B">Changes Since Last Successful Build:</font></b>
<tr> <hr size="2" width="100%" align="center" />
<td> </td>
<ul> </tr>
<li>历史变更记录 : <a href="${PROJECT_URL}changes">${PROJECT_URL}changes</a></li> <tr>
</ul> ${CHANGES_SINCE_LAST_SUCCESS,reverse=true, format="Changes for Build #%n:<br />%c<br />",showPaths=true,changesFormat="<pre>[%a]<br />%m</pre>",pathFormat="&nbsp;&nbsp;&nbsp;&nbsp;%p"} <td>
</td> <ul>
</tr> <li>历史变更记录 : <a href="${PROJECT_URL}changes">${PROJECT_URL}changes</a></li>
<tr> </ul>
<td><b>Failed Test Results</b> ${CHANGES_SINCE_LAST_SUCCESS,reverse=true, format="Changes for Build #%n:<br />%c<br />",showPaths=true,changesFormat="
<hr size="2" width="100%" align="center" /></td> <pre>[%a]<br />%m</pre>
</tr> ",pathFormat="&nbsp;&nbsp;&nbsp;&nbsp;%p"}
<tr> </td>
<td><pre </tr>
style="font-size: 11pt; font-family: Tahoma, Arial, Helvetica, sans-serif">$FAILED_TESTS</pre> <tr>
<br /></td> <td>
</tr> <b>Failed Test Results</b>
<tr> <hr size="2" width="100%" align="center" />
<td><b><font color="#0B610B">构建日志 (最后 100行):</font></b> </td>
<hr size="2" width="100%" align="center" /></td> </tr>
</tr> <tr>
<tr> <td>
<td>Test Logs (if test has ran): <pre style="font-size: 11pt; font-family: Tahoma, Arial, Helvetica, sans-serif">$FAILED_TESTS</pre>
<a href="${PROJECT_URL}ws/TestResult/archive_logs/Log-Build-${BUILD_NUMBER}.zip">${PROJECT_URL}/ws/TestResult/archive_logs/Log-Build-${BUILD_NUMBER}.zip</a> <br />
<br /> </td>
<br /> </tr>
</td> <tr>
</tr> <td>
<tr> <b><font color="#0B610B">构建日志 (最后 100行):</font></b>
<td><textarea cols="80" rows="30" readonly="readonly" <hr size="2" width="100%" align="center" />
style="font-family: Courier New">${BUILD_LOG}</textarea> </td>
</td> </tr>
</tr> <tr>
</table> <td>
</body> Test Logs (if test has ran):
<a href="${PROJECT_URL}ws/TestResult/archive_logs/Log-Build-${BUILD_NUMBER}.zip"
>${PROJECT_URL}/ws/TestResult/archive_logs/Log-Build-${BUILD_NUMBER}.zip</a
>
<br />
<br />
</td>
</tr>
<tr>
<td><textarea cols="80" rows="30" readonly="readonly" style="font-family: Courier New">${BUILD_LOG}</textarea></td>
</tr>
</table>
</body>
</html> </html>

View File

@@ -154,7 +154,18 @@ watch(templateFiles, (files) => {
}, { deep: true }) }, { deep: true })
const rules: FormRules = { 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() const validate = () => formRef.value?.validate()

View File

@@ -58,8 +58,30 @@
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="8" class="mb20"> <el-col :span="8" class="mb20">
<el-form-item label="供应商联系人及电话" prop="supplierContact"> <el-form-item label="资产管理员" prop="assetAdminId">
<el-input v-model="form.supplierContact" placeholder="请输入" clearable /> <el-select
v-model="form.assetAdminId"
placeholder="请输入姓名或工号搜索"
filterable
remote
clearable
reserve-keyword
:remote-method="searchAssetAdmin"
:loading="assetAdminLoading"
style="width: 100%"
@change="onAssetAdminChange"
>
<el-option
v-for="item in assetAdminOptions"
:key="item.teacherNo"
:label="(item.commonDeptName ? item.commonDeptName + ' - ' : '') + (item.realName || item.name) + ' (' + item.teacherNo + ')'"
:value="item.teacherNo"
>
<span>{{ item.commonDeptName ? item.commonDeptName + ' - ' : '' }}{{ item.realName || item.name }}</span>
<span style="color: #999; font-size: 12px; margin-left: 8px;">{{ item.teacherNo }}</span>
</el-option>
</el-select>
<div class="field-note">如入固定资产必填</div>
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="8" class="mb20"> <el-col :span="8" class="mb20">
@@ -167,7 +189,6 @@ const form = reactive({
projectName: '', projectName: '',
deptName: '', deptName: '',
supplierName: '', supplierName: '',
supplierContact: '',
purchaserId: '', purchaserId: '',
purchaserName: '', purchaserName: '',
assetAdminId: '', assetAdminId: '',
@@ -336,4 +357,9 @@ defineExpose({ validate, form })
.mb20 { .mb20 {
margin-bottom: 20px; margin-bottom: 20px;
} }
.field-note {
font-size: 12px;
color: #999;
margin-top: 4px;
}
</style> </style>

View File

@@ -192,7 +192,6 @@ const loadData = async () => {
isInstallment: config.common.isInstallment || '0', isInstallment: config.common.isInstallment || '0',
totalPhases: config.common.totalPhases || 1, totalPhases: config.common.totalPhases || 1,
supplierName: config.common.supplierName || '', supplierName: config.common.supplierName || '',
supplierContact: config.common.supplierContact || '',
transactionAmount: config.common.transactionAmount || null, transactionAmount: config.common.transactionAmount || null,
}) })
} }
@@ -278,7 +277,6 @@ const saveCommonConfig = async () => {
isInstallment: form.isInstallment ?? '0', isInstallment: form.isInstallment ?? '0',
totalPhases: isInstallment ? (Number(form.totalPhases) || 1) : 1, totalPhases: isInstallment ? (Number(form.totalPhases) || 1) : 1,
supplierName: String(form.supplierName ?? ''), supplierName: String(form.supplierName ?? ''),
supplierContact: String(form.supplierContact ?? ''),
purchaserId: String(form.purchaserId ?? ''), purchaserId: String(form.purchaserId ?? ''),
purchaserName: String(form.purchaserName ?? ''), purchaserName: String(form.purchaserName ?? ''),
assetAdminId: String(form.assetAdminId ?? ''), assetAdminId: String(form.assetAdminId ?? ''),
@@ -307,10 +305,7 @@ const saveCurrentBatch = async () => {
const formData = batchFormRef?.getFormData?.() || batchFormRef?.form const formData = batchFormRef?.getFormData?.() || batchFormRef?.form
if (!formData) return if (!formData) return
if (!formData.acceptDate) { // acceptDate is now optional - removed the validation check
useMessage().error('请选择验收日期')
return
}
// templateFileIds: 提取ID数组 // templateFileIds: 提取ID数组
let fileIds: string[] = [] let fileIds: string[] = []
@@ -357,7 +352,6 @@ const DEFAULT_COMMON_FORM = {
isInstallment: '0', isInstallment: '0',
totalPhases: 1, totalPhases: 1,
supplierName: '', supplierName: '',
supplierContact: '',
purchaserId: '', purchaserId: '',
purchaserName: '', purchaserName: '',
assetAdminId: '', assetAdminId: '',

View File

@@ -205,7 +205,7 @@
<upload-file <upload-file
v-model="dataForm.businessNegotiationTable" v-model="dataForm.businessNegotiationTable"
:limit="1" :limit="1"
:file-type="['doc', 'docx', 'pdf']" :file-type="['doc', 'docx', 'pdf', 'jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp']"
:data="{ fileType: FILE_TYPE_MAP.businessNegotiationTable }" :data="{ fileType: FILE_TYPE_MAP.businessNegotiationTable }"
upload-file-url="/purchase/purchasingfiles/upload" upload-file-url="/purchase/purchasingfiles/upload"
:disabled="flowFieldDisabled('businessNegotiationTable')" :disabled="flowFieldDisabled('businessNegotiationTable')"
@@ -226,7 +226,7 @@
<upload-file <upload-file
v-model="dataForm.marketPurchaseMinutes" v-model="dataForm.marketPurchaseMinutes"
:limit="1" :limit="1"
:file-type="['doc', 'docx', 'pdf']" :file-type="['doc', 'docx', 'pdf', 'jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp']"
:data="{ fileType: FILE_TYPE_MAP.marketPurchaseMinutes }" :data="{ fileType: FILE_TYPE_MAP.marketPurchaseMinutes }"
upload-file-url="/purchase/purchasingfiles/upload" upload-file-url="/purchase/purchasingfiles/upload"
:disabled="flowFieldDisabled('marketPurchaseMinutes')" :disabled="flowFieldDisabled('marketPurchaseMinutes')"
@@ -247,7 +247,7 @@
<upload-file <upload-file
v-model="dataForm.onlineMallMaterials" v-model="dataForm.onlineMallMaterials"
:limit="1" :limit="1"
:file-type="['doc', 'docx', 'pdf']" :file-type="['doc', 'docx', 'pdf', 'jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp']"
:data="{ fileType: FILE_TYPE_MAP.onlineMallMaterials }" :data="{ fileType: FILE_TYPE_MAP.onlineMallMaterials }"
upload-file-url="/purchase/purchasingfiles/upload" upload-file-url="/purchase/purchasingfiles/upload"
:disabled="flowFieldDisabled('onlineMallMaterials')" :disabled="flowFieldDisabled('onlineMallMaterials')"
@@ -259,7 +259,7 @@
<upload-file <upload-file
v-model="dataForm.inquiryTemplate" v-model="dataForm.inquiryTemplate"
:limit="1" :limit="1"
:file-type="['doc', 'docx', 'pdf']" :file-type="['doc', 'docx', 'pdf', 'jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp']"
:data="{ fileType: FILE_TYPE_MAP.inquiryTemplate }" :data="{ fileType: FILE_TYPE_MAP.inquiryTemplate }"
upload-file-url="/purchase/purchasingfiles/upload" upload-file-url="/purchase/purchasingfiles/upload"
:disabled="flowFieldDisabled('inquiryTemplate')" :disabled="flowFieldDisabled('inquiryTemplate')"
@@ -278,13 +278,13 @@
<el-col <el-col
:span="8" :span="8"
class="mb12" class="mb12"
v-if="isDeptPurchase && dataForm.budget != null && dataForm.budget >= BUDGET_DEPT_SELF_MEETING_MINUTES" v-if="dataForm.budget != null && dataForm.budget >= BUDGET_DEPT_SELF_MEETING_MINUTES"
> >
<el-form-item label="部门自行采购会议纪要" prop="deptSelfMeetingMinutes" required> <el-form-item label="部门自行采购会议纪要" prop="deptSelfMeetingMinutes" required>
<upload-file <upload-file
v-model="dataForm.deptSelfMeetingMinutes" v-model="dataForm.deptSelfMeetingMinutes"
:limit="1" :limit="1"
:file-type="['doc', 'docx', 'pdf']" :file-type="['doc', 'docx', 'pdf', 'jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp']"
:data="{ fileType: FILE_TYPE_MAP.deptSelfMeetingMinutes }" :data="{ fileType: FILE_TYPE_MAP.deptSelfMeetingMinutes }"
upload-file-url="/purchase/purchasingfiles/upload" upload-file-url="/purchase/purchasingfiles/upload"
:disabled="flowFieldDisabled('deptSelfMeetingMinutes')" :disabled="flowFieldDisabled('deptSelfMeetingMinutes')"
@@ -320,7 +320,7 @@
<upload-file <upload-file
v-model="dataForm.serviceDirectSelect" v-model="dataForm.serviceDirectSelect"
:limit="1" :limit="1"
:file-type="['doc', 'docx', 'pdf']" :file-type="['doc', 'docx', 'pdf', 'jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp']"
:data="{ fileType: FILE_TYPE_MAP.serviceDirectSelect }" :data="{ fileType: FILE_TYPE_MAP.serviceDirectSelect }"
upload-file-url="/purchase/purchasingfiles/upload" upload-file-url="/purchase/purchasingfiles/upload"
:disabled="flowFieldDisabled('serviceDirectSelect')" :disabled="flowFieldDisabled('serviceDirectSelect')"
@@ -351,7 +351,7 @@
<upload-file <upload-file
v-model="dataForm.serviceInviteSelect" v-model="dataForm.serviceInviteSelect"
:limit="1" :limit="1"
:file-type="['doc', 'docx', 'pdf']" :file-type="['doc', 'docx', 'pdf', 'jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp']"
:data="{ fileType: FILE_TYPE_MAP.serviceInviteSelect }" :data="{ fileType: FILE_TYPE_MAP.serviceInviteSelect }"
upload-file-url="/purchase/purchasingfiles/upload" upload-file-url="/purchase/purchasingfiles/upload"
:disabled="flowFieldDisabled('serviceInviteSelect')" :disabled="flowFieldDisabled('serviceInviteSelect')"
@@ -376,7 +376,7 @@
<upload-file <upload-file
v-model="dataForm.purchaseRequirementTemplate" v-model="dataForm.purchaseRequirementTemplate"
:limit="1" :limit="1"
:file-type="['doc', 'docx', 'pdf']" :file-type="['doc', 'docx']"
:data="{ fileType: FILE_TYPE_MAP.purchaseRequirementTemplate }" :data="{ fileType: FILE_TYPE_MAP.purchaseRequirementTemplate }"
upload-file-url="/purchase/purchasingfiles/upload" upload-file-url="/purchase/purchasingfiles/upload"
:disabled="flowFieldDisabled('purchaseRequirementTemplate')" :disabled="flowFieldDisabled('purchaseRequirementTemplate')"
@@ -405,7 +405,7 @@
<upload-file <upload-file
v-model="dataForm.serviceInviteSelect" v-model="dataForm.serviceInviteSelect"
:limit="1" :limit="1"
:file-type="['doc', 'docx', 'pdf']" :file-type="['doc', 'docx', 'pdf', 'jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp']"
:data="{ fileType: FILE_TYPE_MAP.serviceInviteSelect }" :data="{ fileType: FILE_TYPE_MAP.serviceInviteSelect }"
upload-file-url="/purchase/purchasingfiles/upload" upload-file-url="/purchase/purchasingfiles/upload"
:disabled="flowFieldDisabled('serviceInviteSelect')" :disabled="flowFieldDisabled('serviceInviteSelect')"
@@ -426,7 +426,7 @@
<upload-file <upload-file
v-model="dataForm.servicePublicSelectAuto" v-model="dataForm.servicePublicSelectAuto"
:limit="1" :limit="1"
:file-type="['doc', 'docx', 'pdf']" :file-type="['doc', 'docx', 'pdf', 'jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp']"
:data="{ fileType: FILE_TYPE_MAP.servicePublicSelectAuto }" :data="{ fileType: FILE_TYPE_MAP.servicePublicSelectAuto }"
upload-file-url="/purchase/purchasingfiles/upload" upload-file-url="/purchase/purchasingfiles/upload"
:disabled="flowFieldDisabled('servicePublicSelectAuto')" :disabled="flowFieldDisabled('servicePublicSelectAuto')"
@@ -556,7 +556,7 @@
<upload-file <upload-file
v-model="dataForm.feasibilityReport" v-model="dataForm.feasibilityReport"
:limit="1" :limit="1"
:file-type="['doc', 'docx', 'pdf']" :file-type="['doc', 'docx', 'pdf', 'jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp']"
:data="{ fileType: FILE_TYPE_MAP.feasibilityReport }" :data="{ fileType: FILE_TYPE_MAP.feasibilityReport }"
upload-file-url="/purchase/purchasingfiles/upload" upload-file-url="/purchase/purchasingfiles/upload"
:disabled="flowFieldDisabled('feasibilityReport')" :disabled="flowFieldDisabled('feasibilityReport')"
@@ -577,41 +577,28 @@
class="mb12" class="mb12"
v-if=" v-if="
dataForm.budget && dataForm.budget &&
dataForm.budget >= BUDGET_FEASIBILITY_THRESHOLD && (dataForm.budget >= BUDGET_FEASIBILITY_THRESHOLD || !isSpecialType('0'))
!isUrgentSpecial &&
!isSpecialType('2') &&
!isSpecialType('3')
" "
> >
<el-form-item label="校党委会议纪要" prop="meetingMinutes" required> <el-form-item label="校党委会议纪要" prop="meetingMinutes" required>
<upload-file <upload-file
v-model="dataForm.meetingMinutes" v-model="dataForm.meetingMinutes"
:limit="1" :limit="1"
:file-type="['doc', 'docx', 'pdf']" :file-type="['doc', 'docx', 'pdf', 'jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp']"
:data="{ fileType: FILE_TYPE_MAP.meetingMinutes }" :data="{ fileType: FILE_TYPE_MAP.meetingMinutes }"
upload-file-url="/purchase/purchasingfiles/upload" upload-file-url="/purchase/purchasingfiles/upload"
:disabled="flowFieldDisabled('meetingMinutes')" :disabled="flowFieldDisabled('meetingMinutes')"
/> />
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="8" class="mb12" v-if="isUrgentSpecial">
<el-form-item label="校党委会议纪要(紧急)" prop="meetingMinutesUrgent" required>
<upload-file
v-model="dataForm.meetingMinutesUrgent"
:limit="1"
:file-type="['doc', 'docx', 'pdf']"
:data="{ fileType: FILE_TYPE_MAP.meetingMinutesUrgent }"
upload-file-url="/purchase/purchasingfiles/upload"
:disabled="flowFieldDisabled('meetingMinutesUrgent')"
/>
</el-form-item>
</el-col>
<el-col :span="8" class="mb12" v-if="isSpecialType('2')"> <el-col :span="8" class="mb12" v-if="isSpecialType('2')">
<el-form-item label="单一来源论专家证附件" prop="singleSourceProof" required> <el-form-item label="单一来源论专家证附件" prop="singleSourceProof" required>
<upload-file <upload-file
v-model="dataForm.singleSourceProof" v-model="dataForm.singleSourceProof"
:limit="1" :limit="1"
:file-type="['doc', 'docx', 'pdf']" :file-type="['doc', 'docx', 'pdf', 'jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp']"
:data="{ fileType: FILE_TYPE_MAP.singleSourceProof }" :data="{ fileType: FILE_TYPE_MAP.singleSourceProof }"
upload-file-url="/purchase/purchasingfiles/upload" upload-file-url="/purchase/purchasingfiles/upload"
:disabled="flowFieldDisabled('singleSourceProof')" :disabled="flowFieldDisabled('singleSourceProof')"
@@ -632,7 +619,7 @@
<upload-file <upload-file
v-model="dataForm.meetingMinutesSingle" v-model="dataForm.meetingMinutesSingle"
:limit="1" :limit="1"
:file-type="['doc', 'docx', 'pdf']" :file-type="['doc', 'docx', 'pdf', 'jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp']"
:data="{ fileType: FILE_TYPE_MAP.meetingMinutesSingle }" :data="{ fileType: FILE_TYPE_MAP.meetingMinutesSingle }"
upload-file-url="/purchase/purchasingfiles/upload" upload-file-url="/purchase/purchasingfiles/upload"
:disabled="flowFieldDisabled('meetingMinutesSingle')" :disabled="flowFieldDisabled('meetingMinutesSingle')"
@@ -644,7 +631,7 @@
<upload-file <upload-file
v-model="dataForm.importApplication" v-model="dataForm.importApplication"
:limit="1" :limit="1"
:file-type="['doc', 'docx', 'pdf']" :file-type="['doc', 'docx', 'pdf', 'jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp']"
:data="{ fileType: FILE_TYPE_MAP.importApplication }" :data="{ fileType: FILE_TYPE_MAP.importApplication }"
upload-file-url="/purchase/purchasingfiles/upload" upload-file-url="/purchase/purchasingfiles/upload"
:disabled="flowFieldDisabled('importApplication')" :disabled="flowFieldDisabled('importApplication')"
@@ -665,7 +652,7 @@
<upload-file <upload-file
v-model="dataForm.meetingMinutesImport" v-model="dataForm.meetingMinutesImport"
:limit="1" :limit="1"
:file-type="['doc', 'docx', 'pdf']" :file-type="['doc', 'docx', 'pdf', 'jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp']"
:data="{ fileType: FILE_TYPE_MAP.meetingMinutesImport }" :data="{ fileType: FILE_TYPE_MAP.meetingMinutesImport }"
upload-file-url="/purchase/purchasingfiles/upload" upload-file-url="/purchase/purchasingfiles/upload"
:disabled="flowFieldDisabled('meetingMinutesImport')" :disabled="flowFieldDisabled('meetingMinutesImport')"
@@ -693,7 +680,7 @@
<upload-file <upload-file
v-model="dataForm.serviceInviteSelectSchool" v-model="dataForm.serviceInviteSelectSchool"
:limit="1" :limit="1"
:file-type="['doc', 'docx', 'pdf']" :file-type="['doc', 'docx', 'pdf', 'jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp']"
:data="{ fileType: FILE_TYPE_MAP.serviceInviteSelectSchool }" :data="{ fileType: FILE_TYPE_MAP.serviceInviteSelectSchool }"
upload-file-url="/purchase/purchasingfiles/upload" upload-file-url="/purchase/purchasingfiles/upload"
:disabled="flowFieldDisabled('serviceInviteSelectSchool')" :disabled="flowFieldDisabled('serviceInviteSelectSchool')"
@@ -712,7 +699,7 @@
<upload-file <upload-file
v-model="dataForm.servicePublicSelectSchoolAuto" v-model="dataForm.servicePublicSelectSchoolAuto"
:limit="1" :limit="1"
:file-type="['doc', 'docx', 'pdf']" :file-type="['doc', 'docx', 'pdf', 'jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp']"
:data="{ fileType: FILE_TYPE_MAP.servicePublicSelectSchoolAuto }" :data="{ fileType: FILE_TYPE_MAP.servicePublicSelectSchoolAuto }"
upload-file-url="/purchase/purchasingfiles/upload" upload-file-url="/purchase/purchasingfiles/upload"
:disabled="flowFieldDisabled('servicePublicSelectSchoolAuto')" :disabled="flowFieldDisabled('servicePublicSelectSchoolAuto')"
@@ -732,7 +719,7 @@
<upload-file <upload-file
v-model="dataForm.servicePublicSelectSchool" v-model="dataForm.servicePublicSelectSchool"
:limit="1" :limit="1"
:file-type="['doc', 'docx', 'pdf']" :file-type="['doc', 'docx', 'pdf', 'jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp']"
:data="{ fileType: FILE_TYPE_MAP.servicePublicSelectSchool }" :data="{ fileType: FILE_TYPE_MAP.servicePublicSelectSchool }"
upload-file-url="/purchase/purchasingfiles/upload" upload-file-url="/purchase/purchasingfiles/upload"
:disabled="flowFieldDisabled('servicePublicSelectSchool')" :disabled="flowFieldDisabled('servicePublicSelectSchool')"
@@ -751,7 +738,7 @@
<upload-file <upload-file
v-model="dataForm.purchaseRequirement" v-model="dataForm.purchaseRequirement"
:limit="1" :limit="1"
:file-type="['doc', 'docx', 'pdf']" :file-type="['doc', 'docx', 'pdf', 'jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp']"
:data="{ fileType: FILE_TYPE_MAP.purchaseRequirement }" :data="{ fileType: FILE_TYPE_MAP.purchaseRequirement }"
upload-file-url="/purchase/purchasingfiles/upload" upload-file-url="/purchase/purchasingfiles/upload"
:disabled="flowFieldDisabled('purchaseRequirement')" :disabled="flowFieldDisabled('purchaseRequirement')"
@@ -773,7 +760,7 @@
<upload-file <upload-file
v-model="dataForm.governmentPurchaseIntent" v-model="dataForm.governmentPurchaseIntent"
:limit="1" :limit="1"
:file-type="['doc', 'docx', 'pdf']" :file-type="['doc', 'docx', 'pdf', 'jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp']"
:data="{ fileType: FILE_TYPE_MAP.governmentPurchaseIntent }" :data="{ fileType: FILE_TYPE_MAP.governmentPurchaseIntent }"
upload-file-url="/purchase/purchasingfiles/upload" upload-file-url="/purchase/purchasingfiles/upload"
:disabled="flowFieldDisabled('governmentPurchaseIntent')" :disabled="flowFieldDisabled('governmentPurchaseIntent')"
@@ -1285,9 +1272,9 @@ const schoolUnifiedPurchaseFormDefault = computed(() => {
// 申请阶段:始终可选(根据默认值自动选中后,允许用户自行修改) // 申请阶段:始终可选(根据默认值自动选中后,允许用户自行修改)
// 流程嵌入:采购中心节点可编辑,其他节点只读 // 流程嵌入:采购中心节点可编辑,其他节点只读
const schoolUnifiedPurchaseFormDisabled = computed(() => { const schoolUnifiedPurchaseFormDisabled = computed(() => {
if (!isFlowEmbed.value) { // if (!isFlowEmbed.value) {
return false; // return false;
} // }
// 流程嵌入且为采购中心:放开编辑 // 流程嵌入且为采购中心:放开编辑
if (isPurchaseCenter.value) { if (isPurchaseCenter.value) {
return false; return false;
@@ -1610,6 +1597,7 @@ const dataRules = reactive({
], ],
purchaseChannel: [ purchaseChannel: [
{ {
required: true,
validator: (_rule: any, value: string, callback: (e?: Error) => void) => { validator: (_rule: any, value: string, callback: (e?: Error) => void) => {
if (!isDeptPurchase.value) { if (!isDeptPurchase.value) {
callback(); callback();