From d3d08308205dafe8bb2e6906659c5e036e19db18 Mon Sep 17 00:00:00 2001 From: dk <1260500659@qq.com> Date: Mon, 1 Jun 2026 21:37:08 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E6=96=B0=E5=A2=9E=E5=8A=A0=E7=8F=AD?= =?UTF-8?q?=E7=94=B3=E8=AF=B7=E5=8A=9F=E8=83=BD):=20=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E7=94=B3=E8=AF=B7=E5=8A=9F=E8=83=BD=EF=BC=8C=E5=8F=AF=E5=9C=A8?= =?UTF-8?q?=E5=B7=A5=E4=BD=9C=E5=8F=B0=E8=BF=9B=E8=A1=8C=E5=AE=A1=E6=A0=B8?= =?UTF-8?q?=E3=80=82=20fix(dict=5Fdata):=20=E5=9C=A8=E5=AD=97=E5=85=B8?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E6=96=B0=E5=A2=9E=E3=80=81=E7=BC=96=E8=BE=91?= =?UTF-8?q?=E6=97=B6=E5=8F=AF=E4=BB=A5=E6=93=8D=E4=BD=9C=E9=A2=9C=E8=89=B2?= =?UTF-8?q?=E7=B1=BB=E5=9E=8B=E5=AD=97=E6=AE=B5=EF=BC=88color=5Ftype?= =?UTF-8?q?=EF=BC=89=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build/plugins/router.ts | 7 +- docs/frontend-page-resource-manifest.json | 37 +- src/constants/dict.ts | 16 + src/constants/status-tag.ts | 16 +- src/locales/langs/en-us.ts | 3 + src/locales/langs/zh-cn.ts | 3 + src/router/elegant/imports.ts | 1 + src/router/elegant/routes.ts | 14 +- src/router/elegant/transform.ts | 1 + src/service/api/dict.ts | 88 +++- src/service/api/index.ts | 1 + src/service/api/overtime-application.ts | 280 ++++++++++++ src/service/api/system-manage.ts | 13 + src/typings/api/dict.d.ts | 2 +- src/typings/api/overtime-application.d.ts | 78 ++++ src/typings/app.d.ts | 2 + src/typings/components.d.ts | 1 + src/typings/elegant-router.d.ts | 2 + .../overtime-application/index.vue | 401 ++++++++++++++++++ .../overtime-application-action-dialog.vue | 113 +++++ .../overtime-application-detail-dialog.vue | 97 +++++ .../overtime-application-operate-dialog.vue | 273 ++++++++++++ .../modules/overtime-application-search.vue | 95 +++++ .../modules/overtime-application-shared.ts | 77 ++++ ...overtime-application-status-log-dialog.vue | 89 ++++ src/views/system/dict/index.vue | 11 + .../dict/modules/dict-data-operate-modal.vue | 7 + src/views/workbench/homepage.ts | 4 + .../modules/workbench-todo-panel.vue | 257 ++++++++++- 29 files changed, 1966 insertions(+), 23 deletions(-) create mode 100644 src/service/api/overtime-application.ts create mode 100644 src/typings/api/overtime-application.d.ts create mode 100644 src/views/personal-center/overtime-application/index.vue create mode 100644 src/views/personal-center/overtime-application/modules/overtime-application-action-dialog.vue create mode 100644 src/views/personal-center/overtime-application/modules/overtime-application-detail-dialog.vue create mode 100644 src/views/personal-center/overtime-application/modules/overtime-application-operate-dialog.vue create mode 100644 src/views/personal-center/overtime-application/modules/overtime-application-search.vue create mode 100644 src/views/personal-center/overtime-application/modules/overtime-application-shared.ts create mode 100644 src/views/personal-center/overtime-application/modules/overtime-application-status-log-dialog.vue diff --git a/build/plugins/router.ts b/build/plugins/router.ts index cae40b4..a4ab0e8 100644 --- a/build/plugins/router.ts +++ b/build/plugins/router.ts @@ -151,9 +151,14 @@ export function setupElegantRouter() { order: 5, keepAlive: true }, + 'personal-center_overtime-application': { + icon: 'mdi:clock-plus-outline', + order: 6, + keepAlive: true + }, 'personal-center_pending-approval': { icon: 'mdi:check-decagram-outline', - order: 6, + order: 7, keepAlive: true }, system: { diff --git a/docs/frontend-page-resource-manifest.json b/docs/frontend-page-resource-manifest.json index bb5db84..53a0f5d 100644 --- a/docs/frontend-page-resource-manifest.json +++ b/docs/frontend-page-resource-manifest.json @@ -1,12 +1,12 @@ { - "generatedAt": "2026-05-19T07:08:28.081Z", + "generatedAt": "2026-06-01T01:55:51.875Z", "description": "Frontend visible page resource whitelist for backend route/menu configuration.", "rules": { "directoryComponent": "layout.base", "pageComponentPattern": "view.", "singlePageComponentPattern": "layout.$view." }, - "total": 22, + "total": 23, "items": [ { "name": "product_list", @@ -470,6 +470,39 @@ "pageType": "leaf", "source": "generated" }, + { + "name": "personal-center_overtime-application", + "path": "/personal-center/overtime-application", + "component": "view.personal-center_overtime-application", + "title": "加班申请", + "routeTitle": "personal-center_overtime-application", + "i18nKey": "route.personal-center_overtime-application", + "icon": "mdi:clock-plus-outline", + "localIcon": null, + "order": 6, + "hideInMenu": false, + "keepAlive": true, + "activeMenu": null, + "multiTab": false, + "fixedIndexInTab": null, + "redirect": null, + "props": null, + "meta": { + "title": "加班申请", + "i18nKey": "route.personal-center_overtime-application", + "icon": "mdi:clock-plus-outline", + "localIcon": null, + "order": 6, + "keepAlive": true, + "hideInMenu": false, + "activeMenu": null, + "multiTab": false, + "fixedIndexInTab": null + }, + "parentName": "personal-center", + "pageType": "leaf", + "source": "generated" + }, { "name": "system_user", "path": "/system/user", diff --git a/src/constants/dict.ts b/src/constants/dict.ts index 9546e37..b240016 100644 --- a/src/constants/dict.ts +++ b/src/constants/dict.ts @@ -111,3 +111,19 @@ export const RDMS_REQ_CAN_DELETE_STATUS_DICT_CODE = 'rdms_req_can_delete_status' * 来源口径:用户明确指定任务/个人事项工作日志难度下拉来自运行时字典 rdms_task_item_worklog_difficulty */ export const RDMS_WORKLOG_DIFFICULTY_DICT_CODE = 'rdms_task_item_worklog_difficulty'; + +/** + * 加班申请状态字典编码 + * + * 对应业务字段:加班申请中的 statusCode + * 来源口径:`overtime-application-design.md` 明确状态字典为 rdms_overtime_application_status + */ +export const RDMS_OVERTIME_APPLICATION_STATUS_DICT_CODE = 'rdms_overtime_application_status'; + +/** + * 加班时长快捷选项字典编码 + * + * 对应业务字段:加班申请中的 overtimeDuration + * 来源口径:`overtime-application-design.md` 明确时长下拉字典为 rdms_overtime_duration + */ +export const RDMS_OVERTIME_DURATION_DICT_CODE = 'rdms_overtime_duration'; diff --git a/src/constants/status-tag.ts b/src/constants/status-tag.ts index 7a21663..0e7dc10 100644 --- a/src/constants/status-tag.ts +++ b/src/constants/status-tag.ts @@ -16,7 +16,8 @@ export type StatusDomain = | 'product' | 'requirement' | 'workOrder' - | 'personalItem'; + | 'personalItem' + | 'overtimeApplication'; const statusTagTypeRegistry: Record> = { // 项目-执行 @@ -61,6 +62,13 @@ const statusTagTypeRegistry: Record> active: 'primary', completed: 'success', cancelled: 'danger' + }, + // 加班申请 + overtimeApplication: { + pending: 'warning', + approved: 'success', + rejected: 'danger', + cancelled: 'info' } }; @@ -69,9 +77,13 @@ export function getStatusTagType(domain: StatusDomain, statusCode: string | null return 'info'; } - return statusTagTypeRegistry[domain][statusCode] || 'info'; + return statusTagTypeRegistry[domain]?.[statusCode] || 'info'; } export function getPersonalItemStatusTagType(statusCode: string | null | undefined) { return getStatusTagType('personalItem', statusCode); } + +export function getOvertimeApplicationStatusTagType(statusCode: string | null | undefined) { + return getStatusTagType('overtimeApplication', statusCode); +} diff --git a/src/locales/langs/en-us.ts b/src/locales/langs/en-us.ts index 7283392..112cdc1 100644 --- a/src/locales/langs/en-us.ts +++ b/src/locales/langs/en-us.ts @@ -173,6 +173,7 @@ const local: App.I18n.Schema = { 'personal-center_my-monthly': 'My Monthly Report', 'personal-center_my-performance': 'My Performance', 'personal-center_my-application': 'My Application', + 'personal-center_overtime-application': 'Overtime Application', 'personal-center_pending-approval': 'Pending Approval', infra: 'Infra', 'infra_state-machine': 'State Machine', @@ -708,6 +709,7 @@ const local: App.I18n.Schema = { dictStatus: 'Dictionary Status', dictLabel: 'Dictionary Label', dictValue: 'Dictionary Value', + colorType: 'Color Type', sort: 'Sort', remark: 'Remark', form: { @@ -716,6 +718,7 @@ const local: App.I18n.Schema = { dictStatus: 'Please select dictionary status', dictLabel: 'Please enter dictionary label', dictValue: 'Please enter dictionary value', + colorType: 'Please enter color type', sort: 'Please enter sort', remark: 'Please enter remark' }, diff --git a/src/locales/langs/zh-cn.ts b/src/locales/langs/zh-cn.ts index 71d4828..960272e 100644 --- a/src/locales/langs/zh-cn.ts +++ b/src/locales/langs/zh-cn.ts @@ -173,6 +173,7 @@ const local: App.I18n.Schema = { 'personal-center_my-monthly': '我的月报', 'personal-center_my-performance': '我的绩效', 'personal-center_my-application': '我的申请', + 'personal-center_overtime-application': '加班申请', 'personal-center_pending-approval': '待我审批', infra: '基础设施', 'infra_state-machine': '状态机管理', @@ -696,6 +697,7 @@ const local: App.I18n.Schema = { dictStatus: '字典状态', dictLabel: '字典标签', dictValue: '字典键值', + colorType: '颜色类型', sort: '排序', remark: '备注', form: { @@ -704,6 +706,7 @@ const local: App.I18n.Schema = { dictStatus: '请选择字典状态', dictLabel: '请输入字典标签', dictValue: '请输入字典键值', + colorType: '请输入颜色类型', sort: '请输入排序', remark: '请输入备注' }, diff --git a/src/router/elegant/imports.ts b/src/router/elegant/imports.ts index 277c561..7f9e623 100644 --- a/src/router/elegant/imports.ts +++ b/src/router/elegant/imports.ts @@ -39,6 +39,7 @@ export const views: Record Promise import("@/views/personal-center/my-performance/index.vue"), "personal-center_my-profile": () => import("@/views/personal-center/my-profile/index.vue"), "personal-center_my-weekly": () => import("@/views/personal-center/my-weekly/index.vue"), + "personal-center_overtime-application": () => import("@/views/personal-center/overtime-application/index.vue"), "personal-center_pending-approval": () => import("@/views/personal-center/pending-approval/index.vue"), plugin_barcode: () => import("@/views/plugin/barcode/index.vue"), plugin_charts_antv: () => import("@/views/plugin/charts/antv/index.vue"), diff --git a/src/router/elegant/routes.ts b/src/router/elegant/routes.ts index 95d6452..8a032ea 100644 --- a/src/router/elegant/routes.ts +++ b/src/router/elegant/routes.ts @@ -351,6 +351,18 @@ export const generatedRoutes: GeneratedRoute[] = [ keepAlive: true } }, + { + name: 'personal-center_overtime-application', + path: '/personal-center/overtime-application', + component: 'view.personal-center_overtime-application', + meta: { + title: 'personal-center_overtime-application', + i18nKey: 'route.personal-center_overtime-application', + icon: 'mdi:clock-plus-outline', + order: 6, + keepAlive: true + } + }, { name: 'personal-center_pending-approval', path: '/personal-center/pending-approval', @@ -359,7 +371,7 @@ export const generatedRoutes: GeneratedRoute[] = [ title: 'personal-center_pending-approval', i18nKey: 'route.personal-center_pending-approval', icon: 'mdi:check-decagram-outline', - order: 5, + order: 7, keepAlive: true } } diff --git a/src/router/elegant/transform.ts b/src/router/elegant/transform.ts index 08bbf39..67f20c4 100644 --- a/src/router/elegant/transform.ts +++ b/src/router/elegant/transform.ts @@ -196,6 +196,7 @@ const routeMap: RouteMap = { "personal-center_my-performance": "/personal-center/my-performance", "personal-center_my-profile": "/personal-center/my-profile", "personal-center_my-weekly": "/personal-center/my-weekly", + "personal-center_overtime-application": "/personal-center/overtime-application", "personal-center_pending-approval": "/personal-center/pending-approval", "plugin": "/plugin", "plugin_barcode": "/plugin/barcode", diff --git a/src/service/api/dict.ts b/src/service/api/dict.ts index e683bcb..f962c1f 100644 --- a/src/service/api/dict.ts +++ b/src/service/api/dict.ts @@ -1,5 +1,6 @@ import { SYSTEM_SERVICE_PREFIX } from '@/constants/service'; import { request } from '../request'; +import { type ServiceRequestResult, mapServiceResult } from './shared'; const DICT_TYPE_PREFIX = `${SYSTEM_SERVICE_PREFIX}/dict-type`; const DICT_DATA_PREFIX = `${SYSTEM_SERVICE_PREFIX}/dict-data`; @@ -15,6 +16,52 @@ function createBatchDeleteQuery(ids: number[]) { return query.toString(); } +type DictDataResponse = Omit & { + colorType?: string | null; + color_type?: string | null; +}; + +type DictDataPageResponse = Omit, 'list'> & { + list: DictDataResponse[]; +}; + +type FrontendDictDataResponse = Omit & { + colorType?: string | null; + color_type?: string | null; +}; + +type FrontendDictCacheResponse = Record; + +function normalizeColorType(value?: string | null) { + return value?.trim() || null; +} + +function normalizeDictData(data: DictDataResponse): Api.Dict.DictData { + const { color_type: colorTypeFromSnakeCase, ...rest } = data; + + return { + ...rest, + colorType: normalizeColorType(data.colorType ?? colorTypeFromSnakeCase) + }; +} + +function normalizeFrontendDictData(data: FrontendDictDataResponse): Api.Dict.FrontendDictData { + const { color_type: colorTypeFromSnakeCase, ...rest } = data; + + return { + ...rest, + colorType: normalizeColorType(data.colorType ?? colorTypeFromSnakeCase) + }; +} + +function toSaveDictDataRequest(data: Api.Dict.SaveDictDataParams) { + return { + ...data, + colorType: normalizeColorType(data.colorType), + remark: data.remark?.trim() || null + }; +} + /** 获取字典类型分页 */ export function fetchGetDictTypePage(params?: Api.Dict.DictTypeSearchParams) { return request>({ @@ -60,20 +107,40 @@ export function fetchBatchDeleteDictType(ids: number[]) { } /** 获取字典数据分页 */ -export function fetchGetDictDataPage(params: Api.Dict.DictDataSearchParams) { - return request>({ +export async function fetchGetDictDataPage(params: Api.Dict.DictDataSearchParams) { + const result = await request({ url: `${DICT_DATA_PREFIX}/page`, method: 'get', params }); + + if (result.error || !result.data) { + return result as unknown as Awaited>>>; + } + + return { + ...result, + data: { + ...result.data, + list: result.data.list.map(normalizeDictData) + } + }; } /** 获取前端运行时字典缓存 */ -export function fetchGetFrontendDictCache() { - return request({ +export async function fetchGetFrontendDictCache() { + const result = await request({ url: `${DICT_DATA_PREFIX}/frontend-cache`, method: 'get' }); + + return mapServiceResult( + result as ServiceRequestResult, + data => + Object.fromEntries( + Object.entries(data).map(([dictType, list]) => [dictType, list.map(normalizeFrontendDictData)]) + ) as Api.Dict.FrontendDictCache + ); } /** 创建字典数据 */ @@ -81,7 +148,7 @@ export function fetchCreateDictData(data: Api.Dict.SaveDictDataParams) { return request({ url: `${DICT_DATA_PREFIX}/create`, method: 'post', - data + data: toSaveDictDataRequest(data) }); } @@ -90,7 +157,7 @@ export function fetchUpdateDictData(data: { id: number } & Api.Dict.SaveDictData return request({ url: `${DICT_DATA_PREFIX}/update`, method: 'put', - data + data: toSaveDictDataRequest(data) }); } @@ -112,9 +179,14 @@ export function fetchBatchDeleteDictData(ids: number[]) { } /** 通过岗位编码获取该字典的所有字典数据 */ -export function fetchGetDictDataByCode(code: string) { - return request>({ +export async function fetchGetDictDataByCode(code: string) { + const result = await request({ url: `${DICT_DATA_PREFIX}/code?code=${code}`, method: 'get' }); + + return mapServiceResult(result as ServiceRequestResult, data => ({ + ...data, + list: data.list.map(normalizeDictData) + })); } diff --git a/src/service/api/index.ts b/src/service/api/index.ts index 89f7f5d..fad9494 100644 --- a/src/service/api/index.ts +++ b/src/service/api/index.ts @@ -3,6 +3,7 @@ export * from './dict'; export * from './file'; export * from './infra'; export * from './object-context'; +export * from './overtime-application'; export * from './personal-item'; export * from './product'; export * from './project'; diff --git a/src/service/api/overtime-application.ts b/src/service/api/overtime-application.ts new file mode 100644 index 0000000..14120bf --- /dev/null +++ b/src/service/api/overtime-application.ts @@ -0,0 +1,280 @@ +import { WEB_SERVICE_PREFIX } from '@/constants/service'; +import { request } from '../request'; +import { type ProjectLocalDateValue, normalizeProjectLocalDate } from './project-shared'; +import { + type ServiceRequestResult, + mapServiceResult, + normalizeNullableStringId, + normalizeStringId, + safeJsonRequestConfig +} from './shared'; + +const OVERTIME_APPLICATION_PREFIX = `${WEB_SERVICE_PREFIX}/project/overtime-applications`; + +type StringIdResponse = string | number; + +type OvertimeApplicationResponse = Omit< + Api.OvertimeApplication.OvertimeApplication, + 'id' | 'applicantId' | 'approverId' | 'overtimeDate' | 'allowEdit' | 'terminal' +> & { + id: StringIdResponse; + applicantId: StringIdResponse; + approverId: StringIdResponse; + overtimeDate: ProjectLocalDateValue; + allowEdit?: boolean | number | string | null; + terminal?: boolean | number | string | null; +}; + +type OvertimeApplicationPageResponse = Omit & { + total: number | string; + list: OvertimeApplicationResponse[]; +}; + +type OvertimeApplicationStatusLogResponse = Omit< + Api.OvertimeApplication.OvertimeApplicationStatusLog, + 'id' | 'applicationId' | 'operatorUserId' | 'overtimeDateSnapshot' +> & { + id: StringIdResponse; + applicationId: StringIdResponse; + operatorUserId: StringIdResponse; + overtimeDateSnapshot: ProjectLocalDateValue; +}; + +function normalizeBooleanFlag(value: boolean | number | string | null | undefined) { + if (typeof value === 'boolean') { + return value; + } + + if (typeof value === 'number') { + return value === 1; + } + + if (typeof value === 'string') { + const normalized = value.trim().toLowerCase(); + + return !['', '0', 'false', 'n', 'no'].includes(normalized); + } + + return false; +} + +function normalizeTotal(total: number | string) { + const value = Number(total); + + return Number.isFinite(value) ? Math.max(0, value) : 0; +} + +function normalizeOvertimeApplication( + response: OvertimeApplicationResponse +): Api.OvertimeApplication.OvertimeApplication { + return { + ...response, + id: normalizeStringId(response.id), + applicantId: normalizeStringId(response.applicantId), + approverId: normalizeStringId(response.approverId), + overtimeDate: normalizeProjectLocalDate(response.overtimeDate) ?? '', + statusName: response.statusName || response.statusCode, + allowEdit: normalizeBooleanFlag(response.allowEdit), + terminal: normalizeBooleanFlag(response.terminal), + approvalComment: response.approvalComment ?? null, + approvalTime: response.approvalTime ?? null + }; +} + +function normalizeStatusLog( + response: OvertimeApplicationStatusLogResponse +): Api.OvertimeApplication.OvertimeApplicationStatusLog { + return { + ...response, + id: normalizeStringId(response.id), + applicationId: normalizeStringId(response.applicationId), + operatorUserId: normalizeStringId(response.operatorUserId), + overtimeDateSnapshot: normalizeProjectLocalDate(response.overtimeDateSnapshot) ?? '', + fromStatus: normalizeNullableStringId(response.fromStatus), + reason: response.reason ?? null, + remark: response.remark ?? null + }; +} + +function createPageQuery(params: Api.OvertimeApplication.OvertimeApplicationSearchParams = {}) { + const query = new URLSearchParams(); + + query.append('pageNo', String(params.pageNo ?? 1)); + query.append('pageSize', String(params.pageSize ?? 10)); + + if (params.keyword) { + query.append('keyword', params.keyword); + } + + if (params.applicantName) { + query.append('applicantName', params.applicantName); + } + + if (params.approverId) { + query.append('approverId', params.approverId); + } + + if (params.approverName) { + query.append('approverName', params.approverName); + } + + if (params.statusCode) { + query.append('statusCode', params.statusCode); + } + + params.overtimeDate?.forEach(item => { + if (item) { + query.append('overtimeDate', item); + } + }); + + params.createTime?.forEach(item => { + if (item) { + query.append('createTime', item); + } + }); + + return query.toString(); +} + +function toSaveRequest(data: Api.OvertimeApplication.SaveOvertimeApplicationParams) { + return { + overtimeDate: data.overtimeDate, + overtimeDuration: data.overtimeDuration, + overtimeReason: data.overtimeReason.trim(), + overtimeContent: data.overtimeContent.trim(), + approverId: data.approverId + }; +} + +function toStatusActionRequest(data: Api.OvertimeApplication.StatusActionParams = {}) { + return { + reason: data.reason?.trim() || undefined + }; +} + +export async function fetchGetOvertimeApplicationPage( + params: Api.OvertimeApplication.OvertimeApplicationSearchParams = {} +) { + const query = createPageQuery(params); + + const result = await request({ + ...safeJsonRequestConfig, + url: query ? `${OVERTIME_APPLICATION_PREFIX}/page?${query}` : `${OVERTIME_APPLICATION_PREFIX}/page`, + method: 'get' + }); + + return mapServiceResult(result as ServiceRequestResult, data => ({ + total: normalizeTotal(data.total), + list: data.list.map(normalizeOvertimeApplication) + })); +} + +export async function fetchGetOvertimeApplicationApprovalPage( + params: Api.OvertimeApplication.OvertimeApplicationSearchParams = {} +) { + const query = createPageQuery(params); + + const result = await request({ + ...safeJsonRequestConfig, + url: query + ? `${OVERTIME_APPLICATION_PREFIX}/approval-page?${query}` + : `${OVERTIME_APPLICATION_PREFIX}/approval-page`, + method: 'get' + }); + + return mapServiceResult(result as ServiceRequestResult, data => ({ + total: normalizeTotal(data.total), + list: data.list.map(normalizeOvertimeApplication) + })); +} + +export async function fetchGetOvertimeApplicationDetail(id: string) { + const result = await request({ + ...safeJsonRequestConfig, + url: `${OVERTIME_APPLICATION_PREFIX}/${id}`, + method: 'get' + }); + + return mapServiceResult(result as ServiceRequestResult, normalizeOvertimeApplication); +} + +export async function fetchCreateOvertimeApplication(data: Api.OvertimeApplication.SaveOvertimeApplicationParams) { + const result = await request({ + ...safeJsonRequestConfig, + url: OVERTIME_APPLICATION_PREFIX, + method: 'post', + data: toSaveRequest(data) + }); + + return mapServiceResult(result as ServiceRequestResult, normalizeStringId); +} + +export function fetchUpdateRejectedOvertimeApplication( + id: string, + data: Api.OvertimeApplication.SaveOvertimeApplicationParams +) { + return request({ + ...safeJsonRequestConfig, + url: `${OVERTIME_APPLICATION_PREFIX}/${id}`, + method: 'put', + data: toSaveRequest(data) + }); +} + +export function fetchApproveOvertimeApplication(id: string, data: Api.OvertimeApplication.StatusActionParams = {}) { + return request({ + ...safeJsonRequestConfig, + url: `${OVERTIME_APPLICATION_PREFIX}/${id}/approve`, + method: 'post', + data: toStatusActionRequest(data) + }); +} + +export function fetchRejectOvertimeApplication(id: string, data: Api.OvertimeApplication.StatusActionParams) { + return request({ + ...safeJsonRequestConfig, + url: `${OVERTIME_APPLICATION_PREFIX}/${id}/reject`, + method: 'post', + data: toStatusActionRequest(data) + }); +} + +export function fetchCancelOvertimeApplication(id: string, data: Api.OvertimeApplication.StatusActionParams) { + return request({ + ...safeJsonRequestConfig, + url: `${OVERTIME_APPLICATION_PREFIX}/${id}/cancel`, + method: 'post', + data: toStatusActionRequest(data) + }); +} + +export function fetchDeleteOvertimeApplication(id: string) { + return request({ + ...safeJsonRequestConfig, + url: `${OVERTIME_APPLICATION_PREFIX}/${id}`, + method: 'delete' + }); +} + +export async function fetchGetOvertimeApplicationStatusLogs(id: string) { + const result = await request({ + ...safeJsonRequestConfig, + url: `${OVERTIME_APPLICATION_PREFIX}/${id}/status-logs`, + method: 'get' + }); + + return mapServiceResult(result as ServiceRequestResult, data => + data.map(normalizeStatusLog) + ); +} + +export function fetchExportOvertimeApplications(params: Api.OvertimeApplication.OvertimeApplicationSearchParams = {}) { + const query = createPageQuery(params); + + return request({ + url: query ? `${OVERTIME_APPLICATION_PREFIX}/export?${query}` : `${OVERTIME_APPLICATION_PREFIX}/export`, + method: 'get', + responseType: 'blob' + }); +} diff --git a/src/service/api/system-manage.ts b/src/service/api/system-manage.ts index 07af3e8..53ab872 100644 --- a/src/service/api/system-manage.ts +++ b/src/service/api/system-manage.ts @@ -455,6 +455,19 @@ export async function fetchGetUserSimpleList() { ); } +/** 获取当前登录人的直属上级 */ +export async function fetchGetLoginUserDirectManager() { + return request({ + ...safeJsonRequestConfig, + url: `${USER_PREFIX}/profile/direct-manager`, + method: 'get' + }).then(result => + mapServiceResult(result as ServiceRequestResult, data => + data ? normalizeUserSimple(data) : null + ) + ); +} + /** 获取用户分页 */ export function fetchGetUserPage(params?: Api.SystemManage.UserSearchParams) { return request({ diff --git a/src/typings/api/dict.d.ts b/src/typings/api/dict.d.ts index c145cce..3e3f502 100644 --- a/src/typings/api/dict.d.ts +++ b/src/typings/api/dict.d.ts @@ -88,7 +88,7 @@ declare namespace Api { type DictDataSearchParams = CommonType.RecordNullable> & PageParams; /** dict data save params */ - type SaveDictDataParams = Pick & { + type SaveDictDataParams = Pick & { remark?: string | null; }; } diff --git a/src/typings/api/overtime-application.d.ts b/src/typings/api/overtime-application.d.ts new file mode 100644 index 0000000..24b7a8f --- /dev/null +++ b/src/typings/api/overtime-application.d.ts @@ -0,0 +1,78 @@ +declare namespace Api { + namespace OvertimeApplication { + interface PageParams { + pageNo: number; + pageSize: number; + } + + type OvertimeApplicationStatusCode = 'pending' | 'approved' | 'rejected' | 'cancelled'; + + type OvertimeApplicationActionType = 'submit' | 'resubmit' | 'approve' | 'reject' | 'cancel'; + + interface OvertimeApplication { + id: string; + applicantId: string; + applicantName: string; + overtimeDate: string; + overtimeDuration: string; + overtimeReason: string; + overtimeContent: string; + approverId: string; + approverName: string; + statusCode: OvertimeApplicationStatusCode; + statusName: string; + allowEdit: boolean; + terminal: boolean; + approvalComment?: string | null; + submitTime: string; + approvalTime?: string | null; + createTime: string; + updateTime: string; + } + + type OvertimeApplicationSearchParams = CommonType.RecordNullable< + Pick & { + keyword: string; + applicantName: string; + approverId: string; + approverName: string; + statusCode: OvertimeApplicationStatusCode; + overtimeDate: string[]; + createTime: string[]; + } + >; + + interface OvertimeApplicationPageResult { + total: number; + list: OvertimeApplication[]; + } + + interface SaveOvertimeApplicationParams { + overtimeDate: string; + overtimeDuration: string; + overtimeReason: string; + overtimeContent: string; + approverId: string; + } + + interface StatusActionParams { + reason?: string | null; + } + + interface OvertimeApplicationStatusLog { + id: string; + applicationId: string; + actionType: OvertimeApplicationActionType; + fromStatus?: string | null; + toStatus: string; + reason?: string | null; + operatorUserId: string; + operatorName: string; + applicantNameSnapshot: string; + overtimeDateSnapshot: string; + overtimeDurationSnapshot: string; + remark?: string | null; + createTime: string; + } + } +} diff --git a/src/typings/app.d.ts b/src/typings/app.d.ts index 1d69cee..6d85782 100644 --- a/src/typings/app.d.ts +++ b/src/typings/app.d.ts @@ -866,6 +866,7 @@ declare namespace App { dictStatus: string; dictLabel: string; dictValue: string; + colorType: string; sort: string; remark: string; form: { @@ -874,6 +875,7 @@ declare namespace App { dictStatus: string; dictLabel: string; dictValue: string; + colorType: string; sort: string; remark: string; }; diff --git a/src/typings/components.d.ts b/src/typings/components.d.ts index a9ae0ca..b86013e 100644 --- a/src/typings/components.d.ts +++ b/src/typings/components.d.ts @@ -150,6 +150,7 @@ declare module 'vue' { IconMdiCrown: typeof import('~icons/mdi/crown')['default'] IconMdiDeleteOutline: typeof import('~icons/mdi/delete-outline')['default'] IconMdiDotsHorizontal: typeof import('~icons/mdi/dots-horizontal')['default'] + IconMdiDownload: typeof import('~icons/mdi/download')['default'] IconMdiDrag: typeof import('~icons/mdi/drag')['default'] IconMdiFilterVariant: typeof import('~icons/mdi/filter-variant')['default'] IconMdiFolderOpen: typeof import('~icons/mdi/folder-open')['default'] diff --git a/src/typings/elegant-router.d.ts b/src/typings/elegant-router.d.ts index d423099..1f6db65 100644 --- a/src/typings/elegant-router.d.ts +++ b/src/typings/elegant-router.d.ts @@ -50,6 +50,7 @@ declare module "@elegant-router/types" { "personal-center_my-performance": "/personal-center/my-performance"; "personal-center_my-profile": "/personal-center/my-profile"; "personal-center_my-weekly": "/personal-center/my-weekly"; + "personal-center_overtime-application": "/personal-center/overtime-application"; "personal-center_pending-approval": "/personal-center/pending-approval"; "plugin": "/plugin"; "plugin_barcode": "/plugin/barcode"; @@ -187,6 +188,7 @@ declare module "@elegant-router/types" { | "personal-center_my-performance" | "personal-center_my-profile" | "personal-center_my-weekly" + | "personal-center_overtime-application" | "personal-center_pending-approval" | "plugin_barcode" | "plugin_charts_antv" diff --git a/src/views/personal-center/overtime-application/index.vue b/src/views/personal-center/overtime-application/index.vue new file mode 100644 index 0000000..ffee8f1 --- /dev/null +++ b/src/views/personal-center/overtime-application/index.vue @@ -0,0 +1,401 @@ + + + + + diff --git a/src/views/personal-center/overtime-application/modules/overtime-application-action-dialog.vue b/src/views/personal-center/overtime-application/modules/overtime-application-action-dialog.vue new file mode 100644 index 0000000..c19dce5 --- /dev/null +++ b/src/views/personal-center/overtime-application/modules/overtime-application-action-dialog.vue @@ -0,0 +1,113 @@ + + + diff --git a/src/views/personal-center/overtime-application/modules/overtime-application-detail-dialog.vue b/src/views/personal-center/overtime-application/modules/overtime-application-detail-dialog.vue new file mode 100644 index 0000000..9e0b965 --- /dev/null +++ b/src/views/personal-center/overtime-application/modules/overtime-application-detail-dialog.vue @@ -0,0 +1,97 @@ + + + + + diff --git a/src/views/personal-center/overtime-application/modules/overtime-application-operate-dialog.vue b/src/views/personal-center/overtime-application/modules/overtime-application-operate-dialog.vue new file mode 100644 index 0000000..aae7d8e --- /dev/null +++ b/src/views/personal-center/overtime-application/modules/overtime-application-operate-dialog.vue @@ -0,0 +1,273 @@ + + + + + diff --git a/src/views/personal-center/overtime-application/modules/overtime-application-search.vue b/src/views/personal-center/overtime-application/modules/overtime-application-search.vue new file mode 100644 index 0000000..ec70728 --- /dev/null +++ b/src/views/personal-center/overtime-application/modules/overtime-application-search.vue @@ -0,0 +1,95 @@ + + + diff --git a/src/views/personal-center/overtime-application/modules/overtime-application-shared.ts b/src/views/personal-center/overtime-application/modules/overtime-application-shared.ts new file mode 100644 index 0000000..20bc7ae --- /dev/null +++ b/src/views/personal-center/overtime-application/modules/overtime-application-shared.ts @@ -0,0 +1,77 @@ +import dayjs from 'dayjs'; +import { getStatusTagType } from '@/constants/status-tag'; + +export const overtimeApplicationStatusOptions: Array<{ + label: string; + value: Api.OvertimeApplication.OvertimeApplicationStatusCode; +}> = [ + { label: '待审批', value: 'pending' }, + { label: '已通过', value: 'approved' }, + { label: '已退回', value: 'rejected' }, + { label: '已撤销', value: 'cancelled' } +]; + +export const overtimeApplicationActionNameMap: Record = { + submit: '提交', + resubmit: '重新提交', + approve: '通过', + reject: '退回', + cancel: '撤销' +}; + +export function getOvertimeApplicationStatusLabel(statusCode?: string | null, statusName?: string | null) { + if (statusName) { + return statusName; + } + + return overtimeApplicationStatusOptions.find(item => item.value === statusCode)?.label || statusCode || '--'; +} + +export function resolveOvertimeApplicationStatusTagType(statusCode?: string | null) { + return getStatusTagType('overtimeApplication', statusCode); +} + +export function getOvertimeApplicationActionLabel(actionType?: string | null) { + if (!actionType) { + return '--'; + } + + return ( + overtimeApplicationActionNameMap[actionType as Api.OvertimeApplication.OvertimeApplicationActionType] || actionType + ); +} + +export function formatOvertimeDate(value?: string | null) { + if (!value) { + return '--'; + } + + const target = dayjs(value); + + return target.isValid() ? target.format('YYYY-MM-DD') : value; +} + +export function formatOvertimeDateTime(value?: string | null) { + if (!value) { + return '--'; + } + + const target = dayjs(value); + + return target.isValid() ? target.format('YYYY-MM-DD HH:mm:ss') : value; +} + +export function formatEmptyText(value?: string | null) { + return value?.trim() || '--'; +} + +export function downloadBlob(blob: Blob, fileName: string) { + const url = URL.createObjectURL(blob); + const link = document.createElement('a'); + + link.href = url; + link.download = fileName; + link.click(); + + URL.revokeObjectURL(url); +} diff --git a/src/views/personal-center/overtime-application/modules/overtime-application-status-log-dialog.vue b/src/views/personal-center/overtime-application/modules/overtime-application-status-log-dialog.vue new file mode 100644 index 0000000..6d2bce1 --- /dev/null +++ b/src/views/personal-center/overtime-application/modules/overtime-application-status-log-dialog.vue @@ -0,0 +1,89 @@ + + + diff --git a/src/views/system/dict/index.vue b/src/views/system/dict/index.vue index 819f31a..f08d94b 100644 --- a/src/views/system/dict/index.vue +++ b/src/views/system/dict/index.vue @@ -93,6 +93,10 @@ function getDictStatusTagType(status: Api.Dict.DictStatus): UI.ThemeColor { return status === 0 ? 'success' : 'info'; } +function formatColorType(value?: string | null) { + return value?.trim() || '--'; +} + const dictTypeSearchParams = reactive(getInitDictTypeSearchParams()); const dictDataSearchParams = reactive(getInitDictDataSearchParams()); @@ -177,6 +181,13 @@ const { { prop: 'index', type: 'index', label: $t('common.index'), width: 64 }, { prop: 'label', label: $t('page.system.dict.dictLabel'), minWidth: 160 }, { prop: 'value', label: $t('page.system.dict.dictValue'), minWidth: 180 }, + { + prop: 'colorType', + label: $t('page.system.dict.colorType'), + width: 120, + align: 'center', + formatter: row => formatColorType(row.colorType) + }, { prop: 'sort', label: $t('page.system.dict.sort'), width: 90, align: 'center' }, { prop: 'status', diff --git a/src/views/system/dict/modules/dict-data-operate-modal.vue b/src/views/system/dict/modules/dict-data-operate-modal.vue index e2a36ca..61fa928 100644 --- a/src/views/system/dict/modules/dict-data-operate-modal.vue +++ b/src/views/system/dict/modules/dict-data-operate-modal.vue @@ -53,6 +53,7 @@ function createDefaultModel(): Model { label: '', value: '', dictType: '', + colorType: '', sort: 0, status: 0, remark: '' @@ -81,6 +82,7 @@ function handleInitModel() { label: props.rowData.label, value: props.rowData.value, dictType: props.rowData.dictType, + colorType: props.rowData.colorType ?? '', sort: props.rowData.sort, status: props.rowData.status, remark: props.rowData.remark ?? '' @@ -168,6 +170,11 @@ watch(visible, value => { + + + + + { diff --git a/src/views/workbench/modules/workbench-todo-panel.vue b/src/views/workbench/modules/workbench-todo-panel.vue index 85ae25c..401a296 100644 --- a/src/views/workbench/modules/workbench-todo-panel.vue +++ b/src/views/workbench/modules/workbench-todo-panel.vue @@ -1,8 +1,16 @@ @@ -468,6 +684,12 @@ function getDeadlineToneClass(item: WorkbenchTodoItem) { color: rgb(190 18 60 / 96%); } +.workbench-todo__filter-count { + margin-left: 4px; + font-size: 11px; + font-weight: 700; +} + .workbench-todo__content { min-height: 400px; display: flex; @@ -596,6 +818,25 @@ function getDeadlineToneClass(item: WorkbenchTodoItem) { .workbench-todo__trailing { display: flex; align-items: center; + gap: 12px; +} + +.workbench-todo__actions { + display: inline-flex; + align-items: center; + gap: 6px; + white-space: nowrap; +} + +.workbench-todo__actions :deep(.el-button + .el-button) { + margin-left: 0; +} + +:deep(.workbench-todo__action-btn) { + min-width: auto; + height: auto; + padding: 3px; + line-height: 1; } .workbench-todo__deadline {