feat(performance、notify、weekly): 添加绩效考核功能、通知跳转链接、优化工作日志分割逻辑。
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onBeforeUnmount, onMounted, reactive, ref, watch } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useDebounceFn, useInfiniteScroll } from '@vueuse/core';
|
||||
import { OBJECT_CONTEXT_QUERY_KEY } from '@/constants/object-context';
|
||||
import { NOTIFY_MESSAGE_LEVEL_DICT_CODE } from '@/constants/dict';
|
||||
import {
|
||||
fetchGetMyNotifyMessagePage,
|
||||
@@ -14,6 +16,7 @@ import { formatDateTime, formatRelativeTime } from '@/utils/datetime';
|
||||
defineOptions({ name: 'NotificationBell' });
|
||||
|
||||
const dictStore = useDictStore();
|
||||
const router = useRouter();
|
||||
|
||||
const PAGE_SIZE = 10;
|
||||
const UNREAD_COUNT_POLL_INTERVAL = 15 * 1000;
|
||||
@@ -50,6 +53,12 @@ const searchKeyword = ref('');
|
||||
const detailVisible = ref(false);
|
||||
const detailMessage = ref<Api.NotifyMessage.NotifyMessage | null>(null);
|
||||
|
||||
interface NotifyRenderParts {
|
||||
prefix: string;
|
||||
clickable: string;
|
||||
suffix: string;
|
||||
}
|
||||
|
||||
function keywordParam() {
|
||||
return searchKeyword.value.trim() || undefined;
|
||||
}
|
||||
@@ -202,6 +211,89 @@ function openDetail(row: Api.NotifyMessage.NotifyMessage) {
|
||||
}
|
||||
}
|
||||
|
||||
function getClickableText(message: Api.NotifyMessage.NotifyMessage) {
|
||||
const params = message.templateParams;
|
||||
switch (message.templateCode) {
|
||||
case 'execution_assigned':
|
||||
return params?.executionName?.trim() || '';
|
||||
case 'task_assigned':
|
||||
return params?.taskName?.trim() || '';
|
||||
case 'project_created':
|
||||
return params?.projectName?.trim() || '';
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
function canJump(message: Api.NotifyMessage.NotifyMessage) {
|
||||
const params = message.templateParams;
|
||||
return Boolean(params?.jumpType && params.projectId && getClickableText(message));
|
||||
}
|
||||
|
||||
function renderNotifyParts(message: Api.NotifyMessage.NotifyMessage): NotifyRenderParts {
|
||||
const content = message.templateContent || '';
|
||||
const clickable = getClickableText(message);
|
||||
if (!content || !clickable) {
|
||||
return { prefix: content, clickable: '', suffix: '' };
|
||||
}
|
||||
|
||||
const startIndex = content.indexOf(clickable);
|
||||
if (startIndex < 0) {
|
||||
return { prefix: content, clickable: '', suffix: '' };
|
||||
}
|
||||
|
||||
return {
|
||||
prefix: content.slice(0, startIndex),
|
||||
clickable,
|
||||
suffix: content.slice(startIndex + clickable.length)
|
||||
};
|
||||
}
|
||||
|
||||
async function handleNotifyJump(message: Api.NotifyMessage.NotifyMessage) {
|
||||
const params = message.templateParams;
|
||||
if (!params?.jumpType || !params.projectId) return;
|
||||
|
||||
const query: Record<string, string> = {
|
||||
[OBJECT_CONTEXT_QUERY_KEY]: params.projectId
|
||||
};
|
||||
|
||||
let path = '';
|
||||
|
||||
switch (params.jumpType) {
|
||||
case 'project':
|
||||
path = '/project/project/overview';
|
||||
break;
|
||||
case 'execution':
|
||||
path = '/project/project/execution';
|
||||
if (params.executionName) {
|
||||
query.executionName = params.executionName;
|
||||
}
|
||||
break;
|
||||
case 'task':
|
||||
path = '/project/project/execution';
|
||||
if (params.taskName) {
|
||||
query.taskName = params.taskName;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
detailVisible.value = false;
|
||||
drawerOpen.value = false;
|
||||
await router.push({ path, query });
|
||||
}
|
||||
|
||||
async function onNotifyLinkClick(row: Api.NotifyMessage.NotifyMessage) {
|
||||
if (!canJump(row)) return;
|
||||
|
||||
if (!row.readStatus) {
|
||||
await markRead(row);
|
||||
}
|
||||
|
||||
await handleNotifyJump(row);
|
||||
}
|
||||
|
||||
async function markAllRead() {
|
||||
const { error } = await fetchUpdateAllNotifyMessageRead();
|
||||
if (error) return;
|
||||
@@ -281,7 +373,25 @@ onBeforeUnmount(() => {
|
||||
>
|
||||
<span class="notification-bell__row-dot" :style="{ backgroundColor: levelDotColor(row.level) }" />
|
||||
<div class="notification-bell__row-body">
|
||||
<div class="notification-bell__row-title">{{ row.templateContent }}</div>
|
||||
<div class="notification-bell__row-title">
|
||||
<template v-if="canJump(row)">
|
||||
<template v-for="(part, index) in [renderNotifyParts(row)]" :key="`${row.id}-${index}`">
|
||||
<span>{{ part.prefix }}</span>
|
||||
<button
|
||||
v-if="part.clickable"
|
||||
type="button"
|
||||
class="notification-bell__inline-link"
|
||||
@click.stop="onNotifyLinkClick(row)"
|
||||
>
|
||||
{{ part.clickable }}
|
||||
</button>
|
||||
<span>{{ part.suffix }}</span>
|
||||
</template>
|
||||
</template>
|
||||
<template v-else>
|
||||
{{ row.templateContent }}
|
||||
</template>
|
||||
</div>
|
||||
<div class="notification-bell__row-meta">
|
||||
<DictTag :dict-code="NOTIFY_MESSAGE_LEVEL_DICT_CODE" :value="row.level" size="small" round />
|
||||
<span class="notification-bell__row-time">{{ formatRelativeTime(row.createTime) }}</span>
|
||||
@@ -312,7 +422,25 @@ onBeforeUnmount(() => {
|
||||
>
|
||||
<span class="notification-bell__row-dot" :style="{ backgroundColor: levelDotColor(row.level) }" />
|
||||
<div class="notification-bell__row-body">
|
||||
<div class="notification-bell__row-title">{{ row.templateContent }}</div>
|
||||
<div class="notification-bell__row-title">
|
||||
<template v-if="canJump(row)">
|
||||
<template v-for="(part, index) in [renderNotifyParts(row)]" :key="`${row.id}-${index}`">
|
||||
<span>{{ part.prefix }}</span>
|
||||
<button
|
||||
v-if="part.clickable"
|
||||
type="button"
|
||||
class="notification-bell__inline-link"
|
||||
@click.stop="onNotifyLinkClick(row)"
|
||||
>
|
||||
{{ part.clickable }}
|
||||
</button>
|
||||
<span>{{ part.suffix }}</span>
|
||||
</template>
|
||||
</template>
|
||||
<template v-else>
|
||||
{{ row.templateContent }}
|
||||
</template>
|
||||
</div>
|
||||
<div class="notification-bell__row-meta">
|
||||
<DictTag :dict-code="NOTIFY_MESSAGE_LEVEL_DICT_CODE" :value="row.level" size="small" round />
|
||||
<span class="notification-bell__row-time">{{ formatRelativeTime(row.createTime) }}</span>
|
||||
@@ -350,7 +478,25 @@ onBeforeUnmount(() => {
|
||||
</div>
|
||||
</template>
|
||||
<div v-if="detailMessage" class="notification-bell__detail-body">
|
||||
<div class="notification-bell__detail-content">{{ detailMessage.templateContent }}</div>
|
||||
<div class="notification-bell__detail-content">
|
||||
<template v-if="canJump(detailMessage)">
|
||||
<template v-for="(part, index) in [renderNotifyParts(detailMessage)]" :key="`detail-${index}`">
|
||||
<span>{{ part.prefix }}</span>
|
||||
<button
|
||||
v-if="part.clickable"
|
||||
type="button"
|
||||
class="notification-bell__inline-link"
|
||||
@click.stop="onNotifyLinkClick(detailMessage)"
|
||||
>
|
||||
{{ part.clickable }}
|
||||
</button>
|
||||
<span>{{ part.suffix }}</span>
|
||||
</template>
|
||||
</template>
|
||||
<template v-else>
|
||||
{{ detailMessage.templateContent }}
|
||||
</template>
|
||||
</div>
|
||||
<div class="notification-bell__detail-time">收到于 {{ formatDateTime(detailMessage.createTime) }}</div>
|
||||
</div>
|
||||
<template #footer>
|
||||
@@ -578,6 +724,19 @@ onBeforeUnmount(() => {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.notification-bell__inline-link {
|
||||
padding: 0;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
color: var(--el-color-primary);
|
||||
cursor: pointer;
|
||||
font: inherit;
|
||||
}
|
||||
|
||||
.notification-bell__inline-link:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.notification-bell__row-meta {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
16
src/typings/api/notify-message.d.ts
vendored
16
src/typings/api/notify-message.d.ts
vendored
@@ -5,6 +5,18 @@ declare namespace Api {
|
||||
* backend api module: "notify-message"(站内信 · 我的收件箱)
|
||||
*/
|
||||
namespace NotifyMessage {
|
||||
type NotifyJumpType = 'project' | 'execution' | 'task';
|
||||
|
||||
interface NotifyMessageTemplateParams {
|
||||
jumpType?: NotifyJumpType;
|
||||
projectId?: string;
|
||||
projectName?: string;
|
||||
executionName?: string;
|
||||
taskName?: string;
|
||||
roleName?: string;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
interface PageParams {
|
||||
pageNo: number;
|
||||
pageSize: number;
|
||||
@@ -19,6 +31,8 @@ declare namespace Api {
|
||||
interface NotifyMessage {
|
||||
/** 站内信编号(雪花 Long,按 string 接收) */
|
||||
id: string;
|
||||
/** 模板编码 */
|
||||
templateCode: string;
|
||||
/** 发送人名称(模板配置的发件人显示名) */
|
||||
templateNickname: string;
|
||||
/** 最终消息正文(占位符已渲染,直接展示) */
|
||||
@@ -33,6 +47,8 @@ declare namespace Api {
|
||||
readTime: string | number | null;
|
||||
/** 收到时间 */
|
||||
createTime: string | number;
|
||||
/** 模板参数(部分模板支持跳转) */
|
||||
templateParams?: NotifyMessageTemplateParams;
|
||||
}
|
||||
|
||||
/** 我的站内信分页查询参数 */
|
||||
|
||||
@@ -27,19 +27,25 @@ interface Props {
|
||||
mode: 'view' | 'edit' | 'create';
|
||||
subordinateOptions?: SubordinateOption[];
|
||||
showApprovalFooter?: boolean;
|
||||
initialEmployeeId?: string;
|
||||
initialPeriodMonth?: string;
|
||||
hideDraftAction?: boolean;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
rowData: null,
|
||||
subordinateOptions: () => [],
|
||||
showApprovalFooter: false
|
||||
showApprovalFooter: false,
|
||||
initialEmployeeId: '',
|
||||
initialPeriodMonth: '',
|
||||
hideDraftAction: false
|
||||
});
|
||||
|
||||
const visible = defineModel<boolean>('visible', { default: false });
|
||||
|
||||
const emit = defineEmits<{
|
||||
saved: [];
|
||||
savedAndSent: [];
|
||||
saved: [payload: PerformanceSheetSavePayload];
|
||||
savedAndSent: [payload: PerformanceSheetSavePayload];
|
||||
startApproval: [];
|
||||
}>();
|
||||
|
||||
@@ -60,6 +66,13 @@ const createForm = reactive({
|
||||
employeeId: ''
|
||||
});
|
||||
|
||||
interface PerformanceSheetSavePayload {
|
||||
sheet: Api.Performance.Sheet.Sheet;
|
||||
actualScoreTotal: string;
|
||||
baseScoreTotal: string;
|
||||
extraScoreTotal: string;
|
||||
}
|
||||
|
||||
const createFormRules = computed<FormRules>(() => ({
|
||||
periodMonth: [createRequiredRule('请选择绩效月份')],
|
||||
employeeId: [createRequiredRule('请选择下属')]
|
||||
@@ -526,7 +539,16 @@ async function ensureCreatedSheet() {
|
||||
return sheetResult.data;
|
||||
}
|
||||
|
||||
async function executeSave(): Promise<Api.Performance.Sheet.Sheet | null> {
|
||||
function createInitialPeriodMonth() {
|
||||
return props.initialPeriodMonth || createDefaultPeriodMonth();
|
||||
}
|
||||
|
||||
function initializeCreateForm() {
|
||||
createForm.periodMonth = createInitialPeriodMonth();
|
||||
createForm.employeeId = props.initialEmployeeId || '';
|
||||
}
|
||||
|
||||
async function executeSave(): Promise<PerformanceSheetSavePayload | null> {
|
||||
const workbook = getActiveWorkbook();
|
||||
if (!workbook) return null;
|
||||
|
||||
@@ -573,18 +595,23 @@ async function executeSave(): Promise<Api.Performance.Sheet.Sheet | null> {
|
||||
return null;
|
||||
}
|
||||
|
||||
return sheet;
|
||||
return {
|
||||
sheet,
|
||||
actualScoreTotal: scores.actualScoreTotal,
|
||||
baseScoreTotal: scores.baseScoreTotal,
|
||||
extraScoreTotal: scores.extraScoreTotal
|
||||
};
|
||||
}
|
||||
|
||||
async function handleSaveDraft() {
|
||||
saving.value = true;
|
||||
try {
|
||||
const sheet = await executeSave();
|
||||
if (!sheet) return;
|
||||
const saveResult = await executeSave();
|
||||
if (!saveResult) return;
|
||||
|
||||
window.$message?.success(isCreateMode.value ? '绩效表已保存为草稿' : '绩效 Excel 已保存');
|
||||
visible.value = false;
|
||||
emit('saved');
|
||||
emit('saved', saveResult);
|
||||
} catch (error) {
|
||||
window.$message?.error(error instanceof Error ? error.message : '绩效 Excel 保存失败');
|
||||
} finally {
|
||||
@@ -595,15 +622,15 @@ async function handleSaveDraft() {
|
||||
async function handleSaveAndSend() {
|
||||
sending.value = true;
|
||||
try {
|
||||
const sheet = await executeSave();
|
||||
if (!sheet) return;
|
||||
const saveResult = await executeSave();
|
||||
if (!saveResult) return;
|
||||
|
||||
const sendResult = await sendPerformanceSheet(sheet.id);
|
||||
const sendResult = await sendPerformanceSheet(saveResult.sheet.id);
|
||||
if (sendResult.error) return;
|
||||
|
||||
window.$message?.success('绩效表已保存并发送');
|
||||
visible.value = false;
|
||||
emit('savedAndSent');
|
||||
emit('savedAndSent', saveResult);
|
||||
} catch (error) {
|
||||
window.$message?.error(error instanceof Error ? error.message : '绩效表发送失败');
|
||||
} finally {
|
||||
@@ -616,12 +643,14 @@ watch(visible, async isVisible => {
|
||||
disposeUniver();
|
||||
currentSheet.value = null;
|
||||
currentTemplate.value = null;
|
||||
// 重置创建表单
|
||||
createForm.periodMonth = createDefaultPeriodMonth();
|
||||
createForm.employeeId = '';
|
||||
initializeCreateForm();
|
||||
return;
|
||||
}
|
||||
|
||||
if (isCreateMode.value) {
|
||||
initializeCreateForm();
|
||||
}
|
||||
|
||||
await nextTick();
|
||||
await loadWorkbook();
|
||||
});
|
||||
@@ -698,7 +727,9 @@ onMounted(() => {
|
||||
<template v-if="canSave || showApprovalFooter" #footer>
|
||||
<div class="performance-excel-editor__footer">
|
||||
<template v-if="canSave">
|
||||
<ElButton :loading="saving" :disabled="sending" @click="handleSaveDraft">保存草稿</ElButton>
|
||||
<ElButton v-if="!props.hideDraftAction" :loading="saving" :disabled="sending" @click="handleSaveDraft">
|
||||
保存草稿
|
||||
</ElButton>
|
||||
<ElButton type="primary" :loading="sending" :disabled="saving" @click="handleSaveAndSend">发送绩效</ElButton>
|
||||
</template>
|
||||
<template v-else-if="showApprovalFooter">
|
||||
|
||||
@@ -4,9 +4,11 @@ import { computed, nextTick, reactive, ref, watch } from 'vue';
|
||||
import type { FormRules } from 'element-plus';
|
||||
import dayjs from 'dayjs';
|
||||
import { RDMS_REQ_PRIORITY_DICT_CODE, RDMS_TASK_ITEM_TYPE_DICT_CODE } from '@/constants/dict';
|
||||
import { fetchPerformanceSheetPage } from '@/service/api';
|
||||
import { useForm, useFormRules } from '@/hooks/common/form';
|
||||
import { useDict } from '@/hooks/business/dict';
|
||||
import BusinessFormDialog from '@/components/custom/business-form-dialog.vue';
|
||||
import PerformanceExcelEditorDrawer from '../../../my-performance/modules/performance-excel-editor-drawer.vue';
|
||||
import {
|
||||
type WorkReportStructuredSection,
|
||||
type WorkReportStructuredTask,
|
||||
@@ -73,6 +75,10 @@ const EMPTY_TEXT = '';
|
||||
const { getLabel: getTaskItemTypeLabel } = useDict(RDMS_TASK_ITEM_TYPE_DICT_CODE);
|
||||
const { dictData: priorityDictData, getLabel: getPriorityLabel } = useDict(RDMS_REQ_PRIORITY_DICT_CODE);
|
||||
const todayText = computed(() => dayjs().format('YYYY-MM-DD'));
|
||||
const performanceDrawerVisible = ref(false);
|
||||
const performanceDrawerLoading = ref(false);
|
||||
const performanceDrawerMode = ref<'create' | 'edit'>('create');
|
||||
const performanceDrawerRow = ref<Api.Performance.Sheet.Sheet | null>(null);
|
||||
|
||||
const auditDialogVisible = ref(false);
|
||||
const auditForm = reactive({
|
||||
@@ -90,7 +96,6 @@ const pageValidationModel = reactive({
|
||||
}
|
||||
});
|
||||
const pageRules: FormRules = {
|
||||
meetingDate: [createRequiredRule('请选择面谈时间')],
|
||||
performanceResult: [
|
||||
createRequiredRule('请输入绩效考核结果'),
|
||||
{
|
||||
@@ -177,6 +182,24 @@ const performanceForm = reactive({
|
||||
}
|
||||
});
|
||||
|
||||
const performanceTargetOption = computed(() => {
|
||||
const reporterId = props.baseInfo?.reporterId || '';
|
||||
const reporterName = props.baseInfo?.reporterName || '';
|
||||
|
||||
if (!reporterId || !reporterName) return [];
|
||||
|
||||
return [{ label: reporterName, value: reporterId }];
|
||||
});
|
||||
|
||||
const performanceInitialEmployeeId = computed(() => props.baseInfo?.reporterId || '');
|
||||
const performanceInitialPeriodMonth = computed(() => {
|
||||
const periodDate = props.baseInfo?.periodStartDate || props.baseInfo?.periodEndDate || '';
|
||||
if (!periodDate) return '';
|
||||
|
||||
const targetMonth = dayjs(periodDate);
|
||||
return targetMonth.isValid() ? targetMonth.format('YYYY-MM') : '';
|
||||
});
|
||||
|
||||
/** 绩效分数输入限制:0-100,最多一位小数 */
|
||||
function handlePerformanceScoreInput(value: string) {
|
||||
// 只允许数字和一个小数点
|
||||
@@ -563,6 +586,37 @@ function openAuditDialog() {
|
||||
});
|
||||
}
|
||||
|
||||
async function openPerformanceDrawer() {
|
||||
const reporterId = props.baseInfo?.reporterId || '';
|
||||
const periodMonth = performanceInitialPeriodMonth.value;
|
||||
|
||||
performanceDrawerMode.value = 'create';
|
||||
performanceDrawerRow.value = null;
|
||||
|
||||
if (reporterId && periodMonth) {
|
||||
performanceDrawerLoading.value = true;
|
||||
const { error, data } = await fetchPerformanceSheetPage({
|
||||
pageNo: 1,
|
||||
pageSize: 1,
|
||||
employeeId: reporterId,
|
||||
periodMonthRange: [periodMonth, periodMonth]
|
||||
});
|
||||
performanceDrawerLoading.value = false;
|
||||
|
||||
const existingSheet = !error && data?.list?.length ? data.list[0] : null;
|
||||
if (existingSheet) {
|
||||
performanceDrawerMode.value = 'edit';
|
||||
performanceDrawerRow.value = existingSheet;
|
||||
}
|
||||
}
|
||||
|
||||
performanceDrawerVisible.value = true;
|
||||
}
|
||||
|
||||
function handlePerformanceSaved(payload: { actualScoreTotal: string }) {
|
||||
patchApproval('performanceResult', payload.actualScoreTotal);
|
||||
}
|
||||
|
||||
watch(rejectOpinionRequired, async () => {
|
||||
if (!auditDialogVisible.value) return;
|
||||
|
||||
@@ -614,10 +668,7 @@ watch(
|
||||
<ElInput v-model="mainForm.supervisor" disabled />
|
||||
</div>
|
||||
<div class="field field-form-item">
|
||||
<label>
|
||||
面谈时间
|
||||
<span class="field-required-mark">*</span>
|
||||
</label>
|
||||
<label>面谈时间</label>
|
||||
<ElFormItem class="field-inline-form-item" prop="meetingDate">
|
||||
<ElDatePicker v-model="mainForm.meetingDate" type="date" value-format="YYYY-MM-DD" style="width: 100%" />
|
||||
</ElFormItem>
|
||||
@@ -730,12 +781,25 @@ watch(
|
||||
<span class="field-required-mark">*</span>
|
||||
</div>
|
||||
<ElFormItem class="performance-inline-form-item" prop="performanceResult">
|
||||
<div class="performance-input-group">
|
||||
<ElInput
|
||||
v-model="performanceForm.score"
|
||||
class="performance-input"
|
||||
placeholder="请输入考核分数"
|
||||
readonly
|
||||
@input="handlePerformanceScoreInput"
|
||||
/>
|
||||
<ElButton
|
||||
plain
|
||||
size="small"
|
||||
type="primary"
|
||||
:loading="performanceDrawerLoading"
|
||||
:disabled="!performanceTargetOption.length"
|
||||
@click="openPerformanceDrawer"
|
||||
>
|
||||
填写绩效
|
||||
</ElButton>
|
||||
</div>
|
||||
</ElFormItem>
|
||||
</div>
|
||||
</div>
|
||||
@@ -871,6 +935,18 @@ watch(
|
||||
</ElForm>
|
||||
</div>
|
||||
</BusinessFormDialog>
|
||||
|
||||
<PerformanceExcelEditorDrawer
|
||||
v-model:visible="performanceDrawerVisible"
|
||||
:row-data="performanceDrawerRow"
|
||||
:mode="performanceDrawerMode"
|
||||
:subordinate-options="performanceTargetOption"
|
||||
:initial-employee-id="performanceInitialEmployeeId"
|
||||
:initial-period-month="performanceInitialPeriodMonth"
|
||||
hide-draft-action
|
||||
@saved="handlePerformanceSaved"
|
||||
@saved-and-sent="handlePerformanceSaved"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1794,13 +1870,19 @@ watch(
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.performance-input-group {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.performance-input {
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
.performance-input :deep(.el-input__wrapper) {
|
||||
box-shadow: none !important;
|
||||
background: transparent;
|
||||
background: #f8fafc;
|
||||
border-bottom: 1px solid #e2e8f0;
|
||||
border-radius: 0;
|
||||
padding: 0 4px;
|
||||
@@ -1812,6 +1894,10 @@ watch(
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.performance-input :deep(.el-input__inner[readonly]) {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.performance-input :deep(.el-input__inner::placeholder) {
|
||||
color: #cbd5e1;
|
||||
font-weight: 400;
|
||||
|
||||
@@ -359,6 +359,8 @@ function resolveTaskItemTypeLabel(value?: string | null) {
|
||||
}
|
||||
|
||||
const STRUCTURED_TASK_PREFIX_RE = /^(?:(?:\d+[..、])|(?:\d+\s+)|(?:[一二三四五六七八九十百千万]+[、..]))\s*/u;
|
||||
const WORKLOG_DAY_SEPARATOR = '[[WR_DAY_SPLIT]]';
|
||||
const LEGACY_WORKLOG_DAY_SEPARATOR = ';';
|
||||
|
||||
function stripStructuredTaskPrefixV2(value: string) {
|
||||
return value.trim().replace(STRUCTURED_TASK_PREFIX_RE, '');
|
||||
@@ -385,9 +387,10 @@ function formatStructuredTaskDisplayLine(task: StructuredTask, index: number, sh
|
||||
|
||||
function getWorkLogEntries(detail: string): string[] {
|
||||
if (!detail) return [];
|
||||
// 仅按中文分号切分,避免误伤文本中的其他标点;每段作为单独一天的工作日志展示。
|
||||
// 新默认稿优先按显式分隔标记切分;历史数据继续兼容中文分号。
|
||||
const separator = detail.includes(WORKLOG_DAY_SEPARATOR) ? WORKLOG_DAY_SEPARATOR : LEGACY_WORKLOG_DAY_SEPARATOR;
|
||||
return detail
|
||||
.split(';')
|
||||
.split(separator)
|
||||
.map(item => item.trim())
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
@@ -825,7 +825,8 @@ watch(
|
||||
() => props.execution?.id,
|
||||
async () => {
|
||||
await loadExecutionAssigneeOptions();
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
// 范围维度变化(scopedExecutionIds / scopedExecutionInvolveUserId / scopedExecutionStatusCodes 引用变:
|
||||
|
||||
Reference in New Issue
Block a user