Files
guoju0808/ry-vue3/src/views/manager/Plans.vue
T
郭庆泰 b52a334184 chore: biz_org 合并 / BizProject 重构 / 各角色页面调整 (上次重构遗留)
涵盖改动范围:
- 后端 BizOrg / BizProject / BizProjectRating / BizSpecialPlan controller + domain + mapper + service
- 前端 admin (BizSpecialPlanAdmin, ProjectCategory) / executor / leader / manager / sponsor 多页调整
- api/dict.js + AdminLayout.vue 同步
2026-08-18 00:30:34 +08:00

288 lines
14 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="page-card manager-plans">
<div class="breadcrumb">首页 / 项目策划方案</div>
<!-- ========== 筛选区 ( People.vue 风格一致) ========== -->
<el-form inline :model="q" class="filter-form">
<el-form-item label="项目编号"><el-input v-model="q.projectNo" placeholder="输入项目编号" clearable /></el-form-item>
<el-form-item label="策划方案名称"><el-input v-model="q.planName" placeholder="输入策划方案名称" clearable /></el-form-item>
<el-form-item label="项目方向">
<el-select v-model="q.planDirectionId" placeholder="请选择" clearable style="width:200px">
<el-option v-for="opt in planDirectionOptions" :key="opt.id" :label="opt.title" :value="opt.id" />
</el-select>
</el-form-item>
<el-form-item label="项目类别">
<dict-select v-model="q.planCategory" dict-type="biz_project_category" value-field="label" style="width:160px" />
</el-form-item>
<el-form-item label="状态">
<el-select v-model="q.status" placeholder="请选择" clearable style="width:140px">
<el-option label="待审核" value="1" /><el-option label="通过" value="2" /><el-option label="拒绝" value="3" />
</el-select>
</el-form-item>
<el-form-item label="是否结算">
<el-select v-model="q.isSettled" placeholder="请选择" clearable style="width:140px">
<el-option label="已结算" value="Y" /><el-option label="未结算" value="N" />
</el-select>
</el-form-item>
<el-form-item label="备注"><el-input v-model="q.remark" placeholder="输入备注" clearable /></el-form-item>
<el-form-item>
<el-button type="primary" @click="load">查找</el-button>
<el-button @click="reset">重置</el-button>
</el-form-item>
</el-form>
<!-- ========== 批量按钮 ( People.vue 风格一致) ========== -->
<div class="batch-bar">
<el-button type="primary" :disabled="!selection.length" @click="batchSettle">批量结算</el-button>
<span v-if="selection.length" class="filter-tip">已选 {{ selection.length }} </span>
</div>
<!-- ========== 表格 ( People.vue 风格一致, page-card 包裹) ========== -->
<el-table :data="rows" v-loading="loading" stripe border @selection-change="onSel">
<el-table-column type="selection" width="44" />
<el-table-column prop="planName" label="策划方案名称" min-width="200" fixed show-overflow-tooltip />
<el-table-column prop="planDirectionTitle" label="项目方向" min-width="220" show-overflow-tooltip />
<el-table-column prop="planCategory" label="项目类别" width="100" align="center" />
<el-table-column prop="projectForm" label="项目形式" width="100" align="center" />
<el-table-column label="设计文件" width="160" align="center">
<template #default="{ row }">
<template v-if="row.designFileUrl">
<el-button link type="primary" @click="onFile(row)">查看</el-button>
<el-button link type="primary" @click="downloadFile(row)">下载</el-button>
</template>
<span v-else style="color:#c0c4cc">-</span>
</template>
</el-table-column>
<el-table-column label="状态" width="100" align="center">
<template #default="{ row }">
<audit-status-tag :status="row.status" />
</template>
</el-table-column>
<el-table-column label="是否结算" width="100" align="center">
<template #default="{ row }">
<el-tag size="small" :type="row.isSettled==='Y' ? 'success' : 'warning'">
{{ row.isSettled==='Y' ? '已结算' : '未结算' }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="projectNo" label="项目编号" width="140" align="center" />
<el-table-column prop="remark" label="备注" min-width="160" show-overflow-tooltip />
<el-table-column label="操作" width="240" fixed="right">
<template #default="{ row }">
<el-button link type="primary" @click="onAudit(row)">审核</el-button>
<el-button link type="primary" @click="onEdit(row)">修改</el-button>
<el-button link type="primary" @click="onSettle(row)" v-if="row.isSettled!=='Y'">结算</el-button>
<el-button link type="primary" v-if="row.designFileUrl" @click="downloadFile(row)">下载</el-button>
</template>
</el-table-column>
</el-table>
<div class="pager">
<el-pagination v-model:current-page="page.pageNum" v-model:page-size="page.pageSize" :total="page.total" :page-sizes="[10,20,50]" layout="total, sizes, prev, pager, next, jumper" @current-change="load" @size-change="load" />
</div>
<!-- 审核 dialog: 策划案名称 + 投稿ID + 审核结果(固定 退回) + 审核意见 -->
<el-dialog v-model="auditOpen" title="审核策划方案" width="500px">
<el-form :model="auditForm" :rules="auditRules" ref="auditFormRef" label-width="100px">
<el-form-item label="策划案名称"><span>{{ auditForm.planName }}</span></el-form-item>
<el-form-item label="投稿ID"><span>{{ auditForm.planId }}</span></el-form-item>
<el-form-item label="审核结果">
<span style="color:#f56c6c">退回审核不通过</span>
</el-form-item>
<el-form-item label="审核意见" prop="opinion">
<el-input v-model="auditForm.opinion" type="textarea" rows="3" placeholder="请填写审核不通过原因" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="auditOpen=false">取消</el-button>
<el-button type="primary" :loading="submitting" @click="submitAudit">确认</el-button>
</template>
</el-dialog>
<!-- 编辑 dialog -->
<el-dialog v-model="editOpen" title="修改策划方案" width="640px" @closed="resetEditForm">
<el-form :model="editForm" label-width="120px" :rules="editRules" ref="editFormRef">
<el-form-item label="方案名称" prop="planName"><el-input v-model="editForm.planName" /></el-form-item>
<el-form-item label="项目编号" prop="projectNo"><el-input v-model="editForm.projectNo" /></el-form-item>
<el-form-item label="项目方向">
<el-select v-model="editForm.planDirectionId" placeholder="请选择" style="width:100%">
<el-option v-for="opt in planDirectionOptions" :key="opt.id" :label="opt.title" :value="opt.id" />
</el-select>
</el-form-item>
<el-form-item label="项目类别">
<dict-select v-model="editForm.planCategory" dict-type="biz_project_category" value-field="label" style="width:100%" />
</el-form-item>
<el-form-item label="项目形式">
<el-select v-model="editForm.projectForm" placeholder="请选择" style="width:100%">
<el-option label="线上" value="线上" /><el-option label="线下" value="线下" /><el-option label="线上+线下" value="线上+线下" /><el-option label="其他" value="其他" />
</el-select>
</el-form-item>
<el-form-item label="设计方案">
<el-input v-model="editForm.designFileUrl" placeholder="文件 URL(暂以文本输入)" />
</el-form-item>
<el-form-item label="备注"><el-input v-model="editForm.remark" type="textarea" rows="2" /></el-form-item>
</el-form>
<template #footer>
<el-button @click="editOpen=false">取消</el-button>
<el-button type="primary" :loading="submitting" @click="submitEdit">保存</el-button>
</template>
</el-dialog>
<!-- 结算 dialog (按原型: 仅文字 + 确定/取消) -->
<el-dialog v-model="settleOpen" title="结算策划方案" width="420px" :show-close="false">
<p>您确定要结算吗</p>
<template #footer>
<el-button @click="settleOpen=false">取消</el-button>
<el-button type="primary" :loading="submitting" @click="submitSettle">确定</el-button>
</template>
</el-dialog>
<Preview v-model="previewOpen" :url="previewUrl" :title="previewTitle" />
</div>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue'
import { bizList, bizUpdate } from '@/api/public'
import request from '@/utils/request'
import { ElMessage } from 'element-plus'
import Preview from '@/components/Preview.vue'
import DictSelect from '@/components/DictSelect.vue'
import AuditStatusTag from '@/components/AuditStatusTag.vue'
// 专项计划下拉选项 (项目方向): [{id, title}]
const planDirectionOptions = ref([])
async function loadPlanDirectionOptions() {
try {
const { data } = await request.get('/business/specialPlan/options')
planDirectionOptions.value = (data && data.data) || data || []
} catch { planDirectionOptions.value = [] }
}
const q = ref({ planName: '', projectNo: '', planDirectionId: '', planCategory: '', status: '', isSettled: '', remark: '' })
const rows = ref([]); const selection = ref([])
const loading = ref(false)
const submitting = ref(false)
const page = reactive({ pageNum: 1, pageSize: 10, total: 0 })
async function load() {
loading.value = true
try {
const { data } = await bizList('projectPlan', { ...q.value, pageNum: page.pageNum, pageSize: page.pageSize })
rows.value = data?.rows || []
page.total = data?.total || 0
} catch { rows.value = []; page.total = 0 }
finally { loading.value = false }
}
function reset() { q.value = { planName:'', projectNo:'', planDirectionId:'', planCategory:'', status:'', isSettled:'', remark:'' }; page.pageNum = 1; load() }
function onSel(rs) { selection.value = rs }
async function batchSettle() {
if (!selection.value.length) return ElMessage.warning('请先勾选行')
submitting.value = true
let ok = 0
for (const r of selection.value) {
try { await bizUpdate('projectPlan', { planId: r.planId, isSettled: 'Y' }); ok++ } catch {}
}
submitting.value = false
ElMessage.success(`批量结算 ${ok}/${selection.value.length} 条`)
selection.value = []
load()
}
const auditOpen = ref(false)
const auditFormRef = ref(null)
// 审核: 只能审核不通过, 固定写死 status='2'
const auditForm = reactive({ planId: '', planName: '', opinion: '' })
const auditRules = {
opinion: [{ required: true, message: '请填写审核不通过原因', trigger: 'blur' }]
}
function onAudit(row) {
auditForm.planId = row.planId
auditForm.planName = row.planName
auditForm.opinion = ''
auditOpen.value = true
}
async function submitAudit() {
try {
await auditFormRef.value.validate()
submitting.value = true
await bizUpdate('projectPlan', { planId: auditForm.planId, status: '3', auditOpinion: auditForm.opinion })
ElMessage.success('已退回')
auditOpen.value = false
load()
} catch (e) { if (e?.message) ElMessage.error(e.message) }
finally { submitting.value = false }
}
const editOpen = ref(false)
const editFormRef = ref(null)
const editForm = reactive({ planId:'', planName:'', projectNo:'', planDirectionId:'', planCategory:'', projectForm:'', designFileUrl:'', remark:'' })
const editRules = { planName:[{required:true,message:'请输入方案名称',trigger:'blur'}] }
function onEdit(row) {
Object.assign(editForm, {
planId: row.planId, planName: row.planName, projectNo: row.projectNo,
planDirectionId: row.planDirectionId ?? null, planCategory: row.planCategory,
projectForm: row.projectForm, designFileUrl: row.designFileUrl || '', remark: row.remark || ''
})
editOpen.value = true
}
function resetEditForm() { Object.assign(editForm, { planId:'', planName:'', projectNo:'', planDirectionId:'', planCategory:'', projectForm:'', designFileUrl:'', remark:'' }) }
async function submitEdit() {
await editFormRef.value.validate()
try {
submitting.value = true
await bizUpdate('projectPlan', { ...editForm })
ElMessage.success('已保存')
editOpen.value = false
load()
} catch (e) { ElMessage.error(e?.msg || '保存失败') }
finally { submitting.value = false }
}
const settleOpen = ref(false)
const settleForm = reactive({ planId:'', planName:'' })
function onSettle(row) { settleForm.planId = row.planId; settleForm.planName = row.planName; settleOpen.value = true }
async function submitSettle() {
try {
submitting.value = true
await bizUpdate('projectPlan', { planId: settleForm.planId, isSettled: 'Y' })
ElMessage.success('已结算')
settleOpen.value = false
load()
} catch { ElMessage.error('结算失败') }
finally { submitting.value = false }
}
const previewOpen = ref(false); const previewUrl = ref(''); const previewTitle = ref('策划方案预览')
function onFile(row) { previewUrl.value = row.designFileUrl; previewTitle.value = `策划方案 - ${row.planName || ''}`; previewOpen.value = true }
function downloadFile(row) {
if (!row.designFileUrl) return
const a = document.createElement('a')
a.href = row.designFileUrl
a.download = row.planName || '设计文件'
a.target = '_blank'
a.click()
}
onMounted(() => { loadPlanDirectionOptions(); load() })
</script>
<style scoped>
/* 整页面板 (与 People.vue 风格一致) */
.manager-plans { padding: 16px; }
.breadcrumb { font-size: 13px; color: #8c8c8c; margin-bottom: 12px; }
.page-title {
font-size: 22px;
font-weight: 600;
color: #1a1a1a;
margin: 0 0 6px;
padding-left: 10px;
border-left: 3px solid var(--brand-primary);
}
.page-sub { font-size: 13px; color: #909399; margin: 0 0 16px; }
.filter-form { margin-bottom: 12px; }
.batch-bar { display: flex; gap: 8px; margin-bottom: 12px; align-items: center; }
.filter-tip { color: #909399; font-size: 12px; }
.pager { display: flex; justify-content: flex-end; margin-top: 12px; }
</style>