| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427 |
- <template>
- <div class="p-4 md:p-8">
- <!-- 头部工具栏 -->
- <a-card class="mb-6 bg-gray-50 rounded-lg shadow-sm !border-0">
- <a-space size="large" wrap>
- <a-input-search
- v-model:value="searchKeyword"
- placeholder="搜索款号或工单号"
- class="w-full sm:w-80"
- @search="loadWorkOrders"
- />
- <a-select
- v-model:value="filterStatus"
- placeholder="状态筛选"
- class="w-full sm:w-40"
- allowClear
- @change="loadWorkOrders"
- >
- <a-select-option value="pending">待开始</a-select-option>
- <a-select-option value="in_progress">进行中</a-select-option>
- <a-select-option value="completed">已完成</a-select-option>
- <a-select-option value="cancelled">已取消</a-select-option>
- </a-select>
- <a-button type="primary" @click="showCreateModal" class="shadow-sm hover:shadow-md transition-shadow">
- <PlusOutlined class="mr-2" />
- 创建工单
- </a-button>
- </a-space>
- </a-card>
- <!-- 桌面端:工单列表表格 -->
- <a-card v-if="!layoutStore.isMobile" class="rounded-xl shadow-md !border-0">
- <a-table
- :columns="columns"
- :dataSource="workOrders"
- :loading="loading"
- :pagination="pagination"
- @change="handleTableChange"
- rowKey="id"
- >
- <template #status="{ record }">
- <a-tag :color="getStatusColor(record.status)" class="!rounded-full">
- {{ getStatusText(record.status) }}
- </a-tag>
- </template>
- <template #progress="{ record }">
- <a-progress
- :percent="record.total_qty > 0 ? Math.round((record.completed_qty / record.total_qty) * 100) : 0"
- :status="record.status === 'completed' ? 'success' : 'active'"
- />
- </template>
- <template #action="{ record }">
- <a-space>
- <a-button size="small" @click="showEditModal(record)" class="hover:shadow-sm transition-shadow">
- <EditOutlined class="mr-1" />
- 编辑
- </a-button>
- <a-button size="small" type="default" @click="generateQRCode(record.id)" class="hover:shadow-sm transition-shadow">
- <QrcodeOutlined class="mr-1" />
- 生成二维码
- </a-button>
- <a-popconfirm
- title="确定删除此工单吗?"
- ok-text="确定"
- cancel-text="取消"
- @confirm="deleteWorkOrder(record.id)"
- >
- <a-button size="small" danger class="hover:shadow-sm transition-shadow">
- <DeleteOutlined class="mr-1" />
- 删除
- </a-button>
- </a-popconfirm>
- </a-space>
- </template>
- </a-table>
- </a-card>
- <!-- 移动端:工单卡片列表 -->
- <div v-else class="space-y-3">
- <a-spin :spinning="loading">
- <a-empty v-if="!loading && workOrders.length === 0" description="暂无工单数据" class="py-8" />
- <a-card
- v-for="item in workOrders"
- :key="item.id"
- class="rounded-xl shadow-sm !border-0"
- size="small"
- >
- <div class="flex items-start justify-between gap-2 mb-2">
- <span class="font-medium text-gray-900 break-all">{{ item.order_no }}</span>
- <a-tag :color="getStatusColor(item.status)" class="!rounded-full !mr-0 flex-shrink-0">
- {{ getStatusText(item.status) }}
- </a-tag>
- </div>
- <div class="text-sm text-gray-500 space-y-1 mb-2">
- <div>款号:{{ item.style }}</div>
- <div>款号名称:{{ item.style_name }}</div>
- <div>数量:{{ item.completed_qty }} / {{ item.total_qty }}</div>
- </div>
- <a-progress
- :percent="item.total_qty > 0 ? Math.round((item.completed_qty / item.total_qty) * 100) : 0"
- :status="item.status === 'completed' ? 'success' : 'active'"
- size="small"
- class="mb-3"
- />
- <div class="flex items-center gap-2 pt-3 border-t border-gray-100">
- <a-button size="small" @click="showEditModal(item)">
- <EditOutlined class="mr-1" />
- 编辑
- </a-button>
- <a-button size="small" @click="generateQRCode(item.id)">
- <QrcodeOutlined class="mr-1" />
- 二维码
- </a-button>
- <a-popconfirm
- title="确定删除此工单吗?"
- ok-text="确定"
- cancel-text="取消"
- @confirm="deleteWorkOrder(item.id)"
- >
- <a-button size="small" danger>
- <DeleteOutlined class="mr-1" />
- 删除
- </a-button>
- </a-popconfirm>
- </div>
- </a-card>
- </a-spin>
- <div class="flex justify-center mt-4">
- <a-pagination
- v-model:current="pagination.current"
- v-model:pageSize="pagination.pageSize"
- :total="pagination.total"
- simple
- size="small"
- @change="handlePageChange"
- />
- </div>
- </div>
- <!-- 创建/编辑工单模态框 -->
- <a-modal
- v-model:open="modalVisible"
- :title="modalTitle"
- @ok="handleSubmit"
- @cancel="resetForm"
- :confirmLoading="submitting"
- :width="modalWidth"
- class="!rounded-xl"
- >
- <a-form
- ref="formRef"
- :model="formState"
- :rules="formRules"
- layout="vertical"
- >
- <a-form-item label="工单号" name="order_no" v-if="!isEdit">
- <a-input
- v-model:value="formState.order_no"
- placeholder="请输入工单号"
- />
- </a-form-item>
- <a-form-item label="款号" name="style" v-if="!isEdit">
- <a-input
- v-model:value="formState.style"
- placeholder="请输入款号(字母或数字)"
- />
- </a-form-item>
- <a-form-item label="款号名称" name="style_name">
- <a-input
- v-model:value="formState.style_name"
- placeholder="请输入款号名称"
- />
- </a-form-item>
- <a-form-item label="总数量" name="total_qty">
- <a-input-number
- v-model:value="formState.total_qty"
- :min="1"
- :max="100000"
- placeholder="请输入总数量"
- style="width: 100%"
- />
- </a-form-item>
- <a-form-item label="备注" name="remark">
- <a-textarea
- v-model:value="formState.remark"
- placeholder="可选备注信息"
- :rows="3"
- />
- </a-form-item>
- </a-form>
- </a-modal>
- <!-- 二维码展示模态框 -->
- <a-modal
- v-model:open="qrCodeModalVisible"
- title="工单二维码"
- @cancel="qrCodeModalVisible = false"
- :footer="null"
- :width="qrModalWidth"
- class="!rounded-xl"
- >
- <div class="text-center">
- <img :src="qrCodeUrl" alt="工单二维码" style="width: 100%" />
- <p class="mt-4 text-gray-600 text-sm">请使用手机扫描此二维码进行计件</p>
- </div>
- </a-modal>
- </div>
- </template>
- <script setup lang="ts">
- import { ref, reactive, onMounted, computed } from 'vue';
- import { message } from 'ant-design-vue';
- import { PlusOutlined, EditOutlined, DeleteOutlined, QrcodeOutlined } from '@ant-design/icons-vue';
- import { WorkOrderApi, createApiClient } from '@smartcut/api-client';
- import { STORAGE_KEYS } from '@smartcut/shared-utils';
- import { getApiBaseUrl } from '@/apiConfig';
- import { useLayoutStore } from '@/stores/layout';
- import type { WorkOrder } from '@smartcut/types';
- const apiClient = createApiClient({
- baseURL: getApiBaseUrl(),
- tokenKey: STORAGE_KEYS.FACTORY_TOKEN
- });
- const workOrderApi = new WorkOrderApi(apiClient);
- const layoutStore = useLayoutStore();
- const modalWidth = computed(() => layoutStore.isMobile ? '90%' : 600);
- const qrModalWidth = computed(() => layoutStore.isMobile ? '90%' : '400px');
- const workOrders = ref<WorkOrder[]>([]);
- const loading = ref(false);
- const searchKeyword = ref('');
- const filterStatus = ref('');
- const pagination = reactive({
- current: 1,
- pageSize: 10,
- total: 0,
- showSizeChanger: true,
- showTotal: (total: number) => `共 ${total} 条`
- });
- const columns = [
- { title: 'ID', dataIndex: 'id', key: 'id', width: 80 },
- { title: '工单号', dataIndex: 'order_no', key: 'order_no', width: 150 },
- { title: '款号', dataIndex: 'style', key: 'style', width: 120 },
- { title: '款号名称', dataIndex: 'style_name', key: 'style_name', width: 200 },
- { title: '总数量', dataIndex: 'total_qty', key: 'total_qty', width: 100 },
- { title: '完成数量', dataIndex: 'completed_qty', key: 'completed_qty', width: 100 },
- { title: '进度', key: 'progress', width: 150, slots: { customRender: 'progress' } },
- { title: '状态', dataIndex: 'status', key: 'status', width: 100, slots: { customRender: 'status' } },
- { title: '操作', key: 'action', fixed: 'right', width: 200, slots: { customRender: 'action' } }
- ];
- const modalVisible = ref(false);
- const modalTitle = ref('创建工单');
- const isEdit = ref(false);
- const submitting = ref(false);
- const formRef = ref();
- const editingOrderId = ref<number | null>(null);
- const formState = reactive({
- order_no: '',
- style: '',
- style_name: '',
- total_qty: 1,
- remark: ''
- });
- const formRules = {
- order_no: [{ required: true, message: '请输入工单号', trigger: 'blur' }],
- style: [
- { required: true, message: '请输入款号', trigger: 'blur' },
- { pattern: /^[a-zA-Z0-9_-]+$/, message: '款号只能包含字母、数字、下划线或横线', trigger: 'blur' }
- ],
- style_name: [{ required: true, message: '请输入款号名称', trigger: 'blur' }],
- total_qty: [{ required: true, message: '请输入总数量', trigger: 'blur' }]
- };
- const qrCodeModalVisible = ref(false);
- const qrCodeUrl = ref('');
- function getStatusColor(status: string) {
- const colors: Record<string, string> = {
- pending: 'default',
- in_progress: 'blue',
- completed: 'green',
- cancelled: 'red'
- };
- return colors[status] || 'default';
- }
- function getStatusText(status: string) {
- const texts: Record<string, string> = {
- pending: '待开始',
- in_progress: '进行中',
- completed: '已完成',
- cancelled: '已取消'
- };
- return texts[status] || status;
- }
- async function loadWorkOrders() {
- loading.value = true;
- try {
- const result = await workOrderApi.getWorkOrders({
- page: pagination.current,
- page_size: pagination.pageSize,
- status: filterStatus.value,
- keyword: searchKeyword.value
- });
- workOrders.value = result.list;
- pagination.total = result.total;
- } catch (error) {
- message.error('加载工单列表失败');
- } finally {
- loading.value = false;
- }
- }
- function handleTableChange(pag: any) {
- pagination.current = pag.current;
- pagination.pageSize = pag.pageSize;
- loadWorkOrders();
- }
- // 移动端卡片列表分页变化
- function handlePageChange(page: number, pageSize: number) {
- pagination.current = page;
- pagination.pageSize = pageSize;
- loadWorkOrders();
- }
- function showCreateModal() {
- modalTitle.value = '创建工单';
- isEdit.value = false;
- modalVisible.value = true;
- resetForm();
- }
- function showEditModal(record: WorkOrder) {
- modalTitle.value = '编辑工单';
- isEdit.value = true;
- editingOrderId.value = record.id;
- modalVisible.value = true;
- formState.order_no = record.order_no;
- formState.style = record.style;
- formState.style_name = record.style_name;
- formState.total_qty = record.total_qty;
- formState.remark = record.remark;
- }
- async function handleSubmit() {
- try {
- await formRef.value.validate();
- submitting.value = true;
- if (isEdit.value && editingOrderId.value) {
- await workOrderApi.updateWorkOrder(editingOrderId.value, {
- style_name: formState.style_name,
- total_qty: formState.total_qty,
- remark: formState.remark
- });
- message.success('工单已更新');
- } else {
- await workOrderApi.createWorkOrder({
- order_no: formState.order_no,
- style: formState.style,
- style_name: formState.style_name,
- total_qty: formState.total_qty,
- remark: formState.remark
- });
- message.success('工单已创建');
- }
- modalVisible.value = false;
- loadWorkOrders();
- } catch (error: any) {
- const errorMsg = error.response?.data?.msg || '操作失败';
- message.error(errorMsg);
- } finally {
- submitting.value = false;
- }
- }
- function resetForm() {
- formRef.value?.resetFields();
- formState.order_no = '';
- formState.style = '';
- formState.style_name = '';
- formState.total_qty = 1;
- formState.remark = '';
- editingOrderId.value = null;
- }
- async function generateQRCode(id: number) {
- try {
- const blob = await workOrderApi.getWorkOrderQRCode(id);
- qrCodeUrl.value = URL.createObjectURL(blob);
- qrCodeModalVisible.value = true;
- } catch (error) {
- message.error('生成二维码失败');
- }
- }
- async function deleteWorkOrder(id: number) {
- try {
- await workOrderApi.deleteWorkOrder(id);
- message.success('工单已删除');
- loadWorkOrders();
- } catch (error) {
- message.error('删除工单失败');
- }
- }
- onMounted(() => {
- loadWorkOrders();
- });
- </script>
|