up
This commit is contained in:
@@ -25,4 +25,9 @@ export const purchaseApi = {
|
||||
updateOrder(id, data) {
|
||||
return api.post('/purchase/updateorder', { id, ...data })
|
||||
},
|
||||
|
||||
/** 删除订单 */
|
||||
deleteOrder(id) {
|
||||
return api.post('/purchase/deleteorder', { id })
|
||||
},
|
||||
}
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
<script setup>
|
||||
/**
|
||||
* ConfirmDialog —— 通用确认弹窗
|
||||
*
|
||||
* 使用方式:
|
||||
* const ok = await confirmDialog({
|
||||
* title: "提示标题",
|
||||
* message: "确认要删除吗?",
|
||||
* confirmText: "删除",
|
||||
* cancelText: "取消",
|
||||
* danger: true, // 红色确认按钮
|
||||
* })
|
||||
* if (ok) { ... }
|
||||
*
|
||||
* 或者作为组件使用 v-model:
|
||||
* <ConfirmDialog v-model="show" @confirm="..." @cancel="..." />
|
||||
*/
|
||||
import { ref, watch } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
message: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
confirmText: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
cancelText: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
danger: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(["update:modelValue", "confirm", "cancel"]);
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
function close() {
|
||||
emit("update:modelValue", false);
|
||||
emit("cancel");
|
||||
}
|
||||
|
||||
function confirm() {
|
||||
emit("update:modelValue", false);
|
||||
emit("confirm");
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(val) => {
|
||||
if (val) {
|
||||
document.body.style.overflow = "hidden";
|
||||
} else {
|
||||
document.body.style.overflow = "";
|
||||
}
|
||||
},
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Teleport to="body">
|
||||
<Transition name="fade">
|
||||
<div
|
||||
v-if="modelValue"
|
||||
class="fixed inset-0 z-50 flex items-center justify-center bg-black/40"
|
||||
@click.self="close"
|
||||
>
|
||||
<div
|
||||
class="min-w-[320px] max-w-sm rounded-xl border border-gray-200 bg-white shadow-2xl dark:border-dk-muted dark:bg-dk-card"
|
||||
>
|
||||
<!-- 标题 -->
|
||||
<div class="flex items-center justify-between border-b border-gray-100 px-5 py-4 dark:border-dk-muted">
|
||||
<h3 class="text-base font-semibold text-gray-900 dark:text-white">
|
||||
{{ title || t("message.confirm") }}
|
||||
</h3>
|
||||
<button
|
||||
class="ml-4 flex-shrink-0 rounded p-1 text-gray-400 hover:bg-gray-100 hover:text-gray-600 dark:hover:bg-dk-muted dark:hover:text-gray-200"
|
||||
@click="close"
|
||||
>
|
||||
<svg class="h-4 w-4" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 内容 -->
|
||||
<div class="px-5 py-4">
|
||||
<p class="text-sm leading-relaxed text-gray-600 dark:text-gray-300">
|
||||
{{ message }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- 操作栏 -->
|
||||
<div class="flex justify-end gap-3 border-t border-gray-100 px-5 py-4 dark:border-dk-muted">
|
||||
<button
|
||||
class="rounded-lg border border-gray-300 bg-white px-4 py-2 text-sm font-medium text-gray-700 transition-colors hover:bg-gray-50 dark:border-dk-muted dark:bg-dk-base dark:text-gray-200 dark:hover:bg-dk-muted"
|
||||
@click="close"
|
||||
>
|
||||
{{ cancelText || t("message.cancel") }}
|
||||
</button>
|
||||
<button
|
||||
class="rounded-lg px-4 py-2 text-sm font-semibold text-white transition-colors"
|
||||
:class="
|
||||
danger
|
||||
? 'bg-red-500 hover:bg-red-600'
|
||||
: 'bg-blue-600 hover:bg-blue-700'
|
||||
"
|
||||
@click="confirm"
|
||||
>
|
||||
{{ confirmText || t("message.confirm") }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
</Teleport>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.fade-enter-active,
|
||||
.fade-leave-active {
|
||||
transition: opacity 0.15s ease;
|
||||
}
|
||||
.fade-enter-from,
|
||||
.fade-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
</style>
|
||||
@@ -66,6 +66,7 @@
|
||||
"created_at": "Created At",
|
||||
"updated_at": "Updated At",
|
||||
"status": "Status",
|
||||
"filter_all": "All",
|
||||
"status_pending": "Pending",
|
||||
"status_ordered": "Ordered",
|
||||
"status_arrived": "Arrived",
|
||||
@@ -82,8 +83,11 @@
|
||||
"There_are_a_total_of": ",There are a total of",
|
||||
"items": "orders.",
|
||||
"order_detail": "Order Detail",
|
||||
"back": "Back",
|
||||
"back_to_list": "Back to List",
|
||||
"order_not_found": "Order Not Found",
|
||||
"delete_order": "Delete Order",
|
||||
"confirm_delete": "Are you sure you want to delete this order? This action cannot be undone.",
|
||||
"order_info": "Order Information",
|
||||
"cost_detail": "Cost Details",
|
||||
"photo_remarks": "Photos",
|
||||
@@ -227,12 +231,15 @@
|
||||
"administrator": "Administrator",
|
||||
"select_date": "Select a date",
|
||||
"save_ok": "Saved successfully",
|
||||
"delete_ok": "Deleted successfully",
|
||||
"change_ok": "Changed successfully",
|
||||
"type_old_pass": "Enter old password",
|
||||
"type_new_pass": "Enter new password",
|
||||
"type_cof_pass": "Confirm new password",
|
||||
"old_pass_incorrect": "Old password is incorrect",
|
||||
"confirm_password_incorrect": "Confirm password is incorrect",
|
||||
"confirm": "Confirm",
|
||||
"cancel": "Cancel",
|
||||
"save_success": "Saved successfully",
|
||||
"submit": "Submit",
|
||||
"loading": "Loading..."
|
||||
|
||||
@@ -66,6 +66,7 @@
|
||||
"created_at": "创建日期",
|
||||
"updated_at": "更新日期",
|
||||
"status": "状态",
|
||||
"filter_all": "全部",
|
||||
"status_pending": "待处理",
|
||||
"status_ordered": "已下单",
|
||||
"status_arrived": "已到达",
|
||||
@@ -82,8 +83,11 @@
|
||||
"There_are_a_total_of": ",一共",
|
||||
"items": "个订单",
|
||||
"order_detail": "订单详情",
|
||||
"back": "返回",
|
||||
"back_to_list": "返回列表",
|
||||
"order_not_found": "订单不存在",
|
||||
"delete_order": "删除订单",
|
||||
"confirm_delete": "确定要删除此订单吗?此操作不可撤销。",
|
||||
"order_info": "订单信息",
|
||||
"cost_detail": "费用明细",
|
||||
"photo_remarks": "图片备注",
|
||||
@@ -233,6 +237,9 @@
|
||||
"type_cof_pass": "确认新密码",
|
||||
"old_pass_incorrect": "旧密码不正确",
|
||||
"confirm_password_incorrect": "确认密码不正确",
|
||||
"confirm": "确认",
|
||||
"cancel": "取消",
|
||||
"delete_ok": "删除成功",
|
||||
"save_success": "保存成功",
|
||||
"submit": "提交",
|
||||
"loading": "加载中..."
|
||||
|
||||
@@ -5,7 +5,7 @@ import { useI18n } from 'vue-i18n'
|
||||
import { useToastStore } from '@/stores/toast'
|
||||
import { usePageTitle } from '@/composables/usePageTitle'
|
||||
import { purchaseApi } from '@/api/purchase'
|
||||
import { IconPlus, IconChevronLeftPipe, IconChevronRightPipe, IconChevronsLeft, IconChevronsRight, IconSearch } from '@tabler/icons-vue'
|
||||
import { IconPlus, IconChevronLeftPipe, IconChevronRightPipe, IconChevronsLeft, IconChevronsRight } from '@tabler/icons-vue'
|
||||
|
||||
usePageTitle('appname.purchase')
|
||||
const { t, locale } = useI18n()
|
||||
@@ -16,9 +16,19 @@ const orders = ref([])
|
||||
const totalCount = ref(0)
|
||||
const pageSize = ref(10)
|
||||
const currentPage = ref(1)
|
||||
const searchQuery = ref('')
|
||||
const statusFilter = ref('')
|
||||
const loading = ref(false)
|
||||
|
||||
const statusOptions = [
|
||||
{ value: '', labelKey: 'purchase.filter_all' },
|
||||
{ value: 'pending', labelKey: 'purchase.status_pending' },
|
||||
{ value: 'ordered', labelKey: 'purchase.status_ordered' },
|
||||
{ value: 'arrived', labelKey: 'purchase.status_arrived' },
|
||||
{ value: 'received', labelKey: 'purchase.status_received' },
|
||||
{ value: 'lost', labelKey: 'purchase.status_lost' },
|
||||
{ value: 'returned', labelKey: 'purchase.status_returned' },
|
||||
]
|
||||
|
||||
const totalPages = computed(() => Math.ceil(totalCount.value / pageSize.value) || 1)
|
||||
|
||||
const pageRange = computed(() => {
|
||||
@@ -34,7 +44,7 @@ async function fetchOrders() {
|
||||
loading.value = true
|
||||
try {
|
||||
const { errCode, data } = await purchaseApi.getOrders({
|
||||
search: searchQuery.value,
|
||||
status: statusFilter.value,
|
||||
entries: pageSize.value,
|
||||
page: currentPage.value,
|
||||
})
|
||||
@@ -107,11 +117,16 @@ onMounted(fetchOrders)
|
||||
<IconPlus :size="16" />
|
||||
{{ t('purchase.add_part') }}
|
||||
</RouterLink>
|
||||
<button class="rounded-lg border border-gray-300 px-3 py-1.5 text-sm text-gray-600 transition-colors hover:bg-gray-50 dark:border-dk-muted dark:text-gray-400">{{ t('purchase.exp_report') }}</button>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<label class="text-sm text-gray-500">{{ t('purchase.search') }}</label>
|
||||
<input v-model="searchQuery" type="text" class="w-48 rounded-lg border border-gray-300 bg-white px-3 py-1.5 text-sm outline-none transition-colors focus:border-blue-500 focus:ring-2 focus:ring-blue-500/20 dark:border-dk-muted dark:bg-dk-base dark:text-white" @keydown.enter="currentPage=1;fetchOrders()" />
|
||||
<!-- 状态下拉筛选 -->
|
||||
<select
|
||||
v-model="statusFilter"
|
||||
class="rounded-lg border border-gray-300 bg-white px-3 py-1.5 text-sm dark:border-dk-muted dark:bg-dk-base dark:text-white"
|
||||
@change="currentPage = 1; fetchOrders()"
|
||||
>
|
||||
<option v-for="opt in statusOptions" :key="opt.value" :value="opt.value">
|
||||
{{ t(opt.labelKey) }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ import { useValidation } from "@/composables";
|
||||
import { purchaseApi } from "@/api/purchase";
|
||||
import tagadder from "@/components/tagadder.vue";
|
||||
import useDropzone from "@/components/useDropzone.vue";
|
||||
import ConfirmDialog from "@/components/ConfirmDialog.vue";
|
||||
|
||||
usePageTitle("purchase_addorder.edit_order");
|
||||
|
||||
@@ -96,6 +97,7 @@ watch(
|
||||
|
||||
// ==================== 图片上传 ====================
|
||||
const dropzoneRef = ref(null);
|
||||
const showDeleteConfirm = ref(false);
|
||||
|
||||
function getPhotoHashes() {
|
||||
return dropzoneRef.value?.return_files().map((f) => f.hash) ?? [];
|
||||
@@ -152,6 +154,30 @@ onMounted(async () => {
|
||||
}
|
||||
});
|
||||
|
||||
// ==================== 提交 ====================
|
||||
// ==================== 删除订单 ====================
|
||||
async function handleDelete() {
|
||||
showDeleteConfirm.value = true;
|
||||
}
|
||||
|
||||
async function doDelete() {
|
||||
|
||||
loading.value = true;
|
||||
try {
|
||||
const res = await purchaseApi.deleteOrder(orderId);
|
||||
if (res.errCode === 0) {
|
||||
toast.success(t("message.delete_ok"));
|
||||
router.replace("/purchase");
|
||||
} else {
|
||||
toast.error(t("message.server_error"));
|
||||
}
|
||||
} catch {
|
||||
toast.error(t("message.server_error"));
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 提交 ====================
|
||||
async function handleSubmit() {
|
||||
clearErrors();
|
||||
@@ -241,11 +267,24 @@ async function handleSubmit() {
|
||||
<h4 class="text-sm font-semibold text-gray-900 dark:text-white">
|
||||
{{ t("purchase_addorder.edit_order") }}
|
||||
</h4>
|
||||
<!-- 返回按钮 -->
|
||||
<button
|
||||
class="flex items-center gap-1.5 rounded-lg px-3 py-1.5 text-sm text-gray-500 hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-dk-base"
|
||||
@click="router.back()"
|
||||
>
|
||||
<!-- 操作按钮组 -->
|
||||
<div class="flex items-center gap-2">
|
||||
<!-- 删除按钮 -->
|
||||
<button
|
||||
class="flex items-center gap-1.5 rounded-lg px-3 py-1.5 text-sm text-red-500 hover:bg-red-50 dark:hover:bg-red-900/20"
|
||||
:disabled="loading"
|
||||
@click="handleDelete"
|
||||
>
|
||||
<svg class="h-4 w-4" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
|
||||
</svg>
|
||||
{{ t("purchase.delete_order") }}
|
||||
</button>
|
||||
<!-- 返回按钮 -->
|
||||
<button
|
||||
class="flex items-center gap-1.5 rounded-lg px-3 py-1.5 text-sm text-gray-500 hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-dk-base"
|
||||
@click="router.back()"
|
||||
>
|
||||
<svg
|
||||
class="h-4 w-4"
|
||||
fill="none"
|
||||
@@ -259,8 +298,9 @@ async function handleSubmit() {
|
||||
d="M15 19l-7-7 7-7"
|
||||
/>
|
||||
</svg>
|
||||
{{ t("purchase.back_to_list") }}
|
||||
{{ t("purchase.back") }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 错误提示(字段验证) -->
|
||||
@@ -532,4 +572,12 @@ async function handleSubmit() {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 通用确认弹窗 -->
|
||||
<ConfirmDialog
|
||||
v-model="showDeleteConfirm"
|
||||
:title="t('purchase.confirm_delete')"
|
||||
danger
|
||||
@confirm="doDelete"
|
||||
/>
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user