修复文件问题 修改bug

This commit is contained in:
2026-02-06 00:04:44 +08:00
37 changed files with 3732 additions and 327 deletions

BIN
public/excel/dictlist.xlsx Normal file

Binary file not shown.

View File

@@ -0,0 +1,30 @@
/*
* Copyright (c) 2018-2025, cyweb All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* Neither the name of the pig4cloud.com developer nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
*/
import request from '/@/utils/request';
/**
* 获取列表
* @param query
*/
export const fetchList = (query?: any) => {
return request({
url: '/basic/basicAsyncTask/page',
method: 'get',
params: query,
});
};

View File

@@ -1,9 +1,9 @@
import request from '/@/utils/request';
export const exportTeacherInfoBySelf = (data?: any) => {
export const makeExportTeacherInfoBySelfTask = (data?: any) => {
return request({
url: '/professional/file/exportTeacherInfoBySelf',
url: '/professional/file/makeExportTeacherInfoBySelfTask',
method: 'post',
data: data,
});

View File

@@ -0,0 +1,106 @@
import request from "/@/utils/request"
// ========== 基础CRUD接口 ==========
/**
* 分页查询列表数据
* @param query - 查询参数对象
* @returns Promise<分页数据>
*/
export function fetchList(query?: Object) {
return request({
url: '/purchase/acceptanceItemConfig/page',
method: 'get',
params: query
})
}
/**
* 新增数据
* @param obj - 要新增的数据对象
* @returns Promise<boolean> - 操作结果
*/
export function addObj(obj?: Object) {
return request({
url: '/purchase/acceptanceItemConfig',
method: 'post',
data: obj
})
}
/**
* 获取详情数据
* @param obj - 查询参数对象包含ID等
* @returns Promise<数据详情>
*/
export function getObj(obj?: Object) {
return request({
url: '/purchase/acceptanceItemConfig/details',
method: 'get',
params: obj
})
}
/**
* 批量删除数据
* @param ids - 要删除的ID数组
* @returns Promise<操作结果>
*/
export function delObjs(ids?: Object) {
return request({
url: '/purchase/acceptanceItemConfig',
method: 'delete',
data: ids
})
}
/**
* 更新数据
* @param obj - 要更新的数据对象
* @returns Promise<操作结果>
*/
export function putObj(obj?: Object) {
return request({
url: '/purchase/acceptanceItemConfig',
method: 'put',
data: obj
})
}
// ========== 工具函数 ==========
/**
* 验证字段值唯一性
* @param rule - 验证规则对象
* @param value - 要验证的值
* @param callback - 验证回调函数
* @param isEdit - 是否为编辑模式
*
* @example
* // 在表单验证规则中使用
* fieldName: [
* {
* validator: (rule, value, callback) => {
* validateExist(rule, value, callback, form.id !== '');
* },
* trigger: 'blur',
* },
* ]
*/
export function validateExist(rule: any, value: any, callback: any, isEdit: boolean) {
// 编辑模式下跳过验证
if (isEdit) {
return callback();
}
// 查询是否存在相同值
getObj({ [rule.field]: value }).then((response) => {
const result = response.data;
if (result !== null && result.length > 0) {
callback(new Error('数据已经存在'));
} else {
callback();
}
});
}

View File

@@ -0,0 +1,106 @@
import request from "/@/utils/request"
// ========== 基础CRUD接口 ==========
/**
* 分页查询列表数据
* @param query - 查询参数对象
* @returns Promise<分页数据>
*/
export function fetchList(query?: Object) {
return request({
url: '/purchase/puchasingAcceptContent/page',
method: 'get',
params: query
})
}
/**
* 新增数据
* @param obj - 要新增的数据对象
* @returns Promise<boolean> - 操作结果
*/
export function addObj(obj?: Object) {
return request({
url: '/purchase/puchasingAcceptContent',
method: 'post',
data: obj
})
}
/**
* 获取详情数据
* @param obj - 查询参数对象包含ID等
* @returns Promise<数据详情>
*/
export function getObj(obj?: Object) {
return request({
url: '/purchase/puchasingAcceptContent/details',
method: 'get',
params: obj
})
}
/**
* 批量删除数据
* @param ids - 要删除的ID数组
* @returns Promise<操作结果>
*/
export function delObjs(ids?: Object) {
return request({
url: '/purchase/puchasingAcceptContent',
method: 'delete',
data: ids
})
}
/**
* 更新数据
* @param obj - 要更新的数据对象
* @returns Promise<操作结果>
*/
export function putObj(obj?: Object) {
return request({
url: '/purchase/puchasingAcceptContent',
method: 'put',
data: obj
})
}
// ========== 工具函数 ==========
/**
* 验证字段值唯一性
* @param rule - 验证规则对象
* @param value - 要验证的值
* @param callback - 验证回调函数
* @param isEdit - 是否为编辑模式
*
* @example
* // 在表单验证规则中使用
* fieldName: [
* {
* validator: (rule, value, callback) => {
* validateExist(rule, value, callback, form.id !== '');
* },
* trigger: 'blur',
* },
* ]
*/
export function validateExist(rule: any, value: any, callback: any, isEdit: boolean) {
// 编辑模式下跳过验证
if (isEdit) {
return callback();
}
// 查询是否存在相同值
getObj({ [rule.field]: value }).then((response) => {
const result = response.data;
if (result !== null && result.length > 0) {
callback(new Error('数据已经存在'));
} else {
callback();
}
});
}

View File

@@ -0,0 +1,106 @@
import request from "/@/utils/request"
// ========== 基础CRUD接口 ==========
/**
* 分页查询列表数据
* @param query - 查询参数对象
* @returns Promise<分页数据>
*/
export function fetchList(query?: Object) {
return request({
url: '/purchase/puchasingAcceptTeam/page',
method: 'get',
params: query
})
}
/**
* 新增数据
* @param obj - 要新增的数据对象
* @returns Promise<boolean> - 操作结果
*/
export function addObj(obj?: Object) {
return request({
url: '/purchase/puchasingAcceptTeam',
method: 'post',
data: obj
})
}
/**
* 获取详情数据
* @param obj - 查询参数对象包含ID等
* @returns Promise<数据详情>
*/
export function getObj(obj?: Object) {
return request({
url: '/purchase/puchasingAcceptTeam/details',
method: 'get',
params: obj
})
}
/**
* 批量删除数据
* @param ids - 要删除的ID数组
* @returns Promise<操作结果>
*/
export function delObjs(ids?: Object) {
return request({
url: '/purchase/puchasingAcceptTeam',
method: 'delete',
data: ids
})
}
/**
* 更新数据
* @param obj - 要更新的数据对象
* @returns Promise<操作结果>
*/
export function putObj(obj?: Object) {
return request({
url: '/purchase/puchasingAcceptTeam',
method: 'put',
data: obj
})
}
// ========== 工具函数 ==========
/**
* 验证字段值唯一性
* @param rule - 验证规则对象
* @param value - 要验证的值
* @param callback - 验证回调函数
* @param isEdit - 是否为编辑模式
*
* @example
* // 在表单验证规则中使用
* fieldName: [
* {
* validator: (rule, value, callback) => {
* validateExist(rule, value, callback, form.id !== '');
* },
* trigger: 'blur',
* },
* ]
*/
export function validateExist(rule: any, value: any, callback: any, isEdit: boolean) {
// 编辑模式下跳过验证
if (isEdit) {
return callback();
}
// 查询是否存在相同值
getObj({ [rule.field]: value }).then((response) => {
const result = response.data;
if (result !== null && result.length > 0) {
callback(new Error('数据已经存在'));
} else {
callback();
}
});
}

View File

@@ -0,0 +1,173 @@
import request from "/@/utils/request"
// ========== 基础CRUD接口 ==========
/**
* 分页查询列表数据
* @param query - 查询参数对象
* @returns Promise<分页数据>
*/
export function fetchList(query?: Object) {
return request({
url: '/purchase/purchasingAccept/page',
method: 'get',
params: query
})
}
/**
* 新增数据
* @param obj - 要新增的数据对象
* @returns Promise<boolean> - 操作结果
*/
export function addObj(obj?: Object) {
return request({
url: '/purchase/purchasingAccept',
method: 'post',
data: obj
})
}
/**
* 获取详情数据
* @param obj - 查询参数对象包含ID等
* @returns Promise<数据详情>
*/
export function getObj(obj?: Object) {
return request({
url: '/purchase/purchasingAccept/details',
method: 'get',
params: obj
})
}
/**
* 批量删除数据
* @param ids - 要删除的ID数组
* @returns Promise<操作结果>
*/
export function delObjs(ids?: Object) {
return request({
url: '/purchase/purchasingAccept',
method: 'delete',
data: ids
})
}
/**
* 更新数据
* @param obj - 要更新的数据对象
* @returns Promise<操作结果>
*/
export function putObj(obj?: Object) {
return request({
url: '/purchase/purchasingAccept',
method: 'put',
data: obj
})
}
// ========== 履约验收流程接口 ==========
/**
* 第一步:保存履约验收公共配置,按分期次数自动生成批次
*/
export function saveCommonConfig(data: any) {
return request({
url: '/purchase/purchasingAccept/saveCommonConfig',
method: 'post',
data
})
}
/**
* 获取履约验收公共配置及批次列表
*/
export function getCommonConfigWithBatches(purchaseId: string) {
return request({
url: '/purchase/purchasingAccept/commonConfigWithBatches',
method: 'get',
params: { purchaseId }
})
}
/**
* 第二步:更新单个批次
*/
export function updateBatch(data: any) {
return request({
url: '/purchase/purchasingAccept/updateBatch',
method: 'put',
data
})
}
/**
* 获取验收详情(含验收内容、验收小组)
*/
export function getDetail(purchaseId: string, batch?: number) {
return request({
url: '/purchase/purchasingAccept/detail',
method: 'get',
params: { purchaseId, batch }
})
}
/**
* 是否允许填报方式(金额<30万
*/
export function canFillForm(purchaseId: string) {
return request({
url: '/purchase/purchasingAccept/canFillForm',
method: 'get',
params: { purchaseId }
})
}
/**
* 根据品目类型获取验收项配置
*/
export function getAcceptanceItems(acceptanceType: string) {
return request({
url: `/purchase/acceptanceItemConfig/listByType/${acceptanceType}`,
method: 'get'
})
}
// ========== 工具函数 ==========
/**
* 验证字段值唯一性
* @param rule - 验证规则对象
* @param value - 要验证的值
* @param callback - 验证回调函数
* @param isEdit - 是否为编辑模式
*
* @example
* // 在表单验证规则中使用
* fieldName: [
* {
* validator: (rule, value, callback) => {
* validateExist(rule, value, callback, form.id !== '');
* },
* trigger: 'blur',
* },
* ]
*/
export function validateExist(rule: any, value: any, callback: any, isEdit: boolean) {
// 编辑模式下跳过验证
if (isEdit) {
return callback();
}
// 查询是否存在相同值
getObj({ [rule.field]: value }).then((response) => {
const result = response.data;
if (result !== null && result.length > 0) {
callback(new Error('数据已经存在'));
} else {
callback();
}
});
}

View File

@@ -0,0 +1,188 @@
/**
* 列表页卡片布局公共样式(筛选卡片 + 内容卡片 + 表头)
* 各业务模块通用,不限于专业模块
*
* 使用:@import '/@/assets/styles/page-cards.scss';
* 模板类名:.page-cards > .page-wrapper > .search-card / .content-card
*/
// 页面整体
.page-cards {
padding: 12px;
min-height: 100%;
background: var(--el-bg-color-page, #f5f6f8);
}
// 页面包装器
.page-wrapper {
display: flex;
flex-direction: column;
gap: 12px;
}
// 筛选卡片
.search-card {
border-radius: 8px;
border: 1px solid var(--el-border-color-lighter);
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
background: var(--el-bg-color);
:deep(.el-card__body) {
padding: 18px 20px 5px 20px;
}
}
// 列表内容卡片
.content-card {
border-radius: 8px;
border: 1px solid var(--el-border-color-lighter);
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
background: var(--el-bg-color);
:deep(.el-card__header) {
padding: 20px 20px 15px;
border-bottom: 1px solid var(--el-border-color-lighter);
}
:deep(.el-card__body) {
padding: 15px 20px 20px;
}
}
.card-header {
display: flex;
align-items: center;
justify-content: space-between;
flex-wrap: wrap;
}
.card-title {
display: inline-flex;
align-items: center;
gap: 8px;
font-size: 15px;
font-weight: 500;
color: var(--el-text-color-primary);
.title-icon {
font-size: 16px;
color: var(--el-color-primary);
}
}
.header-actions {
display: flex;
align-items: center;
flex-wrap: wrap;
}
.action-group {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 10px;
}
.header-right {
display: flex;
align-items: center;
padding-left: 10px;
}
// 小屏幕适配≤768px
@media screen and (max-width: 768px) {
.page-cards {
padding: 8px;
}
.page-wrapper {
gap: 8px;
}
.search-card :deep(.el-card__body) {
padding: 12px 14px 4px 14px;
}
.content-card :deep(.el-card__header) {
padding: 14px 14px 12px;
}
.content-card :deep(.el-card__body) {
padding: 12px 14px 14px;
}
.card-header {
gap: 8px;
}
.card-title {
font-size: 14px;
.title-icon {
font-size: 15px;
}
}
.header-actions {
gap: 8px;
}
.action-group {
gap: 8px;
// 按钮间距
.el-button {
margin-left: 0;
}
}
.header-right {
padding-left: 8px;
}
}
// 超小屏幕适配≤576px
@media screen and (max-width: 576px) {
.page-cards {
padding: 6px;
}
.page-wrapper {
gap: 6px;
}
.search-card :deep(.el-card__body) {
padding: 10px 12px 14px 12px;
}
.content-card :deep(.el-card__header) {
padding: 12px 12px 10px;
}
.content-card :deep(.el-card__body) {
padding: 10px 12px 12px;
}
.card-header {
flex-direction: column;
align-items: flex-start;
gap: 10px;
}
.header-actions {
width: 100%;
}
.action-group {
gap: 6px;
// 按钮间距
.el-button {
margin-left: 0;
}
}
.header-right {
width: 100%;
padding-left: 0;
padding-top: 8px;
border-left: none;
border-top: 1px solid var(--el-border-color-lighter);
}
}

View File

@@ -2,25 +2,21 @@
<el-tag
:type="type"
:size="size"
:effect="effect"
:class="['clickable-tag', { 'has-action': actualRightIcon !== null}]"
:style="{ width: width ? `${width}px` : 'auto' }"
@click="handleClick">
<!-- 左侧图标 -->
<el-icon
v-if="leftIcon"
:size="size"
class="left-icon">
<!-- 左侧图标支持 Vue 组件Element 图标线条或字符串 FontAwesome class可实心 -->
<i v-if="leftIcon && isLeftIconString" :class="leftIcon" class="left-icon left-icon--fa"></i>
<el-icon v-else-if="leftIcon" :size="size" class="left-icon">
<component :is="leftIcon" />
</el-icon>
<!-- 主要内容 -->
<slot></slot>
<!-- 中间图标如警告图标 -->
<el-icon
v-if="middleIcon"
:size="size"
class="middle-icon">
<!-- 中间图标支持 Vue 组件或字符串 FontAwesome class -->
<i v-if="middleIcon && isMiddleIconString" :class="middleIcon" class="middle-icon middle-icon--fa"></i>
<el-icon v-else-if="middleIcon" :size="size" class="middle-icon">
<component :is="middleIcon" />
</el-icon>
@@ -36,26 +32,32 @@
<script setup lang="ts">
import { computed } from 'vue'
import { Right } from '@element-plus/icons-vue'
import { InfoFilled, Right } from '@element-plus/icons-vue'
interface Props {
type?: 'success' | 'info' | 'warning' | 'danger' | 'primary'
size?: 'large' | 'default' | 'small'
leftIcon?: any // 左侧图标组件
middleIcon?: any // 中间图标组件(如警告图标
rightIcon?: any // 右侧图标组件(默认为 Right null 则不显示
effect?: 'dark' | 'light' | 'plain' // 主题,与 el-tag 一致
leftIcon?: any // 左侧图标Vue 组件Element 图标)或字符串(如 FontAwesome class 'fa-solid fa-circle-xmark'
middleIcon?: any // 中间图标Vue 组件或字符串(如 FontAwesome class
rightIcon?: any // 右侧图标:默认 InfoFilled表示「可查看详情」传 null 不显示;也可用 Right跳转、View查看
width?: string | number // 自定义宽度
}
const props = withDefaults(defineProps<Props>(), {
type: 'primary',
size: 'default',
effect: 'light',
leftIcon: undefined,
middleIcon: undefined,
rightIcon: undefined,
width: undefined
})
// 左侧/中间图标为字符串时(如 FontAwesome class用 <i> 渲染,与 AuditState 实心一致
const isLeftIconString = computed(() => typeof props.leftIcon === 'string')
const isMiddleIconString = computed(() => typeof props.middleIcon === 'string')
// 获取实际的右侧图标:未传值时使用默认图标,传 null 则不显示
const actualRightIcon = computed(() => {
if (props.rightIcon === null) return null
@@ -80,6 +82,11 @@ export default {
<style scoped lang="scss">
.clickable-tag {
transition: all 0.2s;
display: inline-flex;
align-items: center;
.left-icon {
margin-right: 4px;
}
// 覆盖 el-tag 的内部结构
:deep(.el-tag__content) {
@@ -91,10 +98,8 @@ export default {
// 有交互功能时才显示手型光标和悬停效果
&.has-action {
cursor: pointer;
&:hover {
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
.right-icon {
transform: translateX(2px);
}
@@ -103,7 +108,6 @@ export default {
.middle-icon {
animation: pulse 1.5s ease-in-out infinite;
}
.right-icon {
opacity: 0.7;
transition: all 0.2s;

View File

@@ -1,27 +1,31 @@
<template>
<div class="search-form__wrap" :class="{ 'search-form__wrap--with-title': filterTitle }">
<div class="search-form__wrap" :class="{ 'search-form__wrap--with-title': filterTitle && props.showFilterTitle }">
<div class="search-form__bar">
<div class="search-form-container">
<el-form :model="formModel" ref="formRef" :inline="true" @keyup.enter="handleKeyupEnter" :label-width="labelWidth">
<!-- 筛选 + 展开更多 放在同一表单项内保证垂直对齐 -->
<el-form-item v-if="filterTitle || hasCollapsibleItems" class="search-form__left-group">
<el-form-item v-if="(filterTitle && props.showFilterTitle) || hasCollapsibleItems" class="search-form__left-group">
<div class="search-form__left-inner">
<span v-if="filterTitle" class="search-form__title">
<span v-if="filterTitle && props.showFilterTitle" class="search-form__title" :style="{ marginRight: hasCollapsibleItems ? '12px' : '0' }">
<el-icon class="search-form__title-icon"><Search /></el-icon>
{{ filterTitle }}
</span>
<el-button
v-if="hasCollapsibleItems"
link
type="primary"
class="toggle-btn"
@click="toggleExpand"
round
>
<el-icon style="margin-right: 4px">
<ArrowUp v-if="isExpanded" />
<ArrowDown v-else />
<el-icon
class="toggle-btn__icon"
:class="{ 'toggle-btn__icon--expanded': isExpanded }"
>
<CaretBottom />
</el-icon>
{{ isExpanded ? '收起' : '展开更多' }}
<span class="toggle-btn__text">
{{ isExpanded ? '收起筛选' : '更多筛选' }}
</span>
</el-button>
</div>
</el-form-item>
@@ -44,7 +48,7 @@
<script setup lang="ts" name="search-form">
import { ref, watch, computed, onMounted, nextTick } from 'vue';
import { ArrowUp, ArrowDown, Search } from '@element-plus/icons-vue';
import { CaretBottom, Search } from '@element-plus/icons-vue';
const props = defineProps({
/**
@@ -52,7 +56,14 @@ const props = defineProps({
*/
filterTitle: {
type: String,
default: '',
default: '筛选',
},
/**
* 是否显示筛选标题文案(仅控制左侧「筛选」文字和图标)
*/
showFilterTitle: {
type: Boolean,
default: false,
},
/**
* 表单数据模型
@@ -227,7 +238,6 @@ defineExpose({
gap: 6px;
flex-shrink: 0;
padding-right: 12px;
margin-right: 12px;
height: 32px;
line-height: 32px;
font-size: 14px;
@@ -261,13 +271,29 @@ defineExpose({
align-items: center;
}
.search-form__left-inner :deep(.toggle-btn__icon) {
transition: transform 0.25s ease;
}
.search-form__left-inner :deep(.toggle-btn__icon--expanded) {
transform: rotate(180deg);
}
.search-form__left-inner :deep(.toggle-btn) {
font-size: 14px;
font-weight: 500;
height: 32px;
padding: 0 12px;
display: inline-flex;
align-items: center;
border-radius: 6px;
font-weight: 500;
// 默认态直接使用 hover 的视觉样式
color: var(--el-color-primary);
background-color: var(--el-fill-color-lighter);
border: 1px solid var(--el-color-primary-light-7);
transition: color 0.2s ease, background-color 0.2s ease, border-color 0.2s ease;
&:hover {
background-color: var(--el-fill-color-light);
border-color: var(--el-color-primary);
color: var(--el-color-primary);
}
}
:deep(.el-form-item:not(:has(.el-button))) {

View File

@@ -0,0 +1,150 @@
<template>
<el-drawer
v-model="visible"
title="上传和下载任务"
direction="rtl"
size="50%"
destroy-on-close
@open="onOpen"
>
<el-tabs v-model="activeTab">
<el-tab-pane label="上传" name="upload" />
<el-tab-pane label="下载" name="download" />
<el-tab-pane label="其他" name="other" />
</el-tabs>
<div class="task-table-wrap">
<el-table
v-loading="loading[activeTab]"
:data="list[activeTab]"
height="calc(100vh - 240px)"
row-key="id"
:empty-text="emptyText"
size="small"
stripe
border
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle"
>
<el-table-column label="所属模块" prop="moduleName" width="120" show-overflow-tooltip />
<el-table-column label="任务类型" prop="typeLabel" width="100" show-overflow-tooltip />
<el-table-column label="任务名称" prop="detailType" min-width="150" show-overflow-tooltip />
<el-table-column label="任务状态" align="center" show-overflow-tooltip>
<template #default="{ row }">
<el-tag>{{ row.status }}</el-tag>
</template>
</el-table-column>
<el-table-column label="时间" width="150" show-overflow-tooltip>
<template #default="{ row }">
{{ formatTime(row) }}
</template>
</el-table-column>
</el-table>
<div class="task-pagination">
<el-pagination
v-model:current-page="pagination[activeTab].current"
v-model:page-size="pagination[activeTab].size"
:page-sizes="[10, 20, 50]"
:total="pagination[activeTab].total"
layout="prev, pager, next, sizes"
small
@current-change="onPageChange"
@size-change="onSizeChange"
/>
</div>
</div>
</el-drawer>
</template>
<script setup lang="ts">
import { ref, reactive, watch, computed } from 'vue'
import { fetchList } from '/@/api/basic/basicasynctask'
type TaskTab = 'upload' | 'download' | 'other'
const visible = ref(false)
const activeTab = ref<TaskTab>('upload')
const loading = reactive<Record<TaskTab, boolean>>({ upload: false, download: false, other: false })
const list = reactive<Record<TaskTab, any[]>>({ upload: [], download: [], other: [] })
const pagination = reactive<Record<TaskTab, { current: number; size: number; total: number }>>({
upload: { current: 1, size: 10, total: 0 },
download: { current: 1, size: 10, total: 0 },
other: { current: 1, size: 10, total: 0 },
})
/** Tab 对应接口 type1 上传 2 下载 3 其他 */
const TAB_TYPE_MAP: Record<TaskTab, number> = { upload: 1, download: 2, other: 3 }
const EMPTY_TEXT_MAP: Record<TaskTab, string> = { upload: '暂无上传任务', download: '暂无下载任务', other: '暂无其他任务' }
// 表格样式,参考主列表页通用样式
const tableStyle = {
cellStyle: { textAlign: 'center' },
headerCellStyle: {
textAlign: 'center',
background: 'var(--el-table-row-hover-bg-color)',
color: 'var(--el-text-color-primary)',
},
}
const emptyText = computed(() => EMPTY_TEXT_MAP[activeTab.value])
const loadList = async () => {
const type = activeTab.value
const p = pagination[type]
loading[type] = true
try {
const res = await fetchList({ type: TAB_TYPE_MAP[type], current: p.current, size: p.size })
const data = res?.data ?? res
const records = data?.records ?? []
const total = data?.total ?? 0
list[type] = Array.isArray(records) ? records : []
p.total = Number(total) || 0
} catch {
list[type] = []
} finally {
loading[type] = false
}
}
const onPageChange = (page: number) => {
pagination[activeTab.value].current = page
loadList()
}
const onSizeChange = (size: number) => {
pagination[activeTab.value].size = size
pagination[activeTab.value].current = 1
loadList()
}
const onOpen = () => {
loadList()
}
// 切换 Tab 时加载对应列表
watch(activeTab, () => {
if (visible.value) loadList()
})
const formatTime = (item: any) => {
const t = item?.createTime ?? item?.create_time ?? item?.updateTime ?? item?.update_time ?? ''
return t ? (t.slice(0, 16).replace('T', ' ')) : '-'
}
const open = () => {
visible.value = true
}
defineExpose({ open })
</script>
<style scoped lang="scss">
.task-table-wrap {
padding: 0 4px;
}
.task-pagination {
padding: 12px 0;
display: flex;
justify-content: flex-end;
}
</style>

View File

@@ -1,6 +1,6 @@
<template>
<div class="layout-navbars-breadcrumb-user pr15" :style="{ flex: layoutUserFlexNum }">
<el-dropdown :show-timeout="70" :hide-timeout="50" trigger="click" @command="onLanguageChange">
<!-- <el-dropdown :show-timeout="70" :hide-timeout="50" trigger="click" @command="onLanguageChange">
<div class="layout-navbars-breadcrumb-user-icon">
<i class="iconfont" :class="state.disabledI18n === 'en' ? 'icon-fuhao-yingwen' : 'icon-fuhao-zhongwen'" :title="$t('user.title1')"></i>
</div>
@@ -10,7 +10,12 @@
<el-dropdown-item command="en" :disabled="state.disabledI18n === 'en'">English</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</el-dropdown> -->
<div class="layout-navbars-breadcrumb-user-icon" @click="onAsyncTaskClick">
<el-icon title="上传和下载任务">
<ele-FolderOpened />
</el-icon>
</div>
<div class="layout-navbars-breadcrumb-user-icon" @click="onLockClick">
<el-icon :title="$t('layout.threeLockScreenTime')">
<ele-Lock />
@@ -76,6 +81,7 @@
<personal-drawer ref="personalDrawerRef"></personal-drawer>
<change-role ref="ChangeRoleRef" />
<AsyncTaskDrawer ref="asyncTaskDrawerRef" />
</div>
</template>
@@ -95,8 +101,10 @@ import { fetchUserMessageList } from '/@/api/admin/message';
import {useFlowJob} from "/@/flow/stores/flowJob";
const ChangeRoleRef=ref()
const ChangeRoleRef = ref()
const ChangeRole = defineAsyncComponent(() => import('/@/views/admin/system/role/change-role.vue'))
const asyncTaskDrawerRef = ref()
const AsyncTaskDrawer = defineAsyncComponent(() => import('/@/layout/navBars/breadcrumb/asyncTaskDrawer.vue'))
// 引入组件
const GlobalWebsocket = defineAsyncComponent(() => import('/@/components/Websocket/index.vue'));
const UserNews = defineAsyncComponent(() => import('/@/layout/navBars/breadcrumb/userNews.vue'));
@@ -207,6 +215,10 @@ const onHandleCommandClick = (path: string) => {
const onSearchClick = () => {
searchRef.value.openSearch();
};
// 上传/下载任务点击
const onAsyncTaskClick = () => {
asyncTaskDrawerRef.value?.open();
};
// 语言切换
const onLanguageChange = (lang: string) => {
Local.remove('themeConfig');
@@ -241,6 +253,16 @@ const getIsDot = () => {
});
};
// 登录后若存储中无角色信息则弹出角色切换框
const openChangeRoleIfMissing = () => {
const hasRole = Local.get('roleCode') && Local.get('roleName') && Local.get('roleId')
if (!hasRole) {
nextTick(() => {
setTimeout(() => ChangeRoleRef.value?.open(), 100)
})
}
}
// 页面加载时
onMounted(() => {
if (Local.get('themeConfig')) {
@@ -248,8 +270,8 @@ onMounted(() => {
initI18nOrSize('globalI18n', 'disabledI18n');
}
useFlowJob().topJobList()
getIsDot();
getIsDot()
openChangeRoleIfMissing()
});
</script>

View File

@@ -50,7 +50,7 @@ body,
width: 100%;
height: 100%;
.layout-pd {
padding: 10px !important;
padding: 12px !important;
background: linear-gradient(135deg,#f5f7fa,#e9ecef);
}
.layout-flex {

View File

@@ -439,6 +439,12 @@
background-color: #f5f7ff;
}
/* 表格单元格内边距(全局)- 左右用默认,上下 9px */
.el-table .el-table__body td,
.el-table .el-table__header th {
padding: 9px 0;
}
/* scrollbar
------------------------------- */
.el-scrollbar__bar {

View File

@@ -1,60 +1,86 @@
<template>
<el-dialog v-model="visible" title="角色切换" width="50%">
<el-dialog
v-model="visible"
title="角色切换"
width="50%"
:show-close="false"
:close-on-click-modal="false"
:close-on-press-escape="false"
:before-close="handleBeforeClose"
>
<el-form>
<!-- <el-form-item label="学校">-->
<!-- <el-tag>{{schoolName}}</el-tag>-->
<!-- </el-form-item>-->
<el-form-item label="角色">
<el-radio-group v-model="radio">
<el-radio-button v-for="(item,index) in allRole" :key="index" :label="item.roleCode" @click.native="handleChangeRole(item.roleCode)">{{item.roleName}}</el-radio-button>
<el-form-item label="角色" class="role-form-item">
<el-radio-group v-model="radio" class="role-radio-group" @change="handleChangeRole">
<el-radio-button
v-for="item in allRole"
:key="item.roleCode"
:label="item.roleCode"
>
{{ item.roleName }}
</el-radio-button>
</el-radio-group>
</el-form-item>
</el-form>
<template #footer>
<!-- <el-button type="primary" @click="handleChangeRole">切换</el-button>-->
<el-button @click="visible=false"> </el-button>
<!-- <el-button type="primary" @click="handleChangeRole">切换</el-button>-->
<el-button @click="handleFooterClose"> </el-button>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import {listAllRole} from '/@/api/admin/role'
import {Local, Session} from '/@/utils/storage';
import {useMessage} from "/@/hooks/message";
// import {querySchoolName} from "/@/api/admin/tenant"
import { listAllRole } from '/@/api/admin/role'
import { Local } from '/@/utils/storage'
import { useMessage } from '/@/hooks/message'
const visible=ref(false)
const radio=ref('')
const allRole=reactive([])
// const schoolName=ref('')
const visible = ref(false)
const radio = ref('')
const allRole = reactive<any[]>([])
const open=()=>{
visible.value=true
// handleQuerySchoolName()
listAllRole().then(res=>{
Object.assign(allRole,res.data)
radio.value=Local.get("roleCode")
visible.value=true
const open = () => {
visible.value = true
listAllRole().then((res) => {
Object.assign(allRole, res.data)
radio.value = Local.get('roleCode')
visible.value = true
})
}
const handleChangeRole=(label:any)=>{
let obj:any=allRole.find((v:any) => v.roleCode == label)
Local.set("roleCode",obj.roleCode)
Local.set("roleName",obj.roleName)
Local.set("roleId",obj.roleId)
useMessage().success("操作成功")
setTimeout(()=>{
window.location.reload()
},500)
const canClose = () => {
if (!radio.value) {
useMessage().warning('请选择一个角色')
return false
}
return true
}
const handleQuerySchoolName=()=>{
// querySchoolName({id:Session.get("tenantId")}).then((res:any)=>{
// schoolName.value=res.data
// })
const handleBeforeClose = (done: () => void) => {
if (!canClose()) return
done()
}
const handleFooterClose = () => {
if (!canClose()) return
visible.value = false
}
const handleChangeRole = (label: string) => {
const obj = allRole.find((v: any) => v.roleCode === label)
if (!obj) return
Local.set('roleCode', obj.roleCode)
Local.set('roleName', obj.roleName)
Local.set('roleId', obj.roleId)
useMessage().success('操作成功')
setTimeout(() => {
// 切换角色后统一回到首页,避免停留在诸如 jsonflow/run-job/do-job 等内部路由
window.location.hash = '#/'
window.location.reload()
}, 500)
}
defineExpose({
@@ -62,6 +88,28 @@ defineExpose({
})
</script>
<style scoped>
<style scoped lang="scss">
.role-form-item {
:deep(.el-form-item__content) {
flex-wrap: wrap;
}
}
.role-radio-group {
display: flex;
flex-wrap: wrap;
gap: 8px;
/* 每个按钮独立边框,换行后左侧也有边线 */
:deep(.el-radio-button) {
margin: 0;
}
:deep(.el-radio-button__inner) {
border-radius: 6px !important;
border: 1px solid var(--el-border-color) !important;
margin-left: 0 !important;
}
:deep(.el-radio-button.is-active .el-radio-button__inner) {
border-color: var(--el-color-primary) !important;
}
}
</style>

View File

@@ -0,0 +1,199 @@
<template>
<el-form ref="formRef" :model="form" :rules="rules" label-width="160px" class="accept-batch-form">
<el-row :gutter="24">
<el-col :span="12">
<el-form-item label="验收方式" prop="acceptType">
<el-radio-group v-model="form.acceptType" :disabled="readonly">
<el-radio label="1">填写履约验收评价表</el-radio>
<el-radio label="2" :disabled="!canFill">上传履约验收评价表</el-radio>
</el-radio-group>
<div v-if="!canFill" class="el-form-item__tip">金额30万仅支持上传模版</div>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="验收日期" prop="acceptDate">
<el-date-picker
v-model="form.acceptDate"
type="date"
placeholder="请选择"
format="YYYY-MM-DD"
value-format="YYYY-MM-DD"
style="width: 100%"
:disabled="readonly"
/>
</el-form-item>
</el-col>
<!-- 填报方式验收内容表格 -->
<template v-if="form.acceptType === '1' && canFill">
<el-col :span="24">
<el-form-item label="验收内容" prop="acceptContents">
<el-table :data="form.acceptContents" border size="small" max-height="260" class="accept-content-table">
<el-table-column prop="itemName" label="验收项" >
<template #default="{ row }">
{{row.itemName}}
<el-input
v-if="row.type === 'input'"
v-model="row.remark"
placeholder="请输入"
size="small"
:disabled="readonly"
/>
</template>
</el-table-column>
<el-table-column prop="isQualified" label="合格/不合格" width="140" align="center">
<template #default="{ row }">
<el-radio-group v-model="row.isQualified" size="small" :disabled="readonly">
<el-radio label="1">合格</el-radio>
<el-radio label="0">不合格</el-radio>
</el-radio-group>
</template>
</el-table-column>
</el-table>
</el-form-item>
</el-col>
</template>
<!-- 上传方式 -->
<template v-if="form.acceptType === '2'">
<el-col :span="24">
<el-form-item label="履约验收模版" prop="templateFileIds">
<UploadFile
v-model="templateFileIdsStr"
:limit="1"
:data="{ purchaseId: purchaseId || '', fileType: '110' }"
upload-file-url="/purchase/purchasingfiles/upload"
:disabled="readonly"
/>
</el-form-item>
</el-col>
</template>
<!-- 验收小组 -->
<el-col :span="24">
<el-form-item label="验收小组" prop="acceptTeam">
<div class="team-list">
<div v-for="(m, idx) in form.acceptTeam" :key="idx" class="team-row">
<el-input v-model="m.name" placeholder="姓名" size="small" style="width:120px" :disabled="readonly" />
<el-input v-model="m.deptName" placeholder="部门" size="small" style="width:160px" :disabled="readonly" />
<el-button v-if="!readonly && form.acceptTeam.length > 3" type="danger" link size="small" @click="removeTeam(idx)">删除</el-button>
</div>
<el-button v-if="!readonly" type="primary" link size="small" @click="addTeam">+ 增加成员</el-button>
</div>
<div class="el-form-item__tip">至少3人且为单数</div>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" type="textarea" :rows="2" placeholder="请输入" :disabled="readonly" />
</el-form-item>
</el-col>
</el-row>
</el-form>
</template>
<script setup lang="ts">
import { ref, reactive, computed, watch } from 'vue'
import type { FormInstance, FormRules } from 'element-plus'
const props = withDefaults(
defineProps<{
modelValue: Record<string, any>
canFill: boolean
readonly?: boolean
purchaseId?: string
acceptanceItems?: any[]
}>(),
{ readonly: false, canFill: true, purchaseId: '' }
)
const emit = defineEmits(['update:modelValue'])
const formRef = ref<FormInstance>()
const templateFileIdsStr = ref('')
const form = reactive({
acceptType: '1',
acceptDate: '',
acceptContents: [] as any[],
acceptTeam: [
{ name: '', deptCode: '', deptName: '' },
{ name: '', deptCode: '', deptName: '' },
{ name: '', deptCode: '', deptName: '' },
] as any[],
templateFileIds: [] as string[],
remark: '',
...props.modelValue,
})
watch(() => props.modelValue, (val) => Object.assign(form, val || {}), { deep: true })
watch(form, () => emit('update:modelValue', { ...form }), { deep: true })
watch(() => props.acceptanceItems, (items) => {
if (items?.length && form.acceptContents.length === 0) {
form.acceptContents = items.map((it: any) => ({
configId: it.id,
itemName: it.itemName,
type: it.type,
isQualified: '1',
remark: '',
}))
}
}, { immediate: true })
watch(templateFileIdsStr, (s) => {
const arr = s ? s.split(',').map((x: string) => x.trim()).filter(Boolean) : []
if (JSON.stringify(form.templateFileIds) !== JSON.stringify(arr)) {
form.templateFileIds = arr
}
})
watch(() => form.templateFileIds, (arr) => {
if (Array.isArray(arr) && arr.length) templateFileIdsStr.value = arr.join(',')
}, { immediate: true, deep: true })
const addTeam = () => {
form.acceptTeam.push({ name: '', deptCode: '', deptName: '' })
}
const removeTeam = (idx: number) => {
form.acceptTeam.splice(idx, 1)
}
const rules: FormRules = {
acceptType: [{ required: true, message: '请选择验收方式', trigger: 'change' }],
acceptDate: [{ required: true, message: '请选择验收日期', trigger: 'change' }],
}
const validate = () => formRef.value?.validate()
defineExpose({ validate, form })
</script>
<style scoped>
.accept-batch-form :deep(.el-form-item) {
margin-bottom: 24px;
}
.accept-content-table :deep(.el-table__body td) {
padding: 10px 0;
width: 200px;
}
.team-list {
display: flex;
flex-direction: column;
gap: 12px;
}
.team-row {
display: flex;
align-items: center;
gap: 12px;
}
.el-form-item__tip {
font-size: 12px;
color: #909399;
margin-top: 6px;
}
</style>

View File

@@ -0,0 +1,165 @@
<template>
<el-form ref="formRef" :model="form" :rules="rules" label-width="140px" class="accept-common-form compact-form">
<el-row :gutter="24">
<el-col :span="8">
<el-form-item label="项目名称">
<el-input :model-value="projectName || form.projectName" readonly placeholder="-" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="需求部门">
<el-input :model-value="deptName || form.deptName" readonly placeholder="-" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="是否签订合同" prop="hasContract">
<el-radio-group v-model="form.hasContract">
<el-radio label="0"></el-radio>
<el-radio label="1"></el-radio>
</el-radio-group>
</el-form-item>
</el-col>
<el-col :span="12" v-if="form.hasContract === '1'">
<el-form-item label="合同" prop="contractId">
<el-input v-model="form.contractId" placeholder="请选择合同(待对接合同接口)" clearable />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="是否分期验收" prop="isInstallment">
<el-radio-group v-model="form.isInstallment">
<el-radio label="0"></el-radio>
<el-radio label="1"></el-radio>
</el-radio-group>
</el-form-item>
</el-col>
<el-col :span="12" v-if="form.isInstallment === '1'">
<el-form-item label="分期次数" prop="totalPhases">
<el-input-number v-model="form.totalPhases" :min="1" :max="99" placeholder="请输入" style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="供应商名称" prop="supplierName">
<el-input v-model="form.supplierName" placeholder="选择合同后自动带出" clearable />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="供应商联系人及电话" prop="supplierContact">
<el-input v-model="form.supplierContact" placeholder="请输入" clearable />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="采购人员" prop="purchaserId">
<org-selector v-model:orgList="purchaserList" type="user" :multiple="false" @update:orgList="onPurchaserChange" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="资产管理员" prop="assetAdminId">
<org-selector v-model:orgList="assetAdminList" type="user" :multiple="false" @update:orgList="onAssetAdminChange" />
</el-form-item>
</el-col>
</el-row>
</el-form>
</template>
<script setup lang="ts">
import { ref, reactive, watch } from 'vue'
import type { FormInstance, FormRules } from 'element-plus'
const props = defineProps<{
modelValue: Record<string, any>
projectName?: string
deptName?: string
}>()
const emit = defineEmits(['update:modelValue'])
const formRef = ref<FormInstance>()
const purchaserList = ref<any[]>([])
const assetAdminList = ref<any[]>([])
const form = reactive({
hasContract: '0',
contractId: '',
isInstallment: '0',
totalPhases: 1,
supplierName: '',
supplierContact: '',
purchaserId: '',
purchaserName: '',
assetAdminId: '',
assetAdminName: '',
...props.modelValue,
})
watch(() => props.modelValue, (val) => {
Object.assign(form, val || {})
// 人员选择回显
if (form.purchaserId && form.purchaserName) {
purchaserList.value = [{ id: form.purchaserId, name: form.purchaserName, type: 'user' }]
} else {
purchaserList.value = []
}
if (form.assetAdminId && form.assetAdminName) {
assetAdminList.value = [{ id: form.assetAdminId, name: form.assetAdminName, type: 'user' }]
} else {
assetAdminList.value = []
}
}, { deep: true, immediate: true })
watch(form, () => emit('update:modelValue', { ...form }), { deep: true })
const onPurchaserChange = (list: any[]) => {
if (list?.length) {
const u = list[0]
form.purchaserId = u.userId || u.id || ''
form.purchaserName = u.name || u.realName || ''
} else {
form.purchaserId = ''
form.purchaserName = ''
}
}
const onAssetAdminChange = (list: any[]) => {
if (list?.length) {
const u = list[0]
form.assetAdminId = u.userId || u.id || ''
form.assetAdminName = u.name || u.realName || ''
} else {
form.assetAdminId = ''
form.assetAdminName = ''
}
}
const rules: FormRules = {
isInstallment: [{ required: true, message: '请选择是否分期验收', trigger: 'change' }],
totalPhases: [{ required: true, message: '请输入分期次数', trigger: 'blur' }],
}
const validate = () => formRef.value?.validate()
defineExpose({ validate, form })
</script>
<style scoped>
.accept-common-form {
padding: 0 4px;
}
.accept-common-form :deep(.el-form-item) {
margin-bottom: 24px;
}
/* 紧凑表单样式 */
.compact-form {
:deep(.el-form-item) {
margin-bottom: 16px;
}
:deep(.el-form-item__label) {
padding-right: 12px;
font-size: 14px;
}
:deep(.el-input__inner),
:deep(.el-textarea__inner) {
font-size: 14px;
}
}
</style>

View File

@@ -0,0 +1,405 @@
<template>
<el-dialog
v-model="visible"
title="履约验收"
width="85%"
:close-on-click-modal="false"
destroy-on-close
class="purchasing-accept-modal"
@close="handleClose"
>
<div v-loading="loading" class="modal-body">
<div class="main-tabs">
<div class="main-tab-nav">
<div
class="main-tab-item"
:class="{ active: mainTab === 'common' }"
@click="mainTab = 'common'"
>
公共信息
</div>
<div
class="main-tab-item"
:class="{ active: mainTab === 'batch' }"
@click="mainTab = 'batch'"
>
分期验收{{ batches.length > 0 ? ` (${batches.length})` : '' }}
</div>
</div>
<div class="main-tab-content">
<div v-show="mainTab === 'common'" class="tab-content">
<AcceptCommonForm
ref="commonFormRef"
v-model="commonForm"
:project-name="applyInfo?.projectName"
:dept-name="applyInfo?.deptName"
/>
</div>
<div v-show="mainTab === 'batch'" class="tab-content">
<div v-if="batches.length > 0">
<div class="batch-tabs">
<div
v-for="b in batches"
:key="b.id"
class="batch-tab-item"
:class="{ active: String(b.batch) === activeTab, disabled: !canEditBatch(b.batch) }"
@click="canEditBatch(b.batch) && (activeTab = String(b.batch))"
>
<span>{{ b.batch }}</span>
<el-tag v-if="isBatchCompleted(b)" type="success" size="small">已填</el-tag>
<el-tag v-else-if="!canEditBatch(b.batch)" type="info" size="small">需先完成上一期</el-tag>
</div>
</div>
<div class="batch-panel">
<AcceptBatchForm
v-for="b in batches"
v-show="String(b.batch) === activeTab"
:key="b.id"
v-model="batchForms[b.batch]"
:can-fill="canFillForm"
:readonly="false"
:purchase-id="String(purchaseId)"
:acceptance-items="acceptanceItems"
/>
</div>
</div>
<div v-else class="tip-box">
<el-alert type="info" :closable="false" show-icon>
请先在公共信息中填写并点击保存公共配置系统将按分期次数自动生成验收批次
</el-alert>
</div>
</div>
</div>
</div>
</div>
<template #footer>
<span>
<el-button @click="handleClose"> </el-button>
<el-button
v-if="mainTab === 'common' || batches.length === 0"
type="primary"
@click="saveCommonConfig"
:loading="saving"
>
保存公共配置
</el-button>
<el-button
v-else-if="mainTab === 'batch' && activeBatchId"
type="primary"
@click="saveCurrentBatch"
:loading="saving"
>
保存第{{ activeTab }}
</el-button>
</span>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import { ref, reactive, computed, watch, nextTick } from 'vue'
import { useMessage } from '/@/hooks/message'
import {
saveCommonConfig as apiSaveCommonConfig,
getCommonConfigWithBatches,
updateBatch,
canFillForm as apiCanFillForm,
getAcceptanceItems,
getDetail,
} from '/@/api/purchase/purchasingAccept'
import AcceptCommonForm from './AcceptCommonForm.vue'
import AcceptBatchForm from './AcceptBatchForm.vue'
const emit = defineEmits(['refresh'])
const visible = ref(false)
const loading = ref(false)
const saving = ref(false)
const purchaseId = ref<string | number>('')
const applyInfo = ref<any>(null)
const rowProjectType = ref<string>('A')
const canFillForm = ref(true)
const acceptanceItems = ref<any[]>([])
const batches = ref<any[]>([])
const mainTab = ref('common')
const activeTab = ref('1')
const commonFormRef = ref()
const commonForm = reactive<Record<string, any>>({})
const batchForms = reactive<Record<number, any>>({})
const activeBatchId = computed(() => {
const b = batches.value.find((x: any) => String(x.batch) === activeTab.value)
return b?.id || ''
})
const canEditBatch = (batch: number) => {
if (batch === 1) return true
for (let i = 1; i < batch; i++) {
if (!isBatchCompletedByIdx(i)) return false
}
return true
}
const isBatchCompleted = (b: any) => {
return !!(b.acceptType && b.acceptDate)
}
const isBatchCompletedByIdx = (batch: number) => {
const b = batches.value.find((x: any) => x.batch === batch)
return b ? isBatchCompleted(b) : false
}
const loadData = async () => {
if (!purchaseId.value) return
loading.value = true
try {
const [configRes, canFillRes] = await Promise.all([
getCommonConfigWithBatches(String(purchaseId.value)),
apiCanFillForm(String(purchaseId.value)),
])
const config = configRes?.data
canFillForm.value = !!canFillRes?.data
if (config?.common) {
Object.assign(commonForm, {
hasContract: config.common.hasContract || '0',
contractId: config.common.contractId || '',
isInstallment: config.common.isInstallment || '0',
totalPhases: config.common.totalPhases || 1,
supplierName: config.common.supplierName || '',
supplierContact: config.common.supplierContact || '',
purchaserId: config.common.purchaserId || '',
purchaserName: config.common.purchaserName || '',
assetAdminId: config.common.assetAdminId || '',
assetAdminName: config.common.assetAdminName || '',
})
applyInfo.value = config.common
}
const projectType = applyInfo.value?.projectType || rowProjectType.value || 'A'
const typeMap: Record<string, string> = { A: 'A', B: 'B', C: 'C' }
const at = typeMap[projectType] || 'A'
const itemsRes = await getAcceptanceItems(at)
acceptanceItems.value = itemsRes?.data || []
if (config?.batches?.length) {
batches.value = config.batches.sort((a: any, b: any) => (a.batch || 0) - (b.batch || 0))
activeTab.value = String(batches.value[0]?.batch || '1')
mainTab.value = 'batch'
for (const b of batches.value) {
if (!batchForms[b.batch]) batchForms[b.batch] = {}
}
await loadBatchDetails()
} else {
batches.value = []
}
} catch (e: any) {
useMessage().error(e?.msg || '加载失败')
} finally {
loading.value = false
}
}
const loadBatchDetails = async () => {
for (const b of batches.value) {
try {
const res = await getDetail(String(purchaseId.value), b.batch)
const d = res?.data
if (d?.accept) {
const itemMap = (acceptanceItems.value || []).reduce((acc: any, it: any) => {
acc[it.id] = it
return acc
}, {})
batchForms[b.batch] = {
acceptType: d.accept.acceptType || '1',
acceptDate: d.accept.acceptDate || '',
remark: d.accept.remark || '',
templateFileIds: d.accept.templateFileIds || [],
acceptContents: (d.contents || []).map((c: any) => {
const cfg = itemMap[c.configId]
return {
configId: c.configId,
itemName: cfg?.itemName || '',
type: cfg?.type,
isQualified: c.isQualified || '1',
remark: c.remark || '',
}
}),
acceptTeam: (d.team || []).map((t: any) => ({
name: t.name,
deptCode: t.deptCode,
deptName: t.deptName,
})),
}
if (batchForms[b.batch].acceptTeam.length < 3) {
while (batchForms[b.batch].acceptTeam.length < 3) {
batchForms[b.batch].acceptTeam.push({ name: '', deptCode: '', deptName: '' })
}
}
if (acceptanceItems.value.length && (!batchForms[b.batch].acceptContents || batchForms[b.batch].acceptContents.length === 0)) {
batchForms[b.batch].acceptContents = acceptanceItems.value.map((it: any) => ({
configId: it.id,
itemName: it.itemName,
type: it.type,
isQualified: '1',
remark: '',
}))
}
}
} catch (_) {}
}
}
const saveCommonConfig = async () => {
const valid = await commonFormRef.value?.validate?.().catch(() => false)
if (!valid) return
if (commonForm.isInstallment === '1' && (!commonForm.totalPhases || commonForm.totalPhases < 1)) {
useMessage().error('请填写分期次数')
return
}
saving.value = true
try {
await apiSaveCommonConfig({
purchaseId: String(purchaseId.value),
hasContract: commonForm.hasContract,
contractId: commonForm.contractId,
isInstallment: commonForm.isInstallment,
totalPhases: commonForm.isInstallment === '1' ? commonForm.totalPhases : 1,
supplierName: commonForm.supplierName,
supplierContact: commonForm.supplierContact,
purchaserId: commonForm.purchaserId,
purchaserName: commonForm.purchaserName,
assetAdminId: commonForm.assetAdminId,
assetAdminName: commonForm.assetAdminName,
})
useMessage().success('保存成功')
await loadData()
} catch (e: any) {
useMessage().error(e?.msg || '保存失败')
} finally {
saving.value = false
}
}
const saveCurrentBatch = async () => {
const b = batches.value.find((x: any) => String(x.batch) === activeTab.value)
if (!b?.id) return
const form = batchForms[Number(activeTab.value)]
if (!form) return
const team = (form.acceptTeam || []).filter((m: any) => m?.name)
if (team.length < 3 || team.length % 2 === 0) {
useMessage().error('验收小组至少3人且为单数')
return
}
saving.value = true
try {
await updateBatch({
id: b.id,
purchaseId: String(purchaseId.value),
acceptType: form.acceptType,
acceptDate: form.acceptDate,
remark: form.remark,
templateFileIds: form.templateFileIds || [],
acceptContents: form.acceptContents || [],
acceptTeam: team,
})
useMessage().success('保存成功')
await loadData()
} catch (e: any) {
useMessage().error(e?.msg || '保存失败')
} finally {
saving.value = false
}
}
const handleClose = () => {
visible.value = false
emit('refresh')
}
const open = (row: any) => {
purchaseId.value = row?.id ?? ''
rowProjectType.value = row?.projectType || 'A'
visible.value = true
batches.value = []
Object.keys(batchForms).forEach((k) => delete batchForms[Number(k)])
nextTick(() => loadData())
}
defineExpose({ open })
</script>
<style scoped>
.modal-body {
padding: 0;
max-height: 70vh;
overflow-y: auto;
}
.main-tab-nav {
display: flex;
gap: 4px;
margin-bottom: 16px;
border-bottom: 1px solid var(--el-border-color);
}
.main-tab-item {
padding: 12px 20px;
cursor: pointer;
color: var(--el-text-color-regular);
border-bottom: 2px solid transparent;
margin-bottom: -1px;
transition: all 0.2s;
}
.main-tab-item:hover {
color: var(--el-color-primary);
}
.main-tab-item.active {
color: var(--el-color-primary);
font-weight: 600;
border-bottom-color: var(--el-color-primary);
}
.main-tab-content {
padding-top: 4px;
}
.tab-content {
min-height: 200px;
display: block;
}
.tip-box {
padding: 20px 0;
}
.batch-tabs {
display: flex;
gap: 8px;
margin-bottom: 16px;
flex-wrap: wrap;
}
.batch-tab-item {
padding: 8px 16px;
border: 1px solid var(--el-border-color);
border-radius: 4px;
cursor: pointer;
display: flex;
align-items: center;
gap: 6px;
transition: all 0.2s;
}
.batch-tab-item:hover:not(.disabled) {
border-color: var(--el-color-primary);
color: var(--el-color-primary);
}
.batch-tab-item.active {
background: var(--el-color-primary);
border-color: var(--el-color-primary);
color: #fff;
}
.batch-tab-item.disabled {
cursor: not-allowed;
opacity: 0.6;
}
.batch-panel {
min-height: 200px;
}
</style>

View File

@@ -191,7 +191,7 @@
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" fixed="right" width="300">
<el-table-column label="操作" align="center" fixed="right" width="360">
<template #default="scope">
<el-button
icon="View"
@@ -216,6 +216,13 @@
@click="handleDelete(scope.row)">
删除
</el-button>
<el-button
icon="DocumentChecked"
link
type="primary"
@click="handleAccept(scope.row)">
履约验收
</el-button>
</template>
</el-table-column>
</el-table>
@@ -258,6 +265,9 @@
ref="formDialogRef"
:dict-data="dictData"
@refresh="getDataList" />
<!-- 履约验收弹窗 -->
<PurchasingAcceptModal ref="acceptModalRef" @refresh="getDataList" />
</div>
</template>
@@ -269,10 +279,11 @@ import { getPage, delObj } from "/@/api/finance/purchasingrequisition";
import { useMessage, useMessageBox } from "/@/hooks/message";
import { getDicts } from '/@/api/admin/dict';
import { getTree } from '/@/api/finance/purchasingcategory';
import { List, Document, DocumentCopy, Search, Collection, Money, CircleCheck, InfoFilled, Calendar, OfficeBuilding, Warning } from '@element-plus/icons-vue'
import { List, Document, DocumentCopy, Search, Collection, Money, CircleCheck, InfoFilled, Calendar, OfficeBuilding, Warning, DocumentChecked } from '@element-plus/icons-vue'
// 引入组件
const FormDialog = defineAsyncComponent(() => import('./form.vue'));
const PurchasingAcceptModal = defineAsyncComponent(() => import('./accept/PurchasingAcceptModal.vue'));
// 字典数据和品目树数据
const dictData = ref({
@@ -289,6 +300,7 @@ const dictData = ref({
const router = useRouter()
const tableRef = ref()
const formDialogRef = ref()
const acceptModalRef = ref()
const searchFormRef = ref()
const showSearch = ref(true)
const showAddIframe = ref(false)
@@ -363,6 +375,7 @@ const handleIframeMessage = (event: MessageEvent) => {
};
/**
<<<<<<< HEAD
* 打开查看对话框
* @param row - 当前行数据
*/
@@ -376,6 +389,12 @@ const handleView = (row: any) => {
*/
const handleEdit = (row: any) => {
formDialogRef.value?.openDialog('edit', row);
=======
* 履约验收
*/
const handleAccept = (row: any) => {
acceptModalRef.value?.open(row);
>>>>>>> 61380fa2bca0fe9fd37af1c494a51ba523501f7d
};
/**

View File

@@ -0,0 +1,116 @@
<template>
<el-dialog v-model="visible" :title="title" width="600" append-to-body>
<!-- <el-alert-->
<!-- type="warning"-->
<!-- :closable="false"-->
<!-- show-icon-->
<!-- style="margin-bottom: 20px;">-->
<!-- <template #title>-->
<!-- <span>下载模板</span>-->
<!-- </template>-->
<!-- </el-alert>-->
<div style="text-align: center; margin-bottom: 20px">
<el-button type="success" :icon="Download" @click="handleDownloadTemplate">下载模板</el-button>
</div>
<el-upload
class="upload-demo"
:action="uploadUrl"
:headers="headers"
:accept="'.xls,.xlsx'"
:on-success="handleUploadSuccess"
:on-error="handleAvatarError"
:limit="1"
drag
>
<el-icon class="el-icon--upload"><upload-filled /></el-icon>
<div class="el-upload__text">将文件拖到此处<em>点击上传</em></div>
<template #tip>
<div class="el-upload__tip">只能上传 .xls .xlsx 格式的 Excel 文件</div>
</template>
</el-upload>
</el-dialog>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue';
import { ElNotification } from 'element-plus';
import { Download, UploadFilled } from '@element-plus/icons-vue';
import { Session } from '/@/utils/storage';
import { downBlobFile } from '/@/utils/other';
const title = ref('');
// 响应式数据
const visible = ref(false);
// 计算属性
const headers = computed(() => {
return {
Authorization: 'Bearer ' + Session.getToken(),
TENANT_ID: Session.getTenant()
};
});
const uploadUrl = ref('')
const currentType = ref('')
const titleMap: Record<string, string> = {
titleRelation: '职称信息导入',
quaRelation: '职业资格信息导入',
cerRelation: '教师资格证信息导入',
eduDegree: '学历学位信息导入',
partyChange: '党组织变动信息导入',
honor: '综合表彰信息导入'
}
// 方法
const init = (type: any) => {
currentType.value = type
uploadUrl.value = '/professional/file/importTeacherOtherInfo?type=' + type
title.value = titleMap[type] || '信息导入'
visible.value = true
}
// Emits
const emit = defineEmits<{
(e: 'refreshData'): void
}>()
const handleUploadSuccess = () => {
visible.value = false;
ElNotification({
title: '成功',
message: '导入成功',
type: 'success',
});
emit('refreshData')
};
const handleAvatarError = (err: any) => {
const result = JSON.parse(err.message);
if (result.code == '1') {
ElNotification.error({
title: '错误',
message: result.msg,
duration: 30000,
});
}
};
const handleDownloadTemplate = () => {
downBlobFile('/professional/file/exportTeacherInfoTemplate', { type: currentType.value || 'titleRelation' }, '职工信息导入模板.xlsx')
}
// 暴露方法给父组件
defineExpose({
init,
});
</script>
<style scoped lang="scss">
.upload-demo {
:deep(.el-upload-dragger) {
width: 100%;
}
}
</style>

View File

@@ -37,6 +37,20 @@
</template>
</search-form>
<!-- 操作按钮 -->
<el-row>
<div class="mb15" style="width: 100%;">
<el-button
v-auth="'professional_teacherinfo_import'"
type="primary"
plain
icon="UploadFilled"
:loading="exportLoading"
@click="handleImportDialog"
>导入信息</el-button>
</div>
</el-row>
<!-- 表格 -->
<el-table
ref="tableRef"
@@ -77,6 +91,9 @@
@current-change="currentChangeHandle"
@size-change="sizeChangeHandle"
/>
<!-- 子组件 -->
<import-teacher-other-info ref="importTeacherOtherInfoRef" @refreshData="handleFilter" />
</div>
</div>
</template>
@@ -88,6 +105,7 @@ import { BasicTableProps, useTable } from '/@/hooks/table'
import { fetchList } from '/@/api/professional/professionaluser/professionalpartychange'
const TeacherNameNo = defineAsyncComponent(() => import('/@/components/TeacherNameNo/index.vue'))
const ImportTeacherOtherInfo = defineAsyncComponent(() => import('/@/views/professional/common/import-teacher-other-info.vue'))
// 表格引用
const tableRef = ref()
@@ -100,6 +118,10 @@ const search = reactive({
realName: ''
})
// 导入加载状态
const exportLoading = ref(false)
const importTeacherOtherInfoRef = ref()
// 配置 useTable
const state: BasicTableProps = reactive<BasicTableProps>({
pageList: async (params: any) => {
@@ -130,6 +152,11 @@ const resetQuery = () => {
getDataList()
}
// 打开导入弹窗
const handleImportDialog = () => {
importTeacherOtherInfoRef.value?.init('partyChange')
}
// 表格数据由 useTablecreatedIsNeed 默认 true在挂载时自动请求
</script>

View File

@@ -1,9 +1,9 @@
<template>
<div class="layout-padding">
<div class="layout-padding-auto layout-padding-view">
<!-- 搜索表单 -->
<div class="page-cards">
<div class="page-wrapper">
<!-- 筛选卡片 -->
<el-card v-show="showSearch" class="search-card" shadow="never">
<search-form
v-show="showSearch"
:model="search"
ref="searchFormRef"
@keyup-enter="handleFilter"
@@ -24,7 +24,6 @@
/>
</el-select>
</el-form-item>
<el-form-item label="工号" prop="teacherNo">
<el-input
v-model="search.teacherNo"
@@ -32,7 +31,6 @@
placeholder="请输入工号"
/>
</el-form-item>
<el-form-item label="姓名" prop="realName">
<el-input
v-model="search.realName"
@@ -42,8 +40,6 @@
</el-form-item>
</template>
</template>
<!-- 操作按钮 -->
<template #actions>
<el-form-item>
<el-button type="primary" @click="handleFilter" icon="Search">查询</el-button>
@@ -51,26 +47,50 @@
</el-form-item>
</template>
</search-form>
</el-card>
<!-- 操作按钮 -->
<el-row>
<div class="mb15" style="width: 100%;">
<!-- 列表内容卡片 -->
<el-card class="content-card" shadow="never">
<template #header>
<div class="card-header">
<span class="card-title">
<el-icon class="title-icon"><Medal /></el-icon>
职业资格信息
</span>
<div class="header-actions">
<div class="action-group">
<el-button
v-if="hasAuth('professional_professionalqualificationrelation_add')"
type="primary"
icon="FolderAdd"
@click="handleAdd">
</el-button>
@click="handleAdd"
>新增</el-button>
<el-button
v-if="hasAuth('professional_teacherbase_export')"
type="warning"
plain
icon="Download"
@click="handleDownLoadWord"
:loading="exportLoading">导出信息
</el-button>
:loading="exportLoading"
>导出信息</el-button>
<el-button
v-auth="'professional_teacherinfo_import'"
type="primary"
plain
icon="UploadFilled"
:loading="exportLoading"
@click="handleImportDialog"
>导入信息</el-button>
</div>
</el-row>
<div class="header-right">
<RightToolbar
v-model:showSearch="showSearch"
@queryTable="getDataList"
/>
</div>
</div>
</div>
</template>
<!-- 表格 -->
<el-table
@@ -79,6 +99,7 @@
row-key="id"
v-loading="state.loading"
border
stripe
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle"
>
@@ -180,6 +201,8 @@
@current-change="currentChangeHandle"
@size-change="sizeChangeHandle"
/>
</el-card>
</div>
<!-- 材料预览图片直接显示PDF 在组件内部 dialog 中显示 -->
<preview-file
@@ -192,7 +215,7 @@
<!-- 子组件 -->
<DataForm ref="dataFormRef" @refreshData="handleFilter" />
<ProfessionalBackResaon ref="backReasonRef" @refreshData="handleFilter" />
</div>
<import-teacher-other-info ref="importTeacherOtherInfoRef" @refreshData="handleFilter" />
</div>
</template>
@@ -213,11 +236,14 @@ import { getLevelList } from '/@/api/professional/rsbase/professionalqualificati
import { getWorkTypeList } from '/@/api/professional/rsbase/professionalworktype'
import { PROFESSIONAL_AUDIT_STATE_OPTIONS } from '/@/config/global'
import { defineAsyncComponent } from 'vue'
import { Medal } from '@element-plus/icons-vue'
const RightToolbar = defineAsyncComponent(() => import('/@/components/RightToolbar/index.vue'))
const TeacherNameNo = defineAsyncComponent(() => import('/@/components/TeacherNameNo/index.vue'))
const AuditState = defineAsyncComponent(() => import('/@/components/AuditState/index.vue'))
const DataForm = defineAsyncComponent(() => import('./form.vue'))
const ProfessionalBackResaon = defineAsyncComponent(() => import('/@/views/professional/common/professional-back-resaon.vue'))
const previewFile = defineAsyncComponent(() => import('/@/components/tools/preview-file.vue'))
const ImportTeacherOtherInfo = defineAsyncComponent(() => import('/@/views/professional/common/import-teacher-other-info.vue'))
// 审核状态选项
const auditStateOptions = PROFESSIONAL_AUDIT_STATE_OPTIONS
@@ -249,8 +275,9 @@ const search = reactive({
// 材料预览
const imgUrl = ref<Array<{ title: string; url: string }>>([])
// 导出加载状态
// 导出/导入加载状态
const exportLoading = ref(false)
const importTeacherOtherInfoRef = ref()
// 资格等级和工种列表
const qualificationLevelList = ref<any[]>([])
@@ -398,6 +425,11 @@ const getWorkTypeName = (id: string | number) => {
return item ? item.workName : '-'
}
// 打开导入弹窗
const handleImportDialog = () => {
importTeacherOtherInfoRef.value?.init('quaRelation')
}
// 加载字典数据
const loadDictData = async () => {
try {
@@ -424,4 +456,5 @@ onMounted(async () => {
</script>
<style lang="scss" scoped>
@import '/@/assets/styles/page-cards.scss';
</style>

View File

@@ -69,6 +69,14 @@
@click="handleDownLoadWord"
:loading="exportLoading">导出信息
</el-button>
<el-button
v-auth="'professional_teacherinfo_import'"
type="primary"
plain
icon="UploadFilled"
:loading="exportLoading"
@click="handleImportDialog">导入信息
</el-button>
</div>
</el-row>
@@ -218,6 +226,7 @@
<!-- 子组件 -->
<DataForm ref="dataFormRef" @refreshData="handleFilter" />
<ProfessionalBackResaon ref="backReasonRef" @refreshData="handleFilter" />
<import-teacher-other-info ref="importTeacherOtherInfoRef" @refreshData="handleFilter" />
</div>
</div>
</template>
@@ -245,6 +254,7 @@ const AuditState = defineAsyncComponent(() => import('/@/components/AuditState/i
const DataForm = defineAsyncComponent(() => import('./form.vue'))
const ProfessionalBackResaon = defineAsyncComponent(() => import('/@/views/professional/common/professional-back-resaon.vue'))
const previewFile = defineAsyncComponent(() => import('/@/components/tools/preview-file.vue'))
const ImportTeacherOtherInfo = defineAsyncComponent(() => import('/@/views/professional/common/import-teacher-other-info.vue'))
// 审核状态选项
const auditStateOptions = PROFESSIONAL_AUDIT_STATE_OPTIONS
@@ -279,6 +289,7 @@ const imgUrl = ref<Array<{ title: string; url: string }>>([])
// 导出加载状态
const exportLoading = ref(false)
const importTeacherOtherInfoRef = ref()
// 学位、学历和教育类型列表
const degreeList = ref<any[]>([])
@@ -442,6 +453,11 @@ const getEducationTypeName = (id: string | number | undefined) => {
return item ? item.name : '-'
}
// 打开导入弹窗
const handleImportDialog = () => {
importTeacherOtherInfoRef.value?.init('eduDegree')
}
// 加载字典数据
const loadDictData = async () => {
try {

View File

@@ -69,6 +69,14 @@
@click="handleDownLoadWord"
:loading="exportLoading">导出信息
</el-button>
<el-button
v-auth="'professional_teacherinfo_import'"
type="primary"
plain
icon="UploadFilled"
:loading="exportLoading"
@click="handleImportDialog">导入信息
</el-button>
</div>
</el-row>
@@ -188,6 +196,7 @@
<!-- 子组件 -->
<DataForm ref="dataFormRef" @refreshData="handleFilter" />
<ProfessionalBackResaon ref="backReasonRef" @refreshData="handleFilter" />
<import-teacher-other-info ref="importTeacherOtherInfoRef" @refreshData="handleFilter" />
</div>
</div>
</template>
@@ -213,6 +222,7 @@ const AuditState = defineAsyncComponent(() => import('/@/components/AuditState/i
const DataForm = defineAsyncComponent(() => import('./form.vue'))
const ProfessionalBackResaon = defineAsyncComponent(() => import('/@/views/professional/common/professional-back-resaon.vue'))
const previewFile = defineAsyncComponent(() => import('/@/components/tools/preview-file.vue'))
const ImportTeacherOtherInfo = defineAsyncComponent(() => import('/@/views/professional/common/import-teacher-other-info.vue'))
// 审核状态选项(独立定义,防止其他页面修改时被波及)
import type { StateOption } from '/@/components/AuditState/index.vue'
@@ -247,6 +257,7 @@ const imgUrl = ref<Array<{ title: string; url: string }>>([])
// 导出加载状态
const exportLoading = ref(false)
const importTeacherOtherInfoRef = ref()
// 证书列表
const certificateList = ref<any[]>([])
@@ -385,6 +396,11 @@ const getCertificateName = (id: string | number) => {
return item ? item.cretificateName : '-'
}
// 打开导入弹窗
const handleImportDialog = () => {
importTeacherOtherInfoRef.value?.init('cerRelation')
}
// 加载字典数据
const loadDictData = async () => {
try {

View File

@@ -68,6 +68,14 @@
@click="handleDownLoadWord"
:loading="exportLoading">导出信息
</el-button>
<el-button
v-auth="'professional_teacherinfo_import'"
type="primary"
plain
icon="UploadFilled"
:loading="exportLoading"
@click="handleImportDialog">导入信息
</el-button>
</div>
</el-row>
@@ -83,9 +91,45 @@
>
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="state" label="审核状态" width="120" align="center">
<el-table-column prop="state" label="审核状态" width="130" align="center">
<template #default="scope">
<AuditState :state="scope.row.state" :options="auditStateOptions" />
<DetailPopover
v-if="scope.row.state === '-2' && scope.row.backReason"
title="审核详情"
placement="top"
:width="300"
:items="[
{ label: '审核状态', layout: 'horizontal', content: getAuditStateTagConfig(scope.row.state)?.label },
{ label: '驳回理由', content: scope.row.backReason, contentClass: 'reason-content' }
]"
>
<template #reference>
<ClickableTag
:type="getAuditStateTagConfig(scope.row.state)?.type || 'danger'"
:left-icon="getAuditStateTagConfig(scope.row.state)?.leftIcon"
:effect="getAuditStateTagConfig(scope.row.state)?.effect || 'dark'"
>
{{ getAuditStateTagConfig(scope.row.state)?.label || '已驳回' }}
</ClickableTag>
</template>
<template #content-0>
<ClickableTag
:type="getAuditStateTagConfig(scope.row.state)?.type || 'danger'"
:left-icon="getAuditStateTagConfig(scope.row.state)?.leftIcon"
:effect="getAuditStateTagConfig(scope.row.state)?.effect || 'dark'"
:right-icon="null"
>
{{ getAuditStateTagConfig(scope.row.state)?.label || '已驳回' }}
</ClickableTag>
</template>
<template #content-1>
<div class="reason-content">
<el-icon class="reason-icon"><Warning /></el-icon>
<span>{{ scope.row.backReason }}</span>
</div>
</template>
</DetailPopover>
<AuditState v-else :state="scope.row.state" :options="auditStateOptions" />
</template>
</el-table-column>
@@ -114,8 +158,6 @@
</template>
</el-table-column>
<el-table-column prop="backReason" label="驳回理由" min-width="150" align="center" show-overflow-tooltip />
<el-table-column label="操作" width="280" align="center" fixed="right">
<template #default="scope">
<el-button
@@ -158,7 +200,6 @@
type="danger"
link
icon="delete"
style="margin-left: 12px"
@click="handleDel(scope.row)">删除
</el-button>
</template>
@@ -183,6 +224,7 @@
<!-- 子组件 -->
<ProfessionalBackResaon ref="backReasonRef" @refreshData="handleFilter" />
<DataForm ref="dataFormRef" @refreshData="handleFilter" />
<import-teacher-other-info ref="importTeacherOtherInfoRef" @refreshData="handleFilter" />
</div>
</div>
</template>
@@ -199,13 +241,16 @@ import {
examObj,
delObj
} from '/@/api/professional/professionaluser/professionalteacherhonor'
import { PROFESSIONAL_AUDIT_STATE_OPTIONS } from '/@/config/global'
import { PROFESSIONAL_AUDIT_STATE_OPTIONS, getStatusConfig } from '/@/config/global'
import { defineAsyncComponent } from 'vue'
const TeacherNameNo = defineAsyncComponent(() => import('/@/components/TeacherNameNo/index.vue'))
const AuditState = defineAsyncComponent(() => import('/@/components/AuditState/index.vue'))
const ClickableTag = defineAsyncComponent(() => import('/@/components/ClickableTag/index.vue'))
const DetailPopover = defineAsyncComponent(() => import('/@/components/DetailPopover/index.vue'))
const ProfessionalBackResaon = defineAsyncComponent(() => import('/@/views/professional/common/professional-back-resaon.vue'))
const DataForm = defineAsyncComponent(() => import('./form.vue'))
const previewFile = defineAsyncComponent(() => import('/@/components/tools/preview-file.vue'))
const ImportTeacherOtherInfo = defineAsyncComponent(() => import('/@/views/professional/common/import-teacher-other-info.vue'))
// 消息提示 hooks
const message = useMessage()
@@ -217,6 +262,13 @@ const { professional_state: professionalState } = useDict('professional_state')
// 审核状态选项
const auditStateOptions = PROFESSIONAL_AUDIT_STATE_OPTIONS
// 审核状态转 ClickableTag 配置(用 options 的 icon 字符串,与 AuditState 一致为实心)
const getAuditStateTagConfig = (state: string | number) => {
const opt = getStatusConfig(auditStateOptions, state)
if (!opt) return null
return { type: opt.type, label: opt.label, leftIcon: opt.icon, effect: opt.effect || 'dark' }
}
// 无权限即无节点
const { hasAuth } = useAuth()
@@ -239,6 +291,7 @@ const imgUrl = ref<Array<{ title: string; url: string }>>([])
// 导出加载状态
const exportLoading = ref(false)
const importTeacherOtherInfoRef = ref()
// 配置 useTable
const state: BasicTableProps = reactive<BasicTableProps>({
@@ -365,8 +418,38 @@ const handleDownLoadWord = async () => {
}
}
// 打开导入弹窗
const handleImportDialog = () => {
importTeacherOtherInfoRef.value?.init('honor')
}
// 表格数据由 useTablecreatedIsNeed 默认 true在挂载时自动请求
</script>
<style lang="scss" scoped>
/* 驳回理由展示(与 backSchoolCheckin 一致) */
.reason-content {
display: flex;
align-items: flex-start;
gap: 8px;
padding: 8px 12px;
background-color: #fef0f0;
border-radius: 4px;
border-left: 3px solid #f56c6c;
.reason-icon {
color: #f56c6c;
font-size: 16px;
flex-shrink: 0;
margin-top: 2px;
}
span {
color: #f56c6c;
font-size: 13px;
line-height: 1.6;
word-break: break-all;
flex: 1;
}
}
</style>

View File

@@ -1,41 +1,9 @@
<template>
<div class="titlerelation-page">
<div class="page-wrapper">
<!-- 方案 F最上标题+右侧按钮下方搜索再下方表格 -->
<!-- 内容区最上搜索其次标题+按钮再下方表格 -->
<div class="content-block">
<!-- 最上左侧图标+标题右侧所有按钮 -->
<div class="content-block__header">
<span class="card-title">
<el-icon class="title-icon"><Document /></el-icon>
职称关系
</span>
<div class="header-actions">
<div class="action-group">
<el-button
v-if="hasAuth('professional_professionaltitlerelation_add')"
type="primary"
icon="FolderAdd"
@click="handleAdd"
>新增</el-button>
<el-button
v-if="hasAuth('professional_teacherbase_export')"
type="warning"
plain
icon="Download"
@click="handleDownLoadWord"
:loading="exportLoading"
>导出信息</el-button>
</div>
<div class="header-right">
<RightToolbar
v-model:showSearch="showSearch"
@queryTable="getDataList"
/>
</div>
</div>
</div>
<!-- 下方搜索区方案 F 默认收起 -->
<!-- 最上搜索区 -->
<div v-show="showSearch" class="content-block__filter">
<search-form
:model="search"
@@ -118,6 +86,42 @@
</search-form>
</div>
<!-- 其次左侧按钮右侧 RightToolbar -->
<div class="content-block__header">
<div class="header-actions">
<div class="action-group">
<el-button
v-if="hasAuth('professional_professionaltitlerelation_add')"
type="primary"
icon="FolderAdd"
@click="handleAdd"
>新增</el-button>
<el-button
v-if="hasAuth('professional_teacherbase_export')"
type="warning"
plain
icon="Download"
@click="handleDownLoadWord"
:loading="exportLoading"
>导出信息</el-button>
<el-button
v-auth="'professional_teacherinfo_import'"
type="primary"
plain
icon="UploadFilled"
:loading="exportLoading"
@click="handleImportDialog">导入信息
</el-button>
</div>
<div class="header-right">
<RightToolbar
v-model:showSearch="showSearch"
@queryTable="getDataList"
/>
</div>
</div>
</div>
<!-- 再下方表格 -->
<el-table
ref="tableRef"
@@ -242,12 +246,13 @@
<MultiDialog ref="multiDialogRef" @getList="getDataList" :page="state.pagination" :nowRow="null" />
<DataForm ref="dataFormRef" @refreshData="handleFilter" />
<ProfessionalBackResaon ref="backReasonRef" @refreshData="handleFilter" />
<import-teacher-other-info ref="importTeacherOtherInfoRef" @refreshData="handleFilter"></import-teacher-other-info>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted, nextTick } from 'vue'
import { Document } from '@element-plus/icons-vue'
import { BasicTableProps, useTable } from '/@/hooks/table'
import { useAuth } from '/@/hooks/auth'
import { useMessage } from '/@/hooks/message'
@@ -270,6 +275,7 @@ const MultiDialog = defineAsyncComponent(() => import('/@/views/professional/tea
const DataForm = defineAsyncComponent(() => import('./form.vue'))
const ProfessionalBackResaon = defineAsyncComponent(() => import('/@/views/professional/common/professional-back-resaon.vue'))
const previewFile = defineAsyncComponent(() => import('/@/components/tools/preview-file.vue'))
const ImportTeacherOtherInfo = defineAsyncComponent(() => import('/@/views/professional/common/import-teacher-other-info.vue'))
// 无权限即无节点:使用 useAuth + v-if
const { hasAuth } = useAuth()
@@ -467,6 +473,11 @@ const loadDictData = async () => {
}
}
const importTeacherOtherInfoRef=ref()
const handleImportDialog = () => {
importTeacherOtherInfoRef.value?.init('titleRelation')
}
// 初始化:仅加载下拉字典,表格数据由 useTable 在 createdIsNeed: true 时自动请求
onMounted(async () => {
await loadDictData()
@@ -486,10 +497,10 @@ onMounted(async () => {
gap: 0;
}
/* 筛选:方案 F内容区内置筛选区默认收起 */
/* 筛选:内容区最上方,无上外边距;与下方标题栏间距用 margin-bottom */
.content-block__filter {
padding: 16px 20px 5px 20px;
margin-top: 12px;
margin-top: 0;
margin-bottom: 12px;
background: var(--el-fill-color-light);
border-radius: 8px;
@@ -535,21 +546,25 @@ onMounted(async () => {
.header-actions {
display: flex;
align-items: center;
justify-content: space-between;
flex-wrap: wrap;
gap: 12px;
width: 100%;
}
/* 按钮间距按规范 10px与 RightToolbar 区隔 */
.action-group {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 8px;
}
.header-right {
display: flex;
align-items: center;
gap: 8px;
padding-left: 12px;
// border-left: 1px solid var(--el-border-color-lighter);
}
/* 表格 */

View File

@@ -68,7 +68,7 @@ import { ElNotification } from 'element-plus';
import { Download } from '@element-plus/icons-vue';
import global from '/@/components/tools/commondict.vue';
import request from '/@/utils/request';
import { exportTeacherInfoBySelf } from '/@/api/professional/professionalfile';
import { makeExportTeacherInfoBySelfTask } from '/@/api/professional/professionalfile';
// Props
const props = defineProps<{
@@ -224,7 +224,7 @@ const exportTeacherInfo = async () => {
teacherBaseDTO: searchData,
};
exportTeacherInfoBySelf(params).then((res:any)=>{
makeExportTeacherInfoBySelfTask(params).then((res:any)=>{
ElNotification.success({
title: '下载后台进行中',
message: '操作成功'

View File

@@ -18,7 +18,7 @@
<el-upload
class="upload-demo"
action="/professional/file/importTeacherInfoSimple"
action="/professional/file/makeImportTeacherInfoSimpleTask"
:headers="headers"
:accept="'.xls,.xlsx'"
:on-success="handleUploadSuccess"

View File

@@ -1,13 +1,12 @@
<template>
<div class="teacherbase-page">
<div class="page-cards">
<div class="page-wrapper">
<!-- 筛选条件卡片标题由 SearchForm 组件内置 -->
<el-card v-show="showSearch" class="search-card" shadow="never">
<search-form
:model="search"
ref="searchFormRef"
filter-title="筛选"
@keyup-enter="handleFilter(search)"
>
<template #default="{ visible }">
@@ -215,14 +214,14 @@
icon="Lock"
@click="dialogVisible.statusDialogFormVisible=true">状态锁定
</el-button>
<el-button
v-if="permissions.professional_teacherbase_export"
type="warning"
plain
icon="Download"
:loading="exportLoading"
@click="handleDownLoadWord('')">导出WORD
</el-button>
<!-- <el-button-->
<!-- v-if="permissions.professional_teacherbase_export"-->
<!-- type="warning"-->
<!-- plain-->
<!-- icon="Download"-->
<!-- :loading="exportLoading"-->
<!-- @click="handleDownLoadWord('')">导出WORD-->
<!-- </el-button>-->
<el-button
v-if="permissions.professional_teacherbase_export"
type="warning"
@@ -2698,96 +2697,7 @@
</script>
<style lang="scss" scoped>
// 页面整体布局
.teacherbase-page {
padding: 12px;
min-height: 100%;
background: var(--el-bg-color-page, #f5f6f8);
}
.page-wrapper {
display: flex;
flex-direction: column;
gap: 12px;
}
// 筛选卡片(标题栏与表单样式在 SearchForm 组件内)
.search-card {
border-radius: 8px;
border: 1px solid var(--el-border-color-lighter);
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
background: var(--el-bg-color);
:deep(.el-card__body) {
padding: 18px 20px 5px 20px;
}
}
// 列表内容卡片
.content-card {
border-radius: 8px;
border: 1px solid var(--el-border-color-lighter);
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
background: var(--el-bg-color);
:deep(.el-card__header) {
padding: 20px 20px 15px;
border-bottom: 1px solid var(--el-border-color-lighter);
}
:deep(.el-card__body) {
padding: 15px 20px 20px;
}
}
.card-header {
display: flex;
align-items: center;
justify-content: space-between;
flex-wrap: wrap;
gap: 12px;
}
.card-title {
display: inline-flex;
align-items: center;
gap: 8px;
font-size: 15px;
font-weight: 500;
color: var(--el-text-color-primary);
.title-icon {
font-size: 16px;
color: var(--el-color-primary);
}
}
.header-actions {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 12px;
}
.action-group {
display: flex;
align-items: center;
flex-wrap: wrap;
}
.header-right {
display: flex;
align-items: center;
padding-left: 10px;
}
// 主表格行高稍增加
.teacherbase-table {
:deep(.el-table__body td),
:deep(.el-table__header th) {
padding: 9px 0;
}
}
@import '/@/assets/styles/page-cards.scss';
// 列表内链接与占位(主表格与弹窗内通用)
:deep(.certificate-link) {

View File

@@ -0,0 +1,136 @@
<template>
<el-dialog :title="form.id ? '编辑' : '新增'" v-model="visible"
:close-on-click-modal="false" draggable>
<el-form ref="dataFormRef" :model="form" :rules="dataRules" formDialogRef label-width="90px" v-loading="loading">
<el-row :gutter="24">
<el-col :span="12" class="mb20">
<el-form-item label="remark" prop="remark">
<el-input v-model="form.remark" placeholder="请输入remark"/>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="关联验收类型A:货物 B:工程 C:服务" prop="acceptanceType">
<el-input v-model="form.acceptanceType" placeholder="请输入关联验收类型A:货物 B:工程 C:服务"/>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="验收项名称" prop="itemName">
<el-input v-model="form.itemName" placeholder="请输入验收项名称"/>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="是否启用 0不启用 1:启用" prop="isEnabled">
<el-input v-model="form.isEnabled" placeholder="请输入是否启用 0不启用 1:启用"/>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="排序" prop="sortOrder">
<el-input v-model="form.sortOrder" placeholder="请输入排序"/>
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="visible = false"> </el-button>
<el-button type="primary" @click="onSubmit" :disabled="loading"> </el-button>
</span>
</template>
</el-dialog>
</template>
<script setup lang="ts" name="AcceptanceItemConfigDialog">
// ========== 1. 导入语句 ==========
import { useDict } from '/@/hooks/dict';
import { rule } from '/@/utils/validate';
import { useMessage } from "/@/hooks/message";
import { getObj, addObj, putObj, validateExist } from '/@/api/purchase/acceptanceItemConfig';
// ========== 2. 组件定义 ==========
// 定义组件事件
const emit = defineEmits(['refresh']);
// ========== 3. 响应式数据定义 ==========
// 基础响应式变量
const dataFormRef = ref(); // 表单引用
const visible = ref(false); // 弹窗显示状态
const loading = ref(false); // 加载状态
// 表单数据对象
const form = reactive({
id: '', // 主键
remark: '', // ${field.fieldComment}
acceptanceType: '', // 关联验收类型A:货物 B:工程 C:服务
itemName: '', // 验收项名称
isEnabled: '', // 是否启用 0不启用 1:启用
sortOrder: '', // 排序
});
// ========== 4. 字典数据处理 ==========
// ========== 5. 表单校验规则 ==========
const dataRules = ref({
});
// ========== 6. 方法定义 ==========
// 获取详情数据
const getAcceptanceItemConfigData = async (id: string) => {
try {
loading.value = true;
const { data } = await getObj({ id: id });
// 直接将第一条数据赋值给表单
Object.assign(form, data[0]);
} catch (error) {
useMessage().error('获取数据失败');
} finally {
loading.value = false;
}
};
// 打开弹窗方法
const openDialog = (id: string) => {
visible.value = true;
form.id = '';
// 重置表单数据
nextTick(() => {
dataFormRef.value?.resetFields();
});
// 获取AcceptanceItemConfig信息
if (id) {
form.id = id;
getAcceptanceItemConfigData(id);
}
};
// 提交表单方法
const onSubmit = async () => {
loading.value = true; // 防止重复提交
// 表单校验
const valid = await dataFormRef.value.validate().catch(() => {});
if (!valid) {
loading.value = false;
return false;
}
try {
// 根据是否有ID判断是新增还是修改
form.id ? await putObj(form) : await addObj(form);
useMessage().success(form.id ? '修改成功' : '添加成功');
visible.value = false;
emit('refresh'); // 通知父组件刷新列表
} catch (err: any) {
useMessage().error(err.msg);
} finally {
loading.value = false;
}
};
// ========== 7. 对外暴露 ==========
// 暴露方法给父组件
defineExpose({
openDialog
});
</script>

View File

@@ -0,0 +1,223 @@
<template>
<div class="layout-padding">
<div class="layout-padding-auto layout-padding-view">
<!-- 操作按钮区域 -->
<el-row>
<div class="mb8" style="width: 100%">
<el-button
icon="folder-add"
type="primary"
class="ml10"
@click="formDialogRef.openDialog()"
v-auth="'purchase_acceptanceItemConfig_add'"
>
新增
</el-button>
<el-button
plain
icon="upload-filled"
type="primary"
class="ml10"
@click="excelUploadRef.show()"
v-auth="'purchase_acceptanceItemConfig_add'"
>
导入
</el-button>
<el-button
plain
:disabled="multiple"
icon="Delete"
type="primary"
v-auth="'purchase_acceptanceItemConfig_del'"
@click="handleDelete(selectObjs)"
>
删除
</el-button>
<right-toolbar
v-model:showSearch="showSearch"
:export="'purchase_acceptanceItemConfig_export'"
@exportExcel="exportExcel"
class="ml10 mr20"
style="float: right;"
@queryTable="getDataList"
/>
</div>
</el-row>
<!-- 数据表格区域 -->
<el-table
:data="state.dataList"
v-loading="state.loading"
border
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle"
@selection-change="selectionChangHandle"
@sort-change="sortChangeHandle"
>
<el-table-column type="selection" width="40" align="center" />
<el-table-column type="index" label="#" width="40" />
<el-table-column
prop="remark"
label="remark"
show-overflow-tooltip
/>
<el-table-column
prop="acceptanceType"
label="关联验收类型A:货物 B:工程 C:服务"
show-overflow-tooltip
/>
<el-table-column
prop="itemName"
label="验收项名称"
show-overflow-tooltip
/>
<el-table-column
prop="isEnabled"
label="是否启用 0不启用 1:启用"
show-overflow-tooltip
/>
<el-table-column
prop="sortOrder"
label="排序"
show-overflow-tooltip
/>
<el-table-column label="操作" width="150">
<template #default="scope">
<el-button
icon="edit-pen"
text
type="primary"
v-auth="'purchase_acceptanceItemConfig_edit'"
@click="formDialogRef.openDialog(scope.row.id)"
>
编辑
</el-button>
<el-button
icon="delete"
text
type="primary"
v-auth="'purchase_acceptanceItemConfig_del'"
@click="handleDelete([scope.row.id])"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页组件 -->
<pagination
@size-change="sizeChangeHandle"
@current-change="currentChangeHandle"
v-bind="state.pagination"
/>
</div>
<!-- 编辑新增弹窗 -->
<form-dialog ref="formDialogRef" @refresh="getDataList(false)" />
<!-- 导入excel弹窗 (需要在 upms-biz/resources/file 下维护模板) -->
<upload-excel
ref="excelUploadRef"
title="导入"
url="/purchase/acceptanceItemConfig/import"
temp-url="/admin/sys-file/local/file/acceptanceItemConfig.xlsx"
@refreshDataList="getDataList"
/>
</div>
</template>
<script setup lang="ts" name="systemAcceptanceItemConfig">
// ========== 导入声明 ==========
import { BasicTableProps, useTable } from "/@/hooks/table";
import { fetchList, delObjs } from "/@/api/purchase/acceptanceItemConfig";
import { useMessage, useMessageBox } from "/@/hooks/message";
import { useDict } from '/@/hooks/dict';
// ========== 组件声明 ==========
// 异步加载表单弹窗组件
const FormDialog = defineAsyncComponent(() => import('./form.vue'));
// ========== 字典数据 ==========
// ========== 组件引用 ==========
const formDialogRef = ref(); // 表单弹窗引用
const excelUploadRef = ref(); // Excel上传弹窗引用
const queryRef = ref(); // 查询表单引用
// ========== 响应式数据 ==========
const showSearch = ref(true); // 是否显示搜索区域
const selectObjs = ref([]) as any; // 表格多选数据
const multiple = ref(true); // 是否多选
// ========== 表格状态 ==========
const state: BasicTableProps = reactive<BasicTableProps>({
queryForm: {}, // 查询参数
pageList: fetchList // 分页查询方法
});
// ========== Hook引用 ==========
// 表格相关Hook
const {
getDataList,
currentChangeHandle,
sizeChangeHandle,
sortChangeHandle,
downBlobFile,
tableStyle
} = useTable(state);
// ========== 方法定义 ==========
/**
* 重置查询条件
*/
const resetQuery = () => {
// 清空搜索条件
queryRef.value?.resetFields();
// 清空多选
selectObjs.value = [];
// 重新查询
getDataList();
};
/**
* 导出Excel文件
*/
const exportExcel = () => {
downBlobFile(
'/purchase/acceptanceItemConfig/export',
Object.assign(state.queryForm, { ids: selectObjs }),
'acceptanceItemConfig.xlsx'
);
};
/**
* 表格多选事件处理
* @param objs 选中的数据行
*/
const selectionChangHandle = (objs: { id: string }[]) => {
selectObjs.value = objs.map(({ id }) => id);
multiple.value = !objs.length;
};
/**
* 删除数据处理
* @param ids 要删除的数据ID数组
*/
const handleDelete = async (ids: string[]) => {
try {
await useMessageBox().confirm('此操作将永久删除');
} catch {
return;
}
try {
await delObjs(ids);
getDataList();
useMessage().success('删除成功');
} catch (err: any) {
useMessage().error(err.msg);
}
};
</script>

View File

@@ -0,0 +1,130 @@
<template>
<el-dialog :title="form.id ? '编辑' : '新增'" v-model="visible"
:close-on-click-modal="false" draggable>
<el-form ref="dataFormRef" :model="form" :rules="dataRules" formDialogRef label-width="90px" v-loading="loading">
<el-row :gutter="24">
<el-col :span="12" class="mb20">
<el-form-item label="remark" prop="remark">
<el-input v-model="form.remark" placeholder="请输入remark"/>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="配置项ID" prop="configId">
<el-input v-model="form.configId" placeholder="请输入配置项ID"/>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="是否合格 0:不合格 1:合格" prop="isQualified">
<el-input v-model="form.isQualified" placeholder="请输入是否合格 0:不合格 1:合格"/>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="验收批次ID" prop="acceptBatchId">
<el-input v-model="form.acceptBatchId" placeholder="请输入验收批次ID"/>
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="visible = false"> </el-button>
<el-button type="primary" @click="onSubmit" :disabled="loading"> </el-button>
</span>
</template>
</el-dialog>
</template>
<script setup lang="ts" name="PuchasingAcceptContentDialog">
// ========== 1. 导入语句 ==========
import { useDict } from '/@/hooks/dict';
import { rule } from '/@/utils/validate';
import { useMessage } from "/@/hooks/message";
import { getObj, addObj, putObj, validateExist } from '/@/api/purchase/puchasingAcceptContent';
// ========== 2. 组件定义 ==========
// 定义组件事件
const emit = defineEmits(['refresh']);
// ========== 3. 响应式数据定义 ==========
// 基础响应式变量
const dataFormRef = ref(); // 表单引用
const visible = ref(false); // 弹窗显示状态
const loading = ref(false); // 加载状态
// 表单数据对象
const form = reactive({
id: '', // 主键
remark: '', // ${field.fieldComment}
configId: '', // 配置项ID
isQualified: '', // 是否合格 0:不合格 1:合格
acceptBatchId: '', // 验收批次ID
});
// ========== 4. 字典数据处理 ==========
// ========== 5. 表单校验规则 ==========
const dataRules = ref({
});
// ========== 6. 方法定义 ==========
// 获取详情数据
const getPuchasingAcceptContentData = async (id: string) => {
try {
loading.value = true;
const { data } = await getObj({ id: id });
// 直接将第一条数据赋值给表单
Object.assign(form, data[0]);
} catch (error) {
useMessage().error('获取数据失败');
} finally {
loading.value = false;
}
};
// 打开弹窗方法
const openDialog = (id: string) => {
visible.value = true;
form.id = '';
// 重置表单数据
nextTick(() => {
dataFormRef.value?.resetFields();
});
// 获取PuchasingAcceptContent信息
if (id) {
form.id = id;
getPuchasingAcceptContentData(id);
}
};
// 提交表单方法
const onSubmit = async () => {
loading.value = true; // 防止重复提交
// 表单校验
const valid = await dataFormRef.value.validate().catch(() => {});
if (!valid) {
loading.value = false;
return false;
}
try {
// 根据是否有ID判断是新增还是修改
form.id ? await putObj(form) : await addObj(form);
useMessage().success(form.id ? '修改成功' : '添加成功');
visible.value = false;
emit('refresh'); // 通知父组件刷新列表
} catch (err: any) {
useMessage().error(err.msg);
} finally {
loading.value = false;
}
};
// ========== 7. 对外暴露 ==========
// 暴露方法给父组件
defineExpose({
openDialog
});
</script>

View File

@@ -0,0 +1,218 @@
<template>
<div class="layout-padding">
<div class="layout-padding-auto layout-padding-view">
<!-- 操作按钮区域 -->
<el-row>
<div class="mb8" style="width: 100%">
<el-button
icon="folder-add"
type="primary"
class="ml10"
@click="formDialogRef.openDialog()"
v-auth="'purchase_puchasingAcceptContent_add'"
>
新增
</el-button>
<el-button
plain
icon="upload-filled"
type="primary"
class="ml10"
@click="excelUploadRef.show()"
v-auth="'purchase_puchasingAcceptContent_add'"
>
导入
</el-button>
<el-button
plain
:disabled="multiple"
icon="Delete"
type="primary"
v-auth="'purchase_puchasingAcceptContent_del'"
@click="handleDelete(selectObjs)"
>
删除
</el-button>
<right-toolbar
v-model:showSearch="showSearch"
:export="'purchase_puchasingAcceptContent_export'"
@exportExcel="exportExcel"
class="ml10 mr20"
style="float: right;"
@queryTable="getDataList"
/>
</div>
</el-row>
<!-- 数据表格区域 -->
<el-table
:data="state.dataList"
v-loading="state.loading"
border
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle"
@selection-change="selectionChangHandle"
@sort-change="sortChangeHandle"
>
<el-table-column type="selection" width="40" align="center" />
<el-table-column type="index" label="#" width="40" />
<el-table-column
prop="remark"
label="remark"
show-overflow-tooltip
/>
<el-table-column
prop="configId"
label="配置项ID"
show-overflow-tooltip
/>
<el-table-column
prop="isQualified"
label="是否合格 0:不合格 1:合格"
show-overflow-tooltip
/>
<el-table-column
prop="acceptBatchId"
label="验收批次ID"
show-overflow-tooltip
/>
<el-table-column label="操作" width="150">
<template #default="scope">
<el-button
icon="edit-pen"
text
type="primary"
v-auth="'purchase_puchasingAcceptContent_edit'"
@click="formDialogRef.openDialog(scope.row.id)"
>
编辑
</el-button>
<el-button
icon="delete"
text
type="primary"
v-auth="'purchase_puchasingAcceptContent_del'"
@click="handleDelete([scope.row.id])"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页组件 -->
<pagination
@size-change="sizeChangeHandle"
@current-change="currentChangeHandle"
v-bind="state.pagination"
/>
</div>
<!-- 编辑新增弹窗 -->
<form-dialog ref="formDialogRef" @refresh="getDataList(false)" />
<!-- 导入excel弹窗 (需要在 upms-biz/resources/file 下维护模板) -->
<upload-excel
ref="excelUploadRef"
title="导入"
url="/purchase/puchasingAcceptContent/import"
temp-url="/admin/sys-file/local/file/puchasingAcceptContent.xlsx"
@refreshDataList="getDataList"
/>
</div>
</template>
<script setup lang="ts" name="systemPuchasingAcceptContent">
// ========== 导入声明 ==========
import { BasicTableProps, useTable } from "/@/hooks/table";
import { fetchList, delObjs } from "/@/api/purchase/puchasingAcceptContent";
import { useMessage, useMessageBox } from "/@/hooks/message";
import { useDict } from '/@/hooks/dict';
// ========== 组件声明 ==========
// 异步加载表单弹窗组件
const FormDialog = defineAsyncComponent(() => import('./form.vue'));
// ========== 字典数据 ==========
// ========== 组件引用 ==========
const formDialogRef = ref(); // 表单弹窗引用
const excelUploadRef = ref(); // Excel上传弹窗引用
const queryRef = ref(); // 查询表单引用
// ========== 响应式数据 ==========
const showSearch = ref(true); // 是否显示搜索区域
const selectObjs = ref([]) as any; // 表格多选数据
const multiple = ref(true); // 是否多选
// ========== 表格状态 ==========
const state: BasicTableProps = reactive<BasicTableProps>({
queryForm: {}, // 查询参数
pageList: fetchList // 分页查询方法
});
// ========== Hook引用 ==========
// 表格相关Hook
const {
getDataList,
currentChangeHandle,
sizeChangeHandle,
sortChangeHandle,
downBlobFile,
tableStyle
} = useTable(state);
// ========== 方法定义 ==========
/**
* 重置查询条件
*/
const resetQuery = () => {
// 清空搜索条件
queryRef.value?.resetFields();
// 清空多选
selectObjs.value = [];
// 重新查询
getDataList();
};
/**
* 导出Excel文件
*/
const exportExcel = () => {
downBlobFile(
'/purchase/puchasingAcceptContent/export',
Object.assign(state.queryForm, { ids: selectObjs }),
'puchasingAcceptContent.xlsx'
);
};
/**
* 表格多选事件处理
* @param objs 选中的数据行
*/
const selectionChangHandle = (objs: { id: string }[]) => {
selectObjs.value = objs.map(({ id }) => id);
multiple.value = !objs.length;
};
/**
* 删除数据处理
* @param ids 要删除的数据ID数组
*/
const handleDelete = async (ids: string[]) => {
try {
await useMessageBox().confirm('此操作将永久删除');
} catch {
return;
}
try {
await delObjs(ids);
getDataList();
useMessage().success('删除成功');
} catch (err: any) {
useMessage().error(err.msg);
}
};
</script>

View File

@@ -0,0 +1,136 @@
<template>
<el-dialog :title="form.id ? '编辑' : '新增'" v-model="visible"
:close-on-click-modal="false" draggable>
<el-form ref="dataFormRef" :model="form" :rules="dataRules" formDialogRef label-width="90px" v-loading="loading">
<el-row :gutter="24">
<el-col :span="12" class="mb20">
<el-form-item label="remark" prop="remark">
<el-input v-model="form.remark" placeholder="请输入remark"/>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="所在部门编码" prop="deptCode">
<el-input v-model="form.deptCode" placeholder="请输入所在部门编码"/>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="所在部门" prop="deptName">
<el-input v-model="form.deptName" placeholder="请输入所在部门"/>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="姓名" prop="name">
<el-input v-model="form.name" placeholder="请输入姓名"/>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="验收批次ID" prop="acceptBatchId">
<el-input v-model="form.acceptBatchId" placeholder="请输入验收批次ID"/>
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="visible = false"> </el-button>
<el-button type="primary" @click="onSubmit" :disabled="loading"> </el-button>
</span>
</template>
</el-dialog>
</template>
<script setup lang="ts" name="PuchasingAcceptTeamDialog">
// ========== 1. 导入语句 ==========
import { useDict } from '/@/hooks/dict';
import { rule } from '/@/utils/validate';
import { useMessage } from "/@/hooks/message";
import { getObj, addObj, putObj, validateExist } from '/@/api/purchase/puchasingAcceptTeam';
// ========== 2. 组件定义 ==========
// 定义组件事件
const emit = defineEmits(['refresh']);
// ========== 3. 响应式数据定义 ==========
// 基础响应式变量
const dataFormRef = ref(); // 表单引用
const visible = ref(false); // 弹窗显示状态
const loading = ref(false); // 加载状态
// 表单数据对象
const form = reactive({
id: '', // 主键
remark: '', // ${field.fieldComment}
deptCode: '', // 所在部门编码
deptName: '', // 所在部门
name: '', // 姓名
acceptBatchId: '', // 验收批次ID
});
// ========== 4. 字典数据处理 ==========
// ========== 5. 表单校验规则 ==========
const dataRules = ref({
});
// ========== 6. 方法定义 ==========
// 获取详情数据
const getPuchasingAcceptTeamData = async (id: string) => {
try {
loading.value = true;
const { data } = await getObj({ id: id });
// 直接将第一条数据赋值给表单
Object.assign(form, data[0]);
} catch (error) {
useMessage().error('获取数据失败');
} finally {
loading.value = false;
}
};
// 打开弹窗方法
const openDialog = (id: string) => {
visible.value = true;
form.id = '';
// 重置表单数据
nextTick(() => {
dataFormRef.value?.resetFields();
});
// 获取PuchasingAcceptTeam信息
if (id) {
form.id = id;
getPuchasingAcceptTeamData(id);
}
};
// 提交表单方法
const onSubmit = async () => {
loading.value = true; // 防止重复提交
// 表单校验
const valid = await dataFormRef.value.validate().catch(() => {});
if (!valid) {
loading.value = false;
return false;
}
try {
// 根据是否有ID判断是新增还是修改
form.id ? await putObj(form) : await addObj(form);
useMessage().success(form.id ? '修改成功' : '添加成功');
visible.value = false;
emit('refresh'); // 通知父组件刷新列表
} catch (err: any) {
useMessage().error(err.msg);
} finally {
loading.value = false;
}
};
// ========== 7. 对外暴露 ==========
// 暴露方法给父组件
defineExpose({
openDialog
});
</script>

View File

@@ -0,0 +1,223 @@
<template>
<div class="layout-padding">
<div class="layout-padding-auto layout-padding-view">
<!-- 操作按钮区域 -->
<el-row>
<div class="mb8" style="width: 100%">
<el-button
icon="folder-add"
type="primary"
class="ml10"
@click="formDialogRef.openDialog()"
v-auth="'purchase_puchasingAcceptTeam_add'"
>
新增
</el-button>
<el-button
plain
icon="upload-filled"
type="primary"
class="ml10"
@click="excelUploadRef.show()"
v-auth="'purchase_puchasingAcceptTeam_add'"
>
导入
</el-button>
<el-button
plain
:disabled="multiple"
icon="Delete"
type="primary"
v-auth="'purchase_puchasingAcceptTeam_del'"
@click="handleDelete(selectObjs)"
>
删除
</el-button>
<right-toolbar
v-model:showSearch="showSearch"
:export="'purchase_puchasingAcceptTeam_export'"
@exportExcel="exportExcel"
class="ml10 mr20"
style="float: right;"
@queryTable="getDataList"
/>
</div>
</el-row>
<!-- 数据表格区域 -->
<el-table
:data="state.dataList"
v-loading="state.loading"
border
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle"
@selection-change="selectionChangHandle"
@sort-change="sortChangeHandle"
>
<el-table-column type="selection" width="40" align="center" />
<el-table-column type="index" label="#" width="40" />
<el-table-column
prop="remark"
label="remark"
show-overflow-tooltip
/>
<el-table-column
prop="deptCode"
label="所在部门编码"
show-overflow-tooltip
/>
<el-table-column
prop="deptName"
label="所在部门"
show-overflow-tooltip
/>
<el-table-column
prop="name"
label="姓名"
show-overflow-tooltip
/>
<el-table-column
prop="acceptBatchId"
label="验收批次ID"
show-overflow-tooltip
/>
<el-table-column label="操作" width="150">
<template #default="scope">
<el-button
icon="edit-pen"
text
type="primary"
v-auth="'purchase_puchasingAcceptTeam_edit'"
@click="formDialogRef.openDialog(scope.row.id)"
>
编辑
</el-button>
<el-button
icon="delete"
text
type="primary"
v-auth="'purchase_puchasingAcceptTeam_del'"
@click="handleDelete([scope.row.id])"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页组件 -->
<pagination
@size-change="sizeChangeHandle"
@current-change="currentChangeHandle"
v-bind="state.pagination"
/>
</div>
<!-- 编辑新增弹窗 -->
<form-dialog ref="formDialogRef" @refresh="getDataList(false)" />
<!-- 导入excel弹窗 (需要在 upms-biz/resources/file 下维护模板) -->
<upload-excel
ref="excelUploadRef"
title="导入"
url="/purchase/puchasingAcceptTeam/import"
temp-url="/admin/sys-file/local/file/puchasingAcceptTeam.xlsx"
@refreshDataList="getDataList"
/>
</div>
</template>
<script setup lang="ts" name="systemPuchasingAcceptTeam">
// ========== 导入声明 ==========
import { BasicTableProps, useTable } from "/@/hooks/table";
import { fetchList, delObjs } from "/@/api/purchase/puchasingAcceptTeam";
import { useMessage, useMessageBox } from "/@/hooks/message";
import { useDict } from '/@/hooks/dict';
// ========== 组件声明 ==========
// 异步加载表单弹窗组件
const FormDialog = defineAsyncComponent(() => import('./form.vue'));
// ========== 字典数据 ==========
// ========== 组件引用 ==========
const formDialogRef = ref(); // 表单弹窗引用
const excelUploadRef = ref(); // Excel上传弹窗引用
const queryRef = ref(); // 查询表单引用
// ========== 响应式数据 ==========
const showSearch = ref(true); // 是否显示搜索区域
const selectObjs = ref([]) as any; // 表格多选数据
const multiple = ref(true); // 是否多选
// ========== 表格状态 ==========
const state: BasicTableProps = reactive<BasicTableProps>({
queryForm: {}, // 查询参数
pageList: fetchList // 分页查询方法
});
// ========== Hook引用 ==========
// 表格相关Hook
const {
getDataList,
currentChangeHandle,
sizeChangeHandle,
sortChangeHandle,
downBlobFile,
tableStyle
} = useTable(state);
// ========== 方法定义 ==========
/**
* 重置查询条件
*/
const resetQuery = () => {
// 清空搜索条件
queryRef.value?.resetFields();
// 清空多选
selectObjs.value = [];
// 重新查询
getDataList();
};
/**
* 导出Excel文件
*/
const exportExcel = () => {
downBlobFile(
'/purchase/puchasingAcceptTeam/export',
Object.assign(state.queryForm, { ids: selectObjs }),
'puchasingAcceptTeam.xlsx'
);
};
/**
* 表格多选事件处理
* @param objs 选中的数据行
*/
const selectionChangHandle = (objs: { id: string }[]) => {
selectObjs.value = objs.map(({ id }) => id);
multiple.value = !objs.length;
};
/**
* 删除数据处理
* @param ids 要删除的数据ID数组
*/
const handleDelete = async (ids: string[]) => {
try {
await useMessageBox().confirm('此操作将永久删除');
} catch {
return;
}
try {
await delObjs(ids);
getDataList();
useMessage().success('删除成功');
} catch (err: any) {
useMessage().error(err.msg);
}
};
</script>

View File

@@ -0,0 +1,142 @@
<template>
<el-dialog :title="form.id ? '编辑' : '新增'" v-model="visible"
:close-on-click-modal="false" draggable>
<el-form ref="dataFormRef" :model="form" :rules="dataRules" formDialogRef label-width="90px" v-loading="loading">
<el-row :gutter="24">
<el-col :span="12" class="mb20">
<el-form-item label="remark" prop="remark">
<el-input v-model="form.remark" placeholder="请输入remark"/>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="采购申请ID" prop="purchaseId">
<el-input v-model="form.purchaseId" placeholder="请输入采购申请ID"/>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="验收日期" prop="acceptDate">
<el-input v-model="form.acceptDate" placeholder="请输入验收日期"/>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="验收地点" prop="acceptAddress">
<el-input v-model="form.acceptAddress" placeholder="请输入验收地点"/>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="验收批次" prop="batch">
<el-input v-model="form.batch" placeholder="请输入验收批次"/>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="问题意见" prop="question">
<el-input v-model="form.question" placeholder="请输入问题意见"/>
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="visible = false"> </el-button>
<el-button type="primary" @click="onSubmit" :disabled="loading"> </el-button>
</span>
</template>
</el-dialog>
</template>
<script setup lang="ts" name="PurchasingAcceptDialog">
// ========== 1. 导入语句 ==========
import { useDict } from '/@/hooks/dict';
import { rule } from '/@/utils/validate';
import { useMessage } from "/@/hooks/message";
import { getObj, addObj, putObj, validateExist } from '/@/api/purchase/purchasingAccept';
// ========== 2. 组件定义 ==========
// 定义组件事件
const emit = defineEmits(['refresh']);
// ========== 3. 响应式数据定义 ==========
// 基础响应式变量
const dataFormRef = ref(); // 表单引用
const visible = ref(false); // 弹窗显示状态
const loading = ref(false); // 加载状态
// 表单数据对象
const form = reactive({
id: '', // 主键
remark: '', // ${field.fieldComment}
purchaseId: '', // 采购申请ID
acceptDate: '', // 验收日期
acceptAddress: '', // 验收地点
batch: '', // 验收批次
question: '', // 问题意见
});
// ========== 4. 字典数据处理 ==========
// ========== 5. 表单校验规则 ==========
const dataRules = ref({
});
// ========== 6. 方法定义 ==========
// 获取详情数据
const getPurchasingAcceptData = async (id: string) => {
try {
loading.value = true;
const { data } = await getObj({ id: id });
// 直接将第一条数据赋值给表单
Object.assign(form, data[0]);
} catch (error) {
useMessage().error('获取数据失败');
} finally {
loading.value = false;
}
};
// 打开弹窗方法
const openDialog = (id: string) => {
visible.value = true;
form.id = '';
// 重置表单数据
nextTick(() => {
dataFormRef.value?.resetFields();
});
// 获取PurchasingAccept信息
if (id) {
form.id = id;
getPurchasingAcceptData(id);
}
};
// 提交表单方法
const onSubmit = async () => {
loading.value = true; // 防止重复提交
// 表单校验
const valid = await dataFormRef.value.validate().catch(() => {});
if (!valid) {
loading.value = false;
return false;
}
try {
// 根据是否有ID判断是新增还是修改
form.id ? await putObj(form) : await addObj(form);
useMessage().success(form.id ? '修改成功' : '添加成功');
visible.value = false;
emit('refresh'); // 通知父组件刷新列表
} catch (err: any) {
useMessage().error(err.msg);
} finally {
loading.value = false;
}
};
// ========== 7. 对外暴露 ==========
// 暴露方法给父组件
defineExpose({
openDialog
});
</script>

View File

@@ -0,0 +1,228 @@
<template>
<div class="layout-padding">
<div class="layout-padding-auto layout-padding-view">
<!-- 操作按钮区域 -->
<el-row>
<div class="mb8" style="width: 100%">
<el-button
icon="folder-add"
type="primary"
class="ml10"
@click="formDialogRef.openDialog()"
v-auth="'purchase_purchasingAccept_add'"
>
新增
</el-button>
<el-button
plain
icon="upload-filled"
type="primary"
class="ml10"
@click="excelUploadRef.show()"
v-auth="'purchase_purchasingAccept_add'"
>
导入
</el-button>
<el-button
plain
:disabled="multiple"
icon="Delete"
type="primary"
v-auth="'purchase_purchasingAccept_del'"
@click="handleDelete(selectObjs)"
>
删除
</el-button>
<right-toolbar
v-model:showSearch="showSearch"
:export="'purchase_purchasingAccept_export'"
@exportExcel="exportExcel"
class="ml10 mr20"
style="float: right;"
@queryTable="getDataList"
/>
</div>
</el-row>
<!-- 数据表格区域 -->
<el-table
:data="state.dataList"
v-loading="state.loading"
border
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle"
@selection-change="selectionChangHandle"
@sort-change="sortChangeHandle"
>
<el-table-column type="selection" width="40" align="center" />
<el-table-column type="index" label="#" width="40" />
<el-table-column
prop="remark"
label="remark"
show-overflow-tooltip
/>
<el-table-column
prop="purchaseId"
label="采购申请ID"
show-overflow-tooltip
/>
<el-table-column
prop="acceptDate"
label="验收日期"
show-overflow-tooltip
/>
<el-table-column
prop="acceptAddress"
label="验收地点"
show-overflow-tooltip
/>
<el-table-column
prop="batch"
label="验收批次"
show-overflow-tooltip
/>
<el-table-column
prop="question"
label="问题意见"
show-overflow-tooltip
/>
<el-table-column label="操作" width="150">
<template #default="scope">
<el-button
icon="edit-pen"
text
type="primary"
v-auth="'purchase_purchasingAccept_edit'"
@click="formDialogRef.openDialog(scope.row.id)"
>
编辑
</el-button>
<el-button
icon="delete"
text
type="primary"
v-auth="'purchase_purchasingAccept_del'"
@click="handleDelete([scope.row.id])"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页组件 -->
<pagination
@size-change="sizeChangeHandle"
@current-change="currentChangeHandle"
v-bind="state.pagination"
/>
</div>
<!-- 编辑新增弹窗 -->
<form-dialog ref="formDialogRef" @refresh="getDataList(false)" />
<!-- 导入excel弹窗 (需要在 upms-biz/resources/file 下维护模板) -->
<upload-excel
ref="excelUploadRef"
title="导入"
url="/purchase/purchasingAccept/import"
temp-url="/admin/sys-file/local/file/purchasingAccept.xlsx"
@refreshDataList="getDataList"
/>
</div>
</template>
<script setup lang="ts" name="systemPurchasingAccept">
// ========== 导入声明 ==========
import { BasicTableProps, useTable } from "/@/hooks/table";
import { fetchList, delObjs } from "/@/api/purchase/purchasingAccept";
import { useMessage, useMessageBox } from "/@/hooks/message";
import { useDict } from '/@/hooks/dict';
// ========== 组件声明 ==========
// 异步加载表单弹窗组件
const FormDialog = defineAsyncComponent(() => import('./form.vue'));
// ========== 字典数据 ==========
// ========== 组件引用 ==========
const formDialogRef = ref(); // 表单弹窗引用
const excelUploadRef = ref(); // Excel上传弹窗引用
const queryRef = ref(); // 查询表单引用
// ========== 响应式数据 ==========
const showSearch = ref(true); // 是否显示搜索区域
const selectObjs = ref([]) as any; // 表格多选数据
const multiple = ref(true); // 是否多选
// ========== 表格状态 ==========
const state: BasicTableProps = reactive<BasicTableProps>({
queryForm: {}, // 查询参数
pageList: fetchList // 分页查询方法
});
// ========== Hook引用 ==========
// 表格相关Hook
const {
getDataList,
currentChangeHandle,
sizeChangeHandle,
sortChangeHandle,
downBlobFile,
tableStyle
} = useTable(state);
// ========== 方法定义 ==========
/**
* 重置查询条件
*/
const resetQuery = () => {
// 清空搜索条件
queryRef.value?.resetFields();
// 清空多选
selectObjs.value = [];
// 重新查询
getDataList();
};
/**
* 导出Excel文件
*/
const exportExcel = () => {
downBlobFile(
'/purchase/purchasingAccept/export',
Object.assign(state.queryForm, { ids: selectObjs }),
'purchasingAccept.xlsx'
);
};
/**
* 表格多选事件处理
* @param objs 选中的数据行
*/
const selectionChangHandle = (objs: { id: string }[]) => {
selectObjs.value = objs.map(({ id }) => id);
multiple.value = !objs.length;
};
/**
* 删除数据处理
* @param ids 要删除的数据ID数组
*/
const handleDelete = async (ids: string[]) => {
try {
await useMessageBox().confirm('此操作将永久删除');
} catch {
return;
}
try {
await delObjs(ids);
getDataList();
useMessage().success('删除成功');
} catch (err: any) {
useMessage().error(err.msg);
}
};
</script>