批量推送
This commit is contained in:
@@ -29,7 +29,7 @@
|
||||
<span class="info-value">{{ fmtDate(row.endTime) }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="赞助公司">
|
||||
<span class="info-value">{{ row.orgName || '-' }}</span>
|
||||
<span class="info-value">{{ row.sponsorAdminUserName || '-' }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="项目形式">
|
||||
<span class="info-value">{{ row.projectForm || '-' }}</span>
|
||||
|
||||
@@ -3,18 +3,13 @@
|
||||
<div class="breadcrumb">首页 / 项目管理</div>
|
||||
|
||||
<el-form inline :model="q" class="filter-form">
|
||||
<el-form-item label="赞助公司:"><el-input v-model="q.orgName" placeholder="输入赞助公司" clearable /></el-form-item>
|
||||
<el-form-item label="公司类型:">
|
||||
<el-select v-model="q.orgType" placeholder="全部" clearable style="width:120px">
|
||||
<el-option label="赞助方" value="sponsor" />
|
||||
<el-option label="执行方" value="executor" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="赞助方负责人:"><el-input v-model="q.sponsorAdminUserName" placeholder="输入账号/昵称" clearable /></el-form-item>
|
||||
<el-form-item label="服务机构:"><el-input v-model="q.execOrgName" placeholder="输入服务机构" clearable /></el-form-item>
|
||||
<el-form-item label="项目编号:"><el-input v-model="q.projectNo" placeholder="输入项目编号" clearable /></el-form-item>
|
||||
<el-form-item label="项目名称:"><el-input v-model="q.projectName" placeholder="输入项目名称" clearable /></el-form-item>
|
||||
<el-form-item label="项目形式:">
|
||||
<el-select v-model="q.projectForm" placeholder="请选择" clearable>
|
||||
<el-option label="线上" value="线上" /><el-option label="线下" value="下线" />
|
||||
<el-select v-model="q.projectForm" placeholder="请选择" clearable style="width:140px">
|
||||
<el-option label="线上" value="线上" /><el-option label="线下" value="线下" />
|
||||
<el-option label="线上+线下" value="线上+线下" /><el-option label="其他" value="其他" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
@@ -24,12 +19,12 @@
|
||||
<el-date-picker v-model="q.endTime" type="date" placeholder="结束日期" value-format="YYYY-MM-DD HH:mm:ss" />
|
||||
</el-form-item>
|
||||
<el-form-item label="是否结题:">
|
||||
<el-select v-model="q.isFinished" placeholder="请选择" clearable>
|
||||
<el-select v-model="q.isFinished" placeholder="请选择" clearable style="width:120px">
|
||||
<el-option label="已结题" value="1" /><el-option label="未结题" value="0" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="是否结算:">
|
||||
<el-select v-model="q.isSettled" placeholder="请选择" clearable>
|
||||
<el-select v-model="q.isSettled" placeholder="请选择" clearable style="width:120px">
|
||||
<el-option label="已结算" value="1" /><el-option label="未结算" value="0" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
@@ -69,14 +64,7 @@
|
||||
<span v-else style="color:#c0c4cc">-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="orgName" label="赞助公司" width="160" show-overflow-tooltip />
|
||||
<el-table-column prop="orgType" label="公司类型" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.orgType === 'sponsor' ? 'success' : 'primary'" disable-transitions>
|
||||
{{ row.orgType === 'sponsor' ? '赞助方' : '执行方' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="sponsorAdminUserName" label="赞助方负责人" width="160" show-overflow-tooltip />
|
||||
<el-table-column prop="projectForm" label="项目形式" width="100" align="center" />
|
||||
<el-table-column prop="isFinished" label="是否结题" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
@@ -133,11 +121,26 @@
|
||||
<div style="color:#606266;font-size:13px">共 <b>{{ assignBatchProjects.length }}</b> 个项目</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="支持方">
|
||||
<el-select v-model="assignForm.orgId" placeholder="请选择赞助公司" filterable :filter-method="searchSupporters" clearable style="width:100%" @change="onSupporterPick">
|
||||
<el-select v-model="assignForm.sponsorAdminUserId" placeholder="请选择赞助方负责人" filterable :filter-method="searchSupporters" clearable style="width:100%" @change="onSupporterPick">
|
||||
<el-option v-for="u in supporterOptions" :key="u.userId" :label="`${u.userName} (${u.nickName || ''})`" :value="u.userId" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-divider>执行方分配</el-divider>
|
||||
<!-- 单选模式: 显示项目总场次 / 总金额(元) vs 已分配 (实时) -->
|
||||
<div v-if="!assignBatchMode" class="assign-sessions-bar">
|
||||
<span>项目总场次: <b>{{ assignForm.totalSessions || 0 }}</b></span>
|
||||
<span :class="{ 'is-over': singleSessionOver }">已分配场次: <b>{{ assignedSessions }}</b></span>
|
||||
<span v-if="singleSessionOver" class="over-warn">⚠ 已超出 {{ assignedSessions - assignForm.totalSessions }} 场</span>
|
||||
<span class="sep">|</span>
|
||||
<span>总金额(元): <b>{{ assignForm.totalAmount || 0 }}</b></span>
|
||||
<span :class="{ 'is-over': singleAmountOver }">已分配金额: <b>¥ {{ assignedAmount }}</b></span>
|
||||
<span v-if="singleAmountOver" class="over-warn">⚠ 已超出 ¥ {{ Math.round((assignedAmount - assignForm.totalAmount) * 100) / 100 }}</span>
|
||||
</div>
|
||||
<!-- 批量模式: 提示保存时按各项目自身总场次 / 总金额逐项校验 -->
|
||||
<div v-else class="assign-sessions-bar">
|
||||
<span>本次分配: <b>{{ assignedSessions }}</b> 场 / <b>¥ {{ assignedAmount }}</b></span>
|
||||
<span class="hint">(保存时按各项目自身的总场次和总金额逐项校验)</span>
|
||||
</div>
|
||||
<el-table :data="assignForm.execRows" border>
|
||||
<el-table-column type="index" label="#" width="50" />
|
||||
<el-table-column label="执行方名称" min-width="220">
|
||||
@@ -264,7 +267,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted, watch } from 'vue'
|
||||
import { ref, reactive, computed, onMounted, watch } from 'vue'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
import { bizList, bizAdd, bizUpdate, bizDelete } from '@/api/public'
|
||||
import { listExecutor, listSupporters } from '@/api/system'
|
||||
@@ -275,7 +278,7 @@ import Preview from '@/components/Preview.vue'
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
|
||||
const q = ref({ orgName: '', orgType: '', projectNo: '', projectName: '', projectForm: '', startTime: '', endTime: '', isFinished: '', isSettled: '', ratingScore: '' })
|
||||
const q = ref({ sponsorAdminUserName: '', execOrgName: '', projectNo: '', projectName: '', projectForm: '', startTime: '', endTime: '', isFinished: '', isSettled: '', ratingScore: '' })
|
||||
const rows = ref([])
|
||||
const loading = ref(false)
|
||||
const page = reactive({ pageNum: 1, pageSize: 10, total: 0 })
|
||||
@@ -327,7 +330,7 @@ async function doDelete(row) {
|
||||
}
|
||||
}
|
||||
|
||||
function reset() { q.value = { orgName:'', orgType:'', projectNo:'', projectName:'', projectForm:'', startTime:'', endTime:'', isFinished:'', isSettled:'', ratingScore:'' }; page.pageNum = 1; load() }
|
||||
function reset() { q.value = { sponsorAdminUserName:'', execOrgName:'', projectNo:'', projectName:'', projectForm:'', startTime:'', endTime:'', isFinished:'', isSettled:'', ratingScore:'' }; page.pageNum = 1; load() }
|
||||
|
||||
// 导出
|
||||
function exportProjects() { ElMessage.info('导出项目功能开发中') }
|
||||
@@ -563,20 +566,38 @@ const assignBatchProjects = ref([]) // 批量模式下的项目列表 [
|
||||
const assignSubmitting = ref(false)
|
||||
const assignForm = reactive({
|
||||
projectId: '',
|
||||
orgId: null,
|
||||
orgName: '',
|
||||
sponsorAdminUserId: null,
|
||||
sponsorAdminUserName: '',
|
||||
totalSessions: 0, // 项目总场次 (来自项目本身, 不可被分配覆盖)
|
||||
totalAmount: 0, // 项目总金额(元) (来自项目本身, 不可被分配覆盖)
|
||||
execRows: [], // 动态加
|
||||
deadlineDays: 30
|
||||
})
|
||||
const supporterOptions = ref([])
|
||||
let _supporterTimer = null
|
||||
|
||||
// 已分配场次合计 (实时)
|
||||
const assignedSessions = computed(() =>
|
||||
assignForm.execRows.reduce((s, r) => s + Number(r.sessions || 0), 0)
|
||||
)
|
||||
const singleSessionOver = computed(() =>
|
||||
!assignBatchMode.value && Number(assignForm.totalSessions || 0) > 0 && assignedSessions.value > Number(assignForm.totalSessions)
|
||||
)
|
||||
// 已分配金额合计 (实时, 保留 2 位小数避免浮点漂移)
|
||||
const assignedAmount = computed(() => {
|
||||
const sum = assignForm.execRows.reduce((s, r) => s + Number(r.amount || 0), 0)
|
||||
return Math.round(sum * 100) / 100
|
||||
})
|
||||
const singleAmountOver = computed(() =>
|
||||
!assignBatchMode.value && Number(assignForm.totalAmount || 0) > 0 && assignedAmount.value > Number(assignForm.totalAmount)
|
||||
)
|
||||
|
||||
function makeEmptyExecRow() {
|
||||
return { _loading: false, _timer: null, _options: [], execUserId: null, execUserName: '', execNickName: '', execOrg: '', sessions: 0, amount: 0, remark: '' }
|
||||
}
|
||||
|
||||
function resetAssignForm() {
|
||||
Object.assign(assignForm, { projectId:'', orgId:null, orgName:'', execRows:[makeEmptyExecRow()], deadlineDays:30 })
|
||||
Object.assign(assignForm, { projectId:'', sponsorAdminUserId:null, sponsorAdminUserName:'', totalSessions:0, totalAmount:0, execRows:[makeEmptyExecRow()], deadlineDays:30 })
|
||||
supporterOptions.value = []
|
||||
assignBatchMode.value = false
|
||||
assignBatchProjects.value = []
|
||||
@@ -592,8 +613,10 @@ function addExecRow() {
|
||||
function onAssignProjectChange(v) {
|
||||
const proj = rows.value.find(r => r.projectId === v)
|
||||
if (proj) {
|
||||
assignForm.orgId = proj.orgId || null
|
||||
assignForm.orgName = proj.orgName || ''
|
||||
assignForm.sponsorAdminUserId = proj.sponsorAdminUserId || null
|
||||
assignForm.sponsorAdminUserName = proj.sponsorAdminUserName || ''
|
||||
assignForm.totalSessions = Number(proj.totalSessions || 0)
|
||||
assignForm.totalAmount = Number(proj.totalAmount || 0)
|
||||
}
|
||||
// 切换项目时重新拉已分配记录 (回显)
|
||||
loadAssigns(v)
|
||||
@@ -611,9 +634,9 @@ async function loadSupporters(query) {
|
||||
const r = await listSupporters(params)
|
||||
supporterOptions.value = r.data || []
|
||||
// 已选项不在结果里时单独拉取并加首位
|
||||
if (assignForm.orgId && !supporterOptions.value.find(u => u.userId === assignForm.orgId)) {
|
||||
const r2 = await listSupporters({ userId: assignForm.orgId })
|
||||
const sel = (r2.data || []).find(u => u.userId === assignForm.orgId)
|
||||
if (assignForm.sponsorAdminUserId && !supporterOptions.value.find(u => u.userId === assignForm.sponsorAdminUserId)) {
|
||||
const r2 = await listSupporters({ userId: assignForm.sponsorAdminUserId })
|
||||
const sel = (r2.data || []).find(u => u.userId === assignForm.sponsorAdminUserId)
|
||||
if (sel) supporterOptions.value = [sel, ...supporterOptions.value]
|
||||
}
|
||||
} catch (e) {
|
||||
@@ -627,7 +650,7 @@ function searchSupporters(q) { loadSupporters(q) }
|
||||
// 选中支持方后回填名称
|
||||
function onSupporterPick(userId) {
|
||||
const u = supporterOptions.value.find(x => x.userId === userId)
|
||||
if (u) assignForm.orgName = u.nickName || u.userName
|
||||
if (u) assignForm.sponsorAdminUserName = u.nickName || u.userName
|
||||
}
|
||||
|
||||
// 拉取项目已分配的执行方
|
||||
@@ -698,8 +721,34 @@ async function submitAssign() {
|
||||
if (!assignBatchMode.value && !assignForm.projectId) return ElMessage.warning('请选择项目')
|
||||
const valid = assignForm.execRows.filter(r => r.execUserId && Number(r.sessions) > 0)
|
||||
if (!valid.length) return ElMessage.warning('请至少选择 1 个执行方并填写场次')
|
||||
const amount = valid.reduce((s, r) => s + Number(r.amount || 0), 0)
|
||||
const amount = Math.round(valid.reduce((s, r) => s + Number(r.amount || 0), 0) * 100) / 100
|
||||
const sessions = valid.reduce((s, r) => s + Number(r.sessions || 0), 0)
|
||||
|
||||
// 校验: 各执行方分配场次 / 金额总和 <= 项目总场次 / 总金额
|
||||
// (项目自身的 total_sessions / total_amount 是创建时定的, 不再被分配覆盖)
|
||||
if (assignBatchMode.value) {
|
||||
for (const p of assignBatchProjects.value) {
|
||||
const proj = rows.value.find(r => r.projectId === p.projectId)
|
||||
const capS = proj ? Number(proj.totalSessions || 0) : 0
|
||||
const capA = proj ? Number(proj.totalAmount || 0) : 0
|
||||
if (capS > 0 && sessions > capS) {
|
||||
return ElMessage.warning(`项目 ${p.projectNo} 已分配 ${sessions} 场, 超过总场次 ${capS}, 请调整`)
|
||||
}
|
||||
if (capA > 0 && amount > capA) {
|
||||
return ElMessage.warning(`项目 ${p.projectNo} 已分配 ¥${amount}, 超过总金额 ¥${capA}, 请调整`)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const capS = Number(assignForm.totalSessions || 0)
|
||||
const capA = Number(assignForm.totalAmount || 0)
|
||||
if (capS > 0 && sessions > capS) {
|
||||
return ElMessage.warning(`已分配 ${sessions} 场, 超过项目总场次 ${capS}, 请调整`)
|
||||
}
|
||||
if (capA > 0 && amount > capA) {
|
||||
return ElMessage.warning(`已分配 ¥${amount}, 超过项目总金额 ¥${capA}, 请调整`)
|
||||
}
|
||||
}
|
||||
|
||||
const assignBody = valid.map(r => ({
|
||||
execUserId: r.execUserId,
|
||||
execUserName: r.execUserName,
|
||||
@@ -718,10 +767,8 @@ async function submitAssign() {
|
||||
try {
|
||||
await bizUpdate('project', {
|
||||
projectId: p.projectId,
|
||||
orgId: assignForm.orgId,
|
||||
orgName: assignForm.orgName,
|
||||
totalAmount: amount,
|
||||
totalSessions: sessions,
|
||||
sponsorAdminUserId: assignForm.sponsorAdminUserId,
|
||||
sponsorAdminUserName: assignForm.sponsorAdminUserName,
|
||||
submitDeadlineDays: assignForm.deadlineDays,
|
||||
})
|
||||
await request({
|
||||
@@ -740,10 +787,8 @@ async function submitAssign() {
|
||||
// 单条模式
|
||||
await bizUpdate('project', {
|
||||
projectId: assignForm.projectId,
|
||||
orgId: assignForm.orgId,
|
||||
orgName: assignForm.orgName,
|
||||
totalAmount: amount,
|
||||
totalSessions: sessions,
|
||||
sponsorAdminUserId: assignForm.sponsorAdminUserId,
|
||||
sponsorAdminUserName: assignForm.sponsorAdminUserName,
|
||||
submitDeadlineDays: assignForm.deadlineDays,
|
||||
})
|
||||
await request({
|
||||
@@ -830,5 +875,18 @@ watch(() => route.path + (route.query._t || ''), () => load())
|
||||
.score-item { display: flex; align-items: center; gap: 8px; }
|
||||
.score-item span { width: 70px; color: #606266; font-size: 13px; }
|
||||
|
||||
/* 分配 dialog 总场次条 */
|
||||
.assign-sessions-bar {
|
||||
display: flex; align-items: center; gap: 18px;
|
||||
margin: -4px 0 12px; padding: 10px 14px;
|
||||
background: #f5f7fa; border: 1px solid #ebeef5; border-radius: 4px;
|
||||
font-size: 13px; color: #606266;
|
||||
}
|
||||
.assign-sessions-bar b { color: #303133; font-size: 14px; margin: 0 2px; }
|
||||
.assign-sessions-bar .is-over b { color: #f56c6c; }
|
||||
.assign-sessions-bar .over-warn { color: #f56c6c; font-weight: 600; }
|
||||
.assign-sessions-bar .hint { color: #909399; font-size: 12px; }
|
||||
.assign-sessions-bar .sep { color: #dcdfe6; margin: 0 4px; }
|
||||
|
||||
|
||||
</style>
|
||||
@@ -69,7 +69,7 @@
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="支持单位">
|
||||
<el-select v-model="form.orgId" placeholder="请选择赞助公司" filterable clearable style="width:100%" @change="onSupporterPick">
|
||||
<el-select v-model="form.sponsorAdminUserId" placeholder="请选择赞助方负责人" filterable clearable style="width:100%" @change="onSupporterPick">
|
||||
<el-option v-for="u in supporterOptions" :key="u.userId"
|
||||
:label="`${u.userName}${u.nickName ? ' (' + u.nickName + ')' : ''}`"
|
||||
:value="u.userId" />
|
||||
@@ -104,37 +104,21 @@
|
||||
|
||||
<!-- ========== 第三区块:公告文件 ========== -->
|
||||
<div class="new-card-title">公告文件</div>
|
||||
<p class="hint-text">*勾选公告类型后,上传对应文件;发布公告时可同时发布多个类型</p>
|
||||
<p class="hint-text">*支持 PDF / 图片, 单文件 ≤ 10MB; 名称规则: 公告文件类型 + 项目编号后两段 + 项目名称</p>
|
||||
<div class="notice-list">
|
||||
<div v-for="(n, idx) in form.notices" :key="idx" class="notice-row">
|
||||
<el-checkbox v-model="n.enabled" :label="n.key" class="notice-check">
|
||||
{{ n.label }}
|
||||
</el-checkbox>
|
||||
<el-upload
|
||||
v-if="n.enabled"
|
||||
<span class="notice-label">{{ n.label }}</span>
|
||||
<oss-file-uploader
|
||||
class="notice-uploader"
|
||||
:show-file-list="true"
|
||||
:file-list="getFileList(n)"
|
||||
:before-upload="(file) => beforeUpload(file, n)"
|
||||
:http-request="(opts) => httpUpload(opts, n, idx)"
|
||||
:on-remove="(file) => onRemove(n, file)"
|
||||
:on-preview="(file) => onPreview(file)"
|
||||
accept=".pdf,.doc,.docx"
|
||||
drag
|
||||
action="#"
|
||||
>
|
||||
<i class="el-icon-upload"></i>
|
||||
<div class="el-upload__text">
|
||||
将文件拖到此处,或<em>点击上传</em>
|
||||
</div>
|
||||
<div class="el-upload__tip" slot="tip">
|
||||
支持 PDF / Word,单文件 ≤ 20MB
|
||||
</div>
|
||||
</el-upload>
|
||||
<div v-else class="notice-disabled">勾选「{{ n.label }}」后可上传文件</div>
|
||||
v-model="n.url"
|
||||
:dir="`ry8080/project/${n.key}/`"
|
||||
accept=".pdf,.png,.jpg,.jpeg,.gif,.webp"
|
||||
:placeholder="`点击上传${n.label}`"
|
||||
hint="支持 PDF / 图片"
|
||||
block
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<p class="hint-text">*名称规则:公告文件类型+取项目编号的后两段+项目名称</p>
|
||||
</el-form>
|
||||
|
||||
<!-- 底部按钮 -->
|
||||
@@ -152,8 +136,8 @@ import { useRoute, useRouter } from 'vue-router'
|
||||
import { bizAdd, bizUpdate, bizGet } from '@/api/public'
|
||||
import { listExecutor, listSupporters } from '@/api/system'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { uploadToOss } from '@/utils/oss'
|
||||
import request from '@/utils/request'
|
||||
import OssFileUploader from '@/components/OssFileUploader.vue'
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
@@ -161,12 +145,10 @@ const isEdit = !!route.params.projectId
|
||||
const projectId = route.params.projectId || null
|
||||
const formRef = ref(null)
|
||||
const submitting = ref(false)
|
||||
const uploadingIdx = ref(-1)
|
||||
const currentUploadN = ref(null)
|
||||
const fileInputRef = ref(null)
|
||||
|
||||
function defaultRoleRow() { return { role: '', customName: '', amount: 0 } }
|
||||
function defaultNotice(key, label) { return { key, label, url: '', enabled: false, name: '' } }
|
||||
function defaultNotice(key, label) { return { key, label, url: '', name: '' } }
|
||||
|
||||
// 支持方候选 (sponsor 角色)
|
||||
const supporterOptions = ref([])
|
||||
@@ -175,19 +157,24 @@ const form = reactive({
|
||||
projectName: '', projectNo: '', projectForm: '',
|
||||
totalSessions: 0, totalAmount: 0, manageFee: 0,
|
||||
startTime: '', endTime: '',
|
||||
// 赞助公司 (TODO 后续: 下拉源应改为 biz_org,orgType='sponsor' 列表, 当前是 sys_user)
|
||||
orgId: null,
|
||||
orgName: '',
|
||||
// 赞助方负责人 (sys_user.role_type='sponsor', 原 org_id/org_name 字段已重命名)
|
||||
sponsorAdminUserId: null,
|
||||
sponsorAdminUserName: '',
|
||||
// 提交截止(天)
|
||||
submitDeadlineDays: 30,
|
||||
roleRows: Array(6).fill(0).map(() => defaultRoleRow()),
|
||||
// 公告附件(每个 notice 含 enabled/type/label/url/name)
|
||||
// enabled: 是否启用该公告类型(对应原型 checkbox 邀请函/支持函/通知/日程)
|
||||
roleRows: [
|
||||
{ role: '主席', customName: '', amount: 0 },
|
||||
{ role: '主持', customName: '', amount: 0 },
|
||||
{ role: '讲者', customName: '', amount: 0 },
|
||||
{ role: '点评/评审', customName: '', amount: 0 },
|
||||
{ role: '讨论', customName: '', amount: 0 }
|
||||
],
|
||||
// 公告附件 (key/label/url/name, 没有 url 即为空, 不参与提交)
|
||||
notices: [
|
||||
{ key: 'invitation', label: '邀请函', enabled: false, url: '', name: '' },
|
||||
{ key: 'supportLetter', label: '支持函', enabled: false, url: '', name: '' },
|
||||
{ key: 'notice', label: '通知', enabled: false, url: '', name: '' },
|
||||
{ key: 'schedule', label: '日程', enabled: false, url: '', name: '' }
|
||||
{ key: 'invitation', label: '邀请函', url: '', name: '' },
|
||||
{ key: 'supportLetter', label: '支持函', url: '', name: '' },
|
||||
{ key: 'notice', label: '通知', url: '', name: '' },
|
||||
{ key: 'schedule', label: '日程', url: '', name: '' }
|
||||
]
|
||||
})
|
||||
|
||||
@@ -235,8 +222,8 @@ async function loadProject() {
|
||||
form.manageFee = p.manageFee || 0
|
||||
form.startTime = p.startTime || ''
|
||||
form.endTime = p.endTime || ''
|
||||
form.orgId = p.orgId || null
|
||||
form.orgName = p.orgName || ''
|
||||
form.sponsorAdminUserId = p.sponsorAdminUserId || null
|
||||
form.sponsorAdminUserName = p.sponsorAdminUserName || ''
|
||||
form.submitDeadlineDays = p.submitDeadlineDays || 30
|
||||
// 公告文件反显: 优先 publicityFiles JSON 数组, 否则兼容旧字段
|
||||
const files = []
|
||||
@@ -248,7 +235,7 @@ async function loadProject() {
|
||||
// 按 type 反显到对应 notice 行
|
||||
for (const n of form.notices) {
|
||||
const m = files.find(f => f.type === n.key)
|
||||
if (m) { n.url = m.url || ''; n.name = m.name || ''; n.enabled = !!m.url }
|
||||
if (m) { n.url = m.url || ''; n.name = m.name || '' }
|
||||
}
|
||||
} else {
|
||||
// 兼容老数据
|
||||
@@ -262,7 +249,6 @@ async function loadProject() {
|
||||
for (const n of form.notices) {
|
||||
n.url = noticeMap[n.key] || ''
|
||||
n.name = n.url ? (n.url.split('/').pop() || '') : ''
|
||||
n.enabled = !!n.url
|
||||
}
|
||||
}
|
||||
// 加载已有执行方分配 (暂不实现, 留 hook)
|
||||
@@ -284,7 +270,7 @@ async function loadSupporters(query = '') {
|
||||
|
||||
function onSupporterPick(userId) {
|
||||
const u = supporterOptions.value.find(x => x.userId === userId)
|
||||
if (u) form.orgName = u.userName || ''
|
||||
if (u) form.sponsorAdminUserName = u.userName || ''
|
||||
}
|
||||
|
||||
|
||||
@@ -294,81 +280,6 @@ onMounted(() => {
|
||||
loadProject()
|
||||
loadSupporters('')
|
||||
})
|
||||
function beforeUpload(file, n) {
|
||||
// 文件大小校验: 限制 20MB
|
||||
const maxSize = 20 * 1024 * 1024
|
||||
if (file.size > maxSize) {
|
||||
ElMessage.error('文件大小不能超过 20MB')
|
||||
return false
|
||||
}
|
||||
// 文件类型校验
|
||||
const accept = ['.pdf', '.doc', '.docx', '.png', '.jpg', '.jpeg']
|
||||
const ext = '.' + file.name.split('.').pop().toLowerCase()
|
||||
if (!accept.includes(ext)) {
|
||||
ElMessage.error('仅支持 PDF/Word/图片格式')
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
async function httpUpload(opts, n, idx) {
|
||||
const file = opts.file
|
||||
uploadingIdx.value = idx
|
||||
currentUploadN.value = n
|
||||
const dir = 'ry8080/project/' + n.key + '/'
|
||||
try {
|
||||
const url = await uploadToOss(file, dir)
|
||||
n.url = url
|
||||
n.name = file.name
|
||||
n.enabled = true
|
||||
ElMessage.success(`${n.label} 上传成功`)
|
||||
} catch (err) {
|
||||
ElMessage.error(`上传失败: ${err.message || err}`)
|
||||
n.url = ''
|
||||
n.name = ''
|
||||
} finally {
|
||||
uploadingIdx.value = -1
|
||||
currentUploadN.value = null
|
||||
}
|
||||
}
|
||||
|
||||
// el-upload 内置 file-list 需要从 n.url 转换成 {name, url} 对象
|
||||
function getFileList(n) {
|
||||
if (!n.url) return []
|
||||
// 从 url 截取文件名
|
||||
const url = n.url
|
||||
const name = url.split('/').pop() || `${n.label}.pdf`
|
||||
return [{ name, url }]
|
||||
}
|
||||
|
||||
function onPreview(file) {
|
||||
if (file.url) window.open(file.url, '_blank')
|
||||
}
|
||||
|
||||
function onRemove(n, file) {
|
||||
n.url = ''
|
||||
}
|
||||
function previewFile(n) {
|
||||
if (n.url) window.open(n.url, '_blank')
|
||||
}
|
||||
function downloadFile(n) {
|
||||
if (!n.url) return
|
||||
// 触发浏览器下载
|
||||
const a = document.createElement('a')
|
||||
a.href = n.url
|
||||
a.download = n.name || (n.label + '.pdf')
|
||||
a.target = '_blank'
|
||||
document.body.appendChild(a)
|
||||
a.click()
|
||||
document.body.removeChild(a)
|
||||
}
|
||||
function removeNotice(idx) {
|
||||
const n = form.notices[idx]
|
||||
if (!n) return
|
||||
ElMessageBox.confirm(`确定删除"${n.label}"附件?`, '提示', { type: 'warning' })
|
||||
.then(() => { n.url = ''; ElMessage.success('已删除') })
|
||||
.catch(() => {})
|
||||
}
|
||||
function goBack() {
|
||||
// 带 _t 强制 Projects.vue watch 触发 reload, 即便路由一样也重载
|
||||
router.push({ path: '/manager/projects', query: { _t: Date.now() } })
|
||||
@@ -390,8 +301,8 @@ async function submit(mode = 'save') {
|
||||
manageFee: form.manageFee,
|
||||
startTime: form.startTime,
|
||||
endTime: form.endTime,
|
||||
orgId: form.orgId,
|
||||
orgName: form.orgName,
|
||||
sponsorAdminUserId: form.sponsorAdminUserId,
|
||||
sponsorAdminUserName: form.sponsorAdminUserName,
|
||||
submitDeadlineDays: form.submitDeadlineDays
|
||||
}
|
||||
// 公告文件以 publicityFiles JSON 数组存储 (替代旧 invitationUrl/supportLetterUrl/noticeUrl/scheduleUrl)
|
||||
@@ -473,30 +384,22 @@ async function submit(mode = 'save') {
|
||||
.role-custom { flex: 1; max-width: 180px; }
|
||||
.role-amount { width: 180px; }
|
||||
.row-actions { display: flex; gap: 8px; }
|
||||
.notice-list { display: flex; flex-direction: column; gap: 12px; }
|
||||
.notice-row { display: flex; align-items: stretch; gap: 16px; margin-bottom: 16px; }
|
||||
.notice-list { display: flex; flex-direction: column; gap: 10px; }
|
||||
.notice-row { display: flex; align-items: stretch; gap: 12px; margin-bottom: 12px; }
|
||||
.notice-label {
|
||||
width: 100px;
|
||||
width: 90px;
|
||||
display: flex; align-items: center; justify-content: flex-end;
|
||||
color: #606266; font-size: 14px;
|
||||
}
|
||||
.notice-uploader { flex: 1; }
|
||||
.notice-uploader :deep(.el-upload) { width: 100%; }
|
||||
.notice-uploader :deep(.el-upload-dragger) {
|
||||
padding: 12px 16px;
|
||||
display: flex; align-items: center; gap: 12px;
|
||||
}
|
||||
.notice-uploader :deep(.el-upload-dragger .el-upload__text) {
|
||||
font-size: 13px; color: #606266;
|
||||
}
|
||||
.notice-uploader :deep(.el-upload-dragger .el-upload__tip) {
|
||||
font-size: 12px; color: #909399; margin-top: 2px;
|
||||
}
|
||||
.notice-uploader :deep(.el-upload__tip) { margin-top: 0; }
|
||||
.notice-uploader :deep(.el-upload-list) { margin: 0; }
|
||||
.notice-actions { display: flex; flex-direction: column; justify-content: center; gap: 4px; min-width: 70px; }
|
||||
.notice-actions .el-button { padding: 2px 6px; font-size: 12px; }
|
||||
.notice-disabled { flex: 1; color: #c0c4cc; font-size: 13px; padding: 8px 0; }
|
||||
/* 紧凑版 (覆盖 OssFileUploader 默认尺寸) */
|
||||
.notice-uploader :deep(.ht-file-upload) { padding: 6px 12px; min-height: 40px; gap: 8px; }
|
||||
.notice-uploader :deep(.placeholder-text) { font-size: 13px; }
|
||||
.notice-uploader :deep(.placeholder-hint) { font-size: 11px; }
|
||||
.notice-uploader :deep(.file-name) { font-size: 13px; }
|
||||
.notice-uploader :deep(.file-type) { font-size: 11px; }
|
||||
.notice-uploader :deep(.file-icon) { font-size: 18px; }
|
||||
.notice-uploader :deep(.file-icon svg) { width: 18px; height: 18px; }
|
||||
|
||||
/* 底部按钮 */
|
||||
.form-actions { display: flex; justify-content: center; gap: 16px; padding: 20px 0 4px; border-top: 1px solid #f0f0f0; margin-top: 8px; }
|
||||
|
||||
Reference in New Issue
Block a user