Industrial-image-management.../src/views/regulation/proposal/index.vue

907 lines
23 KiB
Vue
Raw Normal View History

<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"
/>
</a-form-item>
<a-form-item label="提案人">
<a-input
v-model="searchForm.proposer"
placeholder="请输入提案人"
allow-clear
style="width: 150px"
/>
</a-form-item>
<a-form-item label="状态">
<a-select
v-model="searchForm.status"
placeholder="请选择状态"
allow-clear
style="width: 150px"
>
<a-option value="">全部</a-option>
<a-option value="DRAFT">草稿</a-option>
2025-08-01 15:37:53 +08:00
<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,
type RegulationProposalSearchRequest
} from '@/apis/regulation/type'
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: '',
proposer: '',
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]: '已否决',
2025-08-01 15:37:53 +08:00
[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 {
2025-08-04 22:15:48 +08:00
const response = await regulationApi.searchRegulationTypes()
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,
title: searchForm.title || undefined,
proposer: searchForm.proposer || undefined,
status: searchForm.status || undefined
})
if (response.status === 200) {
tableData.value = response.data.records
pagination.total = response.data.total
pagination.current = response.data.current
} else {
Message.error('搜索失败')
}
} catch (error) {
console.error('搜索制度提案失败:', error)
Message.error('搜索失败')
} finally {
loading.value = false
}
}
// 搜索
const search = () => {
pagination.current = 1
getTableData()
}
// 重置
const reset = () => {
Object.assign(searchForm, {
title: '',
proposer: '',
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>