优化制度管理模块,新建制度提案页面

This commit is contained in:
chabai 2025-07-31 17:00:32 +08:00
parent 327064b7e5
commit cc65882b8d
8 changed files with 1409 additions and 290 deletions

View File

@ -23,6 +23,7 @@ export const regulationApi = {
scope: string
level: string
remark?: string
createBy?: string
}) => {
return http.post('/regulation/proposal', data)
},
@ -32,17 +33,22 @@ export const regulationApi = {
return http.put(`/regulation/proposal/${regulationId}`, data)
},
// 公示提案
publishProposal: (regulationId: string) => {
return http.post(`/regulation/proposal/${regulationId}/publish`)
},
// 删除制度提案
deleteProposal: (regulationId: string) => {
return http.del(`/regulation/proposal/${regulationId}`)
},
// 发布制度
// 公示制度
publishRegulation: (regulationId: string) => {
return http.post(`/regulation/${regulationId}/publish`)
return http.post(`/regulation/${regulationId}/approve`)
},
// 获取已发布制度列表
// 获取已公示制度列表
getPublishedRegulationList: (params: {
page: number
size: number
@ -61,6 +67,7 @@ export const regulationApi = {
page?: number
size?: number
typeName?: string
isEnabled?: string
remark?: string
}) => {
return http.get('/regulation/types', params)

View File

@ -1,11 +1,8 @@
// 制度状态枚举
export enum RegulationStatus {
DRAFT = 'DRAFT', // 草稿
VOTING = 'VOTING', // 投票中
APPROVED = 'APPROVED', // 已通过
REJECTED = 'REJECTED', // 已否决
PUBLISHED = 'PUBLISHED', // 已发布
ARCHIVED = 'ARCHIVED' // 已归档
PUBLISHED = 'PUBLISHED', // 已公示
}
// 制度级别枚举
@ -22,7 +19,7 @@ export interface Regulation {
content: string
regulationType: string
status: RegulationStatus
publishTime: string
publishTime: string // 公示时间
effectiveTime: string
expireTime: string
scope: string
@ -47,6 +44,7 @@ export interface CreateProposalRequest {
scope: string
level: RegulationLevel
remark?: string
createBy?: string
}
// 分页参数接口

View File

@ -45,6 +45,12 @@ export const systemRoutes: RouteRecordRaw[] = [
component: () => import('@/views/regulation/confirm.vue'),
meta: { title: '制度公示', icon: 'workflow', hidden: false },
},
{
path: '/regulation/proposal',
name: 'RegulationProposal',
component: () => import('@/views/regulation/proposal/index.vue'),
meta: { title: '制度提案', icon: 'edit', hidden: false },
},
{
path: '/regulation/type',
name: 'RegulationType',

View File

@ -1,14 +1,73 @@
<template>
<div class="process-management">
<a-card title="制度公示管理" :bordered="false">
<template #extra>
<a-button type="primary" @click="handleAdd">
<template #icon>
<GiSvgIcon name="plus" />
</template>
提交制度提案
<!-- 搜索区域 -->
<div class="search-container">
<a-form layout="inline" :model="searchForm" class="search-form">
<a-form-item label="提案标题">
<a-input
v-model="searchForm.title"
placeholder="请输入提案标题"
allow-clear
style="width: 200px"
@input="debouncedSearch"
/>
</a-form-item>
<a-form-item label="提案人">
<a-input
v-model="searchForm.createByName"
placeholder="请输入提案人"
allow-clear
style="width: 150px"
@input="debouncedSearch"
/>
</a-form-item>
<a-form-item label="提案类型">
<a-select
v-model="searchForm.regulationType"
placeholder="请选择类型"
allow-clear
style="width: 150px"
@change="debouncedSearch"
>
<a-option value="">全部</a-option>
<a-option value="管理规范">管理规范</a-option>
<a-option value="操作流程">操作流程</a-option>
<a-option value="安全制度">安全制度</a-option>
<a-option value="其他">其他</a-option>
</a-select>
</a-form-item>
<a-form-item label="状态">
<a-select
v-model="searchForm.status"
placeholder="请选择状态"
allow-clear
style="width: 150px"
@change="debouncedSearch"
>
<a-option value="">全部</a-option>
<a-option value="PUBLISHED">已公示</a-option>
<a-option value="APPROVED">已通过</a-option>
</a-select>
</a-form-item>
<a-form-item>
<a-space>
<a-button type="primary" @click="search">
<template #icon><icon-search /></template>
搜索
</a-button>
</template>
<a-button @click="reset">
<template #icon><icon-refresh /></template>
重置
</a-button>
</a-space>
</a-form-item>
</a-form>
</div>
<a-table
:columns="columns"
@ -18,6 +77,11 @@
@page-change="handlePageChange"
@page-size-change="handlePageSizeChange"
>
<template #toolbar-left>
<span class="search-result-info">
共找到 <strong>{{ pagination.total }}</strong> 条记录
</span>
</template>
<template #status="{ record }">
<a-tag :color="getStatusColor(record.status)">
{{ getStatusText(record.status) }}
@ -41,100 +105,14 @@
size="small"
disabled
>
发布
公示
</a-button>
<a-button
v-if="record.publisherId === currentUser && record.status === RegulationStatus.DRAFT"
type="text"
size="small"
@click="handleEdit(record)"
>
编辑
</a-button>
<a-popconfirm
v-if="record.publisherId === currentUser && record.status === RegulationStatus.DRAFT"
content="确定要删除这个提案吗?"
@ok="handleDelete(record)"
>
<a-button type="text" size="small" status="danger">
删除
</a-button>
</a-popconfirm>
</a-space>
</template>
</a-table>
</a-card>
<!-- 提案表单弹窗 -->
<a-modal
v-model:visible="modalVisible"
:title="modalTitle"
width="800px"
@ok="handleSubmit"
@cancel="handleCancel"
>
<a-form
ref="formRef"
:model="formData"
:rules="rules"
layout="vertical"
>
<a-row :gutter="16">
<a-col :span="12">
<a-form-item label="提案标题" field="title">
<a-input v-model="formData.title" placeholder="请输入提案标题" />
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="提案类型" field="regulationType">
<a-select v-model="formData.regulationType" placeholder="请选择提案类型">
<a-option
v-for="type in regulationTypes"
:key="type.typeId"
:value="type.typeName"
:disabled="type.isEnabled === '0'"
>
{{ type.typeName }}
</a-option>
</a-select>
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="16">
<a-col :span="12">
<a-form-item label="适用范围" field="scope">
<a-input v-model="formData.scope" placeholder="请输入适用范围" />
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="制度级别" field="level">
<a-select v-model="formData.level" placeholder="请选择制度级别">
<a-option :value="RegulationLevel.LOW"></a-option>
<a-option :value="RegulationLevel.MEDIUM"></a-option>
<a-option :value="RegulationLevel.HIGH"></a-option>
</a-select>
</a-form-item>
</a-col>
</a-row>
<a-form-item label="提案内容" field="content">
<a-textarea
v-model="formData.content"
placeholder="请详细描述提案的具体内容"
:rows="6"
/>
</a-form-item>
<a-form-item label="备注" field="remark">
<a-textarea
v-model="formData.remark"
placeholder="请输入其他补充说明"
:rows="2"
/>
</a-form-item>
</a-form>
</a-modal>
@ -151,7 +129,7 @@
<div class="detail-header">
<h3>{{ currentProposal.title }}</h3>
<div class="detail-meta">
<span>提案人: {{ currentProposal.createBy }}</span>
<span>提案人: {{ currentProposal.createByName }}</span>
<span>提案类型: {{ currentProposal.regulationType }}</span>
<span>适用范围: {{ currentProposal.scope }}</span>
<span>级别: <a-tag :color="getLevelColor(currentProposal.level)">{{ getLevelText(currentProposal.level) }}</a-tag></span>
@ -177,7 +155,7 @@
v-if="currentProposal.status === RegulationStatus.PUBLISHED"
disabled
>
已自动发布
已自动公示
</a-button>
</div>
</div>
@ -188,6 +166,7 @@
<script setup lang="ts">
import { ref, reactive, onMounted } from 'vue'
import { Message } from '@arco-design/web-vue'
import { IconSearch, IconRefresh } from '@arco-design/web-vue/es/icon'
import { useRegulationStore } from '@/stores/modules/regulation'
import { regulationApi } from '@/apis/regulation'
import {
@ -197,19 +176,36 @@ import {
type RegulationType
} from '@/apis/regulation/type'
//
const debounce = (func: Function, delay: number) => {
let timeoutId: NodeJS.Timeout
return (...args: any[]) => {
clearTimeout(timeoutId)
timeoutId = setTimeout(() => func.apply(null, args), delay)
}
}
defineOptions({ name: 'ProcessManagement' })
//
const columns = [
{ title: '提案标题', dataIndex: 'title', key: 'title' },
{ title: '提案人', dataIndex: 'createBy', key: 'createBy' },
{ title: '提案类型', dataIndex: 'regulationType', key: 'regulationType' },
{ title: '状态', dataIndex: 'status', key: 'status', slotName: 'status' },
{ title: '级别', dataIndex: 'level', key: 'level', slotName: 'level' },
{ title: '创建时间', dataIndex: 'createTime', key: 'createTime' },
{ title: '操作', key: 'operations', slotName: 'operations', width: 280 }
{ title: '提案标题', dataIndex: 'title', key: 'title', width: 200 },
{ title: '提案人', dataIndex: 'createByName', key: 'createByName', width: 120 },
{ title: '提案类型', dataIndex: 'regulationType', key: 'regulationType', width: 120 },
{ title: '状态', dataIndex: 'status', key: 'status', slotName: 'status', width: 100 },
{ title: '级别', dataIndex: 'level', key: 'level', slotName: 'level', width: 80 },
{ title: '创建时间', dataIndex: 'createTime', key: 'createTime', width: 180 },
{ title: '操作', key: 'operations', slotName: 'operations', width: 150 }
]
//
const searchForm = reactive({
title: '',
createByName: '',
regulationType: '',
status: ''
})
//
const tableData = ref<Regulation[]>([])
const loading = ref(false)
@ -222,51 +218,23 @@ const pagination = reactive({
showPageSize: true
})
//
const modalVisible = ref(false)
const modalTitle = ref('提交制度提案')
const formRef = ref()
const formData = reactive({
regulationId: '',
title: '',
regulationType: '',
content: '',
scope: '',
level: RegulationLevel.MEDIUM,
remark: ''
})
const currentProposal = ref<Regulation | null>(null)
//
const detailModalVisible = ref(false)
//
const currentUser = ref('admin') // ID
const currentUser = ref('管理者') // ID
// store
const regulationStore = useRegulationStore()
//
const regulationTypes = ref<RegulationType[]>([])
//
const rules = {
title: [{ required: true, message: '请输入提案标题' }],
regulationType: [{ required: true, message: '请选择提案类型' }],
content: [{ required: true, message: '请输入提案内容' }],
scope: [{ required: true, message: '请输入适用范围' }]
}
//
const getStatusColor = (status: RegulationStatus) => {
const colors = {
[RegulationStatus.DRAFT]: 'blue',
[RegulationStatus.VOTING]: 'orange',
[RegulationStatus.REJECTED]: 'red',
[RegulationStatus.PUBLISHED]: 'purple',
[RegulationStatus.APPROVED]: 'green',
[RegulationStatus.ARCHIVED]: 'gray'
}
return colors[status] || 'blue'
}
@ -274,12 +242,10 @@ const getStatusColor = (status: RegulationStatus) => {
//
const getStatusText = (status: RegulationStatus) => {
const texts = {
[RegulationStatus.DRAFT]: '已公示',
[RegulationStatus.VOTING]: '投票中',
[RegulationStatus.REJECTED]: '已否决',
[RegulationStatus.DRAFT]: '草稿',
[RegulationStatus.PUBLISHED]: '已公告',
[RegulationStatus.APPROVED]: '已通过',
[RegulationStatus.ARCHIVED]: '已归档'
[RegulationStatus.APPROVED]: '已公示',
}
return texts[status] || '草稿'
}
@ -317,30 +283,24 @@ const getLevelText = (level: RegulationLevel) => {
return texts[level] || '中'
}
//
const getRegulationTypes = async () => {
try {
const response = await regulationApi.getRegulationTypes()
if (response.status === 200) {
regulationTypes.value = response.data.records || response.data
}
} catch (error) {
console.error('获取制度类型列表失败:', error)
}
}
//
// -
const getTableData = async () => {
loading.value = true
try {
const response = await regulationApi.getRegulationList({
page: pagination.current,
size: pagination.pageSize
size: pagination.pageSize,
title: searchForm.title || undefined,
createByName: searchForm.createByName || undefined,
regulationType: searchForm.regulationType || undefined,
status: searchForm.status || undefined
})
if (response.status === 200) {
tableData.value = response.data.records
pagination.total = response.data.total
// 稿
const allRecords = response.data.records || []
tableData.value = allRecords.filter(item => item.status !== RegulationStatus.DRAFT)
pagination.total = tableData.value.length
pagination.current = response.data.current
} else {
Message.error('获取数据失败')
@ -353,26 +313,28 @@ const getTableData = async () => {
}
}
//
const handleAdd = () => {
modalTitle.value = '提交制度提案'
modalVisible.value = true
resetForm()
//
const debouncedSearch = debounce(() => {
pagination.current = 1
getTableData()
}, 300)
//
const search = () => {
pagination.current = 1
getTableData()
}
//
const handleEdit = (record: Regulation) => {
modalTitle.value = '编辑制度提案'
modalVisible.value = true
Object.assign(formData, {
regulationId: record.regulationId,
title: record.title,
regulationType: record.regulationType,
content: record.content,
scope: record.scope,
level: record.level,
remark: record.remark
//
const reset = () => {
Object.assign(searchForm, {
title: '',
createByName: '',
regulationType: '',
status: ''
})
pagination.current = 1
getTableData()
}
//
@ -381,79 +343,6 @@ const handleView = (record: Regulation) => {
detailModalVisible.value = true
}
//
const handleDelete = async (record: Regulation) => {
try {
await regulationApi.deleteProposal(record.regulationId)
Message.success('删除成功')
getTableData()
} catch (error) {
console.error('删除失败:', error)
Message.error('删除失败')
}
}
//
const handleSubmit = async () => {
try {
await formRef.value.validate()
if (formData.regulationId) {
//
await regulationApi.updateProposal(formData.regulationId, {
title: formData.title,
regulationType: formData.regulationType,
content: formData.content,
scope: formData.scope,
level: formData.level,
remark: formData.remark
})
Message.success('更新成功')
} else {
//
await regulationApi.createProposal({
title: formData.title,
regulationType: formData.regulationType,
content: formData.content,
scope: formData.scope,
level: formData.level,
remark: formData.remark
})
Message.success('提案提交成功')
}
modalVisible.value = false
getTableData()
} catch (error) {
console.error('操作失败:', error)
Message.error('操作失败')
}
}
//
const handleCancel = () => {
modalVisible.value = false
resetForm()
}
//
const resetForm = () => {
Object.assign(formData, {
regulationId: '',
title: '',
regulationType: '',
content: '',
scope: '',
level: RegulationLevel.MEDIUM,
remark: ''
})
formRef.value?.resetFields()
}
//
const handlePageChange = (page: number) => {
pagination.current = page
@ -468,7 +357,6 @@ const handlePageSizeChange = (pageSize: number) => {
onMounted(() => {
getTableData()
getRegulationTypes()
})
</script>
@ -479,6 +367,45 @@ onMounted(() => {
}
}
.search-container {
padding: 16px;
background: var(--color-bg-2);
border-radius: 6px;
margin-bottom: 16px;
.search-form {
.arco-form-item {
margin-bottom: 0;
margin-right: 16px;
.arco-form-item-label {
font-weight: 500;
color: var(--color-text-1);
margin-right: 8px;
}
}
.arco-input,
.arco-select {
border-radius: 4px;
}
.arco-btn {
border-radius: 4px;
}
}
}
.search-result-info {
color: var(--color-text-2);
font-size: 14px;
strong {
color: var(--color-text-1);
font-weight: 600;
}
}
.proposal-detail {

View File

@ -37,6 +37,7 @@
<script setup lang="tsx">
import { useRoute, useRouter } from 'vue-router'
import SystemRegulation from './repository.vue'
import RegulationProposal from './proposal/index.vue'
import RegulationType from './type/index.vue'
import ProcessManagement from './confirm.vue'
import { useDevice } from '@/hooks'
@ -47,6 +48,7 @@ const { isDesktop } = useDevice()
const data = [
{ name: '制度公告', key: 'system-regulation', icon: 'file-text', value: SystemRegulation, path: '/regulation/system-regulation' },
{ name: '制度提案', key: 'proposal', icon: 'edit', value: RegulationProposal, path: '/regulation/proposal' },
{ name: '制度公示', key: 'process-management', icon: 'workflow', value: ProcessManagement, path: '/regulation/process-management' },
{ name: '制度类型', key: 'type', icon: 'tag', value: RegulationType, path: '/regulation/type' },

View File

@ -0,0 +1,955 @@
<template>
<div class="regulation-proposal">
<a-card title="制度提案管理" :bordered="false">
<template #extra>
<a-button type="primary" @click="handleAdd">
<template #icon>
<GiSvgIcon name="plus" />
</template>
新增制度提案
</a-button>
</template>
<!-- 搜索区域 -->
<div class="search-container">
<a-form layout="inline" :model="searchForm" class="search-form">
<a-form-item label="提案标题">
<a-input
v-model="searchForm.title"
placeholder="请输入提案标题"
allow-clear
style="width: 200px"
@input="debouncedSearch"
/>
</a-form-item>
<a-form-item label="提案人">
<a-input
v-model="searchForm.createByName"
placeholder="请输入提案人"
allow-clear
style="width: 150px"
@input="debouncedSearch"
/>
</a-form-item>
<a-form-item label="提案类型">
<a-select
v-model="searchForm.regulationType"
placeholder="请选择类型"
allow-clear
style="width: 150px"
@change="debouncedSearch"
>
<a-option value="">全部</a-option>
<a-option value="管理规范">管理规范</a-option>
<a-option value="操作流程">操作流程</a-option>
<a-option value="安全制度">安全制度</a-option>
<a-option value="其他">其他</a-option>
</a-select>
</a-form-item>
<a-form-item label="状态">
<a-select
v-model="searchForm.status"
placeholder="请选择状态"
allow-clear
style="width: 150px"
@change="debouncedSearch"
>
<a-option value="">全部</a-option>
<a-option value="DRAFT">草稿</a-option>
<a-option value="PUBLISHED">已公示</a-option>
</a-select>
</a-form-item>
<a-form-item>
<a-space>
<a-button type="primary" @click="search">
<template #icon><icon-search /></template>
搜索
</a-button>
<a-button @click="reset">
<template #icon><icon-refresh /></template>
重置
</a-button>
</a-space>
</a-form-item>
</a-form>
</div>
<a-table
:columns="columns"
:data="tableData"
:loading="loading"
:pagination="pagination"
@page-change="handlePageChange"
@page-size-change="handlePageSizeChange"
>
<template #toolbar-left>
<span class="search-result-info">
共找到 <strong>{{ pagination.total }}</strong> 条记录
</span>
</template>
<template #status="{ record }">
<a-tag :color="getStatusColor(record.status)">
{{ getStatusText(record.status) }}
</a-tag>
</template>
<template #level="{ record }">
<a-tag :color="getLevelColor(record.level)">
{{ getLevelText(record.level) }}
</a-tag>
</template>
<template #operations="{ record }">
<a-space>
<a-button type="text" size="small" @click="handleView(record)">
查看详情
</a-button>
<a-button
v-if="record.status === RegulationStatus.DRAFT"
type="text"
size="small"
@click="handleEdit(record)"
>
编辑
</a-button>
<a-button
v-if="record.status === RegulationStatus.DRAFT"
type="primary"
size="small"
@click="handleConfirm(record)"
>
确认公示
</a-button>
<a-button
v-if="record.status !== RegulationStatus.DRAFT"
type="text"
size="small"
disabled
>
已公示
</a-button>
<a-popconfirm
v-if="record.status === RegulationStatus.DRAFT"
content="确定要删除这个提案吗?"
@ok="handleDelete(record)"
>
<a-button type="text" size="small" status="danger">
删除
</a-button>
</a-popconfirm>
</a-space>
</template>
</a-table>
</a-card>
<!-- 提案表单弹窗 -->
<a-modal
v-model:visible="modalVisible"
:title="modalTitle"
width="900px"
@ok="handleSubmit"
@cancel="handleCancel"
class="proposal-form-modal"
>
<a-form
ref="formRef"
:model="formData"
:rules="rules"
layout="vertical"
class="proposal-form"
>
<a-row :gutter="24">
<a-col :span="12">
<a-form-item label="提案标题" field="title" class="form-item">
<a-input
v-model="formData.title"
placeholder="请输入提案标题"
class="form-input"
allow-clear
/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="提案类型" field="regulationType" class="form-item">
<a-select
v-model="formData.regulationType"
placeholder="请选择提案类型"
class="form-select"
allow-clear
>
<a-option
v-for="type in regulationTypes"
:key="type.typeId"
:value="type.typeName"
:disabled="type.isEnabled === '0'"
>
{{ type.typeName }}
</a-option>
</a-select>
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="24">
<a-col :span="12">
<a-form-item label="适用范围" field="scope" class="form-item">
<a-input
v-model="formData.scope"
placeholder="请输入适用范围"
class="form-input"
allow-clear
/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="制度级别" field="level" class="form-item">
<a-select
v-model="formData.level"
placeholder="请选择制度级别"
class="form-select"
allow-clear
>
<a-option :value="RegulationLevel.LOW"></a-option>
<a-option :value="RegulationLevel.MEDIUM"></a-option>
<a-option :value="RegulationLevel.HIGH"></a-option>
</a-select>
</a-form-item>
</a-col>
</a-row>
<a-form-item label="提案内容" field="content" class="form-item">
<a-textarea
v-model="formData.content"
placeholder="请详细描述提案的具体内容,包括制度的目的、适用范围、具体规定等"
:rows="8"
class="form-textarea"
allow-clear
/>
</a-form-item>
<a-form-item label="备注" field="remark" class="form-item">
<a-textarea
v-model="formData.remark"
placeholder="请输入其他补充说明(可选)"
:rows="3"
class="form-textarea"
allow-clear
/>
</a-form-item>
</a-form>
</a-modal>
<!-- 提案详情弹窗 -->
<a-modal
v-model:visible="detailModalVisible"
title="提案详情"
width="800px"
:footer="false"
>
<div class="proposal-detail" v-if="currentProposal">
<div class="detail-header">
<h3>{{ currentProposal.title }}</h3>
<div class="detail-meta">
<span>提案人: {{ currentProposal.createByName }}</span>
<span>提案类型: {{ currentProposal.regulationType }}</span>
<span>适用范围: {{ currentProposal.scope }}</span>
<span>级别: <a-tag :color="getLevelColor(currentProposal.level)">{{ getLevelText(currentProposal.level) }}</a-tag></span>
<span>创建时间: {{ formatDate(currentProposal.createTime) }}</span>
<span>状态: <a-tag :color="getStatusColor(currentProposal.status)">{{ getStatusText(currentProposal.status) }}</a-tag></span>
</div>
</div>
<a-divider />
<div class="detail-content">
<h4>提案内容</h4>
<div class="content-text">{{ currentProposal.content }}</div>
<h4>备注</h4>
<div class="content-text">{{ currentProposal.remark || '暂无备注' }}</div>
</div>
<a-divider />
<div class="detail-footer">
<a-button
v-if="currentProposal.status === RegulationStatus.DRAFT && currentProposal.createByName === currentUser"
type="primary"
@click="handleConfirm(currentProposal)"
>
确认公示
</a-button>
<a-button @click="detailModalVisible = false">
关闭
</a-button>
</div>
</div>
</a-modal>
<!-- 确认公示弹窗 -->
<a-modal
v-model:visible="confirmModalVisible"
title="确认公示提案"
width="600px"
@ok="submitConfirm"
@cancel="confirmModalVisible = false"
>
<div class="confirm-content">
<div class="confirm-header">
<GiSvgIcon name="exclamation-circle" style="color: #faad14; font-size: 24px; margin-right: 8px;" />
<span style="font-weight: 500; font-size: 16px;">公示确认</span>
</div>
<div class="confirm-body">
<p>您即将公示提案<strong>{{ currentProposal?.title }}</strong></p>
<p>公示后</p>
<ul>
<li>该提案将进入制度公示页面</li>
<li>其他用户可以在制度公示页面查看此提案</li>
<li>提案将进入正式的公示流程</li>
<li>您将无法再编辑此提案</li>
</ul>
</div>
<div class="confirm-footer">
<a-checkbox v-model="agreeDisplay">
我已仔细阅读并确认公示此提案
</a-checkbox>
</div>
</div>
</a-modal>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted } from 'vue'
import { Message } from '@arco-design/web-vue'
import { IconSearch, IconRefresh } from '@arco-design/web-vue/es/icon'
import { useRegulationStore } from '@/stores/modules/regulation'
import { regulationApi } from '@/apis/regulation'
import {
RegulationStatus,
RegulationLevel,
type Regulation,
type RegulationType
} from '@/apis/regulation/type'
//
const debounce = (func: Function, delay: number) => {
let timeoutId: NodeJS.Timeout
return (...args: any[]) => {
clearTimeout(timeoutId)
timeoutId = setTimeout(() => func.apply(null, args), delay)
}
}
defineOptions({ name: 'RegulationProposal' })
//
const columns = [
{ title: '提案标题', dataIndex: 'title', key: 'title', width: 220 },
{ title: '提案人', dataIndex: 'createByName', key: 'createByName', width: 130 },
{ title: '提案类型', dataIndex: 'regulationType', key: 'regulationType', width: 130 },
{ title: '状态', dataIndex: 'status', key: 'status', slotName: 'status', width: 110 },
{ title: '级别', dataIndex: 'level', key: 'level', slotName: 'level', width: 90 },
{ title: '创建时间', dataIndex: 'createTime', key: 'createTime', width: 200 },
{ title: '操作', key: 'operations', slotName: 'operations', width: 320 }
]
//
const searchForm = reactive({
title: '',
createByName: '',
regulationType: '',
status: ''
})
//
const tableData = ref<Regulation[]>([])
const loading = ref(false)
const pagination = reactive({
current: 1,
pageSize: 10,
total: 0,
showTotal: true,
showJumper: true,
showPageSize: true
})
//
const modalVisible = ref(false)
const modalTitle = ref('新增制度提案')
const formRef = ref()
const formData = reactive({
regulationId: '',
title: '',
regulationType: '',
content: '',
scope: '',
level: RegulationLevel.MEDIUM,
remark: ''
})
const currentProposal = ref<Regulation | null>(null)
const detailModalVisible = ref(false)
const confirmModalVisible = ref(false)
const agreeDisplay = ref(false)
//
const currentUser = ref('管理者') // ID
// store
const regulationStore = useRegulationStore()
//
const regulationTypes = ref<RegulationType[]>([])
//
const rules = {
title: [{ required: true, message: '请输入提案标题' }],
regulationType: [{ required: true, message: '请选择提案类型' }],
content: [{ required: true, message: '请输入提案内容' }],
scope: [{ required: true, message: '请输入适用范围' }]
}
//
const getStatusColor = (status: RegulationStatus) => {
const colors = {
[RegulationStatus.DRAFT]: 'blue',
[RegulationStatus.VOTING]: 'orange',
[RegulationStatus.REJECTED]: 'red',
[RegulationStatus.PUBLISHED]: 'purple',
[RegulationStatus.APPROVED]: 'green',
[RegulationStatus.ARCHIVED]: 'gray'
}
return colors[status] || 'blue'
}
//
const getStatusText = (status: RegulationStatus) => {
const texts = {
[RegulationStatus.DRAFT]: '草稿',
[RegulationStatus.VOTING]: '投票中',
[RegulationStatus.REJECTED]: '已否决',
[RegulationStatus.PUBLISHED]: '已公示',
[RegulationStatus.APPROVED]: '已通过',
[RegulationStatus.ARCHIVED]: '已归档'
}
return texts[status] || '草稿'
}
//
const formatDate = (dateString: string) => {
if (!dateString) return '-'
const date = new Date(dateString)
return date.toLocaleDateString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit'
})
}
//
const getLevelColor = (level: RegulationLevel) => {
const colors = {
[RegulationLevel.LOW]: 'blue',
[RegulationLevel.MEDIUM]: 'orange',
[RegulationLevel.HIGH]: 'red'
}
return colors[level] || 'blue'
}
//
const getLevelText = (level: RegulationLevel) => {
const texts = {
[RegulationLevel.LOW]: '低',
[RegulationLevel.MEDIUM]: '中',
[RegulationLevel.HIGH]: '高'
}
return texts[level] || '中'
}
//
const getRegulationTypes = async () => {
try {
const response = await regulationApi.getRegulationTypes()
if (response.status === 200) {
regulationTypes.value = response.data.records || response.data
}
} catch (error) {
console.error('获取制度类型列表失败:', error)
}
}
// - 稿
const getTableData = async () => {
loading.value = true
try {
const response = await regulationApi.getRegulationList({
page: pagination.current,
size: pagination.pageSize,
status: RegulationStatus.DRAFT, // 稿
title: searchForm.title || undefined,
createByName: searchForm.createByName || undefined,
regulationType: searchForm.regulationType || undefined,
status: searchForm.status || undefined
})
if (response.status === 200) {
tableData.value = response.data.records
pagination.total = response.data.total
pagination.current = response.data.current
//
console.log('当前用户:', currentUser.value)
tableData.value.forEach((item, index) => {
console.log(`提案 ${index + 1}:`, {
title: item.title,
createByName: item.createByName,
status: item.status,
canEdit: item.status === RegulationStatus.DRAFT && item.createByName === currentUser.value
})
})
} else {
Message.error('获取数据失败')
}
} catch (error) {
console.error('获取制度列表失败:', error)
Message.error('获取数据失败')
} finally {
loading.value = false
}
}
//
const debouncedSearch = debounce(() => {
pagination.current = 1
getTableData()
}, 300)
//
const search = () => {
pagination.current = 1
getTableData()
}
//
const reset = () => {
Object.assign(searchForm, {
title: '',
createByName: '',
regulationType: '',
status: ''
})
pagination.current = 1
getTableData()
}
//
const handleAdd = () => {
modalTitle.value = '新增制度提案'
modalVisible.value = true
resetForm()
}
//
const handleEdit = (record: Regulation) => {
modalTitle.value = '编辑制度提案'
modalVisible.value = true
Object.assign(formData, {
regulationId: record.regulationId,
title: record.title,
regulationType: record.regulationType,
content: record.content,
scope: record.scope,
level: record.level,
remark: record.remark
})
}
//
const handleView = (record: Regulation) => {
currentProposal.value = record
detailModalVisible.value = true
}
//
const handleConfirm = (record: Regulation) => {
currentProposal.value = record
confirmModalVisible.value = true
agreeDisplay.value = false
}
//
const handleDelete = async (record: Regulation) => {
try {
await regulationApi.deleteProposal(record.regulationId)
Message.success('删除成功')
getTableData()
} catch (error) {
console.error('删除失败:', error)
Message.error('删除失败')
}
}
//
const submitConfirm = async () => {
if (!agreeDisplay.value) {
Message.warning('请先确认公示此提案')
return
}
try {
if (currentProposal.value) {
console.log('准备公示提案:', currentProposal.value.regulationId)
// 使API
const response = await regulationApi.publishRegulation(currentProposal.value.regulationId)
console.log('公示响应:', response)
Message.success('提案公示成功,已进入公示流程')
confirmModalVisible.value = false
getTableData() //
//
if (detailModalVisible.value) {
detailModalVisible.value = false
}
}
} catch (error) {
console.error('公示失败:', error)
console.error('错误详情:', error.response || error)
Message.error('公示失败')
}
}
//
const handleSubmit = async () => {
try {
await formRef.value.validate()
if (formData.regulationId) {
//
await regulationApi.updateProposal(formData.regulationId, {
title: formData.title,
regulationType: formData.regulationType,
content: formData.content,
scope: formData.scope,
level: formData.level,
remark: formData.remark
})
Message.success('更新成功')
} else {
//
const createData = {
title: formData.title,
regulationType: formData.regulationType,
content: formData.content,
scope: formData.scope,
level: formData.level,
remark: formData.remark,
createByName: currentUser.value
}
console.log('新增提案数据:', createData)
const response = await regulationApi.createProposal(createData)
console.log('新增提案响应:', response)
Message.success('提案提交成功')
}
modalVisible.value = false
getTableData()
} catch (error) {
console.error('操作失败:', error)
Message.error('操作失败')
}
}
//
const handleCancel = () => {
modalVisible.value = false
resetForm()
}
//
const resetForm = () => {
Object.assign(formData, {
regulationId: '',
title: '',
regulationType: '',
content: '',
scope: '',
level: RegulationLevel.MEDIUM,
remark: ''
})
formRef.value?.resetFields()
}
//
const handlePageChange = (page: number) => {
pagination.current = page
getTableData()
}
const handlePageSizeChange = (pageSize: number) => {
pagination.pageSize = pageSize
pagination.current = 1
getTableData()
}
onMounted(() => {
getTableData()
getRegulationTypes()
})
</script>
<style scoped lang="scss">
.regulation-proposal {
.arco-card {
margin-bottom: 16px;
}
}
.search-container {
padding: 16px;
background: var(--color-bg-2);
border-radius: 6px;
margin-bottom: 16px;
.search-form {
.arco-form-item {
margin-bottom: 0;
margin-right: 16px;
.arco-form-item-label {
font-weight: 500;
color: var(--color-text-1);
margin-right: 8px;
}
}
.arco-input,
.arco-select {
border-radius: 4px;
}
.arco-btn {
border-radius: 4px;
}
}
}
.search-result-info {
color: var(--color-text-2);
font-size: 14px;
strong {
color: var(--color-text-1);
font-weight: 600;
}
}
//
.proposal-form-modal {
.arco-modal-body {
padding: 24px 32px;
}
.arco-modal-header {
padding: 20px 32px 16px;
border-bottom: 1px solid var(--color-border);
.arco-modal-title {
font-size: 18px;
font-weight: 600;
color: var(--color-text-1);
}
}
.arco-modal-footer {
padding: 16px 32px 24px;
border-top: 1px solid var(--color-border);
}
}
.proposal-form {
.form-item {
margin-bottom: 24px;
.arco-form-item-label {
font-weight: 500;
color: var(--color-text-1);
margin-bottom: 8px;
font-size: 14px;
&::before {
color: #ff4d4f;
margin-right: 4px;
}
}
.arco-form-item-error {
margin-top: 6px;
font-size: 12px;
}
}
.form-input,
.form-select {
border-radius: 6px;
border: 1px solid var(--color-border);
transition: all 0.2s ease;
&:hover {
border-color: var(--color-primary-light-3);
}
&:focus,
&.arco-input-focus,
&.arco-select-focus {
border-color: var(--color-primary);
box-shadow: 0 0 0 2px rgba(var(--primary-6), 0.1);
}
.arco-input-inner {
padding: 8px 12px;
font-size: 14px;
}
}
.form-textarea {
border-radius: 6px;
border: 1px solid var(--color-border);
transition: all 0.2s ease;
&:hover {
border-color: var(--color-primary-light-3);
}
&:focus,
&.arco-textarea-focus {
border-color: var(--color-primary);
box-shadow: 0 0 0 2px rgba(var(--primary-6), 0.1);
}
.arco-textarea-inner {
padding: 12px;
font-size: 14px;
line-height: 1.6;
}
}
//
.arco-select {
.arco-select-view {
padding: 8px 12px;
font-size: 14px;
}
.arco-select-arrow {
color: var(--color-text-3);
}
}
//
.arco-input-inner::placeholder,
.arco-textarea-inner::placeholder {
color: var(--color-text-3);
font-size: 14px;
}
//
.arco-input-clear-btn,
.arco-textarea-clear-btn {
color: var(--color-text-3);
&:hover {
color: var(--color-text-2);
}
}
}
.proposal-detail {
.detail-header {
margin-bottom: 16px;
h3 {
margin-bottom: 12px;
color: var(--color-text-1);
}
.detail-meta {
display: flex;
gap: 16px;
color: var(--color-text-3);
font-size: 14px;
flex-wrap: wrap;
}
}
.detail-content {
h4 {
margin: 16px 0 8px 0;
color: var(--color-text-1);
font-weight: 500;
}
.content-text {
color: var(--color-text-2);
line-height: 1.6;
margin-bottom: 16px;
padding: 12px;
background: var(--color-fill-1);
border-radius: 6px;
}
}
.detail-footer {
display: flex;
gap: 12px;
justify-content: center;
margin-top: 20px;
}
}
.confirm-content {
.confirm-header {
display: flex;
align-items: center;
margin-bottom: 20px;
padding-bottom: 12px;
border-bottom: 1px solid var(--color-border);
}
.confirm-body {
margin-bottom: 20px;
p {
margin-bottom: 12px;
color: var(--color-text-1);
line-height: 1.6;
}
ul {
margin: 12px 0;
padding-left: 20px;
li {
margin-bottom: 8px;
color: var(--color-text-2);
line-height: 1.5;
}
}
}
.confirm-footer {
padding-top: 16px;
border-top: 1px solid var(--color-border);
}
}
</style>

View File

@ -1,7 +1,57 @@
<template>
<div class="system-regulation">
<a-card title="已公告制度确认" :bordered="false">
<!-- 搜索区域 -->
<div class="search-container">
<a-form layout="inline" :model="searchForm" class="search-form">
<a-form-item label="制度标题">
<a-input
v-model="searchForm.title"
placeholder="请输入制度标题"
allow-clear
style="width: 200px"
@input="debouncedSearch"
/>
</a-form-item>
<a-form-item label="公示人">
<a-input
v-model="searchForm.createByName"
placeholder="请输入公示人"
allow-clear
style="width: 150px"
@input="debouncedSearch"
/>
</a-form-item>
<a-form-item label="确认状态">
<a-select
v-model="searchForm.confirmStatus"
placeholder="请选择状态"
allow-clear
style="width: 150px"
@change="debouncedSearch"
>
<a-option value="">全部</a-option>
<a-option value="confirmed">已确认</a-option>
<a-option value="pending">待确认</a-option>
</a-select>
</a-form-item>
<a-form-item>
<a-space>
<a-button type="primary" @click="search">
<template #icon><icon-search /></template>
搜索
</a-button>
<a-button @click="reset">
<template #icon><icon-refresh /></template>
重置
</a-button>
</a-space>
</a-form-item>
</a-form>
</div>
<a-table
:columns="columns"
@ -11,6 +61,11 @@
@page-change="onPageChange"
@page-size-change="onPageSizeChange"
>
<template #toolbar-left>
<span class="search-result-info">
共找到 <strong>{{ pagination.total }}</strong> 条记录
</span>
</template>
<template #confirmStatus="{ record }">
<a-tag :color="record.confirmStatus === 'confirmed' ? 'green' : 'orange'">
{{ record.confirmStatus === 'confirmed' ? '已确认' : '待确认' }}
@ -41,8 +96,8 @@
<div class="detail-header">
<h3>{{ currentRegulation.title }}</h3>
<div class="detail-meta">
<span>发布: {{ currentRegulation.createBy }}</span>
<span>发布时间: {{ currentRegulation.publishTime }}</span>
<span>公示: {{ currentRegulation.createByName }}</span>
<span>公示时间: {{ currentRegulation.publishTime }}</span>
<span>生效日期: {{ currentRegulation.effectiveTime }}</span>
</div>
</div>
@ -104,18 +159,28 @@
import { ref, reactive, onMounted } from 'vue'
import type { Regulation } from '@/apis/regulation/type'
import { Message } from '@arco-design/web-vue'
import { IconSearch, IconRefresh } from '@arco-design/web-vue/es/icon'
import { useRegulationStore } from '@/stores/modules/regulation'
import { regulationApi } from '@/apis/regulation'
import { PDFGenerator } from '@/utils/pdfGenerator'
//
const debounce = (func: Function, delay: number) => {
let timeoutId: NodeJS.Timeout
return (...args: any[]) => {
clearTimeout(timeoutId)
timeoutId = setTimeout(() => func.apply(null, args), delay)
}
}
defineOptions({ name: 'SystemRegulation' })
//
const columns = [
{ title: '制度名称', dataIndex: 'title', key: 'title' },
{ title: '制度类型', dataIndex: 'regulationType', key: 'regulationType' },
{ title: '发布人', dataIndex: 'createBy', key: 'createBy' },
{ title: '发布时间', dataIndex: 'publishTime', key: 'publishTime' },
{ title: '公示人', dataIndex: 'createByName', key: 'createByName' },
{ title: '公示时间', dataIndex: 'publishTime', key: 'publishTime' },
{ title: '生效日期', dataIndex: 'effectiveTime', key: 'effectiveTime' },
{ title: '确认状态', dataIndex: 'confirmStatus', key: 'confirmStatus', slotName: 'confirmStatus' },
{ title: '操作', key: 'operations', slotName: 'operations', width: 250 }
@ -133,6 +198,13 @@ const pagination = reactive({
showPageSize: true
})
//
const searchForm = reactive({
title: '',
createByName: '',
confirmStatus: ''
})
//
const detailModalVisible = ref(false)
const confirmModalVisible = ref(false)
@ -149,7 +221,10 @@ const getTableData = async () => {
const response = await regulationApi.getPublishedRegulationList({
page: pagination.current,
size: pagination.pageSize,
status: "PUBLISHED"
status: "PUBLISHED",
title: searchForm.title || undefined,
createByName: searchForm.createByName || undefined,
confirmStatus: searchForm.confirmStatus || undefined
})
if (response.status === 200) {
@ -161,13 +236,36 @@ const getTableData = async () => {
Message.error('获取数据失败')
}
} catch (error) {
console.error('获取已发布制度列表失败:', error)
console.error('获取已公示制度列表失败:', error)
Message.error('获取数据失败')
} finally {
loading.value = false
}
}
//
const debouncedSearch = debounce(() => {
pagination.current = 1
getTableData()
}, 300)
//
const search = () => {
pagination.current = 1
getTableData()
}
//
const reset = () => {
Object.assign(searchForm, {
title: '',
createByName: '',
confirmStatus: ''
})
pagination.current = 1
getTableData()
}
//
const onPageChange = (page: number) => {
pagination.current = page
@ -257,6 +355,45 @@ onMounted(() => {
}
}
.search-container {
padding: 16px;
background: var(--color-bg-2);
border-radius: 6px;
margin-bottom: 16px;
.search-form {
.arco-form-item {
margin-bottom: 0;
margin-right: 16px;
.arco-form-item-label {
font-weight: 500;
color: var(--color-text-1);
margin-right: 8px;
}
}
.arco-input,
.arco-select {
border-radius: 4px;
}
.arco-btn {
border-radius: 4px;
}
}
}
.search-result-info {
color: var(--color-text-2);
font-size: 14px;
strong {
color: var(--color-text-1);
font-weight: 600;
}
}
.regulation-detail {
.detail-header {
margin-bottom: 16px;

View File

@ -13,14 +13,56 @@
@refresh="search"
>
<template #top>
<GiForm
v-model="searchForm"
search
:columns="queryFormColumns"
size="medium"
@search="search"
@reset="reset"
<div class="search-container">
<a-form layout="inline" :model="searchForm" class="search-form">
<a-form-item label="类型名称">
<a-input
v-model="searchForm.typeName"
placeholder="请输入类型名称"
allow-clear
style="width: 200px"
@input="debouncedSearch"
/>
</a-form-item>
<a-form-item label="状态">
<a-select
v-model="searchForm.isEnabled"
placeholder="请选择状态"
allow-clear
style="width: 150px"
@change="debouncedSearch"
>
<a-option value="">全部</a-option>
<a-option value="1">启用</a-option>
<a-option value="0">禁用</a-option>
</a-select>
</a-form-item>
<a-form-item label="备注">
<a-input
v-model="searchForm.remark"
placeholder="请输入备注内容"
allow-clear
style="width: 200px"
@input="debouncedSearch"
/>
</a-form-item>
<a-form-item>
<a-space>
<a-button type="primary" @click="search">
<template #icon><icon-search /></template>
搜索
</a-button>
<a-button @click="reset">
<template #icon><icon-refresh /></template>
重置
</a-button>
</a-space>
</a-form-item>
</a-form>
</div>
</template>
<template #toolbar-left>
@ -29,6 +71,9 @@
<template #icon><icon-plus /></template>
<template #default>新增类型</template>
</a-button>
<span class="search-result-info">
共找到 <strong>{{ pagination.total }}</strong> 条记录
</span>
</a-space>
</template>
@ -101,8 +146,9 @@
</template>
<script setup lang="ts">
import { ref, reactive, onMounted } from 'vue'
import { ref, reactive, onMounted, watch } from 'vue'
import { Message } from '@arco-design/web-vue'
import { IconSearch, IconRefresh } from '@arco-design/web-vue/es/icon'
import { regulationApi } from '@/apis/regulation'
import {
type RegulationType,
@ -110,6 +156,15 @@ import {
type UpdateRegulationTypeRequest
} from '@/apis/regulation/type'
//
const debounce = (func: Function, delay: number) => {
let timeoutId: NodeJS.Timeout
return (...args: any[]) => {
clearTimeout(timeoutId)
timeoutId = setTimeout(() => func.apply(null, args), delay)
}
}
defineOptions({ name: 'RegulationType' })
//
@ -117,31 +172,13 @@ const tableColumns = [
{ title: '类型名称', dataIndex: 'typeName', key: 'typeName', width: 150 },
{ title: '备注', dataIndex: 'remark', key: 'remark', width: 200 },
{ title: '状态', dataIndex: 'isEnabled', key: 'isEnabled', slotName: 'isEnabled', width: 100 },
{ title: '创建人', dataIndex: 'createBy', key: 'createBy', width: 120 },
{ title: '创建人', dataIndex: 'createrName', key: 'createrName', width: 120 },
{ title: '创建时间', dataIndex: 'createTime', key: 'createTime', width: 180 },
{ title: '排序', dataIndex: 'sortOrder', key: 'sortOrder', width: 100 },
{ title: '操作', key: 'action', slotName: 'action', width: 150, fixed: 'right' }
]
//
const queryFormColumns = [
{
label: '类型名称',
field: 'typeName',
component: 'a-input',
componentProps: {
placeholder: '请输入类型名称'
}
},
{
label: '备注',
field: 'remark',
component: 'a-input',
componentProps: {
placeholder: '请输入备注内容'
}
}
]
//
const dataList = ref<RegulationType[]>([])
@ -158,6 +195,7 @@ const pagination = reactive({
//
const searchForm = reactive({
typeName: '',
isEnabled: '',
remark: ''
})
@ -188,6 +226,7 @@ const getTableData = async () => {
page: pagination.current,
size: pagination.pageSize,
typeName: searchForm.typeName || undefined,
isEnabled: searchForm.isEnabled || undefined,
remark: searchForm.remark || undefined
})
@ -206,16 +245,25 @@ const getTableData = async () => {
}
}
//
const debouncedSearch = debounce(() => {
pagination.current = 1
getTableData()
}, 300)
//
const search = () => {
pagination.current = 1
getTableData()
}
//
const reset = () => {
Object.assign(searchForm, {
typeName: '',
isEnabled: '',
remark: ''
})
pagination.current = 1
@ -330,4 +378,43 @@ onMounted(() => {
margin-bottom: 16px;
}
}
.search-container {
padding: 16px;
background: var(--color-bg-2);
border-radius: 6px;
margin-bottom: 16px;
.search-form {
.arco-form-item {
margin-bottom: 0;
margin-right: 16px;
.arco-form-item-label {
font-weight: 500;
color: var(--color-text-1);
margin-right: 8px;
}
}
.arco-input,
.arco-select {
border-radius: 4px;
}
.arco-btn {
border-radius: 4px;
}
}
}
.search-result-info {
color: var(--color-text-2);
font-size: 14px;
strong {
color: var(--color-text-1);
font-weight: 600;
}
}
</style>