This commit is contained in:
2026-04-23 16:42:10 +08:00
parent dac1102ae5
commit 6b68eb254e
10 changed files with 280 additions and 16 deletions
+5
View File
@@ -35,4 +35,9 @@ export const purchaseApi = {
deleteOrder(id) {
return api.post('/purchase/deleteorder', { id })
},
/** 删除状态记录 */
deleteCommit(orderId, commitId) {
return api.post('/purchase/delete_commit', { orderId, commitId })
},
}
@@ -40,4 +40,9 @@ export const workOrderApi = {
searchPurchaseOrders(search = '', limit = 5) {
return api.post('/work_order/search_purchase_orders', { search, limit })
},
/** 删除进度 */
deleteCommit(workOrderId, commitId) {
return api.post('/work_order/delete_commit', { workOrderId, commitId })
},
}
+4 -1
View File
@@ -108,7 +108,8 @@
"upload_photos": "Upload Photos",
"commit_create": "Order created",
"edit_order": "Edit Order",
"submit_changes":"Submit changes"
"submit_changes":"Submit changes",
"confirm_delete_commit": "Are you sure you want to delete this progress?"
},
"work_order": {
"list_title": "Work Order List",
@@ -146,6 +147,7 @@
"back_to_list": "Back to List",
"not_found": "Work order not found",
"confirm_delete": "Are you sure you want to delete this work order? This action cannot be undone.",
"confirm_delete_commit": "Are you sure you want to delete this progress?",
"submit": "Submit",
"save_changes": "Save Changes"
},
@@ -292,6 +294,7 @@
"cancel": "Cancel",
"save_success": "Saved successfully",
"submit": "Submit",
"submitting": "Submitting...",
"loading": "Loading..."
},
"settings": {
+4 -1
View File
@@ -108,7 +108,8 @@
"upload_photos": "上传图片",
"commit_create": "订单创建",
"edit_order": "编辑订单",
"submit_changes":"提交修改"
"submit_changes":"提交修改",
"confirm_delete_commit": "确定要删除此进度吗?"
},
"work_order": {
"list_title": "工单列表",
@@ -146,6 +147,7 @@
"back_to_list": "返回列表",
"not_found": "工单不存在",
"confirm_delete": "确定要删除此工单吗?此操作不可撤销。",
"confirm_delete_commit": "确定要删除此进度吗?",
"submit": "提交",
"save_changes": "保存修改"
},
@@ -292,6 +294,7 @@
"delete_ok": "删除成功",
"save_success": "保存成功",
"submit": "提交",
"submitting": "提交中...",
"loading": "加载中..."
},
"settings": {
@@ -7,6 +7,7 @@ import { usePageTitle } from "@/composables/usePageTitle";
import { purchaseApi } from "@/api/purchase";
import { useUserStore } from "@/stores/user";
import { useUsersStore } from "@/stores/users";
import ConfirmDialog from "@/components/ConfirmDialog.vue";
import {
IconChevronLeft,
IconExternalLink,
@@ -44,6 +45,47 @@ const pendingComment = ref("");
const pendingPhotos = ref([]); // { hash, url, uploading, error }
const photoInputRef = ref(null);
// 删除进度相关
const showDeleteConfirm = ref(false);
const pendingDeleteCommitId = ref(null);
// 判断是否可以删除进度
function canDeleteCommit(commit, index) {
// 最新状态(第0条)不显示删除按钮
if (index === 0) return false;
// 订单创建者
if (order.value?.UserID === userStore.user?.ID) return true;
// 进度创建者
if (commit.userId === userStore.user?.ID) return true;
// 管理员
if (userStore.user?.Type === 'admin') return true;
return false;
}
function handleDeleteCommit(commitId) {
pendingDeleteCommitId.value = commitId;
showDeleteConfirm.value = true;
}
async function confirmDeleteCommit() {
if (!pendingDeleteCommitId.value) return;
try {
const { errCode } = await purchaseApi.deleteCommit(orderId.value, pendingDeleteCommitId.value);
if (errCode === 0) {
toast.success(t("message.delete_ok"));
// 前端直接移除该 commit,保持滚动位置
commits.value = commits.value.filter(c => c.id !== pendingDeleteCommitId.value);
} else {
toast.error(t("message.server_error"));
}
} catch {
toast.error(t("message.server_error"));
} finally {
pendingDeleteCommitId.value = null;
showDeleteConfirm.value = false;
}
}
// 状态选项
const statusOptions = [
{ value: "pending", labelKey: "status_pending", color: "yellow" },
@@ -644,9 +686,9 @@ onMounted(fetchOrder);
class="divide-y divide-gray-50 px-6 py-2 dark:divide-dk-muted/50"
>
<div
v-for="commit in commits"
v-for="(commit, index) in commits"
:key="commit.id"
class="flex items-start gap-3 py-3"
class="flex items-start gap-3 py-3 rounded-lg border border-gray-100 bg-gray-50/50 px-4 my-2 dark:border-dk-muted dark:bg-dk-base/30"
>
<!-- 左侧头像 + 用户名 -->
<div class="flex w-20 flex-shrink-0 flex-col items-center gap-1">
@@ -697,6 +739,15 @@ onMounted(fetchOrder);
<span class="ml-auto text-xs text-gray-400">{{
formatDate(commit.createdAt)
}}</span>
<!-- 删除按钮 -->
<button
v-if="canDeleteCommit(commit, index)"
class="ml-2 rounded-lg border border-red-200 bg-red-50 px-3 py-1 text-xs font-medium text-red-600 transition-colors hover:bg-red-100 hover:border-red-300 dark:border-red-900/50 dark:bg-red-900/30 dark:text-red-400 dark:hover:bg-red-900/50"
@click="handleDeleteCommit(commit.id)"
>
<IconTrash :size="14" class="mr-1 inline align-middle" />
删除
</button>
</div>
<p
v-if="commit.comment"
@@ -899,6 +950,14 @@ onMounted(fetchOrder);
</div>
</Transition>
</Teleport>
<!-- 删除进度确认弹窗 -->
<ConfirmDialog
v-model="showDeleteConfirm"
:message="t('purchase.confirm_delete_commit')"
danger
@confirm="confirmDeleteCommit"
/>
</template>
<style scoped>
@@ -259,9 +259,9 @@ async function handleSubmit() {
<!-- 删除确认弹窗 -->
<ConfirmDialog
v-if="showDeleteConfirm"
v-model="showDeleteConfirm"
:message="t('work_order.confirm_delete')"
danger
@confirm="doDelete"
@cancel="showDeleteConfirm = false"
/>
</template>
@@ -8,6 +8,7 @@ import { useUserStore } from '@/stores/user'
import { useUsersStore } from '@/stores/users'
import { workOrderApi } from '@/api/work_order'
import useDropzone from '@/components/useDropzone.vue'
import ConfirmDialog from '@/components/ConfirmDialog.vue'
import {
IconChevronLeft,
IconCheck,
@@ -39,7 +40,7 @@ const notFound = ref(false)
const submittingCommit = ref(false)
const commitStatus = ref('pending')
const commitComment = ref('')
const commitPhotos = ref([])
const commitDropzoneRef = ref(null)
// 采购订单关联相关
const purchaseSearchQuery = ref('')
@@ -54,7 +55,7 @@ const purchaseDropdownRef = ref(null)
const canSubmit = computed(() => {
const hasSelectedOrders = selectedPurchaseOrders.value.length > 0
const hasComment = !!commitComment.value
const hasPhotos = commitPhotos.value.length > 0
const hasPhotos = commitDropzoneRef.value?.return_files().filter(f => f.is_upload).length > 0
// 订单、备注、上传图片都为空时才禁止提交
return hasSelectedOrders || hasComment || hasPhotos
})
@@ -138,18 +139,22 @@ async function handleCommit() {
submittingCommit.value = true
try {
const purchaseOrderIds = selectedPurchaseOrders.value.map(p => p.id)
// 从 dropzone 获取已上传的文件 hash
const uploadedPhotos = commitDropzoneRef.value?.return_files()
.filter(f => f.is_upload)
.map(f => f.hash) ?? []
const { errCode } = await workOrderApi.commit(
orderId.value,
commitStatus.value,
commitComment.value,
commitPhotos.value,
uploadedPhotos,
purchaseOrderIds,
)
if (errCode === 0) {
toast.success(t('message.save_ok'))
commitComment.value = ''
commitPhotos.value = []
selectedPurchaseOrders.value = []
// 清空 dropzone(刷新组件即可)
await fetchOrder()
} else {
toast.error(t('message.server_error'))
@@ -161,6 +166,47 @@ async function handleCommit() {
}
}
// ==================== 删除进度 ====================
const showDeleteCommitConfirm = ref(false)
const pendingDeleteCommitId = ref(null)
function handleDeleteCommit(commitId) {
pendingDeleteCommitId.value = commitId
showDeleteCommitConfirm.value = true
}
async function confirmDeleteCommit() {
if (!pendingDeleteCommitId.value) return
try {
const { errCode } = await workOrderApi.deleteCommit(orderId.value, pendingDeleteCommitId.value)
if (errCode === 0) {
toast.success(t('message.delete_ok'))
// 前端直接移除该 commit,保持滚动位置
commits.value = commits.value.filter(c => c.ID !== pendingDeleteCommitId.value)
} else {
toast.error(t('message.server_error'))
}
} catch {
toast.error(t('message.server_error'))
} finally {
pendingDeleteCommitId.value = null
showDeleteCommitConfirm.value = false
}
}
// 判断是否可以删除进度
function canDeleteCommit(commit, index) {
// 最新状态(第0条)不显示删除按钮
if (index === 0) return false
// 订单创建者
if (order.value?.UserID === userStore.user?.ID) return true
// 进度创建者
if (commit.UserID === userStore.user?.ID) return true
// 管理员
if (userStore.user?.Type === 'admin') return true
return false
}
// ==================== 快捷切换状态 ====================
async function quickChangeStatus(newStatus) {
if (newStatus === order.value?.CurrentStatus) return
@@ -492,7 +538,7 @@ onUnmounted(() => {
<div class="mb-3">
<label class="mb-1 block text-xs font-medium text-gray-400">{{ t('work_order.commit_photos_label') }}</label>
<useDropzone
v-model="commitPhotos"
ref="commitDropzoneRef"
:maxFiles="10"
:maxSize="10 * 1024 * 1024"
accept="image/*"
@@ -502,11 +548,11 @@ onUnmounted(() => {
<!-- 第四行提交 -->
<div class="flex justify-end">
<button
:disabled="isCommitting || !canSubmit"
:disabled="submittingCommit || !canSubmit"
class="rounded-lg bg-blue-600 px-6 py-2 text-sm font-medium text-white transition-colors hover:bg-blue-700 disabled:cursor-not-allowed disabled:opacity-50"
@click="handleCommit"
>
{{ isCommitting ? '提交中...' : t('work_order.commit_submit') }}
{{ submittingCommit ? t('message.submitting') : t('work_order.commit_submit') }}
</button>
</div>
</div>
@@ -516,9 +562,9 @@ onUnmounted(() => {
<div v-if="commits.length === 0" class="py-4 text-sm text-gray-400">{{ t('work_order.no_commits') }}</div>
<ol v-else class="relative border-l border-gray-200 dark:border-dk-muted">
<li
v-for="commit in [...commits].reverse()"
v-for="(commit, index) in commits"
:key="commit.ID"
class="mb-6 ml-4"
class="mb-6 ml-4 rounded-lg border border-gray-100 bg-gray-50/50 px-4 py-3 dark:border-dk-muted dark:bg-dk-base/30"
>
<!-- 时间线圆点 -->
<div
@@ -545,6 +591,15 @@ onUnmounted(() => {
</span>
<!-- 时间 -->
<time class="text-xs text-gray-400">{{ formatDate(commit.CreatedAt) }}</time>
<!-- 删除按钮 -->
<button
v-if="canDeleteCommit(commit, index)"
class="ml-auto rounded-lg border border-red-200 bg-red-50 px-3 py-1.5 text-xs font-medium text-red-600 transition-colors hover:bg-red-100 hover:border-red-300 dark:border-red-900/50 dark:bg-red-900/30 dark:text-red-400 dark:hover:bg-red-900/50"
@click="handleDeleteCommit(commit.ID)"
>
<IconTrash :size="14" class="mr-1 inline align-middle" />
删除
</button>
</div>
<!-- 备注文字 -->
@@ -606,4 +661,12 @@ onUnmounted(() => {
</div>
</div>
<!-- 删除进度确认弹窗 -->
<ConfirmDialog
v-model="showDeleteCommitConfirm"
:message="t('work_order.confirm_delete_commit')"
danger
@confirm="confirmDeleteCommit"
/>
</template>