合并代码

This commit is contained in:
2026-01-29 11:47:29 +08:00
parent e35d3c2d16
commit c7a686dcaa
12 changed files with 6674 additions and 4237 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,78 @@
/*
* 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 params 查询参数
*/
export function getPage(params?: any) {
return request({
url: '/purchase/purchasingagent/page',
method: 'get',
params
});
}
/**
* 通过id查询
* @param id ID
*/
export function getObj(id: string | number) {
return request({
url: '/purchase/purchasingagent/' + id,
method: 'get'
});
}
/**
* 新增招标代理
* @param obj 对象数据
*/
export function addObj(obj: any) {
return request({
url: '/purchase/purchasingagent',
method: 'post',
data: obj
});
}
/**
* 修改招标代理
* @param obj 对象数据
*/
export function editObj(obj: any) {
return request({
url: '/purchase/purchasingagent/edit',
method: 'post',
data: obj
});
}
/**
* 删除招标代理
* @param id ID
*/
export function delObj(id: string | number) {
return request({
url: '/purchase/purchasingagent/delete',
method: 'post',
data: id
});
}

View File

@@ -0,0 +1,67 @@
/*
* 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 params 查询参数
*/
export function getTree(params?: any) {
return request({
url: '/purchase/purchasingcategory/tree',
method: 'get',
params
});
}
/**
* 新增
* @param obj 对象数据
*/
export function addObj(obj: any) {
return request({
url: '/purchase/purchasingcategory',
method: 'post',
data: obj
});
}
/**
* 删除
* @param id ID
*/
export function delObj(id: string | number) {
return request({
url: '/purchase/purchasingcategory/delete',
method: 'post',
data: id
});
}
/**
* 更新
* @param obj 对象数据
*/
export function editObj(obj: any) {
return request({
url: '/purchase/purchasingcategory/edit',
method: 'post',
data: obj
});
}

View File

@@ -0,0 +1,102 @@
/*
* 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 params 查询参数
*/
export function getPage(params?: any) {
return request({
url: '/purchase/purchasingapply/page',
method: 'get',
params
});
}
/**
* 通过id查询
* @param id ID
*/
export function getObj(id: number) {
return request({
url: '/purchase/purchasingapply/' + id,
method: 'get'
});
}
/**
* 新增采购申请(提交并启动流程)
* @param obj 对象数据
*/
export function addObj(obj: any) {
return request({
url: '/purchase/purchasingapply/submit',
method: 'post',
data: obj
});
}
/**
* 暂存采购申请
* @param obj 对象数据
*/
export function tempStore(obj: any) {
return request({
url: '/purchase/purchasingapply/temp-store',
method: 'post',
data: obj
});
}
/**
* 提交采购申请(启动流程)
* @param obj 对象数据
*/
export function submitObj(obj: any) {
return request({
url: '/purchase/purchasingapply/submit',
method: 'post',
data: obj
});
}
/**
* 修改采购申请
* @param obj 对象数据
*/
export function editObj(obj: any) {
return request({
url: '/purchase/purchasingapply/edit',
method: 'post',
data: obj
});
}
/**
* 删除采购申请
* @param id ID
*/
export function delObj(id: number) {
return request({
url: '/purchase/purchasingapply/delete',
method: 'post',
data: id
});
}

View File

@@ -0,0 +1,128 @@
<template>
<el-dialog
:title="dataForm.id ? '编辑' : '新增'"
v-model="visible"
width="600px"
:close-on-click-modal="false"
draggable>
<el-form
ref="formRef"
:model="dataForm"
:rules="dataRules"
label-width="100px"
v-loading="loading">
<el-form-item label="代理名称" prop="agentName">
<el-input
v-model="dataForm.agentName"
placeholder="请输入代理名称"
clearable />
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input
v-model="dataForm.remark"
type="textarea"
:rows="3"
placeholder="请输入备注"
clearable />
</el-form-item>
</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="PurchaseAgentForm">
import { reactive, ref, nextTick } from 'vue'
import { getObj, addObj, editObj } from '/@/api/finance/purchaseagent';
import { useMessage } from '/@/hooks/message';
// 定义子组件向父组件传值/事件
const emit = defineEmits(['refresh']);
// 定义变量内容
const formRef = ref();
const dataForm = reactive({
id: '',
agentName: '',
remark: '',
});
const visible = ref(false);
const loading = ref(false);
const dataRules = ref({
agentName: [
{ required: true, message: '请输入代理名称', trigger: 'blur' }
],
});
// 打开弹窗
const openDialog = async (type: string, rowData?: any) => {
visible.value = true;
dataForm.id = '';
dataForm.agentName = '';
dataForm.remark = '';
nextTick(() => {
formRef.value?.resetFields();
if (type === 'edit' && rowData?.id) {
// 编辑时,先获取详情数据
loading.value = true;
getObj(rowData.id).then((res: any) => {
if (res.data) {
Object.assign(dataForm, {
id: res.data.id || '',
agentName: res.data.agentName || '',
remark: res.data.remark || '',
});
}
loading.value = false;
}).catch((err: any) => {
useMessage().error(err.msg || '获取详情失败');
loading.value = false;
});
}
});
};
// 提交
const onSubmit = async () => {
// 立即设置 loading防止重复点击
if (loading.value) return;
loading.value = true;
try {
const valid = await formRef.value.validate().catch(() => {});
if (!valid) {
loading.value = false;
return false;
}
if (dataForm.id) {
await editObj(dataForm);
useMessage().success('编辑成功');
} else {
await addObj(dataForm);
useMessage().success('新增成功');
}
visible.value = false;
emit('refresh');
} catch (err: any) {
useMessage().error(err.msg || (dataForm.id ? '编辑失败' : '新增失败'));
} finally {
loading.value = false;
}
};
// 暴露变量
defineExpose({
openDialog,
});
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,185 @@
<template>
<div class="modern-page-container">
<div class="page-wrapper">
<!-- 搜索表单卡片 -->
<el-card v-show="showSearch" class="search-card" shadow="never">
<template #header>
<div class="card-header">
<span class="card-title">
<el-icon class="title-icon"><Search /></el-icon>
筛选条件
</span>
</div>
</template>
<el-form :model="state.queryForm" ref="searchFormRef" :inline="true" @keyup.enter="getDataList" class="search-form">
<el-form-item label="代理名称" prop="agentName">
<el-input
v-model="state.queryForm.agentName"
placeholder="请输入代理名称"
clearable
style="width: 200px" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="getDataList">查询</el-button>
<el-button icon="Refresh" @click="handleReset">重置</el-button>
</el-form-item>
</el-form>
</el-card>
<!-- 内容卡片 -->
<el-card class="content-card" shadow="never">
<template #header>
<div class="card-header">
<span class="card-title">
<el-icon class="title-icon"><Document /></el-icon>
招标代理管理
</span>
<div class="header-actions">
<el-button
icon="FolderAdd"
type="primary"
@click="formDialogRef.openDialog('add')">
新增
</el-button>
<right-toolbar v-model:showSearch="showSearch" class="ml10" @queryTable="getDataList" />
</div>
</div>
</template>
<!-- 表格 -->
<el-table
ref="tableRef"
:data="state.dataList"
v-loading="state.loading"
stripe
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle"
class="modern-table">
<el-table-column type="index" label="序号" width="70" align="center">
<template #header>
<el-icon><List /></el-icon>
</template>
</el-table-column>
<el-table-column prop="agentName" label="代理名称" min-width="200" show-overflow-tooltip>
<template #header>
<el-icon><Document /></el-icon>
<span style="margin-left: 4px">代理名称</span>
</template>
</el-table-column>
<el-table-column prop="remark" label="备注" min-width="300" show-overflow-tooltip>
<template #header>
<el-icon><EditPen /></el-icon>
<span style="margin-left: 4px">备注</span>
</template>
</el-table-column>
<el-table-column prop="createTime" label="创建时间" width="180" show-overflow-tooltip>
<template #header>
<el-icon><Clock /></el-icon>
<span style="margin-left: 4px">创建时间</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" fixed="right" width="150">
<template #default="scope">
<el-button
icon="Edit"
link
type="primary"
@click="formDialogRef.openDialog('edit', scope.row)">
编辑
</el-button>
<el-button
icon="Delete"
link
type="danger"
@click="handleDelete(scope.row)">
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<pagination
v-show="state.total > 0"
:total="state.total"
v-model:page="state.page"
v-model:limit="state.limit"
@pagination="getDataList"
/>
</el-card>
</div>
<!-- 编辑新增表单对话框 -->
<FormDialog ref="formDialogRef" @refresh="getDataList" />
</div>
</template>
<script setup lang="ts" name="PurchaseAgent">
import { ref, reactive, defineAsyncComponent } from 'vue'
import { BasicTableProps, useTable } from "/@/hooks/table";
import { getPage, delObj } from "/@/api/finance/purchaseagent";
import { useMessage, useMessageBox } from "/@/hooks/message";
import { List, Document, EditPen, Clock, Search } from '@element-plus/icons-vue'
// 引入组件
const FormDialog = defineAsyncComponent(() => import('./form.vue'));
// 定义变量内容
const tableRef = ref()
const formDialogRef = ref()
const searchFormRef = ref()
const showSearch = ref(true)
/**
* 定义响应式表格数据
*/
const state: BasicTableProps = reactive<BasicTableProps>({
pageList: getPage,
queryForm: {
agentName: '',
},
createdIsNeed: true
});
/**
* 使用 useTable 定义表格相关操作
*/
const { getDataList, tableStyle } = useTable(state);
/**
* 重置搜索表单
*/
const handleReset = () => {
searchFormRef.value?.resetFields();
getDataList();
};
/**
* 删除当前行
* @param row - 当前行数据
*/
const handleDelete = async (row: any) => {
try {
await useMessageBox().confirm('确定要删除该记录吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
});
} catch {
return;
}
try {
await delObj(row.id);
useMessage().success('删除成功');
getDataList();
} catch (err: any) {
useMessage().error(err.msg || '删除失败');
}
};
</script>
<style scoped lang="scss">
@import '/@/assets/styles/modern-page.scss';
</style>

View File

@@ -0,0 +1,182 @@
<template>
<el-dialog
:title="dataForm.id ? '编辑' : '新增'"
v-model="visible"
width="600px"
:close-on-click-modal="false"
draggable>
<el-form
ref="formRef"
:model="dataForm"
:rules="dataRules"
label-width="100px"
v-loading="loading">
<el-form-item label="父级节点" prop="parentCode">
<el-tree-select
v-model="dataForm.parentCode"
:data="parentData"
:props="{ value: 'code', label: 'name', children: 'children' }"
class="w100"
clearable
check-strictly
:render-after-expand="false"
placeholder="请选择父级节点(不选则为根节点)"
/>
</el-form-item>
<el-form-item label="品目编码" prop="code">
<el-input
v-model="dataForm.code"
placeholder="请输入品目编码"
clearable
:disabled="!!dataForm.id" />
</el-form-item>
<el-form-item label="品目名称" prop="name">
<el-input
v-model="dataForm.name"
placeholder="请输入品目名称"
clearable />
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input
v-model="dataForm.remark"
type="textarea"
:rows="3"
placeholder="请输入备注"
clearable />
</el-form-item>
</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="PurchasingCategoryForm">
import { reactive, ref, nextTick } from 'vue'
import { getTree, addObj, editObj } from '/@/api/finance/purchasingcategory';
import { useMessage } from '/@/hooks/message';
// 定义子组件向父组件传值/事件
const emit = defineEmits(['refresh']);
// 定义变量内容
const formRef = ref();
const dataForm = reactive({
id: '',
parentCode: '',
code: '',
name: '',
remark: '',
isMallService: '',
isMallProject: '',
});
const parentData = ref<any[]>([]);
const visible = ref(false);
const loading = ref(false);
const dataRules = ref({
code: [
{ required: true, message: '请输入品目编码', trigger: 'blur' }
],
name: [
{ required: true, message: '请输入品目名称', trigger: 'blur' }
],
});
// 打开弹窗
const openDialog = (type: string, rowData?: any) => {
visible.value = true;
dataForm.id = '';
dataForm.parentCode = '';
dataForm.code = '';
dataForm.name = '';
dataForm.remark = '';
dataForm.isMallService = '';
dataForm.isMallProject = '';
nextTick(() => {
formRef.value?.resetFields();
if (type === 'add' && rowData?.code) {
// 新增时rowData 是父节点数据,设置父级编码
dataForm.parentCode = rowData.code;
} else if (type === 'edit' && rowData) {
// 编辑时rowData 是当前行数据
Object.assign(dataForm, {
id: rowData.id || '',
parentCode: rowData.parentCode || '',
code: rowData.code || '',
name: rowData.name || '',
remark: rowData.remark || '',
isMallService: rowData.isMallService || '',
isMallProject: rowData.isMallProject || '',
});
}
});
getTreeData();
};
// 提交
const onSubmit = async () => {
// 立即设置 loading防止重复点击
if (loading.value) return;
loading.value = true;
try {
const valid = await formRef.value.validate().catch(() => {});
if (!valid) {
loading.value = false;
return false;
}
if (dataForm.id) {
await editObj(dataForm);
useMessage().success('编辑成功');
} else {
await addObj(dataForm);
useMessage().success('新增成功');
}
visible.value = false;
emit('refresh');
} catch (err: any) {
useMessage().error(err.msg || (dataForm.id ? '编辑失败' : '新增失败'));
} finally {
loading.value = false;
}
};
// 从后端获取树形数据
const getTreeData = async () => {
try {
const res = await getTree();
parentData.value = [];
const root = {
code: '',
name: '根节点',
children: [] as any[],
};
if (res.data && Array.isArray(res.data)) {
root.children = res.data;
}
parentData.value.push(root);
} catch (err: any) {
useMessage().error(err.msg || '获取树形数据失败');
parentData.value = [];
}
};
// 暴露变量
defineExpose({
openDialog,
});
</script>
<style scoped>
.w100 {
width: 100%;
}
</style>

View File

@@ -0,0 +1,164 @@
<template>
<div class="modern-page-container">
<div class="page-wrapper">
<!-- 内容卡片 -->
<el-card class="content-card" shadow="never">
<template #header>
<div class="card-header">
<span class="card-title">
<el-icon class="title-icon"><Document /></el-icon>
采购品目管理
</span>
<div class="header-actions">
<el-button
icon="FolderAdd"
type="primary"
@click="formDialogRef.openDialog('add')">
新增
</el-button>
<right-toolbar class="ml10" />
</div>
</div>
</template>
<!-- 树形表格 -->
<el-table
ref="tableRef"
:data="state.dataList"
v-loading="state.loading"
stripe
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle"
class="modern-table"
row-key="code"
:tree-props="{ children: 'children', hasChildren: 'hasChildren' }">
<el-table-column type="index" label="序号" width="70" align="center">
<template #header>
<el-icon><List /></el-icon>
</template>
<template #default="{ $index, row }">
{{ getRowIndex($index, row) }}
</template>
</el-table-column>
<el-table-column prop="code" label="品目编码" min-width="150" show-overflow-tooltip>
<template #header>
<el-icon><DocumentCopy /></el-icon>
<span style="margin-left: 4px">品目编码</span>
</template>
</el-table-column>
<el-table-column prop="name" label="品目名称" min-width="200" show-overflow-tooltip>
<template #header>
<el-icon><Document /></el-icon>
<span style="margin-left: 4px">品目名称</span>
</template>
</el-table-column>
<el-table-column prop="remark" label="备注" min-width="200" show-overflow-tooltip>
<template #header>
<el-icon><EditPen /></el-icon>
<span style="margin-left: 4px">备注</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" fixed="right" width="150">
<template #default="scope">
<el-button
icon="Edit"
link
type="primary"
@click="formDialogRef.openDialog('edit', scope.row)">
编辑
</el-button>
<el-button
icon="Delete"
link
type="danger"
@click="handleDelete(scope.row)">
删除
</el-button>
</template>
</el-table-column>
</el-table>
</el-card>
</div>
<!-- 编辑新增表单对话框 -->
<FormDialog ref="formDialogRef" @refresh="getDataList" />
</div>
</template>
<script setup lang="ts" name="PurchasingCategory">
import { ref, reactive, defineAsyncComponent } from 'vue'
import { BasicTableProps, useTable } from "/@/hooks/table";
import { getTree, delObj } from "/@/api/finance/purchasingcategory";
import { useMessage, useMessageBox } from "/@/hooks/message";
import { List, Document, DocumentCopy, EditPen } from '@element-plus/icons-vue'
// 引入组件
const FormDialog = defineAsyncComponent(() => import('./form.vue'));
// 定义变量内容
const tableRef = ref()
const formDialogRef = ref()
/**
* 查询树形数据方法
* @param params - 查询参数
* @returns Promise<any>
*/
const queryTree = (params?: any) => {
return getTree(params);
};
/**
* 定义响应式表格数据
*/
const state: BasicTableProps = reactive<BasicTableProps>({
pageList: queryTree,
queryForm: {},
isPage: false, // 树形表格不分页
});
/**
* 使用 useTable 定义表格相关操作
*/
const { getDataList, tableStyle } = useTable(state);
/**
* 计算行序号(考虑树形结构)
*/
const getRowIndex = (index: number, row: any) => {
// 对于树形表格,序号需要根据实际显示的行来计算
// 这里简化处理,直接返回 index + 1
return index + 1;
};
/**
* 删除当前行
* @param row - 当前行数据
*/
const handleDelete = async (row: any) => {
try {
await useMessageBox().confirm('确定要删除该记录吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
});
} catch {
return;
}
try {
// 使用 id 或 code 作为删除参数(根据后端接口决定)
const deleteId = row.id || row.code;
await delObj(deleteId);
useMessage().success('删除成功');
getDataList();
} catch (err: any) {
useMessage().error(err.msg || '删除失败');
}
};
</script>
<style scoped lang="scss">
@import '/@/assets/styles/modern-page.scss';
</style>

View File

@@ -0,0 +1,434 @@
<template>
<el-dialog
:title="dialogTitle"
v-model="visible"
width="900px"
:close-on-click-modal="false"
draggable>
<el-form
ref="formRef"
:model="dataForm"
:rules="dataRules"
label-width="140px"
v-loading="loading">
<el-row :gutter="24">
<el-col :span="12" class="mb20">
<el-form-item label="采购项目名称" prop="projectName">
<el-input
v-model="dataForm.projectName"
placeholder="请输入采购项目名称"
clearable
:disabled="isView" />
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="项目类别" prop="projectType">
<el-select
v-model="dataForm.projectType"
placeholder="请选择项目类别"
clearable
:disabled="isView"
style="width: 100%">
<el-option label="货物" value="A" />
<el-option label="工程" value="B" />
<el-option label="服务" value="C" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="24">
<el-col :span="12" class="mb20">
<el-form-item label="品目编码" prop="categoryCode">
<el-tree-select
v-model="dataForm.categoryCode"
:data="categoryTreeData"
:props="{ value: 'code', label: 'name', children: 'children' }"
placeholder="请选择品目编码"
clearable
check-strictly
:render-after-expand="false"
:disabled="isView"
style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="填报日期" prop="applyDate">
<el-date-picker
v-model="dataForm.applyDate"
type="date"
placeholder="请选择填报日期"
format="YYYY-MM-DD"
value-format="YYYY-MM-DD"
:disabled="isView"
style="width: 100%" />
</el-form-item>
</el-col>
</el-row>
<el-form-item label="采购内容" prop="projectContent" class="mb20">
<el-input
v-model="dataForm.projectContent"
type="textarea"
:rows="3"
placeholder="请输入采购内容"
clearable
:disabled="isView" />
</el-form-item>
<el-row :gutter="24">
<el-col :span="12" class="mb20">
<el-form-item label="资金来源" prop="fundSource">
<el-input
v-model="dataForm.fundSource"
placeholder="请输入资金来源"
clearable
:disabled="isView" />
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="预算金额(元)" prop="budget">
<el-input-number
v-model="dataForm.budget"
:min="0.01"
:precision="2"
placeholder="请输入预算金额"
:disabled="isView"
style="width: 100%" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="24">
<el-col :span="12" class="mb20">
<el-form-item label="是否集采" prop="isCentralized">
<el-select
v-model="dataForm.isCentralized"
placeholder="请选择是否集采"
clearable
:disabled="isView"
style="width: 100%">
<el-option label="否" value="0" />
<el-option label="是" value="1" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="是否特殊情况" prop="isSpecial">
<el-select
v-model="dataForm.isSpecial"
placeholder="请选择是否特殊情况"
clearable
:disabled="isView"
style="width: 100%">
<el-option label="否" value="0" />
<el-option label="紧急" value="1" />
<el-option label="单一" value="2" />
<el-option label="进口" value="3" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="24">
<el-col :span="12" class="mb20">
<el-form-item label="采购形式" prop="purchaseMode">
<el-select
v-model="dataForm.purchaseMode"
placeholder="请选择采购形式"
clearable
:disabled="isView"
style="width: 100%">
<el-option label="部门自行采购" value="0" />
<el-option label="学校统一采购" value="2" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="学校统一采购方式" prop="purchaseSchool">
<el-select
v-model="dataForm.purchaseSchool"
placeholder="请选择学校统一采购方式"
clearable
:disabled="isView || dataForm.purchaseMode !== '2'"
style="width: 100%">
<el-option label="无" value="0" />
<el-option label="政府采购" value="1" />
<el-option label="学校自主采购" value="2" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-form-item label="采购方式" prop="purchaseType" class="mb20">
<el-input
v-model="dataForm.purchaseType"
placeholder="请输入采购方式"
clearable
:disabled="isView" />
</el-form-item>
<el-form-item label="附件" prop="fileIds" class="mb20">
<upload-file
v-model="dataForm.fileIds"
:limit="10"
:disabled="isView"
upload-file-url="/purchase/purchasingfiles/upload" />
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input
v-model="dataForm.remark"
type="textarea"
:rows="3"
placeholder="请输入备注"
clearable
:disabled="isView" />
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="visible = false">{{ isView ? '关闭' : '取消' }}</el-button>
<template v-if="!isView">
<el-button v-if="dataForm.id && dataForm.status === '-1'" type="warning" @click="handleTempStore" :disabled="loading">暂存</el-button>
<el-button type="primary" @click="handleSubmit" :disabled="loading">{{ dataForm.id ? '保存' : '提交' }}</el-button>
</template>
</span>
</template>
</el-dialog>
</template>
<script setup lang="ts" name="PurchasingRequisitionForm">
import { reactive, ref, nextTick, computed } from 'vue'
import { getObj, addObj, editObj, tempStore, submitObj } from '/@/api/finance/purchasingrequisition';
import { getTree } from '/@/api/finance/purchasingcategory';
import { useMessage } from '/@/hooks/message';
import UploadFile from '/@/components/Upload/index.vue';
// 定义子组件向父组件传值/事件
const emit = defineEmits(['refresh']);
// 定义变量内容
const formRef = ref();
const dataForm = reactive({
id: '',
projectName: '',
projectType: '',
projectContent: '',
applyDate: '',
fundSource: '',
budget: null as number | null,
isCentralized: '',
isSpecial: '',
purchaseMode: '',
purchaseSchool: '',
purchaseType: '',
categoryCode: '',
fileIds: '',
remark: '',
status: '',
});
const categoryTreeData = ref<any[]>([]);
const visible = ref(false);
const loading = ref(false);
const dialogType = ref<'add' | 'edit' | 'view'>('add');
const isView = computed(() => dialogType.value === 'view');
const dialogTitle = computed(() => {
if (dialogType.value === 'view') return '查看采购申请';
if (dialogType.value === 'edit') return '编辑采购申请';
return '新增采购申请';
});
const dataRules = reactive({
projectContent: [
{ required: true, message: '采购内容不能为空', trigger: 'blur' }
],
budget: [
{ required: true, message: '预算金额不能为空', trigger: 'blur' },
{ type: 'number', min: 0.01, message: '预算金额必须大于0.01', trigger: 'blur' }
],
isCentralized: [
{ required: true, message: '请选择是否集采', trigger: 'change' }
],
isSpecial: [
{ required: true, message: '请选择是否特殊情况', trigger: 'change' }
],
});
// 打开弹窗
const openDialog = async (type: 'add' | 'edit' | 'view', rowData?: any) => {
dialogType.value = type;
visible.value = true;
// 重置表单
dataForm.id = '';
dataForm.projectName = '';
dataForm.projectType = '';
dataForm.projectContent = '';
dataForm.applyDate = '';
dataForm.fundSource = '';
dataForm.budget = null;
dataForm.isCentralized = '';
dataForm.isSpecial = '';
dataForm.purchaseMode = '';
dataForm.purchaseSchool = '';
dataForm.purchaseType = '';
dataForm.categoryCode = '';
dataForm.fileIds = '';
dataForm.remark = '';
dataForm.status = '';
await getCategoryTreeData();
nextTick(() => {
formRef.value?.resetFields();
if (type === 'edit' || type === 'view') {
loading.value = true;
getObj(rowData.id)
.then((res) => {
Object.assign(dataForm, {
id: res.data.id || '',
projectName: res.data.projectName || '',
projectType: res.data.projectType || '',
projectContent: res.data.projectContent || '',
applyDate: res.data.applyDate || '',
fundSource: res.data.fundSource || '',
budget: res.data.budget || null,
isCentralized: res.data.isCentralized || '',
isSpecial: res.data.isSpecial || '',
purchaseMode: res.data.purchaseMode || '',
purchaseSchool: res.data.purchaseSchool || '',
purchaseType: res.data.purchaseType || '',
categoryCode: res.data.categoryCode || '',
fileIds: res.data.fileIds ? (Array.isArray(res.data.fileIds) ? res.data.fileIds.join(',') : res.data.fileIds) : '',
remark: res.data.remark || '',
status: res.data.status || '',
});
})
.catch((err: any) => {
useMessage().error(err.msg || '获取详情失败');
})
.finally(() => {
loading.value = false;
});
}
});
};
// 获取品目树形数据
const getCategoryTreeData = async () => {
try {
const res = await getTree();
categoryTreeData.value = [];
if (res.data && Array.isArray(res.data)) {
categoryTreeData.value = res.data;
}
} catch (err: any) {
console.error('获取品目树形数据失败', err);
categoryTreeData.value = [];
}
};
// 处理文件ID字符串转数组
// 从URL中提取文件IDURL格式通常为: /admin/sys-file/show?fileName=xxx&id=xxx
const getFileIdsArray = (): string[] => {
if (!dataForm.fileIds) return [];
if (Array.isArray(dataForm.fileIds)) return dataForm.fileIds;
// 如果是逗号分隔的URL字符串需要从URL中提取文件ID
const urls = dataForm.fileIds.split(',').filter(url => url.trim());
const fileIds: string[] = [];
urls.forEach(url => {
try {
// 尝试从URL参数中提取id
const urlObj = new URL(url, window.location.origin);
const id = urlObj.searchParams.get('id') || urlObj.searchParams.get('fileName');
if (id) {
fileIds.push(id);
} else {
// 如果没有id参数尝试从路径中提取
const pathParts = urlObj.pathname.split('/');
const lastPart = pathParts[pathParts.length - 1];
if (lastPart) {
fileIds.push(lastPart);
}
}
} catch {
// 如果URL解析失败直接使用原始值
fileIds.push(url);
}
});
return fileIds;
};
// 提交(新增或编辑)
const handleSubmit = async () => {
if (loading.value) return;
loading.value = true;
try {
const valid = await formRef.value?.validate().catch(() => {});
if (!valid) {
loading.value = false;
return false;
}
const submitData = {
...dataForm,
fileIds: getFileIdsArray(),
};
if (dataForm.id) {
// 编辑
await editObj(submitData);
useMessage().success('保存成功');
} else {
// 新增(暂存,不启动流程)
await addObj(submitData);
useMessage().success('提交成功');
}
visible.value = false;
emit('refresh');
} catch (err: any) {
useMessage().error(err.msg || (dataForm.id ? '保存失败' : '提交失败'));
} finally {
loading.value = false;
}
};
// 暂存
const handleTempStore = async () => {
if (loading.value) return;
loading.value = true;
try {
const valid = await formRef.value?.validate().catch(() => {});
if (!valid) {
loading.value = false;
return false;
}
const submitData = {
...dataForm,
fileIds: getFileIdsArray(),
};
await tempStore(submitData);
useMessage().success('暂存成功');
visible.value = false;
emit('refresh');
} catch (err: any) {
useMessage().error(err.msg || '暂存失败');
} finally {
loading.value = false;
}
};
// 暴露变量
defineExpose({
openDialog,
});
</script>
<style scoped>
.mb20 {
margin-bottom: 20px;
}
</style>

View File

@@ -0,0 +1,293 @@
<template>
<div class="modern-page-container">
<div class="page-wrapper">
<!-- 搜索表单卡片 -->
<el-card v-show="showSearch" class="search-card" shadow="never">
<template #header>
<div class="card-header">
<span class="card-title">
<el-icon class="title-icon"><Search /></el-icon>
筛选条件
</span>
</div>
</template>
<el-form :model="state.queryForm" ref="searchFormRef" :inline="true" @keyup.enter="getDataList" class="search-form">
<el-form-item label="采购编号" prop="purchaseNo">
<el-input
v-model="state.queryForm.purchaseNo"
placeholder="请输入采购编号"
clearable
style="width: 200px" />
</el-form-item>
<el-form-item label="采购项目名称" prop="projectName">
<el-input
v-model="state.queryForm.projectName"
placeholder="请输入采购项目名称"
clearable
style="width: 200px" />
</el-form-item>
<el-form-item label="项目类别" prop="projectType">
<el-select
v-model="state.queryForm.projectType"
placeholder="请选择项目类别"
clearable
style="width: 200px">
<el-option label="货物" value="A" />
<el-option label="工程" value="B" />
<el-option label="服务" value="C" />
</el-select>
</el-form-item>
<el-form-item label="状态" prop="status">
<el-select
v-model="state.queryForm.status"
placeholder="请选择状态"
clearable
style="width: 200px">
<el-option label="撤回" value="-2" />
<el-option label="暂存" value="-1" />
<el-option label="运行中" value="0" />
<el-option label="完成" value="1" />
<el-option label="作废" value="2" />
<el-option label="终止" value="3" />
</el-select>
</el-form-item>
<el-form-item label="是否集采" prop="isCentralized">
<el-select
v-model="state.queryForm.isCentralized"
placeholder="请选择是否集采"
clearable
style="width: 200px">
<el-option label="否" value="0" />
<el-option label="是" value="1" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="getDataList">查询</el-button>
<el-button icon="Refresh" @click="handleReset">重置</el-button>
</el-form-item>
</el-form>
</el-card>
<!-- 内容卡片 -->
<el-card class="content-card" shadow="never">
<template #header>
<div class="card-header">
<span class="card-title">
<el-icon class="title-icon"><Document /></el-icon>
采购申请管理
</span>
<div class="header-actions">
<el-button
icon="FolderAdd"
type="primary"
@click="formDialogRef.openDialog('add')">
新增
</el-button>
<right-toolbar v-model:showSearch="showSearch" class="ml10" @queryTable="getDataList" />
</div>
</div>
</template>
<!-- 表格 -->
<el-table
ref="tableRef"
:data="state.dataList"
v-loading="state.loading"
stripe
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle"
class="modern-table">
<el-table-column type="index" label="序号" width="70" align="center">
<template #header>
<el-icon><List /></el-icon>
</template>
</el-table-column>
<el-table-column prop="code" label="编号" min-width="120" show-overflow-tooltip>
<template #header>
<el-icon><DocumentCopy /></el-icon>
<span style="margin-left: 4px">编号</span>
</template>
</el-table-column>
<el-table-column prop="purchaseNo" label="采购编号" min-width="120" show-overflow-tooltip>
<template #header>
<el-icon><DocumentCopy /></el-icon>
<span style="margin-left: 4px">采购编号</span>
</template>
</el-table-column>
<el-table-column prop="projectName" label="采购项目名称" min-width="200" show-overflow-tooltip>
<template #header>
<el-icon><Document /></el-icon>
<span style="margin-left: 4px">采购项目名称</span>
</template>
</el-table-column>
<el-table-column prop="projectType" label="项目类别" width="100" align="center">
<template #header>
<el-icon><Collection /></el-icon>
<span style="margin-left: 4px">项目类别</span>
</template>
<template #default="scope">
<el-tag v-if="scope.row.projectType === 'A'" type="success">货物</el-tag>
<el-tag v-else-if="scope.row.projectType === 'B'" type="warning">工程</el-tag>
<el-tag v-else-if="scope.row.projectType === 'C'" type="info">服务</el-tag>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column prop="budget" label="预算金额(元)" width="120" align="right">
<template #header>
<el-icon><Money /></el-icon>
<span style="margin-left: 4px">预算金额</span>
</template>
<template #default="scope">
{{ scope.row.budget ? Number(scope.row.budget).toLocaleString() : '-' }}
</template>
</el-table-column>
<el-table-column prop="isCentralized" label="是否集采" width="100" align="center">
<template #header>
<el-icon><CircleCheck /></el-icon>
<span style="margin-left: 4px">是否集采</span>
</template>
<template #default="scope">
<el-tag v-if="scope.row.isCentralized === '1'" type="success"></el-tag>
<el-tag v-else-if="scope.row.isCentralized === '0'" type="info"></el-tag>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column prop="status" label="状态" width="100" align="center">
<template #header>
<el-icon><InfoFilled /></el-icon>
<span style="margin-left: 4px">状态</span>
</template>
<template #default="scope">
<el-tag v-if="scope.row.status === '-2'" type="info">撤回</el-tag>
<el-tag v-else-if="scope.row.status === '-1'" type="warning">暂存</el-tag>
<el-tag v-else-if="scope.row.status === '0'" type="primary">运行中</el-tag>
<el-tag v-else-if="scope.row.status === '1'" type="success">完成</el-tag>
<el-tag v-else-if="scope.row.status === '2'" type="danger">作废</el-tag>
<el-tag v-else-if="scope.row.status === '3'" type="info">终止</el-tag>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column prop="createTime" label="创建时间" width="180" show-overflow-tooltip>
<template #header>
<el-icon><Clock /></el-icon>
<span style="margin-left: 4px">创建时间</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" fixed="right" width="200">
<template #default="scope">
<el-button
icon="View"
link
type="primary"
@click="formDialogRef.openDialog('view', scope.row)">
查看
</el-button>
<el-button
v-if="scope.row.status === '-1'"
icon="Edit"
link
type="primary"
@click="formDialogRef.openDialog('edit', scope.row)">
编辑
</el-button>
<el-button
v-if="scope.row.status === '-1'"
icon="Delete"
link
type="danger"
@click="handleDelete(scope.row)">
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<pagination
v-show="state.total > 0"
:total="state.total"
v-model:page="state.page"
v-model:limit="state.limit"
@pagination="getDataList"
/>
</el-card>
</div>
<!-- 编辑新增表单对话框 -->
<FormDialog ref="formDialogRef" @refresh="getDataList" />
</div>
</template>
<script setup lang="ts" name="PurchasingRequisition">
import { ref, reactive, defineAsyncComponent } from 'vue'
import { BasicTableProps, useTable } from "/@/hooks/table";
import { getPage, delObj } from "/@/api/finance/purchasingrequisition";
import { useMessage, useMessageBox } from "/@/hooks/message";
import { List, Document, DocumentCopy, Search, Collection, Money, CircleCheck, InfoFilled, Clock } from '@element-plus/icons-vue'
// 引入组件
const FormDialog = defineAsyncComponent(() => import('./form.vue'));
// 定义变量内容
const tableRef = ref()
const formDialogRef = ref()
const searchFormRef = ref()
const showSearch = ref(true)
/**
* 定义响应式表格数据
*/
const state: BasicTableProps = reactive<BasicTableProps>({
pageList: getPage,
queryForm: {
purchaseNo: '',
projectName: '',
projectType: '',
status: '',
isCentralized: '',
},
createdIsNeed: true
});
/**
* 使用 useTable 定义表格相关操作
*/
const { getDataList, tableStyle } = useTable(state);
/**
* 重置搜索表单
*/
const handleReset = () => {
searchFormRef.value?.resetFields();
getDataList();
};
/**
* 删除当前行
* @param row - 当前行数据
*/
const handleDelete = async (row: any) => {
try {
await useMessageBox().confirm('确定要删除该记录吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
});
} catch {
return;
}
try {
await delObj(row.id);
useMessage().success('删除成功');
getDataList();
} catch (err: any) {
useMessage().error(err.msg || '删除失败');
}
};
</script>
<style scoped lang="scss">
@import '/@/assets/styles/modern-page.scss';
</style>

View File

@@ -19,7 +19,7 @@
clearable clearable
filterable filterable
style="width: 200px"> style="width: 200px">
<el-option <el-option
v-for="item in schoolYearList" v-for="item in schoolYearList"
:key="item.year" :key="item.year"
:label="item.year" :label="item.year"