| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844 |
- <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 isOrderCancelPie(order: OrderModel): boolean {
- return props.orderType === 'offline' && order.type === 4;
- }
- // 判断订单是否已发货(实体商品)
- 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: '当日订单数' },
- { field: 'evaluateScore', 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) || isOrderCancelPie(order)" class="verified-badge">
- <img v-if="isOrderVerified(order)" src="@/assets/images/verify.png" alt="已核销"
- style="width: 100px; height: 100px;" />
- <img v-else-if="isOrderCancelPie(order)" src="@/assets/images/cancel.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>
|