|
|
@@ -0,0 +1,1838 @@
|
|
|
+<script setup lang="ts">
|
|
|
+import { ref, computed, watch, reactive, onMounted } from 'vue';
|
|
|
+import { message } from 'ant-design-vue';
|
|
|
+import dayjs, { type Dayjs } from 'dayjs';
|
|
|
+import type { VxeGridInstance, VxeGridProps } from 'vxe-table';
|
|
|
+import {
|
|
|
+ todayOrderMethod, pendingOrderMethod, allPieOrderMethod, todayPhysicalOrderMethod, pendingPhysicalOrderMethod, allPhysicalOrderMethod,
|
|
|
+ getOrderLiaisonListMethod, confirmPieOrderMethod, confirmPhysicalOrderMethod, getPieOrderCountMethod
|
|
|
+} from '@/request/api/order.api';
|
|
|
+import type { OrderModel, OrderQuery, OrderLiaisonListModel } from '@/model/order.model';
|
|
|
+import DateTimePicker from './DateTimePicker.vue';
|
|
|
+
|
|
|
+defineOptions({
|
|
|
+ name: 'DispatchOrderPanel',
|
|
|
+});
|
|
|
+
|
|
|
+interface Institution extends OrderLiaisonListModel {
|
|
|
+ address: string;
|
|
|
+}
|
|
|
+
|
|
|
+const props = defineProps<{
|
|
|
+ orderType: 'offline' | 'physical'; // 线下服务 | 实体商品
|
|
|
+}>();
|
|
|
+
|
|
|
+// 当前选中的子tab
|
|
|
+const activeSubTab = ref<'pending' | 'today' | 'all'>('pending');
|
|
|
+
|
|
|
+// 订单列表数据
|
|
|
+const orderList = ref<OrderModel[]>([]);
|
|
|
+// 可派单机构列表
|
|
|
+const institutionList = ref<Institution[]>([]);
|
|
|
+// 当前选中的订单
|
|
|
+const selectedOrderId = ref<number | null>(null);
|
|
|
+
|
|
|
+// 订单计数
|
|
|
+const orderCounts = ref({
|
|
|
+ offlineCount: 0, // 线下服务订单总和
|
|
|
+ allPieOfflineOrderCount: 0, // 线下服务——全部指派订单数量
|
|
|
+ pendPieOfflineOrderCount: 0, // 线下服务——待指派订单数量
|
|
|
+ todayPieOfflineOrderCount: 0, // 线下服务——今日指派订单数量
|
|
|
+ onlineCount: 0, // 实体商品数量订单总和
|
|
|
+ allPieOnlineOrderCount: 0, // 实体商品——全部指派订单数量
|
|
|
+ pendPieOnlineOrderCount: 0, // 实体商品——待指派订单数量
|
|
|
+ todayPieOnlineOrderCount: 0, // 实体商品——今日指派订单数量
|
|
|
+});
|
|
|
+
|
|
|
+// 当前订单的分配机构输入值
|
|
|
+const assignedInstitutionMap = ref<Record<number, string>>({});
|
|
|
+// 记录是否从右边列表选择指派(用于控制按钮显示)
|
|
|
+const assignedFromRightList = ref<Record<number, boolean>>({});
|
|
|
+
|
|
|
+// 日期时间选择弹窗相关
|
|
|
+const dateTimePickerVisible = ref(false);
|
|
|
+const currentPickerOrderId = ref<number | null>(null);
|
|
|
+const pickerType = ref<'date' | 'time'>('date'); // 选择器类型:日期或时间
|
|
|
+const isFromTimeButton = ref(false); // 是否从时间按钮进入
|
|
|
+
|
|
|
+// 加载状态
|
|
|
+const loading = ref(false);
|
|
|
+const institutionLoading = ref(false);
|
|
|
+
|
|
|
+// 订单列表分页配置
|
|
|
+const orderPageConfig = reactive({
|
|
|
+ currentPage: 1,
|
|
|
+ pageSize: 10,
|
|
|
+ total: 0,
|
|
|
+});
|
|
|
+
|
|
|
+// 机构列表分页配置
|
|
|
+const institutionPageConfig = reactive({
|
|
|
+ currentPage: 1,
|
|
|
+ pageSize: 10,
|
|
|
+ total: 0,
|
|
|
+});
|
|
|
+
|
|
|
+// 搜索条件
|
|
|
+const searchForm = reactive({
|
|
|
+ customerName: '',
|
|
|
+ contactPhone: '',
|
|
|
+ orderTypes: [] as string[], // 线下服务: 'booked' | 'verified' | 实体商品: 'pendingShip' | 'shipped'
|
|
|
+});
|
|
|
+
|
|
|
+// 获取可派单机构名称列表
|
|
|
+const availableInstitutionNames = computed(() => {
|
|
|
+ return allInstitutionData.value.map(inst => inst.name);
|
|
|
+});
|
|
|
+
|
|
|
+// 获取订单的完整地址
|
|
|
+function getOrderAddress(order: OrderModel): string {
|
|
|
+ const addressParts = [
|
|
|
+ order.provinceName,
|
|
|
+ order.cityName,
|
|
|
+ order.areaName,
|
|
|
+ order.detailAddress,
|
|
|
+ ].filter(Boolean);
|
|
|
+ return addressParts.join('');
|
|
|
+}
|
|
|
+
|
|
|
+function formatCustomerInfo(order: OrderModel): string {
|
|
|
+ const parts: string[] = [];
|
|
|
+ if (order.liaison) {
|
|
|
+ parts.push(order.liaison);
|
|
|
+ }
|
|
|
+ if (order.phone) {
|
|
|
+ parts.push(order.phone);
|
|
|
+ }
|
|
|
+ const address = getOrderAddress(order);
|
|
|
+ if (address) {
|
|
|
+ parts.push(address);
|
|
|
+ }
|
|
|
+ return parts.join(',');
|
|
|
+}
|
|
|
+
|
|
|
+// 计算结束时间
|
|
|
+function calculateEndTime(order: OrderModel): string | null {
|
|
|
+ if (!order.arrangeTime) return null;
|
|
|
+
|
|
|
+ // 如果没有服务时长,无法计算结束时间
|
|
|
+ if (!order.offlineDuration) return null;
|
|
|
+
|
|
|
+ // 根据开始时间和服务时长计算结束时间
|
|
|
+ const durationMinutes = parseDurationToMinutes(order.offlineDuration);
|
|
|
+ if (durationMinutes > 0) {
|
|
|
+ const start = dayjs(order.arrangeTime, 'HH:mm');
|
|
|
+ const end = start.add(durationMinutes, 'minute');
|
|
|
+ return end.format('HH:mm');
|
|
|
+ }
|
|
|
+
|
|
|
+ return null;
|
|
|
+}
|
|
|
+
|
|
|
+// 判断订单是否已核销(线下服务)
|
|
|
+function isOrderVerified(order: OrderModel): boolean {
|
|
|
+ return props.orderType === 'offline' && order.type === 3;
|
|
|
+}
|
|
|
+
|
|
|
+// 判断订单是否已发货(实体商品)
|
|
|
+function isOrderShipped(order: OrderModel): boolean {
|
|
|
+ return props.orderType === 'physical' && order.type === 3;
|
|
|
+}
|
|
|
+
|
|
|
+// 右侧机构表格配置
|
|
|
+const gridRef = ref<VxeGridInstance<Institution>>();
|
|
|
+const gridOptions = reactive<VxeGridProps<Institution>>({
|
|
|
+ border: true,
|
|
|
+ autoResize: true,
|
|
|
+ height: '100%',
|
|
|
+ scrollY: { enabled: true },
|
|
|
+ columns: [
|
|
|
+ {
|
|
|
+ field: 'action',
|
|
|
+ title: '操作',
|
|
|
+ width: 150,
|
|
|
+ align: 'center',
|
|
|
+ showOverflow: false,
|
|
|
+ cellRender: {
|
|
|
+ name: 'VxeButton',
|
|
|
+ props: {
|
|
|
+ type: 'primary',
|
|
|
+ size: 'large',
|
|
|
+ content: '指派',
|
|
|
+ },
|
|
|
+ events: {
|
|
|
+ click({ row }: { row: Institution }) {
|
|
|
+ handleAssignInstitution(row.name);
|
|
|
+ },
|
|
|
+ },
|
|
|
+ },
|
|
|
+ },
|
|
|
+ { field: 'name', title: '机构名称' },
|
|
|
+ { field: 'detailAddress', title: '地址' },
|
|
|
+ { field: 'phone', title: '联系电话' },
|
|
|
+ { field: 'todayOrderQuantity', title: '当日订单数' },
|
|
|
+ ],
|
|
|
+ data: [],
|
|
|
+ pagerConfig: {
|
|
|
+ enabled: false,
|
|
|
+ },
|
|
|
+});
|
|
|
+
|
|
|
+// 初始化
|
|
|
+onMounted(async () => {
|
|
|
+ await loadOrderCounts();
|
|
|
+ await loadOrderList();
|
|
|
+ if (orderList.value.length > 0) {
|
|
|
+ selectedOrderId.value = getOrderId(orderList.value[0]);
|
|
|
+ if (activeSubTab.value !== 'pending') {
|
|
|
+ orderList.value.forEach(order => {
|
|
|
+ if (order.conditioningProgramSupplierName) {
|
|
|
+ const orderId = getOrderId(order);
|
|
|
+ assignedInstitutionMap.value[orderId] = order.conditioningProgramSupplierName;
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+ // 选中订单后再加载机构列表
|
|
|
+ await loadInstitutionList();
|
|
|
+ }
|
|
|
+});
|
|
|
+
|
|
|
+// 监听子tab切换
|
|
|
+watch(activeSubTab, async () => {
|
|
|
+ orderPageConfig.currentPage = 1;
|
|
|
+ allInstitutionData.value = [];
|
|
|
+ institutionList.value = [];
|
|
|
+ gridRef.value?.loadData([]);
|
|
|
+ // 切换tab时,清空所有按钮显示标记,这样按钮就不会显示
|
|
|
+ assignedFromRightList.value = {};
|
|
|
+ await loadOrderCounts();
|
|
|
+ await loadOrderList();
|
|
|
+ if (orderList.value.length > 0) {
|
|
|
+ selectedOrderId.value = getOrderId(orderList.value[0]);
|
|
|
+ if (activeSubTab.value !== 'pending') {
|
|
|
+ orderList.value.forEach(order => {
|
|
|
+ if (order.conditioningProgramSupplierName) {
|
|
|
+ const orderId = getOrderId(order);
|
|
|
+ assignedInstitutionMap.value[orderId] = order.conditioningProgramSupplierName;
|
|
|
+ }
|
|
|
+ });
|
|
|
+ } else {
|
|
|
+ orderList.value.forEach(order => {
|
|
|
+ const orderId = getOrderId(order);
|
|
|
+ assignedInstitutionMap.value[orderId] = '';
|
|
|
+ assignedFromRightList.value[orderId] = false;
|
|
|
+ });
|
|
|
+ }
|
|
|
+ await loadInstitutionList();
|
|
|
+ } else {
|
|
|
+ selectedOrderId.value = null;
|
|
|
+ allInstitutionData.value = [];
|
|
|
+ institutionList.value = [];
|
|
|
+ gridRef.value?.loadData([]);
|
|
|
+ }
|
|
|
+});
|
|
|
+
|
|
|
+// 监听订单类型切换
|
|
|
+watch(() => props.orderType, async () => {
|
|
|
+ activeSubTab.value = 'pending';
|
|
|
+ selectedOrderId.value = null;
|
|
|
+ assignedInstitutionMap.value = {};
|
|
|
+ // 切换订单类型时,清空所有按钮显示标记
|
|
|
+ assignedFromRightList.value = {};
|
|
|
+ orderPageConfig.currentPage = 1;
|
|
|
+ searchForm.customerName = '';
|
|
|
+ searchForm.contactPhone = '';
|
|
|
+ searchForm.orderTypes = [];
|
|
|
+ allInstitutionData.value = [];
|
|
|
+ institutionList.value = [];
|
|
|
+ gridRef.value?.loadData([]);
|
|
|
+
|
|
|
+ await loadOrderCounts();
|
|
|
+ await loadOrderList();
|
|
|
+
|
|
|
+ if (orderList.value.length > 0) {
|
|
|
+ const firstOrder = orderList.value[0];
|
|
|
+ const orderId = firstOrder.id ?? firstOrder.patientConditioningProgramId;
|
|
|
+ selectedOrderId.value = orderId;
|
|
|
+ const verifyOrder = orderList.value.find(order => {
|
|
|
+ const orderId = order.id ?? order.patientConditioningProgramId;
|
|
|
+ return orderId === selectedOrderId.value;
|
|
|
+ });
|
|
|
+ if (!verifyOrder) {
|
|
|
+ console.error('错误:选中的订单ID不在订单列表中!', {
|
|
|
+ selectedOrderId: selectedOrderId.value,
|
|
|
+ orderListIds: orderList.value.map(o => o.id ?? o.patientConditioningProgramId),
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ if (activeSubTab.value !== 'pending') {
|
|
|
+ orderList.value.forEach(order => {
|
|
|
+ if (order.conditioningProgramSupplierName) {
|
|
|
+ const orderId = getOrderId(order);
|
|
|
+ assignedInstitutionMap.value[orderId] = order.conditioningProgramSupplierName;
|
|
|
+ }
|
|
|
+ });
|
|
|
+ } else {
|
|
|
+ // 待指派订单tab:清空所有已分配的机构
|
|
|
+ orderList.value.forEach(order => {
|
|
|
+ const orderId = getOrderId(order);
|
|
|
+ assignedInstitutionMap.value[orderId] = '';
|
|
|
+ assignedFromRightList.value[orderId] = false;
|
|
|
+ });
|
|
|
+ }
|
|
|
+ // 选中订单后必须加载机构列表(切换订单类型时)
|
|
|
+ await loadInstitutionList();
|
|
|
+ } else {
|
|
|
+ // 如果没有订单,清空机构列表
|
|
|
+ allInstitutionData.value = [];
|
|
|
+ institutionPageConfig.total = 0;
|
|
|
+ institutionList.value = [];
|
|
|
+ gridRef.value?.loadData([]);
|
|
|
+ }
|
|
|
+}, { immediate: false });
|
|
|
+
|
|
|
+
|
|
|
+function buildOrderQuery(): OrderQuery | undefined {
|
|
|
+ if (activeSubTab.value !== 'all') {
|
|
|
+ return undefined;
|
|
|
+ }
|
|
|
+
|
|
|
+ const query: Partial<OrderQuery> = {};
|
|
|
+
|
|
|
+ // 联系人(客户姓名)
|
|
|
+ if (searchForm.customerName) {
|
|
|
+ query.liaison = searchForm.customerName;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 电话
|
|
|
+ if (searchForm.contactPhone) {
|
|
|
+ query.phone = searchForm.contactPhone;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 订单类型转换
|
|
|
+ if (searchForm.orderTypes && searchForm.orderTypes.length > 0) {
|
|
|
+ const types: string[] = [];
|
|
|
+ if (props.orderType === 'offline') {
|
|
|
+ // 线下服务
|
|
|
+ if (searchForm.orderTypes.includes('booked')) {
|
|
|
+ types.push('2'); // 已派单但未核销
|
|
|
+ }
|
|
|
+ if (searchForm.orderTypes.includes('verified')) {
|
|
|
+ types.push('3'); // 已核销
|
|
|
+ }
|
|
|
+ } else if (props.orderType === 'physical') {
|
|
|
+ // 实体商品
|
|
|
+ if (searchForm.orderTypes.includes('pendingShip')) {
|
|
|
+ types.push('2'); // 已派单但未核销(待发货)
|
|
|
+ }
|
|
|
+ if (searchForm.orderTypes.includes('shipped')) {
|
|
|
+ types.push('3'); // 已核销(已发货)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (types.length > 0) {
|
|
|
+ query.types = types;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return Object.keys(query).length > 0 ? (query as OrderQuery) : undefined;
|
|
|
+}
|
|
|
+
|
|
|
+// 加载订单列表
|
|
|
+async function loadOrderList() {
|
|
|
+ loading.value = true;
|
|
|
+ try {
|
|
|
+ let response;
|
|
|
+
|
|
|
+ if (props.orderType === 'offline') {
|
|
|
+ // 线下服务接口
|
|
|
+ if (activeSubTab.value === 'pending') {
|
|
|
+ // 待指派订单
|
|
|
+ response = await pendingOrderMethod(orderPageConfig.currentPage, orderPageConfig.pageSize);
|
|
|
+ } else if (activeSubTab.value === 'today') {
|
|
|
+ // 今日指派订单
|
|
|
+ response = await todayOrderMethod(orderPageConfig.currentPage, orderPageConfig.pageSize);
|
|
|
+ } else if (activeSubTab.value === 'all') {
|
|
|
+ // 全部指派订单
|
|
|
+ const query = buildOrderQuery();
|
|
|
+ response = await allPieOrderMethod(orderPageConfig.currentPage, orderPageConfig.pageSize, query);
|
|
|
+ } else {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ // 实体商品接口
|
|
|
+ if (activeSubTab.value === 'pending') {
|
|
|
+ // 待指派订单
|
|
|
+ response = await pendingPhysicalOrderMethod(orderPageConfig.currentPage, orderPageConfig.pageSize);
|
|
|
+ } else if (activeSubTab.value === 'today') {
|
|
|
+ // 今日指派订单
|
|
|
+ response = await todayPhysicalOrderMethod(orderPageConfig.currentPage, orderPageConfig.pageSize);
|
|
|
+ } else if (activeSubTab.value === 'all') {
|
|
|
+ // 全部指派订单
|
|
|
+ const query = buildOrderQuery();
|
|
|
+ response = await allPhysicalOrderMethod(orderPageConfig.currentPage, orderPageConfig.pageSize, query);
|
|
|
+ } else {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ orderList.value = response.data;
|
|
|
+ orderPageConfig.total = response.total;
|
|
|
+ } catch (error) {
|
|
|
+ orderList.value = [];
|
|
|
+ orderPageConfig.total = 0;
|
|
|
+ } finally {
|
|
|
+ loading.value = false;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const allInstitutionData = ref<Institution[]>([]);
|
|
|
+function getInstitutionAddress(institution: OrderLiaisonListModel): string {
|
|
|
+ const addressParts = [
|
|
|
+ institution.cityName,
|
|
|
+ institution.areaName,
|
|
|
+ institution.detailAddress,
|
|
|
+ ].filter(Boolean);
|
|
|
+ return addressParts.join('');
|
|
|
+}
|
|
|
+
|
|
|
+function transformToInstitution(item: OrderLiaisonListModel): Institution {
|
|
|
+ return {
|
|
|
+ ...item,
|
|
|
+ address: getInstitutionAddress(item),
|
|
|
+ };
|
|
|
+}
|
|
|
+
|
|
|
+// 加载可派单机构列表
|
|
|
+async function loadInstitutionList() {
|
|
|
+ institutionLoading.value = true;
|
|
|
+ allInstitutionData.value = [];
|
|
|
+ institutionList.value = [];
|
|
|
+ gridRef.value?.loadData([]);
|
|
|
+
|
|
|
+ try {
|
|
|
+ const selectedOrder = selectedOrderId.value
|
|
|
+ ? orderList.value.find(order => {
|
|
|
+ const orderId = order.id ?? order.patientConditioningProgramId;
|
|
|
+ return orderId === selectedOrderId.value;
|
|
|
+ })
|
|
|
+ : null;
|
|
|
+
|
|
|
+ if (!selectedOrder) {
|
|
|
+ allInstitutionData.value = [];
|
|
|
+ institutionPageConfig.total = 0;
|
|
|
+ institutionList.value = [];
|
|
|
+ gridRef.value?.loadData([]);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (selectedOrder.institutionId == null || selectedOrder.conditioningProgramType == null) {
|
|
|
+ allInstitutionData.value = [];
|
|
|
+ institutionPageConfig.total = 0;
|
|
|
+ institutionList.value = [];
|
|
|
+ gridRef.value?.loadData([]);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (props.orderType === 'offline') {
|
|
|
+ if (!selectedOrder.arrangeDate || !selectedOrder.arrangeTime) {
|
|
|
+ allInstitutionData.value = [];
|
|
|
+ institutionPageConfig.total = 0;
|
|
|
+ institutionList.value = [];
|
|
|
+ gridRef.value?.loadData([]);
|
|
|
+ // 提示用户需要先选择日期和时间(仅线下服务)
|
|
|
+ if (!selectedOrder.arrangeDate && !selectedOrder.arrangeTime) {
|
|
|
+ message.error('请先选择日期和时间');
|
|
|
+ } else if (!selectedOrder.arrangeDate) {
|
|
|
+ message.error('请先选择日期');
|
|
|
+ } else if (!selectedOrder.arrangeTime) {
|
|
|
+ message.error('请先选择时间');
|
|
|
+ }
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ const data: Partial<any> = {
|
|
|
+ institutionId: selectedOrder.institutionId,
|
|
|
+ conditioningProgramType: selectedOrder.conditioningProgramType,
|
|
|
+ ...(props.orderType === 'offline' && selectedOrder.arrangeDate && selectedOrder.arrangeTime
|
|
|
+ ? { timeStart: `${selectedOrder.arrangeDate} ${selectedOrder.arrangeTime}` }
|
|
|
+ : {}),
|
|
|
+ // 线下服务 type=2,实体商品 type=1
|
|
|
+ type: props.orderType === 'offline' ? 2 : 1
|
|
|
+ };
|
|
|
+
|
|
|
+ // 获取可派单机构列表
|
|
|
+ const response = await getOrderLiaisonListMethod(data);
|
|
|
+ const responseData = Array.isArray(response) ? response : (response?.data || []);
|
|
|
+ const transformedData = responseData.map(transformToInstitution);
|
|
|
+
|
|
|
+ allInstitutionData.value = transformedData;
|
|
|
+
|
|
|
+
|
|
|
+ institutionList.value = transformedData;
|
|
|
+
|
|
|
+ // 更新表格数据
|
|
|
+ gridRef.value?.loadData(institutionList.value);
|
|
|
+
|
|
|
+ } catch (error) {
|
|
|
+ allInstitutionData.value = [];
|
|
|
+ institutionPageConfig.total = 0;
|
|
|
+ institutionList.value = [];
|
|
|
+ } finally {
|
|
|
+ institutionLoading.value = false;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+// 处理日期按钮点击
|
|
|
+function handleDateBtnClick(orderId: number) {
|
|
|
+ const order = orderList.value.find(o => getOrderId(o) === orderId);
|
|
|
+ if (!order) return;
|
|
|
+
|
|
|
+ if (selectedOrderId.value !== orderId) {
|
|
|
+ selectedOrderId.value = orderId;
|
|
|
+ loadInstitutionList();
|
|
|
+ }
|
|
|
+
|
|
|
+ currentPickerOrderId.value = orderId;
|
|
|
+ pickerType.value = 'date';
|
|
|
+ isFromTimeButton.value = false;
|
|
|
+ dateTimePickerVisible.value = true;
|
|
|
+}
|
|
|
+
|
|
|
+// 处理时间按钮点击
|
|
|
+function handleTimeBtnClick(orderId: number) {
|
|
|
+ const order = orderList.value.find(o => getOrderId(o) === orderId);
|
|
|
+ if (!order) return;
|
|
|
+
|
|
|
+ if (selectedOrderId.value !== orderId) {
|
|
|
+ selectedOrderId.value = orderId;
|
|
|
+ loadInstitutionList();
|
|
|
+ }
|
|
|
+
|
|
|
+ currentPickerOrderId.value = orderId;
|
|
|
+ isFromTimeButton.value = true;
|
|
|
+
|
|
|
+ if (order.arrangeDate) {
|
|
|
+ pickerType.value = 'time';
|
|
|
+ } else {
|
|
|
+ pickerType.value = 'date'; // 先选择日期
|
|
|
+ }
|
|
|
+
|
|
|
+ dateTimePickerVisible.value = true;
|
|
|
+}
|
|
|
+
|
|
|
+// 处理日期时间选择器的事件
|
|
|
+function handleDateTimeDateSelect(date: Dayjs) {
|
|
|
+ if (currentPickerOrderId.value !== null) {
|
|
|
+ handleDateChange(currentPickerOrderId.value, date);
|
|
|
+
|
|
|
+
|
|
|
+ if (isFromTimeButton.value) {
|
|
|
+ pickerType.value = 'time';
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function handleDateTimeTimeSelect(time: string, date: Dayjs) {
|
|
|
+ if (currentPickerOrderId.value !== null) {
|
|
|
+ const order = orderList.value.find(o => getOrderId(o) === currentPickerOrderId.value);
|
|
|
+ if (order) {
|
|
|
+ if (!order.arrangeDate) {
|
|
|
+ order.arrangeDate = date.format('YYYY-MM-DD');
|
|
|
+ }
|
|
|
+ // 更新时间
|
|
|
+ handleTimeChange(currentPickerOrderId.value, dayjs(time, 'HH:mm'), order);
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function handleDateTimeDateConfirm(date: Dayjs) {
|
|
|
+ handleDateTimeDateSelect(date);
|
|
|
+}
|
|
|
+
|
|
|
+function handleDateTimeClose() {
|
|
|
+ dateTimePickerVisible.value = false;
|
|
|
+ currentPickerOrderId.value = null;
|
|
|
+ isFromTimeButton.value = false;
|
|
|
+}
|
|
|
+
|
|
|
+// 处理日期修改
|
|
|
+function handleDateChange(orderId: number, date: Dayjs | null) {
|
|
|
+ if (!date) return;
|
|
|
+
|
|
|
+ // 更新订单的日期
|
|
|
+ const orderIndex = orderList.value.findIndex(o => getOrderId(o) === orderId);
|
|
|
+ if (orderIndex !== -1) {
|
|
|
+ const order = orderList.value[orderIndex];
|
|
|
+ const newDateStr = date.format('YYYY-MM-DD');
|
|
|
+
|
|
|
+ // 如果日期相同,不需要更新
|
|
|
+ if (order.arrangeDate !== newDateStr) {
|
|
|
+ order.arrangeDate = newDateStr;
|
|
|
+
|
|
|
+ orderList.value = [...orderList.value];
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 清空分配机构
|
|
|
+ assignedInstitutionMap.value[orderId] = '';
|
|
|
+ assignedFromRightList.value[orderId] = false;
|
|
|
+
|
|
|
+ loadInstitutionList();
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+function handleTimeChange(orderId: number, startTime: any, order: OrderModel) {
|
|
|
+ if (!startTime) return;
|
|
|
+
|
|
|
+ // 更新订单的开始时间
|
|
|
+ const targetOrder = orderList.value.find(o => getOrderId(o) === orderId);
|
|
|
+ if (!targetOrder) return;
|
|
|
+
|
|
|
+ const durationSource = targetOrder.offlineDuration || order.offlineDuration;
|
|
|
+
|
|
|
+ const start = dayjs.isDayjs(startTime) ? startTime : dayjs(startTime);
|
|
|
+ targetOrder.arrangeTime = start.format('HH:mm');
|
|
|
+
|
|
|
+ if (durationSource) {
|
|
|
+ const durationMinutes = parseDurationToMinutes(durationSource);
|
|
|
+ if (durationMinutes > 0) {
|
|
|
+ const end = start.add(durationMinutes, 'minute');
|
|
|
+ targetOrder.arrangeEndTime = end.format('HH:mm');
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 清空分配机构
|
|
|
+ assignedInstitutionMap.value[orderId] = '';
|
|
|
+ assignedFromRightList.value[orderId] = false;
|
|
|
+
|
|
|
+ // 刷新可派单机构列表
|
|
|
+ loadInstitutionList();
|
|
|
+}
|
|
|
+
|
|
|
+// 解析服务时长为分钟数
|
|
|
+function parseDurationToMinutes(duration: string): number {
|
|
|
+ if (!duration) return 0;
|
|
|
+
|
|
|
+ const cleanDuration = duration.trim();
|
|
|
+
|
|
|
+ const numericValue = parseFloat(cleanDuration);
|
|
|
+ if (!isNaN(numericValue) && cleanDuration === numericValue.toString()) {
|
|
|
+ return numericValue;
|
|
|
+ }
|
|
|
+
|
|
|
+ const minutesMatch = cleanDuration.match(/([\d.]+)\s*分钟/);
|
|
|
+ if (minutesMatch) {
|
|
|
+ return Math.round(parseFloat(minutesMatch[1]));
|
|
|
+ }
|
|
|
+
|
|
|
+ const hoursMatch = cleanDuration.match(/([\d.]+)\s*小时/);
|
|
|
+ if (hoursMatch) {
|
|
|
+ return Math.round(parseFloat(hoursMatch[1]) * 60);
|
|
|
+ }
|
|
|
+
|
|
|
+ const hoursMinutesMatch = cleanDuration.match(/([\d.]+)\s*小时\s*([\d.]+)\s*分钟/);
|
|
|
+ if (hoursMinutesMatch) {
|
|
|
+ const hours = parseFloat(hoursMinutesMatch[1]);
|
|
|
+ const minutes = parseFloat(hoursMinutesMatch[2]);
|
|
|
+ return Math.round(hours * 60 + minutes);
|
|
|
+ }
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+function handleLastInstitutionClick(orderId: number, institutionName: string) {
|
|
|
+ if (!institutionName) return;
|
|
|
+
|
|
|
+ if (selectedOrderId.value !== orderId) {
|
|
|
+ selectedOrderId.value = orderId;
|
|
|
+ loadInstitutionList();
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!availableInstitutionNames.value.includes(institutionName)) {
|
|
|
+ message.warning('供应商不可选择,且不填充此机构');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ assignedInstitutionMap.value[orderId] = institutionName;
|
|
|
+ // 标记为已填充(可以显示按钮)
|
|
|
+ assignedFromRightList.value[orderId] = true;
|
|
|
+}
|
|
|
+
|
|
|
+function handleSupplierClick(orderId: number, supplierName: string) {
|
|
|
+ if (!supplierName) return;
|
|
|
+
|
|
|
+ if (selectedOrderId.value !== orderId) {
|
|
|
+ selectedOrderId.value = orderId;
|
|
|
+ loadInstitutionList();
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!availableInstitutionNames.value.includes(supplierName)) {
|
|
|
+ message.warning('供应商不可选择,且不填充此机构');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ assignedInstitutionMap.value[orderId] = supplierName;
|
|
|
+ // 标记为已填充(可以显示按钮)
|
|
|
+ assignedFromRightList.value[orderId] = true;
|
|
|
+}
|
|
|
+
|
|
|
+function handleAssignInstitution(institutionName: string) {
|
|
|
+ if (selectedOrderId.value !== null) {
|
|
|
+ const selectedOrder = orderList.value.find(order => {
|
|
|
+ const orderId = order.id ?? order.patientConditioningProgramId;
|
|
|
+ return orderId === selectedOrderId.value;
|
|
|
+ });
|
|
|
+
|
|
|
+ if (selectedOrder && (isOrderVerified(selectedOrder))) {
|
|
|
+ message.warning('已核销的订单不能指派');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (selectedOrder && (isOrderShipped(selectedOrder))) {
|
|
|
+ message.warning('已发货的订单不能指派');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ assignedInstitutionMap.value[selectedOrderId.value] = institutionName;
|
|
|
+ // 标记为从右边列表选择
|
|
|
+ assignedFromRightList.value[selectedOrderId.value] = true;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function validateAndGetInstitution(orderId: number) {
|
|
|
+ const institutionName = assignedInstitutionMap.value[orderId];
|
|
|
+ if (!institutionName) {
|
|
|
+ message.warning('请先选择分配机构');
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ const institution = allInstitutionData.value.find(inst => inst.name === institutionName);
|
|
|
+ if (!institution) {
|
|
|
+ message.error('未找到对应的供应商信息');
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ return { institutionName, conditioningProgramSupplierId: institution.id };
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+function validateAndGetOrder(orderId: number) {
|
|
|
+ const orderIndex = orderList.value.findIndex(order => getOrderId(order) === orderId);
|
|
|
+ if (orderIndex === -1) {
|
|
|
+ message.error('未找到对应的订单');
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ return { orderIndex, order: orderList.value[orderIndex] };
|
|
|
+}
|
|
|
+
|
|
|
+// 验证线下服务订单的必要信息
|
|
|
+function validateOfflineOrder(order: OrderModel) {
|
|
|
+ if (!order.id) {
|
|
|
+ message.error('订单缺少必要信息(id)');
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!order.arrangeDate || !order.arrangeTime) {
|
|
|
+ message.error('请先选择日期和时间');
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ const endTime = order.arrangeEndTime || (order.offlineDuration ? calculateEndTime(order) : null);
|
|
|
+ if (!endTime) {
|
|
|
+ message.error('无法计算服务结束时间,请检查服务时长设置');
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ return {
|
|
|
+ id: order.id,
|
|
|
+ pieTimeStart: `${order.arrangeDate} ${order.arrangeTime}`,
|
|
|
+ pieTimeEnd: `${order.arrangeDate} ${endTime}`,
|
|
|
+ };
|
|
|
+}
|
|
|
+
|
|
|
+// 验证实体商品订单的必要信息
|
|
|
+function validatePhysicalOrder(order: OrderModel) {
|
|
|
+ if (!order.patientConditioningProgramId) {
|
|
|
+ message.error('订单缺少必要信息(patientConditioningProgramId)');
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ return {
|
|
|
+ patientConditioningProgramId: order.patientConditioningProgramId,
|
|
|
+ };
|
|
|
+}
|
|
|
+
|
|
|
+async function callConfirmOrderAPI(order: OrderModel, conditioningProgramSupplierId: number) {
|
|
|
+ if (props.orderType === 'offline') {
|
|
|
+ const offlineData = validateOfflineOrder(order);
|
|
|
+ if (!offlineData) return false;
|
|
|
+
|
|
|
+ await confirmPieOrderMethod({
|
|
|
+ id: offlineData.id,
|
|
|
+ conditioningProgramSupplierId,
|
|
|
+ pieTimeStart: offlineData.pieTimeStart,
|
|
|
+ pieTimeEnd: offlineData.pieTimeEnd,
|
|
|
+ });
|
|
|
+ } else {
|
|
|
+ const physicalData = validatePhysicalOrder(order);
|
|
|
+ if (!physicalData) return false;
|
|
|
+
|
|
|
+ await confirmPhysicalOrderMethod({
|
|
|
+ patientConditioningProgramId: physicalData.patientConditioningProgramId,
|
|
|
+ conditioningProgramSupplierId,
|
|
|
+ });
|
|
|
+ }
|
|
|
+ return true;
|
|
|
+}
|
|
|
+
|
|
|
+// 处理订单列表更新
|
|
|
+async function handleOrderListAfterAssign(orderId: number, orderIndex: number) {
|
|
|
+ if (activeSubTab.value !== 'pending') {
|
|
|
+ // 如果当前在"今日指派订单"或"全部指派订单"tab,订单保留但显示已指派状态
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果当前在"待指派订单"tab,指派成功后从列表中移除该订单
|
|
|
+ orderList.value.splice(orderIndex, 1);
|
|
|
+
|
|
|
+ // 如果移除后列表为空,取消选中
|
|
|
+ if (orderList.value.length === 0) {
|
|
|
+ selectedOrderId.value = null;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果移除的是当前选中的订单,选中第一个订单
|
|
|
+ if (selectedOrderId.value === orderId) {
|
|
|
+ selectedOrderId.value = orderList.value[0] ? getOrderId(orderList.value[0]) : null;
|
|
|
+ await loadInstitutionList();
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 处理确认指派
|
|
|
+async function handleConfirmAssign(orderId: number) {
|
|
|
+ // 如果订单未选中,自动选中该订单并加载机构列表
|
|
|
+ if (selectedOrderId.value !== orderId) {
|
|
|
+ selectedOrderId.value = orderId;
|
|
|
+ await loadInstitutionList();
|
|
|
+ }
|
|
|
+
|
|
|
+ // 验证并获取机构信息
|
|
|
+ const institutionInfo = validateAndGetInstitution(orderId);
|
|
|
+ if (!institutionInfo) return;
|
|
|
+
|
|
|
+ // 验证并获取订单信息
|
|
|
+ const orderInfo = validateAndGetOrder(orderId);
|
|
|
+ if (!orderInfo) return;
|
|
|
+
|
|
|
+ const { orderIndex, order } = orderInfo;
|
|
|
+ const originalOrder = { ...order };
|
|
|
+
|
|
|
+ // 先更新本地数据
|
|
|
+ order.conditioningProgramSupplierName = institutionInfo.institutionName;
|
|
|
+ order.conditioningProgramSupplierId = institutionInfo.conditioningProgramSupplierId;
|
|
|
+
|
|
|
+ try {
|
|
|
+ const success = await callConfirmOrderAPI(order, institutionInfo.conditioningProgramSupplierId);
|
|
|
+ if (!success) {
|
|
|
+ // 验证失败
|
|
|
+ orderList.value[orderIndex] = originalOrder;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 处理订单列表更新
|
|
|
+ await handleOrderListAfterAssign(orderId, orderIndex);
|
|
|
+
|
|
|
+ // 清空分配机构输入框
|
|
|
+ assignedInstitutionMap.value[orderId] = '';
|
|
|
+ assignedFromRightList.value[orderId] = false;
|
|
|
+
|
|
|
+ // 静默更新订单计数(不显示loading,避免闪烁)
|
|
|
+ loadOrderCounts().catch(err => {
|
|
|
+ console.error('更新订单计数失败:', err);
|
|
|
+ });
|
|
|
+
|
|
|
+ message.success('派单成功');
|
|
|
+ } catch (error) {
|
|
|
+ //API调用失败,恢复原始数据
|
|
|
+ orderList.value[orderIndex] = originalOrder;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 处理取消
|
|
|
+function handleCancel(orderId: number) {
|
|
|
+ // 只清空分配机构,保持按钮显示(不清空 assignedFromRightList)
|
|
|
+ // 使用 null 表示用户主动清空,这样在非 pending tab 中也不会显示原有的供应商名称
|
|
|
+ assignedInstitutionMap.value[orderId] = null as any;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+function getOrderId(order: OrderModel): number {
|
|
|
+ return order.id ?? order.patientConditioningProgramId;
|
|
|
+}
|
|
|
+
|
|
|
+// 判断订单是否被选中
|
|
|
+function isOrderSelected(order: OrderModel): boolean {
|
|
|
+ const orderId = getOrderId(order);
|
|
|
+ const result = selectedOrderId.value === orderId;
|
|
|
+ return result;
|
|
|
+}
|
|
|
+
|
|
|
+// 获取分配机构显示值
|
|
|
+function getAssignedInstitutionDisplayValue(order: OrderModel): string {
|
|
|
+ const orderId = getOrderId(order);
|
|
|
+ // 检查 key 是否存在(使用 hasOwnProperty 或 in 操作符)
|
|
|
+ const hasKey = orderId in assignedInstitutionMap.value;
|
|
|
+ const assignedValue = assignedInstitutionMap.value[orderId];
|
|
|
+
|
|
|
+
|
|
|
+ if (hasKey) {
|
|
|
+ if (assignedValue === null) {
|
|
|
+ return '';
|
|
|
+ }
|
|
|
+ return assignedValue || '';
|
|
|
+ }
|
|
|
+
|
|
|
+ if (activeSubTab.value !== 'pending') {
|
|
|
+ return order.conditioningProgramSupplierName || '';
|
|
|
+ }
|
|
|
+
|
|
|
+ return '';
|
|
|
+}
|
|
|
+
|
|
|
+// 判断是否应该显示确认指派按钮
|
|
|
+function shouldShowConfirmAssignButton(order: OrderModel): boolean {
|
|
|
+ // 如果订单已核销或已发货,不显示
|
|
|
+ if (isOrderVerified(order) || isOrderShipped(order)) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ const orderId = getOrderId(order);
|
|
|
+ // 检查是否已标记为可显示按钮(从右边列表选择、上次供应商或下面供应商填充)
|
|
|
+ // 只要标记为可显示,就显示按钮(即使后来点击取消清空了输入框)
|
|
|
+ const canShowButton = assignedFromRightList.value[orderId];
|
|
|
+ return canShowButton === true;
|
|
|
+}
|
|
|
+
|
|
|
+// 处理订单选中
|
|
|
+function handleOrderSelect(order: OrderModel) {
|
|
|
+ const orderId = getOrderId(order);
|
|
|
+
|
|
|
+ if (selectedOrderId.value === orderId) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ selectedOrderId.value = orderId;
|
|
|
+
|
|
|
+ loadInstitutionList();
|
|
|
+}
|
|
|
+
|
|
|
+// 处理查询
|
|
|
+async function handleSearch() {
|
|
|
+ // 重置到第一页
|
|
|
+ orderPageConfig.currentPage = 1;
|
|
|
+ await loadOrderList();
|
|
|
+}
|
|
|
+
|
|
|
+// 处理重置
|
|
|
+function handleReset() {
|
|
|
+ searchForm.customerName = '';
|
|
|
+ searchForm.contactPhone = '';
|
|
|
+ searchForm.orderTypes = [];
|
|
|
+ orderPageConfig.currentPage = 1;
|
|
|
+ loadOrderList();
|
|
|
+}
|
|
|
+
|
|
|
+// 处理订单列表分页变化
|
|
|
+function handleOrderPageChange({ page, pageSize }: { page?: number; pageSize?: number }) {
|
|
|
+ if (page !== undefined) {
|
|
|
+ orderPageConfig.currentPage = page;
|
|
|
+ }
|
|
|
+ if (pageSize !== undefined) {
|
|
|
+ orderPageConfig.pageSize = pageSize;
|
|
|
+ }
|
|
|
+ loadOrderList();
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+// 获取订单数量
|
|
|
+const pendingCount = computed(() => {
|
|
|
+ return props.orderType === 'offline'
|
|
|
+ ? orderCounts.value.pendPieOfflineOrderCount
|
|
|
+ : orderCounts.value.pendPieOnlineOrderCount;
|
|
|
+});
|
|
|
+const todayCount = computed(() => {
|
|
|
+ return props.orderType === 'offline'
|
|
|
+ ? orderCounts.value.todayPieOfflineOrderCount
|
|
|
+ : orderCounts.value.todayPieOnlineOrderCount;
|
|
|
+});
|
|
|
+const allCount = computed(() => {
|
|
|
+ return props.orderType === 'offline'
|
|
|
+ ? orderCounts.value.allPieOfflineOrderCount
|
|
|
+ : orderCounts.value.allPieOnlineOrderCount;
|
|
|
+});
|
|
|
+
|
|
|
+// 加载订单计数
|
|
|
+async function loadOrderCounts() {
|
|
|
+ try {
|
|
|
+ const response: any = await getPieOrderCountMethod();
|
|
|
+ const data = response?.data ?? response;
|
|
|
+
|
|
|
+ orderCounts.value = {
|
|
|
+ // offlineCount: Number(data?.offlineCount) || 0, // 线下服务订单总和
|
|
|
+ offlineCount: Number(data?.pendPieOfflineOrderCount) || 0,
|
|
|
+ allPieOfflineOrderCount: Number(data?.allPieOfflineOrderCount) || 0, // 线下服务——全部指派订单数量
|
|
|
+ pendPieOfflineOrderCount: Number(data?.pendPieOfflineOrderCount) || 0, // 线下服务——待指派订单数量
|
|
|
+ todayPieOfflineOrderCount: Number(data?.todayPieOfflineOrderCount) || 0, // 线下服务——今日指派订单数量
|
|
|
+ // onlineCount: Number(data?.onlineCount) || 0, // 实体商品数量订单总和
|
|
|
+ onlineCount: Number(data?.pendPieOnlineOrderCount) || 0, // 实体商品数量订单总和
|
|
|
+ allPieOnlineOrderCount: Number(data?.allPieOnlineOrderCount) || 0, // 实体商品——全部指派订单数量
|
|
|
+ pendPieOnlineOrderCount: Number(data?.pendPieOnlineOrderCount) || 0, // 实体商品——待指派订单数量
|
|
|
+ todayPieOnlineOrderCount: Number(data?.todayPieOnlineOrderCount) || 0, // 实体商品——今日指派订单数量
|
|
|
+ };
|
|
|
+ } catch (error) {
|
|
|
+ // 失败时使用默认值
|
|
|
+ orderCounts.value = {
|
|
|
+ offlineCount: 0, // 线下服务数量
|
|
|
+ allPieOfflineOrderCount: 0, // 线下服务——全部指派订单数量
|
|
|
+ pendPieOfflineOrderCount: 0, // 线下服务——待指派订单数量
|
|
|
+ todayPieOfflineOrderCount: 0, // 线下服务——今日指派订单数量
|
|
|
+ onlineCount: 0, // 实体商品数量
|
|
|
+ allPieOnlineOrderCount: 0, // 实体商品——全部指派订单数量
|
|
|
+ pendPieOnlineOrderCount: 0, // 实体商品——待指派订单数量
|
|
|
+ todayPieOnlineOrderCount: 0, // 实体商品——今日指派订单数量
|
|
|
+ };
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 计算总订单数(直接使用后端返回的总和)
|
|
|
+const offlineCount = computed(() => {
|
|
|
+ return orderCounts.value.offlineCount;
|
|
|
+});
|
|
|
+const onlineCount = computed(() => {
|
|
|
+ return orderCounts.value.onlineCount;
|
|
|
+});
|
|
|
+
|
|
|
+// 暴露方法给父组件
|
|
|
+defineExpose({
|
|
|
+ offlineCount,
|
|
|
+ onlineCount,
|
|
|
+ refresh: () => {
|
|
|
+ loadOrderList();
|
|
|
+ loadInstitutionList();
|
|
|
+ },
|
|
|
+});
|
|
|
+</script>
|
|
|
+
|
|
|
+<template>
|
|
|
+ <div class="dispatch-order-panel">
|
|
|
+ <!-- 主要内容区域 -->
|
|
|
+ <div class="main-content">
|
|
|
+ <!-- 左侧区域 -->
|
|
|
+ <div class="left-panel">
|
|
|
+ <!-- 子tab -->
|
|
|
+ <div class="sub-tabs">
|
|
|
+ <div class="sub-tab" :class="{ active: activeSubTab === 'pending' }" @click="activeSubTab = 'pending'">
|
|
|
+ 待指派订单 ({{ pendingCount }})
|
|
|
+ </div>
|
|
|
+ <div class="sub-tab" :class="{ active: activeSubTab === 'today' }" @click="activeSubTab = 'today'">
|
|
|
+ 今日指派订单 ({{ todayCount }})
|
|
|
+ </div>
|
|
|
+ <div class="sub-tab" :class="{ active: activeSubTab === 'all' }" @click="activeSubTab = 'all'">
|
|
|
+ 全部指派订单 ({{ allCount }})
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 订单列表 -->
|
|
|
+ <div class="order-list-panel">
|
|
|
+ <!-- 固定头部区域 -->
|
|
|
+ <div class="order-list-header">
|
|
|
+ <!-- 警告 - 今日指派和全部指派都显示 -->
|
|
|
+ <div v-if="(activeSubTab === 'today' || activeSubTab === 'all')" class="warning-banner">
|
|
|
+ <span class="warning-text">
|
|
|
+ {{ props.orderType === 'physical' ? '已发货不能修改' : '距离开始时间1小时内,修改请先联系商家和客户' }}
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ <!-- 搜索 - 仅全部指派时显示 -->
|
|
|
+ <div v-if="activeSubTab === 'all'" class="search-section">
|
|
|
+ <div class="search-form">
|
|
|
+ <div class="search-item">
|
|
|
+ <span class="search-label">客户姓名:</span>
|
|
|
+ <a-input v-model:value="searchForm.customerName" placeholder="请输入" class="search-input" />
|
|
|
+ </div>
|
|
|
+ <div class="search-item">
|
|
|
+ <span class="search-label">联系电话:</span>
|
|
|
+ <a-input v-model:value="searchForm.contactPhone" placeholder="请输入" class="search-input" />
|
|
|
+ </div>
|
|
|
+ <div class="search-item">
|
|
|
+ <span class="search-label">订单类型:</span>
|
|
|
+ <a-checkbox-group v-model:value="searchForm.orderTypes" class="checkbox-group">
|
|
|
+ <!-- 线下服务显示:已预约、已核销 -->
|
|
|
+ <template v-if="props.orderType === 'offline'">
|
|
|
+ <a-checkbox value="booked">已预约</a-checkbox>
|
|
|
+ <a-checkbox value="verified">已核销</a-checkbox>
|
|
|
+ </template>
|
|
|
+ <!-- 实体商品显示:待发货、已发货 -->
|
|
|
+ <template v-else-if="props.orderType === 'physical'">
|
|
|
+ <a-checkbox value="pendingShip">待发货</a-checkbox>
|
|
|
+ <a-checkbox value="shipped">已发货</a-checkbox>
|
|
|
+ </template>
|
|
|
+ </a-checkbox-group>
|
|
|
+ </div>
|
|
|
+ <div class="search-actions">
|
|
|
+ <a-button type="primary" @click="handleSearch">查询</a-button>
|
|
|
+ <a-button @click="handleReset">重置</a-button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 可滚动的订单列表内容 -->
|
|
|
+ <div class="order-list-content">
|
|
|
+
|
|
|
+ <div v-if="loading" class="loading">加载中...</div>
|
|
|
+ <div v-else-if="orderList.length === 0" class="empty">暂无订单</div>
|
|
|
+ <template v-else>
|
|
|
+ <div v-for="(order, index) in orderList" :key="getOrderId(order)" class="order-card"
|
|
|
+ :class="{ active: isOrderSelected(order) }" @click="handleOrderSelect(order)">
|
|
|
+ <!-- 已核销/已发货印章 -->
|
|
|
+ <!-- 线下服务:已核销状态显示已核销图标 -->
|
|
|
+ <!-- 实体商品:已发货状态显示已发货图标 -->
|
|
|
+ <div v-if="isOrderVerified(order) || isOrderShipped(order)" class="verified-badge">
|
|
|
+ <img v-if="isOrderVerified(order)" src="@/assets/images/verify.png" alt="已核销"
|
|
|
+ style="width: 100px; height: 100px;" />
|
|
|
+ <img v-else-if="isOrderShipped(order)" src="@/assets/images/shipment.png" alt="已发货"
|
|
|
+ style="width: 100px; height: 100px;" />
|
|
|
+ </div>
|
|
|
+ <!-- 左侧:图钉图标和数字 -->
|
|
|
+ <div class="order-header-left">
|
|
|
+ <div class="pin-icon-wrapper">
|
|
|
+ <div class="pin-icon-inner">
|
|
|
+ <span class="pin-icon-number">{{ index + 1 }}</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <span class="service-name">
|
|
|
+ {{ order.conditioningProgramName }}
|
|
|
+ <span v-if="order.conditioningProgramType">({{ order.conditioningProgramType }})</span>
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 右侧:日期和时间按钮(仅线下服务显示) -->
|
|
|
+ <div v-if="props.orderType === 'offline'" class="order-header-right">
|
|
|
+ <!-- 日期按钮 -->
|
|
|
+ <div v-if="isOrderVerified(order) || isOrderShipped(order)"
|
|
|
+ class="date-display-btn date-display-btn-disabled">
|
|
|
+ {{ order.arrangeDate || '--' }}
|
|
|
+ </div>
|
|
|
+ <template v-else>
|
|
|
+ <div class="date-display-btn" @click.stop="handleDateBtnClick(getOrderId(order))">
|
|
|
+ {{ order.arrangeDate || '选择日期' }}
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+
|
|
|
+ <!-- 分隔符 -->
|
|
|
+ <span class="separator-dash">-</span>
|
|
|
+
|
|
|
+ <!-- 时间范围按钮 -->
|
|
|
+ <div v-if="isOrderVerified(order) || isOrderShipped(order)"
|
|
|
+ class="time-range-display-btn time-range-display-btn-disabled">
|
|
|
+ <template v-if="order.arrangeTime">
|
|
|
+ {{ order.arrangeTime }}
|
|
|
+ <template v-if="calculateEndTime(order)">-{{ calculateEndTime(order) }}</template>
|
|
|
+ </template>
|
|
|
+ <template v-else>--</template>
|
|
|
+ </div>
|
|
|
+ <div v-else class="time-range-display-btn" @click.stop="handleTimeBtnClick(getOrderId(order))">
|
|
|
+ <template v-if="order.arrangeTime">
|
|
|
+ {{ order.arrangeTime }}
|
|
|
+ <template v-if="calculateEndTime(order)">-{{ calculateEndTime(order) }}</template>
|
|
|
+ </template>
|
|
|
+ <template v-else>选择时间</template>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 客户信息 -->
|
|
|
+ <div class="customer-info">
|
|
|
+ {{ formatCustomerInfo(order) }}
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 分配机构 -->
|
|
|
+ <div class="assign-section">
|
|
|
+ <span class="assign-label">分配机构:</span>
|
|
|
+ <a-input
|
|
|
+ :value="getAssignedInstitutionDisplayValue(order)"
|
|
|
+ placeholder="请点击右侧指派按钮或点击上次/供应商选择分配机构" class="assign-input" readonly
|
|
|
+ :disabled="isOrderVerified(order) || isOrderShipped(order)" />
|
|
|
+ <a-button
|
|
|
+ v-if="shouldShowConfirmAssignButton(order)"
|
|
|
+ type="default"
|
|
|
+ size="small"
|
|
|
+ @click.stop="handleCancel(getOrderId(order))">
|
|
|
+ 取消
|
|
|
+ </a-button>
|
|
|
+ <a-button v-if="shouldShowConfirmAssignButton(order)" type="primary" size="small"
|
|
|
+ @click.stop="handleConfirmAssign(getOrderId(order))">
|
|
|
+ 确认指派
|
|
|
+ </a-button>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 上次机构和供应商 -->
|
|
|
+ <div class="institution-options">
|
|
|
+ <div v-if="order.preConditioningProgramSupplierName" class="institution-option"
|
|
|
+ :class="{ disabled: isOrderVerified(order) || isOrderShipped(order) }"
|
|
|
+ @click.stop="!(isOrderVerified(order) || isOrderShipped(order)) && handleLastInstitutionClick(getOrderId(order), order.preConditioningProgramSupplierName)">
|
|
|
+ 上次:{{ order.preConditioningProgramSupplierName }}
|
|
|
+ </div>
|
|
|
+ <div v-if="order.conditioningProgramSupplierName" class="institution-option"
|
|
|
+ :class="{ disabled: isOrderVerified(order) || isOrderShipped(order) }"
|
|
|
+ @click.stop="!(isOrderVerified(order) || isOrderShipped(order)) && handleSupplierClick(getOrderId(order), order.conditioningProgramSupplierName)">
|
|
|
+ 供应商:{{ order.conditioningProgramSupplierName }}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 订单信息 -->
|
|
|
+ <div class="order-info">
|
|
|
+ <div class="order-info-item" v-if="order.orderNo">订单编号: {{ order.orderNo }}</div>
|
|
|
+ <div class="order-info-item" v-if="order.applyTime">用户操作时间: {{ order.applyTime }}</div>
|
|
|
+ <div class="order-info-item" v-if="activeSubTab !== 'pending' && order.pieBy">派单员: {{ order.pieBy }}
|
|
|
+ </div>
|
|
|
+ <div class="order-info-item" v-if="order.offlineDuration">服务时长: {{ order.offlineDuration }}分钟</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 订单列表分页器 -->
|
|
|
+ <div class="order-pager-wrapper">
|
|
|
+ <vxe-pager v-model:current-page="orderPageConfig.currentPage" v-model:page-size="orderPageConfig.pageSize"
|
|
|
+ :total="orderPageConfig.total"
|
|
|
+ :layouts="['PrevJump', 'PrevPage', 'Number', 'NextPage', 'NextJump', 'Sizes', 'FullJump', 'Total']"
|
|
|
+ @page-change="handleOrderPageChange" />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 右侧可派单机构列表 -->
|
|
|
+ <div class="institution-panel">
|
|
|
+ <div class="panel-title">可派单机构</div>
|
|
|
+ <div class="institution-grid-wrapper">
|
|
|
+ <vxe-grid ref="gridRef" v-bind="gridOptions" :loading="institutionLoading" />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 日期时间选择弹窗 -->
|
|
|
+ <DateTimePicker v-model:visible="dateTimePickerVisible" :picker-type="pickerType"
|
|
|
+ :initial-date="currentPickerOrderId ? (orderList.find(o => getOrderId(o) === currentPickerOrderId)?.arrangeDate || null) : null"
|
|
|
+ :initial-time="currentPickerOrderId ? (orderList.find(o => getOrderId(o) === currentPickerOrderId)?.arrangeTime || null) : null"
|
|
|
+ :is-from-time-button="isFromTimeButton" @date-select="handleDateTimeDateSelect"
|
|
|
+ @time-select="handleDateTimeTimeSelect" @date-confirm="handleDateTimeDateConfirm" @close="handleDateTimeClose">
|
|
|
+ </DateTimePicker>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<style scoped lang="scss">
|
|
|
+.dispatch-order-panel {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ height: 100%;
|
|
|
+ min-height: 0;
|
|
|
+
|
|
|
+ .main-content {
|
|
|
+ display: flex;
|
|
|
+ gap: 16px;
|
|
|
+ flex: 1;
|
|
|
+ min-height: 0;
|
|
|
+ overflow: hidden;
|
|
|
+ align-items: stretch;
|
|
|
+
|
|
|
+ .left-panel {
|
|
|
+ flex: 1;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ min-height: 0;
|
|
|
+ overflow: hidden;
|
|
|
+ border: 1px solid #8bc34a;
|
|
|
+
|
|
|
+ .sub-tabs {
|
|
|
+ display: flex;
|
|
|
+ gap: 0;
|
|
|
+ margin-bottom: 0;
|
|
|
+ background: #8bc34a;
|
|
|
+ padding: 0;
|
|
|
+ align-items: flex-end;
|
|
|
+ box-sizing: border-box;
|
|
|
+
|
|
|
+ .sub-tab {
|
|
|
+ padding: 12px 20px;
|
|
|
+ cursor: pointer;
|
|
|
+ color: #fff;
|
|
|
+ background: #8bc34a;
|
|
|
+ border: none;
|
|
|
+ border-bottom: 3px solid transparent;
|
|
|
+ transition: all 0.3s;
|
|
|
+ font-size: 14px;
|
|
|
+ position: relative;
|
|
|
+ white-space: nowrap;
|
|
|
+
|
|
|
+ &.active {
|
|
|
+ background: #fff;
|
|
|
+ color: #52c41a;
|
|
|
+ border-bottom-color: #52c41a;
|
|
|
+ border-bottom-width: 3px;
|
|
|
+ font-weight: 500;
|
|
|
+ }
|
|
|
+
|
|
|
+ &:hover:not(.active) {
|
|
|
+ background: #a5d6a7;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .order-list-panel {
|
|
|
+ flex: 1;
|
|
|
+ min-height: 0;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ overflow: hidden;
|
|
|
+
|
|
|
+
|
|
|
+ .order-list-header {
|
|
|
+ flex-shrink: 0;
|
|
|
+ padding: 16px;
|
|
|
+ padding-bottom: 0;
|
|
|
+ background: #fff;
|
|
|
+
|
|
|
+ .warning-banner {
|
|
|
+ margin-bottom: 16px;
|
|
|
+ border-radius: 4px;
|
|
|
+
|
|
|
+ .warning-text {
|
|
|
+ color: #ff4d4f;
|
|
|
+ font-size: 14px;
|
|
|
+ font-weight: 500;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .search-section {
|
|
|
+ margin-bottom: 16px;
|
|
|
+ padding: 16px;
|
|
|
+ background: #fff;
|
|
|
+ border: 1px solid #e8e8e8;
|
|
|
+ border-radius: 4px;
|
|
|
+
|
|
|
+ .search-form {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 16px;
|
|
|
+ flex-wrap: wrap;
|
|
|
+
|
|
|
+ .search-item {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 8px;
|
|
|
+
|
|
|
+ .search-label {
|
|
|
+ white-space: nowrap;
|
|
|
+ font-size: 14px;
|
|
|
+ color: #333;
|
|
|
+ }
|
|
|
+
|
|
|
+ .search-input {
|
|
|
+ width: 180px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .checkbox-group {
|
|
|
+ display: flex;
|
|
|
+ gap: 8px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .search-actions {
|
|
|
+ display: flex;
|
|
|
+ gap: 8px;
|
|
|
+
|
|
|
+ :deep(.ant-btn) {
|
|
|
+ height: 32px;
|
|
|
+ padding: 4px 15px;
|
|
|
+ font-size: 14px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 可滚动的订单列表内容
|
|
|
+ .order-list-content {
|
|
|
+ flex: 1;
|
|
|
+ min-height: 0;
|
|
|
+ overflow-y: auto;
|
|
|
+ overflow-x: hidden;
|
|
|
+ padding: 16px;
|
|
|
+ padding-top: 0;
|
|
|
+ padding-right: 24px;
|
|
|
+
|
|
|
+ .loading,
|
|
|
+ .empty {
|
|
|
+ text-align: center;
|
|
|
+ padding: 40px;
|
|
|
+ color: #999;
|
|
|
+ }
|
|
|
+
|
|
|
+ .order-card {
|
|
|
+ padding: 16px;
|
|
|
+ margin-bottom: 16px;
|
|
|
+ border: 1px solid #e8e8e8;
|
|
|
+ border-radius: 4px;
|
|
|
+ background: #fff;
|
|
|
+ cursor: pointer;
|
|
|
+ transition: all 0.3s;
|
|
|
+ position: relative;
|
|
|
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.03);
|
|
|
+ overflow: visible;
|
|
|
+
|
|
|
+ &.active {
|
|
|
+ background: rgba(24, 144, 255, 0.08);
|
|
|
+ box-shadow: 0 2px 8px rgba(24, 144, 255, 0.2);
|
|
|
+ }
|
|
|
+
|
|
|
+ .verified-badge {
|
|
|
+ position: absolute;
|
|
|
+ bottom: 35px;
|
|
|
+ right: 16px;
|
|
|
+ width: 100px;
|
|
|
+ height: 100px;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ pointer-events: none;
|
|
|
+ z-index: 1;
|
|
|
+
|
|
|
+ &::before {
|
|
|
+ content: '';
|
|
|
+ position: absolute;
|
|
|
+ top: 0;
|
|
|
+ left: 0;
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ }
|
|
|
+
|
|
|
+ &::after {
|
|
|
+ content: '';
|
|
|
+ position: absolute;
|
|
|
+ top: 12px;
|
|
|
+ left: 12px;
|
|
|
+ width: calc(100% - 24px);
|
|
|
+ height: calc(100% - 24px);
|
|
|
+ }
|
|
|
+
|
|
|
+ span {
|
|
|
+ position: relative;
|
|
|
+ z-index: 1;
|
|
|
+ display: inline-block;
|
|
|
+
|
|
|
+ &::before {
|
|
|
+ content: '';
|
|
|
+ position: absolute;
|
|
|
+ top: 50%;
|
|
|
+ left: 50%;
|
|
|
+ transform: translate(-50%, -50%);
|
|
|
+ width: 50px;
|
|
|
+ height: 50px;
|
|
|
+ border: 1px solid #5a9fe3;
|
|
|
+ border-radius: 50%;
|
|
|
+ opacity: 0.6;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .order-header-left {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 12px;
|
|
|
+ margin-bottom: 12px;
|
|
|
+
|
|
|
+ .pin-icon-wrapper {
|
|
|
+ width: 24px;
|
|
|
+ height: 32px;
|
|
|
+ position: relative;
|
|
|
+
|
|
|
+ .pin-icon-inner {
|
|
|
+ position: absolute;
|
|
|
+ top: 0;
|
|
|
+ left: 0;
|
|
|
+ width: 25px;
|
|
|
+ height: 25px;
|
|
|
+ transform: rotate(-45deg);
|
|
|
+ transform-origin: 15px 14px;
|
|
|
+
|
|
|
+
|
|
|
+ &::before {
|
|
|
+ content: '';
|
|
|
+ position: absolute;
|
|
|
+ width: 25px;
|
|
|
+ height: 25px;
|
|
|
+ background: #5a9fe3;
|
|
|
+ border-radius: 50% 50% 50% 0;
|
|
|
+ top: 0;
|
|
|
+ left: 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ .pin-icon-number {
|
|
|
+ position: absolute;
|
|
|
+ top: 50%;
|
|
|
+ left: 50%;
|
|
|
+ transform: translate(-50%, -50%) rotate(45deg);
|
|
|
+ z-index: 1;
|
|
|
+ color: #fff;
|
|
|
+ font-size: 12px;
|
|
|
+ font-weight: 600;
|
|
|
+ line-height: 1;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ width: 16px;
|
|
|
+ height: 16px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ &::after {
|
|
|
+ content: '';
|
|
|
+ position: absolute;
|
|
|
+ width: 0;
|
|
|
+ height: 0;
|
|
|
+ border-left: 4px solid transparent;
|
|
|
+ border-right: 4px solid transparent;
|
|
|
+ border-top: 6px solid #5a9fe3;
|
|
|
+ bottom: 0;
|
|
|
+ left: 50%;
|
|
|
+ transform: translateX(-50%);
|
|
|
+ z-index: 0;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .service-name {
|
|
|
+ font-size: 16px;
|
|
|
+ font-weight: 500;
|
|
|
+ color: #333;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .order-header-right {
|
|
|
+ position: absolute;
|
|
|
+ top: 16px;
|
|
|
+ right: 16px;
|
|
|
+ display: flex;
|
|
|
+ gap: 8px;
|
|
|
+ align-items: center;
|
|
|
+ z-index: 10;
|
|
|
+ visibility: visible;
|
|
|
+ opacity: 1;
|
|
|
+
|
|
|
+
|
|
|
+ .date-display-btn {
|
|
|
+ background: #8bc34a;
|
|
|
+ color: #fff;
|
|
|
+ padding: 4px 11px;
|
|
|
+ border-radius: 4px;
|
|
|
+ font-size: 14px;
|
|
|
+ line-height: 1.5715;
|
|
|
+ cursor: pointer;
|
|
|
+ user-select: none;
|
|
|
+ white-space: nowrap;
|
|
|
+ min-width: 110px;
|
|
|
+ text-align: center;
|
|
|
+ transition: background-color 0.3s;
|
|
|
+
|
|
|
+ &:hover {
|
|
|
+ background: #7cb342;
|
|
|
+ }
|
|
|
+
|
|
|
+ &.date-display-btn-disabled {
|
|
|
+ cursor: default;
|
|
|
+ opacity: 0.8;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ .separator-dash {
|
|
|
+ color: #999;
|
|
|
+ font-size: 14px;
|
|
|
+ margin: 0 4px;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ .time-range-display-btn {
|
|
|
+ background: #8bc34a;
|
|
|
+ color: #fff;
|
|
|
+ padding: 4px 11px;
|
|
|
+ border-radius: 4px;
|
|
|
+ font-size: 14px;
|
|
|
+ line-height: 1.5715;
|
|
|
+ cursor: pointer;
|
|
|
+ user-select: none;
|
|
|
+ white-space: nowrap;
|
|
|
+ min-width: 100px;
|
|
|
+ text-align: center;
|
|
|
+ transition: background-color 0.3s;
|
|
|
+
|
|
|
+ &:hover {
|
|
|
+ background: #7cb342;
|
|
|
+ }
|
|
|
+
|
|
|
+ &.time-range-display-btn-disabled {
|
|
|
+ cursor: default;
|
|
|
+ opacity: 0.8;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ .hidden-date-picker,
|
|
|
+ .hidden-time-picker {
|
|
|
+ position: absolute;
|
|
|
+ opacity: 0;
|
|
|
+ pointer-events: none;
|
|
|
+ width: 0;
|
|
|
+ height: 0;
|
|
|
+ overflow: hidden;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .customer-info {
|
|
|
+ font-size: 14px;
|
|
|
+ color: #5da1f9;
|
|
|
+ margin-bottom: 12px;
|
|
|
+ line-height: 1.6;
|
|
|
+ }
|
|
|
+
|
|
|
+ .assign-section {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 8px;
|
|
|
+ margin-bottom: 8px;
|
|
|
+ flex-wrap: wrap;
|
|
|
+
|
|
|
+ .assign-label {
|
|
|
+ white-space: nowrap;
|
|
|
+ color: #333;
|
|
|
+ font-size: 14px;
|
|
|
+ margin-right: 4px;
|
|
|
+ width: 72px;
|
|
|
+ flex-shrink: 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ .assign-input {
|
|
|
+ flex: 1;
|
|
|
+ min-width: 200px;
|
|
|
+ max-width: 300px;
|
|
|
+
|
|
|
+ :deep(.ant-input) {
|
|
|
+ border-color: #52c41a;
|
|
|
+ cursor: default;
|
|
|
+
|
|
|
+ &[readonly] {
|
|
|
+ background-color: #fafafa;
|
|
|
+ cursor: not-allowed;
|
|
|
+ }
|
|
|
+
|
|
|
+ &:disabled {
|
|
|
+ background-color: #f5f5f5;
|
|
|
+ cursor: not-allowed;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ :deep(.ant-btn) {
|
|
|
+ height: 32px;
|
|
|
+ padding: 4px 15px;
|
|
|
+ font-size: 14px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .assigned-institution {
|
|
|
+ font-size: 14px;
|
|
|
+ color: #666;
|
|
|
+ margin-bottom: 12px;
|
|
|
+ padding-left: 80px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .institution-options {
|
|
|
+ display: flex;
|
|
|
+ gap: 12px;
|
|
|
+ margin-bottom: 12px;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ margin-left: 85px;
|
|
|
+
|
|
|
+ .institution-option {
|
|
|
+ padding: 4px 12px;
|
|
|
+ border-radius: 4px;
|
|
|
+ font-size: 13px;
|
|
|
+ color: #5da1f9;
|
|
|
+ cursor: pointer;
|
|
|
+ transition: all 0.3s;
|
|
|
+
|
|
|
+ &.disabled {
|
|
|
+ opacity: 0.5;
|
|
|
+ cursor: not-allowed;
|
|
|
+ color: #999;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .order-info {
|
|
|
+ font-size: 13px;
|
|
|
+ color: black;
|
|
|
+ line-height: 1.8;
|
|
|
+ display: flex;
|
|
|
+
|
|
|
+ .order-info-item {
|
|
|
+ margin-right: 30px;
|
|
|
+ }
|
|
|
+
|
|
|
+ div {
|
|
|
+ margin-bottom: 4px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ .order-pager-wrapper {
|
|
|
+ flex-shrink: 0;
|
|
|
+ border-top: 1px solid #e8e8e8;
|
|
|
+ background: #fff;
|
|
|
+ padding: 8px 16px;
|
|
|
+ margin-top: 16px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .institution-panel {
|
|
|
+ flex: 1;
|
|
|
+ min-height: 0;
|
|
|
+ border: 1px solid #e8e8e8;
|
|
|
+ border-radius: 4px;
|
|
|
+ background: #fff;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ overflow: hidden;
|
|
|
+
|
|
|
+ .panel-title {
|
|
|
+ padding: 12px 16px;
|
|
|
+ font-size: 16px;
|
|
|
+ font-weight: 500;
|
|
|
+ border-bottom: 1px solid #e8e8e8;
|
|
|
+ background: #5a9fe3;
|
|
|
+ color: white;
|
|
|
+ height: 48px;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ box-sizing: border-box;
|
|
|
+ flex-shrink: 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ .institution-grid-wrapper {
|
|
|
+ flex: 1;
|
|
|
+ min-height: 0;
|
|
|
+ overflow: hidden;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ }
|
|
|
+
|
|
|
+ .institution-pager-wrapper {
|
|
|
+ flex-shrink: 0;
|
|
|
+ border-top: 1px solid #e8e8e8;
|
|
|
+ background: #fff;
|
|
|
+ padding: 8px 16px;
|
|
|
+ }
|
|
|
+
|
|
|
+ :deep(.vxe-grid) {
|
|
|
+ flex: 1;
|
|
|
+ overflow: hidden;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ min-height: 0;
|
|
|
+ height: 100%;
|
|
|
+
|
|
|
+ // 表格主体区域
|
|
|
+ .vxe-table--wrapper {
|
|
|
+ flex: 1;
|
|
|
+ overflow: hidden;
|
|
|
+ min-height: 0;
|
|
|
+ height: 100%;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ }
|
|
|
+
|
|
|
+ .vxe-table {
|
|
|
+ height: 100%;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ }
|
|
|
+
|
|
|
+ .vxe-table--header-wrapper {
|
|
|
+ flex-shrink: 0;
|
|
|
+ position: relative;
|
|
|
+ z-index: 10;
|
|
|
+ }
|
|
|
+
|
|
|
+ .vxe-table--header {
|
|
|
+ background: #fafafa;
|
|
|
+
|
|
|
+ .vxe-header--column {
|
|
|
+ font-weight: 500;
|
|
|
+ color: #333;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .vxe-table--body-wrapper {
|
|
|
+ flex: 1;
|
|
|
+ min-height: 0;
|
|
|
+ position: relative;
|
|
|
+ }
|
|
|
+
|
|
|
+ :deep(.vxe-table--body-wrapper),
|
|
|
+ :deep(.vxe-table--body),
|
|
|
+ :deep(.vxe-table--body-wrapper > *),
|
|
|
+ :deep([class*="scroll"]) {
|
|
|
+ &::-webkit-scrollbar {
|
|
|
+ width: 8px;
|
|
|
+ }
|
|
|
+
|
|
|
+ &::-webkit-scrollbar-thumb {
|
|
|
+ background: #d9d9d9;
|
|
|
+ border-radius: 4px;
|
|
|
+ }
|
|
|
+
|
|
|
+ &::-webkit-scrollbar-thumb:hover {
|
|
|
+ background: #bfbfbf;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .vxe-table--body {
|
|
|
+ .vxe-body--row {
|
|
|
+ &:hover {
|
|
|
+ background: #f5f5f5;
|
|
|
+ }
|
|
|
+
|
|
|
+ .vxe-body--column {
|
|
|
+ padding: 12px 8px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .vxe-button {
|
|
|
+ padding: 4px 15px;
|
|
|
+ height: 35px;
|
|
|
+ font-size: 14px;
|
|
|
+ width: 100px;
|
|
|
+ background: #1890ff;
|
|
|
+ color: white;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+</style>
|