Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fded62bedf | ||
|
|
0c3c35c0f8 | ||
|
|
8e1b1fc209 |
+20
-14
@@ -56,10 +56,10 @@ public class InvitationPdfService {
|
||||
/** 公章尺寸 (宽高, px), 复用 PdfService 劳务协议章口径 */
|
||||
private static final float SEAL_WIDTH = 120f;
|
||||
private static final float SEAL_HEIGHT = 120f;
|
||||
/** 需盖章的落款机构名 (取全文最后一个) */
|
||||
private static final String ORG_TEXT = "北京整合医学学会";
|
||||
/** 姓名占位符 (忽略大小写, 兼容 xxx / XXX) */
|
||||
private static final String PLACEHOLDER = "xxx";
|
||||
/** 需盖章的落款机构名 (取全文最后一个), 兼容无空格/每字空格两种排版 */
|
||||
private static final String[] ORG_TEXTS = {"北京整合医学学会", "北 京 整 合 医 学 学 会"};
|
||||
/** 姓名占位符 (忽略大小写, 由 toLowerCase 归一), 兼容无空格/带空格两种排版 */
|
||||
private static final String[] PLACEHOLDERS = {"xxx", "x x x"};
|
||||
|
||||
/** 按 URL 下载邀请函模板 PDF 字节 (批量发送时对同一会议只调用一次) */
|
||||
public byte[] downloadTemplate(String url) {
|
||||
@@ -144,21 +144,27 @@ public class InvitationPdfService {
|
||||
}
|
||||
|
||||
String lower = full.toLowerCase(Locale.ROOT);
|
||||
int i = 0;
|
||||
while ((i = lower.indexOf(PLACEHOLDER, i)) >= 0) {
|
||||
nameTargets.add(buildNameTarget(charInfos, i, PLACEHOLDER.length(), p));
|
||||
i += PLACEHOLDER.length();
|
||||
// 姓名占位符: 兼容无空格/带空格两种写法 (xxx / x x x, 大小写由 toLowerCase 归一)
|
||||
for (String ph : PLACEHOLDERS) {
|
||||
int i = 0;
|
||||
while ((i = lower.indexOf(ph, i)) >= 0) {
|
||||
nameTargets.add(buildNameTarget(charInfos, i, ph.length(), p));
|
||||
i += ph.length();
|
||||
}
|
||||
}
|
||||
int j = 0;
|
||||
while ((j = full.indexOf(ORG_TEXT, j)) >= 0) {
|
||||
seal = buildSealTarget(charInfos, j, ORG_TEXT.length(), p);
|
||||
j += ORG_TEXT.length();
|
||||
// 落款机构: 兼容无空格/每字空格两种写法, 取全文最后一个匹配
|
||||
for (String org : ORG_TEXTS) {
|
||||
int j = 0;
|
||||
while ((j = full.indexOf(org, j)) >= 0) {
|
||||
seal = buildSealTarget(charInfos, j, org.length(), p);
|
||||
j += org.length();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 第二遍: 叠加
|
||||
if (nameTargets.isEmpty()) {
|
||||
log.warn("[invitation] 未找到占位符 xxx, 跳过姓名替换");
|
||||
log.warn("[invitation] 未找到姓名占位符 (xxx / x x x), 跳过姓名替换");
|
||||
} else {
|
||||
PdfFont cjkFont = PdfFontFactory.createFont("simsun.ttc,0", PdfEncodings.IDENTITY_H, false);
|
||||
for (NameTarget t : nameTargets) {
|
||||
@@ -166,7 +172,7 @@ public class InvitationPdfService {
|
||||
}
|
||||
}
|
||||
if (seal == null) {
|
||||
log.warn("[invitation] 未找到 '{}', 跳过盖章", ORG_TEXT);
|
||||
log.warn("[invitation] 未找到落款机构名 (北京整合医学学会), 跳过盖章");
|
||||
} else {
|
||||
drawSeal(doc, seal);
|
||||
}
|
||||
|
||||
@@ -56,12 +56,12 @@ async function loadStats() {
|
||||
stats.value.settledProjects = await projectCount({ isSettled: 'Y' })
|
||||
stats.value.completedProjects = await projectCount({ isFinished: 'Y' })
|
||||
// 会议统计走 /business/meeting/list (后端已按 roleType MAIN/SUB 隔离)
|
||||
const ms = await bizList('meeting', { pageNum: 1, pageSize: 1 })
|
||||
const meetingTotal = ms.total || ms.data?.total || 0
|
||||
// 已执行会议 = currentStage='RUNNING' (阶段已过开始时间, 执行方未提交)
|
||||
const em = await bizList('meeting', { pageNum: 1, pageSize: 1, currentStage: 'RUNNING' })
|
||||
stats.value.executedMeetings = em.total || em.data?.total || 0
|
||||
stats.value.pendingMeetings = Math.max(0, meetingTotal - stats.value.executedMeetings)
|
||||
// 未执行会议 = currentStage='NOT_STARTED' (与跳转链接 /executor/meetings?currentStage=NOT_STARTED 口径一致, 不再用"总数-RUNNING"减法)
|
||||
const pm = await bizList('meeting', { pageNum: 1, pageSize: 1, currentStage: 'NOT_STARTED' })
|
||||
stats.value.pendingMeetings = pm.total || pm.data?.total || 0
|
||||
} catch (e) {
|
||||
console.warn('loadStats failed', e)
|
||||
}
|
||||
|
||||
@@ -84,6 +84,14 @@
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="12">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="发布时间" prop="publishTime">
|
||||
<el-date-picker v-model="form.publishTime" type="datetime" value-format="YYYY-MM-DD HH:mm:ss"
|
||||
:disabled-date="disableFutureDate" placeholder="请选择发布时间" style="width:100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- ========== 第二区块:角色劳务设置 ========== -->
|
||||
<div class="new-card-title">角色劳务设置</div>
|
||||
@@ -153,12 +161,31 @@ const fileInputRef = ref(null)
|
||||
|
||||
function defaultRoleRow() { return { role: '', customName: '', amount: 0 } }
|
||||
function defaultNotice(key, label) { return { key, label, url: '', name: '' } }
|
||||
// 当前本地时间 "yyyy-MM-dd HH:mm:ss" (发布用, 与 el-date-picker 的本地时间口径一致; 不能用 toISOString 的 UTC)
|
||||
// 当前本地时间 "yyyy-MM-dd HH:mm:ss" (发布时间默认值/校验用, 与 el-date-picker 的本地时间口径一致; 不能用 toISOString 的 UTC)
|
||||
function nowLocalDatetime() {
|
||||
const d = new Date()
|
||||
const p = n => String(n).padStart(2, '0')
|
||||
return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}:${p(d.getSeconds())}`
|
||||
}
|
||||
// 发布时间: 禁用今天之后的日历日期 (未来日期不可选)
|
||||
function disableFutureDate(date) {
|
||||
const today = new Date()
|
||||
today.setHours(0, 0, 0, 0)
|
||||
return date.getTime() > today.getTime()
|
||||
}
|
||||
// 发布时间校验: 必填 + 不能晚于当前时间
|
||||
// value 是 "YYYY-MM-DD HH:mm:ss" 定长补零字符串, 字典序 = 时间序, 直接字符串比较 (无需 Date 解析/时区)
|
||||
function validatePublishTime(rule, value, callback) {
|
||||
if (!value) {
|
||||
callback(new Error('请选择发布时间'))
|
||||
return
|
||||
}
|
||||
if (value > nowLocalDatetime()) {
|
||||
callback(new Error('发布时间不能晚于当前时间'))
|
||||
return
|
||||
}
|
||||
callback()
|
||||
}
|
||||
|
||||
// 项目负责人候选 (sys_user.role_type='leader')
|
||||
const leaderOptions = ref([])
|
||||
@@ -172,6 +199,8 @@ const form = reactive({
|
||||
isBidProject: 'N',
|
||||
totalSessions: 0, totalAmount: 0, manageFee: 0,
|
||||
startTime: '', endTime: '',
|
||||
// 发布时间: 默认打开页面时的本地时间, 用户可修改 (不可设未来时间)
|
||||
publishTime: nowLocalDatetime(),
|
||||
roleRows: [
|
||||
{ role: '主席', customName: '', amount: 0 },
|
||||
{ role: '主持', customName: '', amount: 0 },
|
||||
@@ -195,7 +224,8 @@ const rules = {
|
||||
totalSessions: [{ required: true, message: '请输入总场次', trigger: 'blur' }],
|
||||
totalAmount: [{ required: true, message: '请输入总金额', trigger: 'blur' }],
|
||||
startTime: [{ required: true, message: '请选择开始时间', trigger: 'change' }],
|
||||
endTime: [{ required: true, message: '请选择结束时间', trigger: 'change' }]
|
||||
endTime: [{ required: true, message: '请选择结束时间', trigger: 'change' }],
|
||||
publishTime: [{ validator: validatePublishTime, trigger: 'change' }]
|
||||
}
|
||||
|
||||
function onRoleSelectChange(r) {
|
||||
@@ -289,6 +319,8 @@ async function loadProject() {
|
||||
form.manageFee = p.manageFee || 0
|
||||
form.startTime = p.startTime || ''
|
||||
form.endTime = p.endTime || ''
|
||||
// 发布时间: 已有值反显, 否则沿用页面打开时的默认时间
|
||||
form.publishTime = p.publishTime || nowLocalDatetime()
|
||||
form.leadUserId = p.leadUserId || null
|
||||
form.isBidProject = p.isBidProject || 'N'
|
||||
// 角色劳务反显: biz_project.role_labor JSON 数组 [{role, customName, amount}]
|
||||
@@ -403,10 +435,10 @@ async function submit(mode = 'save') {
|
||||
else if (n.key === 'notice') payload.publishUrl = n.url
|
||||
else if (n.key === 'schedule') payload.scheduleUrl = n.url
|
||||
}
|
||||
// 发布时设置 is_published='Y' + publish_time (同步后端字段)
|
||||
// 发布时设置 is_published='Y' + publish_time (用表单里用户可编辑的发布时间)
|
||||
if (mode === 'publish') {
|
||||
payload.isPublished = 'Y'
|
||||
payload.publishTime = nowLocalDatetime()
|
||||
payload.publishTime = form.publishTime
|
||||
}
|
||||
// 多执行方分配已移至"项目分配"页面单独管理
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user