涵盖改动范围: - 后端 BizOrg / BizProject / BizProjectRating / BizSpecialPlan controller + domain + mapper + service - 前端 admin (BizSpecialPlanAdmin, ProjectCategory) / executor / leader / manager / sponsor 多页调整 - api/dict.js + AdminLayout.vue 同步
288 lines
14 KiB
Vue
288 lines
14 KiB
Vue
<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>
|