diff --git a/pc/src/api/tasks.js b/pc/src/api/tasks.js index 07bc95a..dc4e27d 100644 --- a/pc/src/api/tasks.js +++ b/pc/src/api/tasks.js @@ -1,5 +1,10 @@ import request from '@/utils/request' +/** + * 获取任务列表 + * @param {Object} params 查询参数 + * @returns {Promise} + */ export function listTasks(params) { return request({ url: '/api/oa/tasks', @@ -8,6 +13,11 @@ export function listTasks(params) { }) } +/** + * 获取任务详情 + * @param {number|string} id 任务ID + * @returns {Promise} + */ export function getTask(id) { return request({ url: `/api/oa/tasks/${id}`, @@ -15,6 +25,11 @@ export function getTask(id) { }) } +/** + * 创建任务 + * @param {Object} data 任务数据 + * @returns {Promise} + */ export function createTask(data) { return request({ url: '/api/oa/tasks', @@ -23,6 +38,12 @@ export function createTask(data) { }) } +/** + * 更新任务 + * @param {number|string} id 任务ID + * @param {Object} data 更新的数据 + * @returns {Promise} + */ export function updateTask(id, data) { return request({ url: `/api/oa/tasks/${id}`, @@ -31,9 +52,30 @@ export function updateTask(id, data) { }) } +/** + * 删除任务 + * @param {number|string} id 任务ID + * @returns {Promise} + */ export function deleteTask(id) { return request({ url: `/api/oa/tasks/${id}`, method: 'delete' }) } + +/** + * 获取待办任务列表 + * @param {Object} params + * @param {number} params.tenantId 租户ID + * @param {number} params.id 用户ID + * @param {number} [params.limit] 限制条数 + * @returns {Promise} + */ +export function getTodoTasks(params = {}) { + return request({ + url: '/api/oa/tasks/todo', + method: 'get', + params, + }); +} \ No newline at end of file diff --git a/pc/src/components/CommonHeader.vue b/pc/src/components/CommonHeader.vue index 7be6a5e..9320972 100644 --- a/pc/src/components/CommonHeader.vue +++ b/pc/src/components/CommonHeader.vue @@ -5,36 +5,22 @@ - 首页 - + 首页 + {{ item.label }}
- - + + - - + + @@ -43,11 +29,15 @@ + diff --git a/pc/src/views/apps/oa/tasks/components/edit.vue b/pc/src/views/apps/oa/tasks/components/edit.vue index 2e7b013..24b9830 100644 --- a/pc/src/views/apps/oa/tasks/components/edit.vue +++ b/pc/src/views/apps/oa/tasks/components/edit.vue @@ -46,6 +46,25 @@ + + + + + + + + ({ // 关联人(仅提交 id 数组在 team_employee_ids) const teamEmployeeIds = ref>([]) const teamEmployeeList = ref>([]) +// 附件 +const attachmentIds = ref>([]) +const uploadFileList = ref([]) +const previewVisible = ref(false) +const previewImageUrl = ref('') +const resolveFileUrl = (path: string): string => { + if (!path) return '' + if (/^https?:\/\//i.test(path)) return path + const base = (request as any)?.defaults?.baseURL || '' + try { + const origin = new URL(base, window.location.origin).origin + return origin.replace(/\/+$/, '') + '/' + path.replace(/^\/+/, '') + } catch { + return path + } +} const authStore = useAuthStore() const loadTeamEmployeeNames = async (ids: Array) => { if (!ids || ids.length === 0) { @@ -183,6 +221,16 @@ watch(() => props.task, (t) => { } else { loadTeamEmployeeNames(teamEmployeeIds.value) } + // 初始化附件 + if (Array.isArray(t?.attachment_ids)) { + attachmentIds.value = [...t.attachment_ids] + } else if (typeof t?.attachment_ids === 'string' && t.attachment_ids) { + attachmentIds.value = t.attachment_ids.split(',').map((x: string) => x.trim()).filter(Boolean) + } else { + attachmentIds.value = [] + } + // 无法从任务上还原文件URL时,保持为空列表,由用户重新上传 + uploadFileList.value = uploadFileList.value }, { immediate: true }) const rules = reactive>({ @@ -239,6 +287,14 @@ const onSubmit = async () => { const n = Number(x) return isNaN(n) ? x : n }) + // 追加附件 ID,逗号分隔(后端字段为 attachment_ids),如果返回了 id + if (attachmentIds.value.length) { + const ids = (attachmentIds.value || []).map((x: any) => { + const n = Number(x) + return isNaN(n) ? String(x) : String(n) + }) + payload.attachment_ids = ids.join(',') + } if (props.isEdit && form.id) { res = await updateTask(form.id, payload) @@ -264,6 +320,63 @@ const onSubmit = async () => { const onClosed = () => { // reset if needed } + +// 上传前校验(参考租户编辑) +const beforeUpload = (file: File) => { + const isImage = file.type.startsWith('image/') + const isLt5M = file.size / 1024 / 1024 < 5 + if (!isImage) { + ElMessage.error('仅支持图片类型') + return false + } + if (!isLt5M) { + ElMessage.error('图片大小不能超过 5MB') + return false + } + return true +} + +// 自定义上传(参考租户编辑) +const handleCustomUpload = async (options: any) => { + const { file, onError, onSuccess } = options + try { + const formData = new FormData() + formData.append('file', file as File) + const res = await uploadFile(formData, { category: '图片' }) + onSuccess && onSuccess(res) + } catch (err) { + ElMessage.error('上传失败') + onError && onError(err) + } +} + +// 上传回调(兼容 id 与 file_url 返回) +const handleUploadSuccess = (response: any, file: any, fileList: any[]) => { + const id = response?.data?.id ?? response?.id + const rawUrl = response?.data?.file_url || response?.data?.url || response?.url || '' + if (id != null) { + attachmentIds.value.push(id) + } + const abs = rawUrl ? resolveFileUrl(rawUrl) : (file?.url || '') + // 用服务器返回的绝对地址覆盖当前项的 url,避免后续预览使用失效的 blob: URL + uploadFileList.value = fileList.map((f: any) => { + if (f.uid === file.uid) { + return { ...f, url: abs } + } + return { ...f, url: f.url } + }) + if (abs) ElMessage.success('上传成功') +} +const handleUploadRemove = (file: any, fileList: any[]) => { + const id = file?.response?.data?.id ?? file?.name + attachmentIds.value = attachmentIds.value.filter(x => String(x) !== String(id)) + uploadFileList.value = fileList +} +const handleUploadPreview = (file: any) => { + const url = file?.url || '' + previewImageUrl.value = url.startsWith('blob:') ? url : resolveFileUrl(url) + previewVisible.value = !!previewImageUrl.value +} diff --git a/pc/src/views/system/tenant/components/detail.vue b/pc/src/views/system/tenant/components/detail.vue index aacb308..e08eb95 100644 --- a/pc/src/views/system/tenant/components/detail.vue +++ b/pc/src/views/system/tenant/components/detail.vue @@ -1,11 +1,5 @@