diff --git a/backend/components.d.ts b/backend/components.d.ts index 75eea65..e69de29 100644 --- a/backend/components.d.ts +++ b/backend/components.d.ts @@ -1,95 +0,0 @@ -/* eslint-disable */ -// @ts-nocheck -// biome-ignore lint: disable -// oxlint-disable -// ------ -// Generated by unplugin-vue-components -// Read more: https://github.com/vuejs/core/pull/3399 - -export {} - -/* prettier-ignore */ -declare module 'vue' { - export interface GlobalComponents { - CommonAside: typeof import('./src/components/CommonAside.vue')['default'] - CommonHeader: typeof import('./src/components/CommonHeader.vue')['default'] - ElAlert: typeof import('element-plus/es')['ElAlert'] - ElAside: typeof import('element-plus/es')['ElAside'] - ElAutocomplete: typeof import('element-plus/es')['ElAutocomplete'] - ElAvatar: typeof import('element-plus/es')['ElAvatar'] - ElBacktop: typeof import('element-plus/es')['ElBacktop'] - ElBadge: typeof import('element-plus/es')['ElBadge'] - ElBreadcrumb: typeof import('element-plus/es')['ElBreadcrumb'] - ElBreadcrumbItem: typeof import('element-plus/es')['ElBreadcrumbItem'] - ElButton: typeof import('element-plus/es')['ElButton'] - ElButtonGroup: typeof import('element-plus/es')['ElButtonGroup'] - ElCard: typeof import('element-plus/es')['ElCard'] - ElCascader: typeof import('element-plus/es')['ElCascader'] - ElCheckbox: typeof import('element-plus/es')['ElCheckbox'] - ElCheckboxGroup: typeof import('element-plus/es')['ElCheckboxGroup'] - ElCol: typeof import('element-plus/es')['ElCol'] - ElCollapse: typeof import('element-plus/es')['ElCollapse'] - ElCollapseItem: typeof import('element-plus/es')['ElCollapseItem'] - ElCollapseTransition: typeof import('element-plus/es')['ElCollapseTransition'] - ElConfigProvider: typeof import('element-plus/es')['ElConfigProvider'] - ElContainer: typeof import('element-plus/es')['ElContainer'] - ElDatePicker: typeof import('element-plus/es')['ElDatePicker'] - ElDescriptions: typeof import('element-plus/es')['ElDescriptions'] - ElDescriptionsItem: typeof import('element-plus/es')['ElDescriptionsItem'] - ElDialog: typeof import('element-plus/es')['ElDialog'] - ElDivider: typeof import('element-plus/es')['ElDivider'] - ElDrawer: typeof import('element-plus/es')['ElDrawer'] - ElDropdown: typeof import('element-plus/es')['ElDropdown'] - ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem'] - ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu'] - ElEmpty: typeof import('element-plus/es')['ElEmpty'] - ElForm: typeof import('element-plus/es')['ElForm'] - ElFormItem: typeof import('element-plus/es')['ElFormItem'] - ElHeader: typeof import('element-plus/es')['ElHeader'] - ElIcon: typeof import('element-plus/es')['ElIcon'] - ElImage: typeof import('element-plus/es')['ElImage'] - ElInput: typeof import('element-plus/es')['ElInput'] - ElInputNumber: typeof import('element-plus/es')['ElInputNumber'] - ElLink: typeof import('element-plus/es')['ElLink'] - ElMain: typeof import('element-plus/es')['ElMain'] - ElMenu: typeof import('element-plus/es')['ElMenu'] - ElMenuItem: typeof import('element-plus/es')['ElMenuItem'] - ElOption: typeof import('element-plus/es')['ElOption'] - ElOptionGroup: typeof import('element-plus/es')['ElOptionGroup'] - ElPagination: typeof import('element-plus/es')['ElPagination'] - ElPopover: typeof import('element-plus/es')['ElPopover'] - ElProgress: typeof import('element-plus/es')['ElProgress'] - ElRadio: typeof import('element-plus/es')['ElRadio'] - ElRadioButton: typeof import('element-plus/es')['ElRadioButton'] - ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup'] - ElRow: typeof import('element-plus/es')['ElRow'] - ElScrollbar: typeof import('element-plus/es')['ElScrollbar'] - ElSelect: typeof import('element-plus/es')['ElSelect'] - ElSkeleton: typeof import('element-plus/es')['ElSkeleton'] - ElSlider: typeof import('element-plus/es')['ElSlider'] - ElStep: typeof import('element-plus/es')['ElStep'] - ElSteps: typeof import('element-plus/es')['ElSteps'] - ElSubMenu: typeof import('element-plus/es')['ElSubMenu'] - ElSwitch: typeof import('element-plus/es')['ElSwitch'] - ElTable: typeof import('element-plus/es')['ElTable'] - ElTableColumn: typeof import('element-plus/es')['ElTableColumn'] - ElTabPane: typeof import('element-plus/es')['ElTabPane'] - ElTabs: typeof import('element-plus/es')['ElTabs'] - ElTag: typeof import('element-plus/es')['ElTag'] - ElTimeline: typeof import('element-plus/es')['ElTimeline'] - ElTimelineItem: typeof import('element-plus/es')['ElTimelineItem'] - ElTimeSelect: typeof import('element-plus/es')['ElTimeSelect'] - ElTooltip: typeof import('element-plus/es')['ElTooltip'] - ElTree: typeof import('element-plus/es')['ElTree'] - ElTreeSelect: typeof import('element-plus/es')['ElTreeSelect'] - ElUpload: typeof import('element-plus/es')['ElUpload'] - FlowCanvas: typeof import('./src/components/approval/FlowCanvas.vue')['default'] - MessageDetailDialog: typeof import('./src/components/MessageDetailDialog.vue')['default'] - RouterLink: typeof import('vue-router')['RouterLink'] - RouterView: typeof import('vue-router')['RouterView'] - TenantSwitcher: typeof import('./src/components/TenantSwitcher.vue')['default'] - } - export interface GlobalDirectives { - vLoading: typeof import('element-plus/es')['ElLoadingDirective'] - } -} diff --git a/backend/src/api/oaDailyReport.js b/backend/src/api/oaDailyReport.js index 4ea62ef..2d2dd57 100644 --- a/backend/src/api/oaDailyReport.js +++ b/backend/src/api/oaDailyReport.js @@ -21,8 +21,19 @@ export function getMyDailyReports(params) { }) } +// 我的日报近期热力图:params = { months }(默认 3,含本月往前数) +// data = { start_date, end_date, total, submitted, days: [{ date, status, id }] } +export function getMyDailyReportCalendar(params) { + return request({ + url: '/backend/oa/dailyreport/calendar', + method: 'get', + params + }) +} + // 保存日报:{ date, today_done, tomorrow_plan, issues, attachments, submit } // submit=true 提交,false 存草稿;同一人同一天只会有一条记录 +// 注意:已提交的日报会被锁定,需先调用 withdrawDailyReport 撤销提交后才能修改 export function saveDailyReport(data) { return request({ url: '/backend/oa/dailyreport/save', @@ -31,7 +42,16 @@ export function saveDailyReport(data) { }) } -// 日报详情:{ report, comments, can_edit, can_delete, can_comment } +// 撤销提交:已提交日报退回草稿,同时把本次提交的内容快照写入修改痕迹,改完需重新提交 +export function withdrawDailyReport(id) { + return request({ + url: `/backend/oa/dailyreport/withdraw/${id}`, + method: 'post' + }) +} + +// 日报详情:{ report, comments, histories, is_author, can_edit, can_delete, can_comment, can_withdraw } +// can_edit 仅在「本人草稿且处于补写窗口」时为 true;can_withdraw 仅在已提交且可撤销时为 true export function getDailyReportDetail(id) { return request({ url: `/backend/oa/dailyreport/detail/${id}`, diff --git a/backend/src/views/apps/oa/dailyreport/components/ReportView.vue b/backend/src/views/apps/oa/dailyreport/components/ReportView.vue index 025516b..539b57e 100644 --- a/backend/src/views/apps/oa/dailyreport/components/ReportView.vue +++ b/backend/src/views/apps/oa/dailyreport/components/ReportView.vue @@ -25,7 +25,25 @@
评论 {{ detail.comments.length }}
@@ -155,6 +203,8 @@ import { ElMessage, ElMessageBox } from 'element-plus' import { Paperclip } from '@element-plus/icons-vue' import { getDailyReportDetail, + saveDailyReport, + withdrawDailyReport, addDailyReportComment, deleteDailyReportComment, deleteDailyReport @@ -171,6 +221,8 @@ const detail = ref(null) const commentText = ref('') const replyTo = ref(null) const submitting = ref(false) +const submittingReport = ref(false) +const withdrawing = ref(false) watch( () => props.reportId, @@ -201,6 +253,62 @@ function startReply(comment) { replyTo.value = { id: comment.id, user_name: comment.user_name } } +// 草稿直接在详情页提交:提交后内容锁定,需撤销提交才能再改。 +async function handleSubmitReport() { + try { + await ElMessageBox.confirm( + '提交后日报将锁定,如需修改需先撤销提交。确定提交这条日报吗?', + '提交日报', + { type: 'warning', confirmButtonText: '提交', cancelButtonText: '取消' } + ) + } catch { + return + } + submittingReport.value = true + try { + const report = detail.value.report + await saveDailyReport({ + date: report.report_date, + today_done: report.today_done || '', + tomorrow_plan: report.tomorrow_plan || '', + issues: report.issues || '', + attachments: JSON.stringify(report.attachments || []), + submit: true + }) + ElMessage.success('日报已提交') + await loadDetail() + emit('changed') + } catch (err) { + ElMessage.error(err?.message || '提交失败') + } finally { + submittingReport.value = false + } +} + +// 撤销提交:退回草稿并留存修改痕迹,修改后可重新提交。 +async function handleWithdraw() { + try { + await ElMessageBox.confirm( + '撤销提交后日报退回草稿,本次提交的内容会作为修改痕迹保留,修改完成后需重新提交。确定撤销吗?', + '撤销提交', + { type: 'warning', confirmButtonText: '撤销提交', cancelButtonText: '取消' } + ) + } catch { + return + } + withdrawing.value = true + try { + await withdrawDailyReport(props.reportId) + ElMessage.success('已撤销提交,可修改后重新提交') + await loadDetail() + emit('changed') + } catch (err) { + ElMessage.error(err?.message || '撤销提交失败') + } finally { + withdrawing.value = false + } +} + async function submitComment() { const content = commentText.value.trim() if (!content) { @@ -321,6 +429,53 @@ function formatSize(size) { gap: 4px; } +.lock-tip { + margin-top: 10px; + padding: 8px 12px; + border-radius: 6px; + background: var(--el-color-warning-light-9, #fdf6ec); + font-size: 12px; + color: var(--el-color-warning, #e6a23c); +} + +.history-list { + display: flex; + flex-direction: column; + gap: 10px; +} + +.history-item { + padding: 10px 12px; + border: 1px dashed #ebeef5; + border-radius: 6px; + background: #fafafa; +} + +.history-head { + display: flex; + align-items: center; + gap: 8px; + font-size: 12px; +} + +.history-time { + margin-left: auto; + color: #c0c4cc; +} + +.history-text { + margin-top: 6px; + font-size: 13px; + line-height: 1.7; + color: #606266; + white-space: pre-wrap; + word-break: break-word; +} + +.history-label { + color: #909399; +} + .detail-section { padding: 14px 0; border-bottom: 1px dashed #ebeef5; @@ -470,10 +625,24 @@ html.dark & { .comment-time, .file-size, .comment-disabled, + .history-label, .reply-banner { color: var(--el-text-color-secondary); } + .history-item { + background: var(--el-fill-color-light); + border-color: var(--el-border-color-lighter); + } + + .history-text { + color: var(--el-text-color-regular); + } + + .lock-tip { + background: var(--el-color-warning-light-9); + } + .attachment-item, .comment-item { background: var(--el-fill-color-light); diff --git a/backend/src/views/apps/oa/dailyreport/index.vue b/backend/src/views/apps/oa/dailyreport/index.vue index 574ad0e..533dd24 100644 --- a/backend/src/views/apps/oa/dailyreport/index.vue +++ b/backend/src/views/apps/oa/dailyreport/index.vue @@ -16,61 +16,118 @@