| New file |
| | |
| | | <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="20" height="20" viewBox="0 0 20 20"><rect x="0" y="0" width="20" height="20" rx="0" fill="#FFFFFF" fill-opacity="0"/><g><path d="M15.244305,15.529691680541992C16.636475,14.149884680541993,17.5,12.227533380541992,17.5,10.103079780541993C17.5,5.910315480541993,14.135309,2.500030517581992,9.9707899,2.500030517581992C9.5168405,2.500030517581992,9.071764,2.540669441541992,8.6390858,2.6186924005419923L8.8609142,3.848852180541992C9.2207422,3.783966080541992,9.5915866,3.750030480541992,9.9707899,3.750030480541992C13.43245,3.750030480541992,16.25,6.588101380541993,16.25,10.103079780541993C16.25,11.883513480541993,15.526778,13.491476680541993,14.361572,14.644706680541992L14.361572,12.404691680541992L13.111572,12.404691680541992L13.111572,16.154691680541994C13.111572,16.49986968054199,13.391394,16.779691680541994,13.736572,16.779691680541994L17.486572000000002,16.779691680541994L17.486572000000002,15.529691680541992L15.244305,15.529691680541992ZM4.7556951000000005,4.470121380541992L2.514648438,4.470121380541992L2.514648438,3.2201213805419924L6.2646484000000004,3.2201213805419924C6.6098266,3.2201213805419924,6.8896484,3.499943380541992,6.8896484,3.8451213805419924L6.8896484,7.595121380541992L5.6396484000000004,7.595121380541992L5.6396484000000004,5.353899280541992C4.4737275,6.507213580541992,3.75,8.115678280541992,3.75,9.896733280541993C3.75,13.411711680541993,6.5675497,16.24978268054199,10.0292101,16.24978268054199C10.4084134,16.24978268054199,10.7792578,16.215846680541993,11.1390858,16.150960680541992L11.3609142,17.38112068054199C10.928236,17.45914368054199,10.4831595,17.49978268054199,10.0292101,17.49978268054199C5.8646902999999995,17.49978268054199,2.5,14.089497680541992,2.5,9.896733280541993C2.5,7.772280180541992,3.36352563,5.849929080541992,4.7556951000000005,4.470121380541992Z" fill-rule="evenodd" fill="#0068FF" fill-opacity="1"/></g></svg> |
| | |
| | | </script> |
| | | |
| | | <template> |
| | | <div class="w-full h-full relative viewContent roboTaxi" :class="mode=='view' && 'content'"> |
| | | <div class="w-full h-full relative viewContent roboTaxi" :class="mode == 'view' && 'content'"> |
| | | <div class="p-12px"> |
| | | <NForm |
| | | ref="formRef" |
| | |
| | | </NTabPane> |
| | | </NTabs> |
| | | |
| | | <div v-else class="add-tree-container" :class="mode=='view' && 'tree-container'"> |
| | | <div v-else class="add-tree-container" :class="mode == 'view' && 'tree-container'"> |
| | | <NTree |
| | | block-line |
| | | cascade |
| | |
| | | } |
| | | } |
| | | .viewContent{ |
| | | |
| | | |
| | | height: calc(100vh - 76px) !important; |
| | | width: 100%; |
| | | overflow-y: auto; |
| | |
| | | padding: 12px; |
| | | border-radius: 4px; |
| | | |
| | | &::-webkit-scrollbar { |
| | | width: 6px; |
| | | height: 6px; |
| | | } |
| | | &::-webkit-scrollbar-thumb { |
| | | background-color: rgba(0, 0, 0, 0.2); |
| | | border-radius: 3px; |
| | | } |
| | | &::-webkit-scrollbar-track { |
| | | background: transparent; |
| | | } |
| | | |
| | | .n-tree { |
| | | height: 100%; |
| | | .n-empty { |
| | |
| | | } |
| | | .add-tree-container{ |
| | | height: calc(100vh - 374px); |
| | | overflow-y: auto; |
| | | |
| | | &::-webkit-scrollbar { |
| | | width: 6px; |
| | | height: 6px; |
| | | } |
| | | &::-webkit-scrollbar-thumb { |
| | | background-color: rgba(0, 0, 0, 0.2); |
| | | border-radius: 3px; |
| | | } |
| | | &::-webkit-scrollbar-track { |
| | | background: transparent; |
| | | } |
| | | } |
| | | </style> |
| | |
| | | </script> |
| | | |
| | | <template> |
| | | <n-modal v-model:show="modalVisible" :mask-closable="false"> |
| | | <n-card |
| | | :bordered="false" |
| | | :segmented="{ content: true }" |
| | | style="width: 480px; border-radius: 8px; display: flex; flex-direction: column;" |
| | | content-style="padding: 24px; display: flex; flex-direction: column; gap: 24px;" |
| | | footer-style="padding: 16px 24px; border-top: 1px solid rgba(23, 31, 38, 0.08);" |
| | | > |
| | | <template #header> |
| | | <span class="modal-title">{{ modalName }}</span> |
| | | </template> |
| | | <template #header-extra> |
| | | <div class="modal-close" @click="closeModal"> |
| | | × |
| | | </div> |
| | | </template> |
| | | |
| | | <div class="modal-content"> |
| | | <div class="form-row"> |
| | | <span class="form-label">是否豁免费用:</span> |
| | | <n-radio-group v-model:value="formModel.isWaiveFee" name="isWaiveFeeGroup"> |
| | | <n-space :size="32"> |
| | | <n-radio :value="1"> |
| | | 是 |
| | | </n-radio> |
| | | <n-radio :value="0"> |
| | | 否 |
| | | </n-radio> |
| | | </n-space> |
| | | </n-radio-group> |
| | | </div> |
| | | <n-modal |
| | | v-model:show="modalVisible" :mask-closable="false" preset="card" :title="modalName" class="w-400px" |
| | | :segmented="{ |
| | | content: true, |
| | | action: true, |
| | | }" |
| | | > |
| | | <div class="modal-content"> |
| | | <div class="form-row"> |
| | | <span class="form-label">是否豁免费用:</span> |
| | | <n-radio-group v-model:value="formModel.isWaiveFee" name="isWaiveFeeGroup"> |
| | | <n-space :size="16"> |
| | | <n-radio :value="1"> |
| | | 是 |
| | | </n-radio> |
| | | <n-radio :value="0"> |
| | | 否 |
| | | </n-radio> |
| | | </n-space> |
| | | </n-radio-group> |
| | | </div> |
| | | </div> |
| | | |
| | | <template #footer> |
| | | <div class="footer-actions"> |
| | | <n-button class="footer-btn" @click="closeModal"> |
| | | 取消 |
| | | </n-button> |
| | | <n-button type="primary" class="footer-btn" @click="submitModal"> |
| | | 确定 |
| | | </n-button> |
| | | </div> |
| | | </template> |
| | | </n-card> |
| | | <template #action> |
| | | <n-space justify="end"> |
| | | <NButton @click="closeModal"> |
| | | 取消 |
| | | </NButton> |
| | | <NButton type="primary" @click="submitModal"> |
| | | 确定 |
| | | </NButton> |
| | | </n-space> |
| | | </template> |
| | | </n-modal> |
| | | </template> |
| | | |
| | | <style scoped lang="scss"> |
| | | .modal-title { |
| | | font-size: 16px; |
| | | font-weight: 700; |
| | | color: #171f26; |
| | | } |
| | | |
| | | .modal-close { |
| | | font-size: 18px; |
| | | line-height: 1; |
| | | color: rgba(23, 31, 38, 0.6); |
| | | cursor: pointer; |
| | | } |
| | | |
| | | .modal-content { |
| | | padding: 12px 0 24px; |
| | | } |
| | | |
| | | .form-row { |
| | | display: flex; |
| | | align-items: center; |
| | | } |
| | | |
| | | .form-label { |
| | | font-size: 14px; |
| | | color: #171f26; |
| | | margin-right: 24px; |
| | | margin-left: 12px; |
| | | } |
| | | |
| | | .footer-actions { |
| | | display: flex; |
| | | justify-content: flex-end; |
| | | gap: 8px; |
| | | } |
| | | |
| | | .footer-btn { |
| | | width: 84px; |
| | | height: 32px; |
| | | } |
| | | |
| | | :deep(.n-card-header__main) { |
| | | font-size: 16px; |
| | | } |
| | | |
| | | :deep(.n-card-header) { |
| | | padding: 16px 24px; |
| | | border-bottom: 1px solid rgba(23, 31, 38, 0.08); |
| | | } |
| | | </style> |
| | |
| | | <script setup lang="ts"> |
| | | <script setup lang="tsx"> |
| | | import type { DataTableColumns, DataTableRowKey } from 'naive-ui' |
| | | import { useBoolean } from '@/hooks' |
| | | import { getAvailableVehicleList, reassignOrder } from '@/service/api/transaction' |
| | | import refreshIcon from '@/assets/pricingManagement/refresh.svg' |
| | | |
| | | interface Props { |
| | | modalName?: string |
| | | } |
| | | |
| | | interface VehicleItem { |
| | | id: number |
| | | vehicleCode: string |
| | | plateNumber: string |
| | | vehicleModel: string |
| | | fleetName: string |
| | | estimatedArrivalTime: string |
| | | estimatedDistance: string |
| | | } |
| | | |
| | | const { |
| | | modalName = '', |
| | | modalName = '订单改派', |
| | | } = defineProps<Props>() |
| | | |
| | | const emit = defineEmits<{ |
| | |
| | | }>() |
| | | |
| | | const { bool: modalVisible, setTrue: showModal, setFalse: hiddenModal } = useBoolean(false) |
| | | |
| | | const { bool: submitLoading, setTrue: startLoading, setFalse: endLoading } = useBoolean(false) |
| | | |
| | | const formDefault = { |
| | | id: '', |
| | | vehicleId: '', |
| | | orderId: '', |
| | | vehicleId: null as number | null, |
| | | reason: '', |
| | | } |
| | | |
| | | const searchDefault = { |
| | | plateNumber: '', |
| | | fleetName: '', |
| | | } |
| | | |
| | | const formModel = ref({ ...formDefault }) |
| | | const searchModel = ref({ ...searchDefault }) |
| | | const checkedRowKeys = ref<DataTableRowKey[]>([]) |
| | | const vehicleList = ref<VehicleItem[]>([]) |
| | | const tableData = ref<VehicleItem[]>([]) |
| | | |
| | | const columns = computed<DataTableColumns<VehicleItem>>(() => [ |
| | | { |
| | | type: 'selection', |
| | | width: 56, |
| | | }, |
| | | { |
| | | title: '序号', |
| | | key: 'index', |
| | | width: 60, |
| | | render: (_row, index) => index + 1, |
| | | resizable: true, |
| | | }, |
| | | { |
| | | title: '车辆编码', |
| | | key: 'vehicleCode', |
| | | minWidth: 120, |
| | | ellipsis: true, |
| | | resizable: true, |
| | | }, |
| | | { |
| | | title: '车牌号', |
| | | key: 'plateNumber', |
| | | minWidth: 120, |
| | | ellipsis: true, |
| | | resizable: true, |
| | | }, |
| | | { |
| | | title: '型号', |
| | | key: 'vehicleModel', |
| | | minWidth: 80, |
| | | ellipsis: true, |
| | | resizable: true, |
| | | }, |
| | | { |
| | | title: '预计到达出发点时间', |
| | | key: 'estimatedArrivalTime', |
| | | minWidth: 200, |
| | | ellipsis: true, |
| | | resizable: true, |
| | | }, |
| | | { |
| | | title: '预计到达出发点距离', |
| | | key: 'estimatedDistance', |
| | | minWidth: 200, |
| | | ellipsis: true, |
| | | }, |
| | | ]) |
| | | |
| | | function rowKey(row: VehicleItem) { |
| | | return row.id |
| | | } |
| | | |
| | | function handleRowClick(row: VehicleItem) { |
| | | checkedRowKeys.value = [row.id] |
| | | formModel.value.vehicleId = row.id |
| | | } |
| | | |
| | | function getRowProps(row: VehicleItem) { |
| | | return { |
| | | onClick: () => handleRowClick(row), |
| | | } |
| | | } |
| | | |
| | | function applyFilters() { |
| | | const plateNumber = searchModel.value.plateNumber.trim() |
| | | const fleetName = searchModel.value.fleetName.trim() |
| | | |
| | | tableData.value = vehicleList.value.filter((item) => { |
| | | const matchPlateNumber = !plateNumber || item.plateNumber.includes(plateNumber) |
| | | const matchFleetName = !fleetName || item.fleetName.includes(fleetName) |
| | | return matchPlateNumber && matchFleetName |
| | | }) |
| | | } |
| | | |
| | | async function getList(orderId: number) { |
| | | const res = await getAvailableVehicleList({ orderId, orderType: 1 }) |
| | | if (res.code === 200) { |
| | | vehicleList.value = res.data.map((item: any) => ({ |
| | | id: item.id, |
| | | vehicleCode: item.vehicleCode || '-', |
| | | plateNumber: item.plateNumber || '-', |
| | | vehicleModel: item.vehicleModel || '-', |
| | | fleetName: item.fleetName || '-', |
| | | estimatedArrivalTime: item.estimatedArrivalTime || '-', |
| | | estimatedDistance: item.estimatedDistance || '-', |
| | | })) |
| | | } |
| | | } |
| | | |
| | | async function openModal(data: any) { |
| | | emit('open') |
| | | formModel.value = { |
| | | ...formDefault, |
| | | orderId: data?.id ?? '', |
| | | } |
| | | searchModel.value = { ...searchDefault } |
| | | checkedRowKeys.value = [] |
| | | vehicleList.value = [] |
| | | tableData.value = [] |
| | | await getList(data.id) |
| | | applyFilters() |
| | | showModal() |
| | | formModel.value = { ...data } |
| | | } |
| | | |
| | | function closeModal() { |
| | |
| | | emit('close') |
| | | } |
| | | |
| | | defineExpose({ |
| | | openModal, |
| | | }) |
| | | function handleSearch() { |
| | | applyFilters() |
| | | } |
| | | |
| | | const formRef = ref() |
| | | function handleReset() { |
| | | searchModel.value = { ...searchDefault } |
| | | applyFilters() |
| | | } |
| | | |
| | | function handleRefreshRecommend() { |
| | | if (formModel.value.orderId) { |
| | | getList(Number(formModel.value.orderId)).then(() => { |
| | | checkedRowKeys.value = [] |
| | | formModel.value.vehicleId = null |
| | | applyFilters() |
| | | }) |
| | | } |
| | | } |
| | | |
| | | function handleCheck(rowKeys: DataTableRowKey[]) { |
| | | const selectedKey = rowKeys.length > 0 ? Number(rowKeys[rowKeys.length - 1]) : null |
| | | checkedRowKeys.value = selectedKey === null ? [] : [selectedKey] |
| | | formModel.value.vehicleId = selectedKey |
| | | } |
| | | |
| | | async function submitModal() { |
| | | await formRef.value?.validate() |
| | | if (!formModel.value.vehicleId) { |
| | | window.$message.warning('请选择改派车辆') |
| | | return |
| | | } |
| | | |
| | | if (!formModel.value.reason.trim()) { |
| | | window.$message.warning('请输入改派原因') |
| | | return |
| | | } |
| | | |
| | | startLoading() |
| | | reassignOrder({ |
| | | orderId: formModel.value.id, |
| | | orderId: formModel.value.orderId, |
| | | vehicleId: formModel.value.vehicleId, |
| | | // reason: formModel.value.reason, // 接口可能需要传递原因,如果是请放开 |
| | | }).then((res: any) => { |
| | | if (res.code === 0) { |
| | | if (res.code === 200) { |
| | | window.$message.success('改派成功') |
| | | emit('refresh') |
| | | closeModal() |
| | |
| | | endLoading() |
| | | }) |
| | | } |
| | | const isElectronicAreaIdOpen = ref(false) |
| | | const rules = { |
| | | vehicleId: { |
| | | required: true, |
| | | message: '请输入名称', |
| | | trigger: 'blur', |
| | | }, |
| | | } |
| | | |
| | | const options = ref() |
| | | |
| | | async function getList(orderId: number) { |
| | | const res = await getAvailableVehicleList({orderId,orderType:1}) |
| | | if (res.code === 200) { |
| | | options.value = res.data.map((item: any) => ({ |
| | | label: item.plateNumber, |
| | | value: item.id, |
| | | })) |
| | | } |
| | | } |
| | | defineExpose({ |
| | | openModal, |
| | | }) |
| | | </script> |
| | | |
| | | <template> |
| | | <n-modal |
| | | v-model:show="modalVisible" :mask-closable="false" preset="card" :title="modalName" class="w-400px" |
| | | v-model:show="modalVisible" :mask-closable="false" preset="card" :title="modalName" class="w-1000px" |
| | | :segmented="{ |
| | | content: true, |
| | | action: true, |
| | | }" |
| | | > |
| | | <n-form ref="formRef" :rules="rules" label-placement="top" :model="formModel" require-mark-placement="left"> |
| | | <n-form-item :span="1" label="改派车辆" path="vehicleId"> |
| | | <n-select v-model:value="formModel.vehicleId" :options="options" @update:show="(val: any) => isElectronicAreaIdOpen = val" > |
| | | <template #arrow> |
| | | <img |
| | | src="https://huishou-1323682843.cos.ap-nanjing.myqcloud.com/images/e120f81c-9f15-4e1a-9938-22252898edf9.png" |
| | | :class="`arrow-icon ${isElectronicAreaIdOpen ? 'arrow-icon--open' : ''}`" |
| | | style="width: 16px; height: 16px" |
| | | alt="arrow" |
| | | /> |
| | | </template> |
| | | <template #empty> |
| | | <div class="flex-center"> |
| | | <img |
| | | src="/src/assets/element-img/no-data.png" |
| | | style="width: 50%" |
| | | alt="no-data" |
| | | /> |
| | | <div class="modal-content"> |
| | | <div class="search-wrapper"> |
| | | <div class="search-item"> |
| | | <div class="search-label"> |
| | | 车牌号 |
| | | </div> |
| | | </template> |
| | | </n-select> |
| | | </n-form-item> |
| | | </n-form> |
| | | <n-input v-model:value="searchModel.plateNumber" placeholder="请输入" clearable /> |
| | | </div> |
| | | <div class="search-item"> |
| | | <div class="search-label"> |
| | | 车队 |
| | | </div> |
| | | <n-input v-model:value="searchModel.fleetName" placeholder="请输入" clearable /> |
| | | </div> |
| | | <div class="search-actions"> |
| | | <n-button class="action-btn" @click="handleReset"> |
| | | 重置 |
| | | </n-button> |
| | | <n-button type="primary" class="action-btn" @click="handleSearch"> |
| | | 查询 |
| | | </n-button> |
| | | </div> |
| | | </div> |
| | | |
| | | <div class="table-section"> |
| | | <div class="table-wrapper"> |
| | | <n-data-table |
| | | v-model:checked-row-keys="checkedRowKeys" |
| | | flex-height |
| | | style="height: 100%" |
| | | :columns="columns" |
| | | :data="tableData" |
| | | :bordered="false" |
| | | :pagination="false" |
| | | :row-key="rowKey" |
| | | :scroll-x="836" |
| | | :row-props="getRowProps" |
| | | @update:checked-row-keys="handleCheck" |
| | | > |
| | | <template #empty> |
| | | <div class="flex-center"> |
| | | <img |
| | | src="/src/assets/element-img/no-data.png" |
| | | style="width: 123px; height: 125px" |
| | | alt="no-data" |
| | | > |
| | | </div> |
| | | </template> |
| | | </n-data-table> |
| | | </div> |
| | | |
| | | <div class="refresh-action" @click="handleRefreshRecommend"> |
| | | <img :src="refreshIcon" alt="refresh-icon" class="refresh-icon"> |
| | | <span>刷新推荐车辆</span> |
| | | </div> |
| | | </div> |
| | | |
| | | <div class="reason-section"> |
| | | <div class="reason-label"> |
| | | 改派原因 |
| | | </div> |
| | | <n-input |
| | | v-model:value="formModel.reason" |
| | | type="textarea" |
| | | placeholder="请输入" |
| | | :maxlength="200" |
| | | show-count |
| | | :autosize="{ minRows: 3, maxRows: 3 }" |
| | | /> |
| | | </div> |
| | | </div> |
| | | |
| | | <template #action> |
| | | <n-space justify="end"> |
| | | <NButton @click="closeModal"> |
| | | 取消 |
| | | </NButton> |
| | | <NButton type="primary" :loading="submitLoading" @click="submitModal"> |
| | | 确认 |
| | | 确定 |
| | | </NButton> |
| | | </n-space> |
| | | </template> |
| | | </n-modal> |
| | | </template> |
| | | |
| | | <style scoped lang="scss"> |
| | | .modal-content { |
| | | display: flex; |
| | | flex-direction: column; |
| | | flex: 1; |
| | | min-height: 0; |
| | | overflow: hidden; |
| | | } |
| | | |
| | | .search-wrapper { |
| | | display: flex; |
| | | align-items: flex-end; |
| | | gap: 24px; |
| | | flex-shrink: 0; |
| | | } |
| | | |
| | | .search-item { |
| | | width: 300px; |
| | | flex-shrink: 0; |
| | | } |
| | | |
| | | .search-label, |
| | | .reason-label { |
| | | margin-bottom: 8px; |
| | | font-size: 13px; |
| | | color: #171f26; |
| | | } |
| | | |
| | | .search-actions { |
| | | display: flex; |
| | | gap: 8px; |
| | | margin-left: auto; |
| | | } |
| | | |
| | | .action-btn, |
| | | .footer-btn { |
| | | width: 84px; |
| | | height: 32px; |
| | | } |
| | | |
| | | .table-section { |
| | | display: flex; |
| | | flex: 1; |
| | | flex-direction: column; |
| | | min-height: 0; |
| | | margin-top: 16px; |
| | | } |
| | | |
| | | .table-wrapper { |
| | | flex: 1; |
| | | min-height: 0; |
| | | border-radius: 4px; |
| | | overflow: hidden; |
| | | } |
| | | |
| | | .refresh-action { |
| | | display: inline-flex; |
| | | align-items: center; |
| | | gap: 8px; |
| | | width: fit-content; |
| | | margin-top: 8px; |
| | | color: #0068ff; |
| | | font-weight: 500; |
| | | cursor: pointer; |
| | | flex-shrink: 0; |
| | | } |
| | | |
| | | .refresh-icon { |
| | | width: 20px; |
| | | height: 20px; |
| | | } |
| | | |
| | | .reason-section { |
| | | margin-top: 16px; |
| | | flex-shrink: 0; |
| | | } |
| | | </style> |
| | |
| | | <script setup lang="tsx"> |
| | | import { computed, onMounted, onUnmounted, ref } from 'vue' |
| | | import type { DataTableColumns, DataTableRowKey } from 'naive-ui' |
| | | import { useBoolean } from '@/hooks' |
| | | import { cancelAndCloseOrder, deleteOrder, exportOrderExcel, getFleetList, getOrderList } from '@/service/api/transaction' |
| | |
| | | import SearchPanel from '@/components/custom/SearchPanel.vue' |
| | | import dayjs from 'dayjs' |
| | | import { downloadFile } from '@/utils' |
| | | // import TableModal from './components/TableModal.vue' |
| | | import ReassignModal from './components/ReassignModal.vue' |
| | | import TableModal from './components/TableModal.vue' |
| | | // import ReassignModal from './components/ReassignModal.vue' |
| | | import CloseOrderModal from './components/CloseOrderModal.vue' |
| | | import Modal from './components/Modal.vue' |
| | | |
| | |
| | | return row.id |
| | | } |
| | | |
| | | let timer: any = null |
| | | |
| | | onMounted(() => { |
| | | getList() |
| | | fetchCascaderData() |
| | | fetchFleetList() |
| | | timer = setInterval(() => { |
| | | getList() |
| | | }, 6000) |
| | | }) |
| | | |
| | | onUnmounted(() => { |
| | | if (timer) { |
| | | clearInterval(timer) |
| | | } |
| | | }) |
| | | |
| | | function handleCheck(rowKeys: DataTableRowKey[]) { |
| | |
| | | <NButton @click="handleExport"> |
| | | 批量导出订单 |
| | | </NButton> |
| | | <span style="color: rgba(23, 31, 38, 0.6);font-weight: 400; margin-left: 8px;"> |
| | | 订单列表每60S自动刷新一次 |
| | | </span> |
| | | </div> |
| | | </n-flex> |
| | | </template> |
| | |
| | | /> |
| | | </div> |
| | | </n-card> |
| | | <!-- <TableModal ref="modalRef" modal-name="改派" @refresh="handleSearch" /> --> |
| | | <ReassignModal ref="modalRef" modal-name="订单改派" @refresh="handleSearch" /> |
| | | <TableModal ref="modalRef" modal-name="改派" @refresh="handleSearch" /> |
| | | <!-- <ReassignModal ref="modalRef" modal-name="订单改派" @refresh="handleSearch" /> --> |
| | | <CloseOrderModal ref="closeOrderModalRef" modal-name="关闭订单" @refresh="handleSearch" /> |
| | | <Modal ref="modalRef1" modal-name="改价" @refresh="handleSearch" /> |
| | | </div> |
| | |
| | | NSwitch, |
| | | useDialog, |
| | | useMessage, |
| | | } from "naive-ui"; |
| | | import type { FormInst, FormRules } from "naive-ui"; |
| | | import { addWhitelist, editWhitelist } from "@/service/api/whitelist"; |
| | | } from 'naive-ui' |
| | | import type { FormInst, FormRules } from 'naive-ui' |
| | | import { addWhitelist, editWhitelist } from '@/service/api/whitelist' |
| | | |
| | | interface Props { |
| | | visible?: boolean; |
| | | mode?: "add" | "edit"; |
| | | rowData?: any; |
| | | visible?: boolean |
| | | mode?: 'add' | 'edit' |
| | | rowData?: any |
| | | } |
| | | |
| | | interface Emits { |
| | | (e: "update:visible", visible: boolean): void; |
| | | (e: "success"): void; |
| | | (e: 'update:visible', visible: boolean): void |
| | | (e: 'success'): void |
| | | } |
| | | |
| | | const props = withDefaults(defineProps<Props>(), { |
| | | visible: false, |
| | | mode: "add", |
| | | mode: 'add', |
| | | rowData: null, |
| | | }); |
| | | }) |
| | | |
| | | const emit = defineEmits<Emits>(); |
| | | const message = useMessage(); |
| | | const dialog = useDialog(); |
| | | const emit = defineEmits<Emits>() |
| | | const message = useMessage() |
| | | const dialog = useDialog() |
| | | |
| | | interface FormModel { |
| | | phones: string; |
| | | account: string; |
| | | nickName: string; |
| | | status: boolean; |
| | | isPriorityDispatch: boolean; |
| | | expireDate: string | null; |
| | | maxOrderCount: number | null; |
| | | phones: string |
| | | account: string |
| | | nickName: string |
| | | status: boolean |
| | | isPriorityDispatch: boolean |
| | | expireDate: string | null |
| | | maxOrderCount: number | null |
| | | } |
| | | |
| | | function createDefaultFormModel(): FormModel { |
| | | return { |
| | | phones: "", |
| | | account: "", |
| | | nickName: "", |
| | | phones: '', |
| | | account: '', |
| | | nickName: '', |
| | | status: true, |
| | | isPriorityDispatch: true, |
| | | expireDate: null, |
| | | maxOrderCount: null, |
| | | }; |
| | | } |
| | | } |
| | | |
| | | function formatDateValue(value: unknown) { |
| | | if (value === null || value === undefined || value === "") return null; |
| | | if (value === null || value === undefined || value === '') |
| | | return null |
| | | |
| | | if (typeof value === "string" && /^\d{4}\/\d{2}\/\d{2}$/.test(value.trim())) |
| | | return value.trim(); |
| | | if (typeof value === 'string' && /^\d{4}\/\d{2}\/\d{2}$/.test(value.trim())) |
| | | return value.trim() |
| | | |
| | | const date = new Date(value as string | number | Date); |
| | | if (Number.isNaN(date.getTime())) return null; |
| | | const date = new Date(value as string | number | Date) |
| | | if (Number.isNaN(date.getTime())) |
| | | return null |
| | | |
| | | const year = date.getFullYear(); |
| | | const month = String(date.getMonth() + 1).padStart(2, "0"); |
| | | const day = String(date.getDate()).padStart(2, "0"); |
| | | return `${year}/${month}/${day}`; |
| | | const year = date.getFullYear() |
| | | const month = String(date.getMonth() + 1).padStart(2, '0') |
| | | const day = String(date.getDate()).padStart(2, '0') |
| | | return `${year}/${month}/${day}` |
| | | } |
| | | |
| | | function toOptionalNumber(value: unknown) { |
| | | if (value === null || value === undefined || value === "") return null; |
| | | if (value === null || value === undefined || value === '') |
| | | return null |
| | | |
| | | const n = Number(value); |
| | | return Number.isFinite(n) ? n : null; |
| | | const n = Number(value) |
| | | return Number.isFinite(n) ? n : null |
| | | } |
| | | |
| | | const formRef = ref<FormInst | null>(null); |
| | | const formModel = ref<FormModel>(createDefaultFormModel()); |
| | | const formRef = ref<FormInst | null>(null) |
| | | const formModel = ref<FormModel>(createDefaultFormModel()) |
| | | |
| | | // 表单验证规则 |
| | | const rules: FormRules = { |
| | | phones: [ |
| | | { |
| | | required: true, |
| | | message: "请输入白名单账号", |
| | | trigger: ["blur", "change"], |
| | | message: '请输入白名单账号', |
| | | trigger: ['blur', 'change'], |
| | | }, |
| | | ], |
| | | account: [ |
| | | { |
| | | required: true, |
| | | message: "请输入白名单账号", |
| | | trigger: ["blur", "change"], |
| | | message: '请输入白名单账号', |
| | | trigger: ['blur', 'change'], |
| | | }, |
| | | ], |
| | | }; |
| | | } |
| | | |
| | | function parseAccountsInput(input: string) { |
| | | return input |
| | | .split(/[,\uFF0C\s]+/g) |
| | | .map((s) => s.trim()) |
| | | .filter(Boolean); |
| | | .map(s => s.trim()) |
| | | .filter(Boolean) |
| | | } |
| | | |
| | | watch( |
| | | () => props.visible, |
| | | (val) => { |
| | | if (val) { |
| | | if (props.mode === "edit" && props.rowData) { |
| | | if (props.mode === 'edit' && props.rowData) { |
| | | formModel.value = { |
| | | ...createDefaultFormModel(), |
| | | account: props.rowData.account, |
| | | nickName: props.rowData.nickName, |
| | | status: props.rowData.status === 1 || props.rowData.status === true, |
| | | isPriorityDispatch: |
| | | props.rowData.isPriorityDispatch === 1 || |
| | | props.rowData.isPriorityDispatch === true, |
| | | props.rowData.isPriorityDispatch === 1 |
| | | || props.rowData.isPriorityDispatch === true, |
| | | expireDate: formatDateValue( |
| | | props.rowData.expireDate ?? |
| | | props.rowData.effectiveDate ?? |
| | | props.rowData.validityDate ?? |
| | | props.rowData.expireTime |
| | | props.rowData.expireDate |
| | | ?? props.rowData.effectiveDate |
| | | ?? props.rowData.validityDate |
| | | ?? props.rowData.expireTime, |
| | | ), |
| | | maxOrderCount: toOptionalNumber( |
| | | props.rowData.maxOrderCount ?? |
| | | props.rowData.maxOrderNum ?? |
| | | props.rowData.orderLimitCount |
| | | props.rowData.maxOrderCount |
| | | ?? props.rowData.maxOrderNum |
| | | ?? props.rowData.orderLimitCount, |
| | | ), |
| | | }; |
| | | } else { |
| | | formModel.value = createDefaultFormModel(); |
| | | } |
| | | } |
| | | else { |
| | | formModel.value = createDefaultFormModel() |
| | | } |
| | | } |
| | | } |
| | | ); |
| | | }, |
| | | ) |
| | | |
| | | function closeModal() { |
| | | emit("update:visible", false); |
| | | emit('update:visible', false) |
| | | } |
| | | |
| | | function handleCancel() { |
| | | handleRequestClose(); |
| | | handleRequestClose() |
| | | } |
| | | |
| | | function hasUnsavedInput() { |
| | | if (props.mode !== "add") return false; |
| | | if (props.mode !== 'add') |
| | | return false |
| | | return ( |
| | | parseAccountsInput(formModel.value.phones).length > 0 || |
| | | !!formModel.value.expireDate || |
| | | formModel.value.maxOrderCount !== null || |
| | | formModel.value.isPriorityDispatch !== |
| | | createDefaultFormModel().isPriorityDispatch |
| | | ); |
| | | parseAccountsInput(formModel.value.phones).length > 0 |
| | | || !!formModel.value.expireDate |
| | | || formModel.value.maxOrderCount !== null |
| | | || formModel.value.isPriorityDispatch |
| | | !== createDefaultFormModel().isPriorityDispatch |
| | | ) |
| | | } |
| | | |
| | | function handleRequestClose() { |
| | | if (!hasUnsavedInput()) { |
| | | closeModal(); |
| | | return; |
| | | closeModal() |
| | | return |
| | | } |
| | | |
| | | dialog.warning({ |
| | | title: "确认关闭?", |
| | | content: "当前已输入内容,关闭后将丢失,确定关闭吗?", |
| | | positiveText: "确定关闭", |
| | | negativeText: "继续编辑", |
| | | title: '确认关闭?', |
| | | content: '当前已输入内容,关闭后将丢失,确定关闭吗?', |
| | | positiveText: '确定关闭', |
| | | negativeText: '继续编辑', |
| | | maskClosable: false, |
| | | autoFocus: false, |
| | | onPositiveClick: () => { |
| | | closeModal(); |
| | | closeModal() |
| | | }, |
| | | }); |
| | | }) |
| | | } |
| | | |
| | | async function handleSave() { |
| | | try { |
| | | await formRef.value?.validate(); |
| | | await formRef.value?.validate() |
| | | |
| | | if (props.mode === "add") { |
| | | if (props.mode === 'add') { |
| | | // 新增逻辑 |
| | | const accounts = parseAccountsInput(formModel.value.phones); |
| | | const accounts = parseAccountsInput(formModel.value.phones) |
| | | const res: any = await addWhitelist({ |
| | | account: accounts.join(","), |
| | | account: accounts.join(','), |
| | | status: formModel.value.status ? 1 : 0, |
| | | isPriorityDispatch: formModel.value.isPriorityDispatch ? 1 : 0, |
| | | expireDate: formModel.value.expireDate || undefined, |
| | | maxOrderCount: formModel.value.maxOrderCount ?? undefined, |
| | | }); |
| | | }) |
| | | |
| | | if (res.code === 200) { |
| | | message.success(`${res.msg}`); |
| | | emit("success"); |
| | | closeModal(); |
| | | message.success(`${res.msg}`) |
| | | emit('success') |
| | | closeModal() |
| | | } |
| | | } else { |
| | | } |
| | | else { |
| | | // 编辑逻辑 |
| | | const res = await editWhitelist({ |
| | | id: props.rowData.id, |
| | |
| | | isPriorityDispatch: formModel.value.isPriorityDispatch ? 1 : 0, |
| | | expireDate: formModel.value.expireDate || undefined, |
| | | maxOrderCount: formModel.value.maxOrderCount ?? undefined, |
| | | }); |
| | | }) |
| | | if (res.code === 200) { |
| | | message.success("编辑白名单成功"); |
| | | emit("success"); |
| | | closeModal(); |
| | | message.success('编辑白名单成功') |
| | | emit('success') |
| | | closeModal() |
| | | } |
| | | } |
| | | } catch (error) { |
| | | console.error("表单验证失败:", error); |
| | | } |
| | | catch (error) { |
| | | console.error('表单验证失败:', error) |
| | | } |
| | | } |
| | | </script> |
| | |
| | | preset="card" |
| | | :title="mode === 'add' ? '新增白名单' : '编辑白名单'" |
| | | class="whitelist-modal" |
| | | :style="{ width: '600px' }" |
| | | :style="{ width: '400px' }" |
| | | :segmented="{ |
| | | content: true, |
| | | action: true, |
| | |
| | | v-if="mode === 'edit'" |
| | | style="display: flex; align-items: center; margin-bottom: 16px" |
| | | > |
| | | <span style="margin-right: 12px; font-weight: 500; width: 100px" |
| | | >状态</span |
| | | > |
| | | <span style="margin-right: 12px; font-weight: 500; width: 100px">状态</span> |
| | | <NSwitch v-model:value="formModel.status" /> |
| | | </div> |
| | | |
| | | <div style="display: flex; align-items: center"> |
| | | <span style="margin-right: 12px; font-weight: 500; width: 100px" |
| | | >是否优先派单</span |
| | | > |
| | | <span style="margin-right: 12px; font-weight: 500; width: 100px">是否优先派单</span> |
| | | <NSwitch v-model:value="formModel.isPriorityDispatch" /> |
| | | </div> |
| | | |
| | | <div class="flex align-center justify-between mt-16px"> |
| | | <div class="flex align-center gap-12px mt-16px"> |
| | | <NFormItem label="有效期"> |
| | | <div class="form-item-content"> |
| | | <NDatePicker |
| | |
| | | :actions="[]" |
| | | style="width: 100%" |
| | | /> |
| | | <div class="form-hint">不设置则默认永久有效</div> |
| | | <div class="form-hint"> |
| | | 不设置则默认永久有效 |
| | | </div> |
| | | </div> |
| | | </NFormItem> |
| | | |
| | |
| | | placeholder="请输入最大下单数量" |
| | | style="width: 100%" |
| | | /> |
| | | <div class="form-hint">不设置则默认不限制下单数量</div> |
| | | <div class="form-hint"> |
| | | 不设置则默认不限制下单数量 |
| | | </div> |
| | | </div> |
| | | </NFormItem> |
| | | </div> |
| | |
| | | } |
| | | |
| | | .form-item-content { |
| | | width: 100%; |
| | | width: 170px; |
| | | } |
| | | |
| | | .form-hint { |
| | | margin-top: 6px; |
| | | color: rgba(23, 31, 38, 0.45); |
| | | margin-top: 4px; |
| | | color: rgba(23, 31, 38, 0.6); |
| | | font-size: 12px; |
| | | line-height: 16px; |
| | | } |
| | |
| | | <script setup lang="tsx"> |
| | | import { computed, onMounted, ref } from 'vue' |
| | | import { useRouter } from 'vue-router' |
| | | import SearchPanel from '@/components/custom/SearchPanel.vue' |
| | | import { |
| | | NButton, |
| | | NCard, |
| | | NCascader, |
| | | NDataTable, |
| | | NDatePicker, |
| | |
| | | NGi, |
| | | NInput, |
| | | NSelect, |
| | | NSpace, |
| | | NTabPane, |
| | | NTabs, |
| | | NTag, |
| | | } from 'naive-ui' |
| | | import type { DataTableColumns } from 'naive-ui' |
| | | import { useRouter } from 'vue-router' |
| | | import Pagination from '@/components/common/Pagination.vue' |
| | | import SearchPanel from '@/components/custom/SearchPanel.vue' |
| | | import { getFleetList } from '@/service/api/transaction' |
| | | import { getRegionCascaderNoArea } from '@/service/api/operations' |
| | | |
| | | const router = useRouter() |
| | | import dayjs from 'dayjs' |
| | | |
| | | // 当前激活的 Tab |
| | | const activeTab = ref('basicInfo') |
| | | const activeTab = ref('activityRecord') |
| | | const router = useRouter() |
| | | |
| | | // 基础信息数据 (模拟) |
| | | const basicInfoData = ref({ |
| | | userName: '名称名称名称', |
| | | whitelistAccount: '19282837281', |
| | | userName: '用户名称用户名称用户名称', |
| | | whitelistAccount: '18898765432', |
| | | status: '正常', |
| | | isPriorityDispatch: '否', |
| | | expireDate: '2026年04月09日', |
| | | maxOrderCount: '100', |
| | | isPriorityDispatch: '是', |
| | | expireDate: '2025/09/09', |
| | | maxOrderCount: '1000', |
| | | creator: '杨佩芳', |
| | | createTime: '2025/09/09 11:09:09', |
| | | }) |
| | | |
| | | // 活动参与记录 - 搜索条件 |
| | |
| | | }) |
| | | |
| | | // 状态选项 |
| | | const useStatusOptions = [ |
| | | { label: '可使用', value: 1 }, |
| | | { label: '已使用', value: 2 }, |
| | | { label: '已到期', value: 3 }, |
| | | const useStatusOptions: { type: 'info' | 'success' | 'default', label: string, value: number }[] = [ |
| | | { label: '可使用', value: 1, type: 'info' }, |
| | | { label: '已使用', value: 2, type: 'success' }, |
| | | { label: '已到期', value: 3, type: 'default' }, |
| | | ] |
| | | |
| | | // 下拉框展开状态 |
| | |
| | | const activityPage = ref({ pageNum: 1, pageSize: 10 }) |
| | | const selectedRowKeys = ref<number[]>([]) |
| | | |
| | | // 活动参与记录 - 搜索项配置 |
| | | const activityFilterItems = computed(() => [ |
| | | { |
| | | label: '领取时间', |
| | | path: 'timeRange', |
| | | render: () => <NDatePicker v-model:value={activitySearchModel.value.timeRange} type="daterange" clearable />, |
| | | }, |
| | | { |
| | | label: '所属活动名称', |
| | | path: 'activityName', |
| | | render: () => <NInput v-model:value={activitySearchModel.value.activityName} placeholder="请输入" clearable />, |
| | | }, |
| | | { |
| | | label: '状态', |
| | | path: 'useStatus', |
| | | render: () => ( |
| | | <NSelect |
| | | v-model:value={activitySearchModel.value.useStatus} |
| | | options={useStatusOptions} |
| | | clearable |
| | | placeholder="请选择" |
| | | onUpdate:show={(value: boolean) => isUseStatusSelectOpen.value = value} |
| | | > |
| | | {{ |
| | | arrow: () => ( |
| | | <img |
| | | src="https://huishou-1323682843.cos.ap-nanjing.myqcloud.com/images/e120f81c-9f15-4e1a-9938-22252898edf9.png" |
| | | class={`arrow-icon ${isUseStatusSelectOpen.value ? 'arrow-icon--open' : ''}`} |
| | | style={{ width: '16px', height: '16px' }} |
| | | alt="arrow" |
| | | /> |
| | | ), |
| | | empty: () => ( |
| | | <div class="flex-center"> |
| | | <img |
| | | src="/src/assets/element-img/no-data.png" |
| | | style={{ width: '50%' }} |
| | | /> |
| | | </div> |
| | | ), |
| | | }} |
| | | </NSelect> |
| | | ), |
| | | }, |
| | | ]) |
| | | |
| | | // 活动参与记录 - 表格列定义 |
| | | const activityColumns = [ |
| | | { type: 'selection' as const, width: 50 }, |
| | | { title: '领取时间', key: 'receiveTime', align: 'left' as const, width: 180 }, |
| | | { title: '领取优惠券名称', key: 'couponName', align: 'left' as const, width: 200 }, |
| | | { title: '所属活动名称', key: 'activityName', align: 'left' as const, width: 220 }, |
| | | { title: '领取金额', key: 'amount', align: 'left' as const, width: 120 }, |
| | | { title: '到期时间', key: 'expireTime', align: 'left' as const, width: 180 }, |
| | | const activityColumns = computed<DataTableColumns<any>>(() => [ |
| | | { type: 'selection', width: 50 }, |
| | | { title: '领取时间', key: 'receiveTime', width: 180, resizable: true }, |
| | | { title: '领取优惠券名称', key: 'couponName', width: 200, resizable: true }, |
| | | { title: '所属活动名称', key: 'activityName', width: 220, resizable: true }, |
| | | { title: '领取金额', key: 'amount', width: 120, resizable: true }, |
| | | { title: '到期时间', key: 'expireTime', width: 180, resizable: true }, |
| | | { |
| | | title: '状态', |
| | | key: 'status', |
| | | align: 'left' as const, |
| | | width: 120, |
| | | render: (row: any) => { |
| | | const statusMap: Record<number, { type: 'info' | 'default', text: string, color: string, bgColor: string }> = { |
| | | 1: { type: 'info', text: '可使用', color: '#0068FF', bgColor: '#E6F4FF' }, |
| | | 2: { type: 'default', text: '已使用', color: 'rgba(23, 31, 38, 0.45)', bgColor: 'rgba(23, 31, 38, 0.04)' }, |
| | | 3: { type: 'default', text: '已到期', color: 'rgba(23, 31, 38, 0.45)', bgColor: 'rgba(23, 31, 38, 0.04)' }, |
| | | } |
| | | const status = statusMap[row.status] |
| | | const status = useStatusOptions[row.status] |
| | | return status |
| | | ? ( |
| | | <NTag |
| | | bordered={false} |
| | | style={{ |
| | | color: status.color, |
| | | backgroundColor: status.bgColor, |
| | | }} |
| | | type={status.type} |
| | | > |
| | | {status.text} |
| | | {status.label} |
| | | </NTag> |
| | | ) |
| | | : '-' |
| | | }, |
| | | }, |
| | | ] |
| | | ]) |
| | | |
| | | // 搜索操作 |
| | | function handleSearchActivity() { |
| | |
| | | // 订单记录 - 搜索条件 |
| | | const orderSearchModel = ref({ |
| | | orderNumber: '', |
| | | userPhone: '', |
| | | vehicleNumber: '', |
| | | nickName: '', |
| | | plateNumber: '', |
| | | fleetId: null, |
| | | channel: null, |
| | | cityId: null, |
| | | orderSource: null, |
| | | cityCode: null, |
| | | status: null, |
| | | orderTime: null as [number, number] | null, |
| | | }) |
| | | |
| | | // 下拉框展开状态 |
| | | // 模拟选项和状态 |
| | | const isOrderFleetOpen = ref(false) |
| | | const isOrderChannelOpen = ref(false) |
| | | const isOrderSourceOpen = ref(false) |
| | | const isOrderCityOpen = ref(false) |
| | | const isOrderStatusOpen = ref(false) |
| | | |
| | | // 模拟选项 |
| | | const fleetOptions = ref<any[]>([]) |
| | | const channelOptions = [{ label: '小鹏小程序', value: 1 }] |
| | | const cityOptions = ref<any[]>([]) |
| | | const orderStatusOptions = [ |
| | | { label: '待接单', value: 1, type: 'primary' }, |
| | | { label: '待出发', value: 2, type: 'success' }, |
| | | { label: '待到达预约地点', value: 3, type: 'warning' }, |
| | | { label: '待乘客上车', value: 4, type: 'primary' }, |
| | | { label: '乘客已上车', value: 5, type: 'success' }, |
| | | { label: '服务中', value: 6, type: 'success' }, |
| | | { label: '待客下车', value: 7, type: 'warning' }, |
| | | { label: '待支付', value: 8, type: 'warning' }, |
| | | { label: '待评价', value: 9, type: 'default' }, |
| | | { label: '已完成', value: 10, type: 'success' }, |
| | | { label: '已取消', value: 0, type: 'default' }, |
| | | { label: '待接单', value: 1 }, |
| | | { label: '待出发', value: 2 }, |
| | | { label: '待到达预约地点', value: 3 }, |
| | | { label: '待乘客上车', value: 4 }, |
| | | { label: '乘客已上车', value: 5 }, |
| | | { label: '服务中', value: 6 }, |
| | | { label: '待客下车', value: 7 }, |
| | | { label: '待支付', value: 8 }, |
| | | { label: '待评价', value: 9 }, |
| | | { label: '已完成', value: 10 }, |
| | | { label: '已取消', value: 0 }, |
| | | ] |
| | | |
| | | // 搜索项配置 |
| | |
| | | { |
| | | label: '订单号', |
| | | path: 'orderNumber', |
| | | render: () => <NInput v-model:value={orderSearchModel.value.orderNumber} placeholder="请输入" clearable />, |
| | | render: () => <NInput v-model:value={orderSearchModel.value.orderNumber} placeholder="请输入订单编号" clearable />, |
| | | }, |
| | | { |
| | | label: '用户', |
| | | path: 'userPhone', |
| | | render: () => <NInput v-model:value={orderSearchModel.value.userPhone} placeholder="请输入" clearable />, |
| | | path: 'nickName', |
| | | render: () => <NInput v-model:value={orderSearchModel.value.nickName} placeholder="请输入用户昵称" clearable />, |
| | | }, |
| | | { |
| | | label: '车辆', |
| | | path: 'vehicleNumber', |
| | | render: () => <NInput v-model:value={orderSearchModel.value.vehicleNumber} placeholder="请输入" clearable />, |
| | | path: 'plateNumber', |
| | | render: () => <NInput v-model:value={orderSearchModel.value.plateNumber} placeholder="请输入车牌号" clearable />, |
| | | }, |
| | | { |
| | | label: '车队', |
| | |
| | | }, |
| | | { |
| | | label: '渠道', |
| | | path: 'channel', |
| | | path: 'orderSource', |
| | | render: () => ( |
| | | <NSelect |
| | | v-model:value={orderSearchModel.value.channel} |
| | | v-model:value={orderSearchModel.value.orderSource} |
| | | options={channelOptions} |
| | | clearable |
| | | placeholder="请选择" |
| | | onUpdate:show={(value: boolean) => isOrderChannelOpen.value = value} |
| | | onUpdate:show={(value: boolean) => isOrderSourceOpen.value = value} |
| | | > |
| | | {{ |
| | | arrow: () => ( |
| | | <img |
| | | src="https://huishou-1323682843.cos.ap-nanjing.myqcloud.com/images/e120f81c-9f15-4e1a-9938-22252898edf9.png" |
| | | class={`arrow-icon ${isOrderChannelOpen.value ? 'arrow-icon--open' : ''}`} |
| | | class={`arrow-icon ${isOrderSourceOpen.value ? 'arrow-icon--open' : ''}`} |
| | | style={{ width: '16px', height: '16px' }} |
| | | alt="arrow" |
| | | /> |
| | |
| | | }, |
| | | { |
| | | label: '城市', |
| | | path: 'cityId', |
| | | path: 'cityCode', |
| | | render: () => ( |
| | | <NCascader |
| | | v-model:value={orderSearchModel.value.cityId} |
| | | v-model:value={orderSearchModel.value.cityCode} |
| | | options={cityOptions.value} |
| | | value-field="code" |
| | | label-field="name" |
| | |
| | | </NSelect> |
| | | ), |
| | | }, |
| | | { |
| | | label: '订单时间', |
| | | path: 'orderTime', |
| | | render: () => ( |
| | | <NDatePicker |
| | | v-model:value={orderSearchModel.value.orderTime} |
| | | type="daterange" |
| | | clearable |
| | | start-placeholder="开始时间" |
| | | end-placeholder="结束时间" |
| | | style={{ width: '260px' }} |
| | | actions={[]} |
| | | close-on-select={true} |
| | | > |
| | | {{ |
| | | separator: () => <span>至</span>, |
| | | }} |
| | | </NDatePicker> |
| | | ), |
| | | }, |
| | | ]) |
| | | |
| | | const orderRecords = ref([ |
| | | { |
| | | id: 1, |
| | | orderNumber: '121314', |
| | | orderTime: '2022.07.05 12:09', |
| | | orderSource: '运维小程序', |
| | | rideTime: '2022.07.05 12:09', |
| | | userName: '张三', |
| | | userPhone: '18380591471', |
| | | startLocation: '成都天府新谷', |
| | | orderTime: '2021/07/29 10:59', |
| | | couponName: '优惠券名称名称', |
| | | startLocation: '小鹏汽车大厦', |
| | | endLocation: '天府广场', |
| | | distance: '3.1 km', |
| | | estimatedAmount: '¥ 88.00', |
| | | actualAmount: '¥ 0.00', |
| | | channelName: '小鹏小程序', |
| | | status: 1, // 待接单 |
| | | }, |
| | | { |
| | | id: 2, |
| | | orderNumber: '121314', |
| | | orderTime: '2022.07.05 12:09', |
| | | orderSource: '运维小程序', |
| | | rideTime: '2022.07.05 12:09', |
| | | userName: '张三', |
| | | userPhone: '18380591471', |
| | | startLocation: '成都天府新谷', |
| | | orderTime: '2021/07/29 10:59', |
| | | couponName: '优惠券名称名称', |
| | | startLocation: '小鹏汽车大厦', |
| | | endLocation: '天府广场', |
| | | status: 2, // 待出发 |
| | | distance: '3.1 km', |
| | | estimatedAmount: '¥ 88.00', |
| | | actualAmount: '¥ 0.00', |
| | | channelName: '小鹏小程序', |
| | | status: 2, |
| | | }, |
| | | { |
| | | id: 3, |
| | | orderNumber: '121314', |
| | | orderTime: '2022.07.05 12:09', |
| | | orderSource: '运维小程序', |
| | | rideTime: '2022.07.05 12:09', |
| | | userName: '张三', |
| | | userPhone: '18380591471', |
| | | startLocation: '成都天府新谷', |
| | | orderTime: '2021/07/29 10:59', |
| | | couponName: '优惠券名称名称', |
| | | startLocation: '小鹏汽车大厦', |
| | | endLocation: '天府广场', |
| | | status: 3, // 待到达预约地 |
| | | distance: '3.1 km', |
| | | estimatedAmount: '¥ 88.00', |
| | | actualAmount: '¥ 0.00', |
| | | channelName: '小鹏小程序', |
| | | status: 10, |
| | | }, |
| | | { |
| | | id: 4, |
| | | orderNumber: '121314', |
| | | orderTime: '2022.07.05 12:09', |
| | | orderSource: '运维小程序', |
| | | rideTime: '2022.07.05 12:09', |
| | | userName: '张三', |
| | | userPhone: '18380591471', |
| | | startLocation: '成都天府新谷', |
| | | orderTime: '2021/07/29 10:59', |
| | | couponName: '优惠券名称名称', |
| | | startLocation: '小鹏汽车大厦', |
| | | endLocation: '天府广场', |
| | | status: 4, // 待乘客上车 |
| | | distance: '3.1 km', |
| | | estimatedAmount: '¥ 88.00', |
| | | actualAmount: '¥ 0.00', |
| | | channelName: '小鹏小程序', |
| | | status: 0, |
| | | }, |
| | | { |
| | | id: 5, |
| | | orderNumber: '121314', |
| | | orderTime: '2022.07.05 12:09', |
| | | orderSource: '运维小程序', |
| | | rideTime: '2022.07.05 12:09', |
| | | userName: '张三', |
| | | userPhone: '18380591471', |
| | | startLocation: '成都天府新谷', |
| | | orderTime: '2021/07/29 10:59', |
| | | couponName: '优惠券名称名称', |
| | | startLocation: '小鹏汽车大厦', |
| | | endLocation: '天府广场', |
| | | status: 6, // 服务中 (关闭订单/详情/删除) |
| | | }, |
| | | { |
| | | id: 6, |
| | | orderNumber: '121314', |
| | | orderTime: '2022.07.05 12:09', |
| | | orderSource: '运维小程序', |
| | | rideTime: '2022.07.05 12:09', |
| | | userName: '张三', |
| | | userPhone: '18380591471', |
| | | startLocation: '成都天府新谷', |
| | | endLocation: '天府广场', |
| | | status: 8, // 待支付 |
| | | }, |
| | | { |
| | | id: 7, |
| | | orderNumber: '121314', |
| | | orderTime: '2022.07.05 12:09', |
| | | orderSource: '运维小程序', |
| | | rideTime: '2022.07.05 12:09', |
| | | userName: '张三', |
| | | userPhone: '18380591471', |
| | | startLocation: '成都天府新谷', |
| | | endLocation: '天府广场', |
| | | status: 9, // 待评价 |
| | | }, |
| | | { |
| | | id: 8, |
| | | orderNumber: '121314', |
| | | orderTime: '2022.07.05 12:09', |
| | | orderSource: '运维小程序', |
| | | rideTime: '2022.07.05 12:09', |
| | | userName: '张三', |
| | | userPhone: '18380591471', |
| | | startLocation: '成都天府新谷', |
| | | endLocation: '天府广场', |
| | | status: 6, // 服务中 |
| | | }, |
| | | { |
| | | id: 9, |
| | | orderNumber: '121314', |
| | | orderTime: '2022.07.05 12:09', |
| | | orderSource: '运维小程序', |
| | | rideTime: '2022.07.05 12:09', |
| | | userName: '张三', |
| | | userPhone: '18380591471', |
| | | startLocation: '成都天府新谷', |
| | | endLocation: '天府广场', |
| | | status: 6, // 服务中 |
| | | }, |
| | | { |
| | | id: 10, |
| | | orderNumber: '121314', |
| | | orderTime: '2022.07.05 12:09', |
| | | orderSource: '运维小程序', |
| | | rideTime: '2022.07.05 12:09', |
| | | userName: '张三', |
| | | userPhone: '18380591471', |
| | | startLocation: '成都天府新谷', |
| | | endLocation: '天府广场', |
| | | status: 6, // 服务中 |
| | | distance: '3.1 km', |
| | | estimatedAmount: '¥ 88.00', |
| | | actualAmount: '¥ 0.00', |
| | | channelName: '小鹏小程序', |
| | | status: 10, |
| | | }, |
| | | ]) |
| | | |
| | | const orderPage = ref({ pageNum: 1, pageSize: 10 }) |
| | | const selectedOrderRowKeys = ref<number[]>([]) |
| | | |
| | | // 订单状态 |
| | | const statusOptions = [ |
| | | { |
| | | label: '待接单', |
| | | value: 1, |
| | | type: 'primary', |
| | | }, |
| | | { |
| | | label: '待出发', |
| | | value: 2, |
| | | type: 'success', |
| | | }, |
| | | { |
| | | label: '待到达预约地点', |
| | | value: 3, |
| | | type: 'warning', |
| | | }, |
| | | { |
| | | label: '待乘客上车', |
| | | value: 4, |
| | | type: 'primary', |
| | | }, |
| | | { |
| | | label: '乘客已上车', |
| | | value: 5, |
| | | type: 'success', |
| | | }, |
| | | { |
| | | label: '服务中', |
| | | value: 6, |
| | | type: 'success', |
| | | }, |
| | | { |
| | | label: '待客下车', |
| | | value: 7, |
| | | type: 'warning', |
| | | }, |
| | | { |
| | | label: '待支付', |
| | | value: 8, |
| | | type: 'warning', |
| | | }, |
| | | { |
| | | label: '待评价', |
| | | value: 9, |
| | | type: 'default', |
| | | }, |
| | | { |
| | | label: '已完成', |
| | | value: 10, |
| | | type: 'success', |
| | | }, |
| | | { |
| | | label: '已取消', |
| | | value: 0, |
| | | type: 'default', |
| | | }, |
| | | ] |
| | | |
| | | // 订单记录 - 表格列定义 |
| | | const orderColumns = [ |
| | | { type: 'selection' as const, width: 50 }, |
| | | { title: '订单编号', key: 'orderNumber', align: 'left' as const, width: 100 }, |
| | | { title: '下单时间', key: 'orderTime', align: 'left' as const, width: 140 }, |
| | | { title: '订单来源', key: 'orderSource', align: 'left' as const, width: 120 }, |
| | | { title: '乘车时间', key: 'rideTime', align: 'left' as const, width: 140 }, |
| | | { title: '下单用户昵称', key: 'userName', align: 'left' as const, width: 120 }, |
| | | { title: '下单用户手机', key: 'userPhone', align: 'left' as const, width: 120 }, |
| | | { title: '起点', key: 'startLocation', align: 'left' as const, width: 140 }, |
| | | { title: '终点', key: 'endLocation', align: 'left' as const, width: 140 }, |
| | | const orderColumns = computed<DataTableColumns<any>>(() => [ |
| | | { |
| | | type: 'selection', |
| | | width: 40, |
| | | fixed: 'left', |
| | | }, |
| | | { |
| | | title: '订单编号', |
| | | key: 'orderNumber', |
| | | width: 150, |
| | | resizable: true, |
| | | fixed: 'left', |
| | | }, |
| | | { |
| | | title: '下单时间', |
| | | key: 'createTime', |
| | | width: 160, |
| | | resizable: true, |
| | | render: row => <span>{row.createTime ? dayjs(row.createTime).format('YYYY/MM/DD HH:mm') : '-'}</span>, |
| | | }, |
| | | { |
| | | title: '订单来源', |
| | | key: 'orderSource', |
| | | width: 100, |
| | | resizable: true, |
| | | render(row) { |
| | | return row.orderSource === 1 ? '小鹏小程序' : '-' |
| | | }, |
| | | }, |
| | | { |
| | | title: '下单用户昵称', |
| | | key: 'nickName', |
| | | width: 120, |
| | | resizable: true, |
| | | }, |
| | | { |
| | | title: '下单用户手机', |
| | | key: 'phoneNumber', |
| | | width: 120, |
| | | resizable: true, |
| | | }, |
| | | { |
| | | title: '起点', |
| | | key: 'startPointName', |
| | | width: 160, |
| | | resizable: true, |
| | | ellipsis: { |
| | | tooltip: true, |
| | | }, |
| | | }, |
| | | { |
| | | title: '终点', |
| | | key: 'endPointName', |
| | | width: 160, |
| | | resizable: true, |
| | | ellipsis: { |
| | | tooltip: true, |
| | | }, |
| | | }, |
| | | { |
| | | title: '状态', |
| | | key: 'status', |
| | | align: 'left' as const, |
| | | width: 120, |
| | | render: (row: any) => { |
| | | const statusMap: Record<number, { type: 'info' | 'success' | 'warning' | 'default', text: string, color: string, bgColor: string }> = { |
| | | 1: { type: 'info', text: '待接单', color: '#0068FF', bgColor: '#E6F4FF' }, |
| | | 2: { type: 'success', text: '待出发', color: '#06B752', bgColor: '#E6F8ED' }, |
| | | 3: { type: 'warning', text: '待到达预约地', color: '#F29B13', bgColor: '#FEF5E7' }, |
| | | 4: { type: 'info', text: '待乘客上车', color: '#0068FF', bgColor: '#E6F4FF' }, |
| | | 6: { type: 'success', text: '服务中', color: '#06B752', bgColor: '#E6F8ED' }, |
| | | 8: { type: 'warning', text: '待支付', color: '#F29B13', bgColor: '#FEF5E7' }, |
| | | 9: { type: 'default', text: '待评价', color: '#9013FE', bgColor: '#F4E7FE' }, |
| | | 0: { type: 'default', text: '已取消', color: 'rgba(23, 31, 38, 0.45)', bgColor: 'rgba(23, 31, 38, 0.04)' }, |
| | | } |
| | | const status = statusMap[row.status] |
| | | return status |
| | | ? ( |
| | | <NTag |
| | | bordered={false} |
| | | style={{ |
| | | color: status.color, |
| | | backgroundColor: status.bgColor, |
| | | }} |
| | | > |
| | | {status.text} |
| | | </NTag> |
| | | ) |
| | | : '-' |
| | | resizable: true, |
| | | render(row) { |
| | | const option = statusOptions.find(item => item.value === row.status) |
| | | return ( |
| | | <n-tag |
| | | type={option?.type || 'default'} |
| | | bordered={false} |
| | | style={row.status === 9 |
| | | ? { |
| | | color: '#764DFF', |
| | | backgroundColor: '#F6F0FF', |
| | | } |
| | | : {}} |
| | | > |
| | | {option?.label || ''} |
| | | </n-tag> |
| | | ) |
| | | }, |
| | | }, |
| | | { |
| | | title: '操作', |
| | | key: 'actions', |
| | | align: 'left' as const, |
| | | width: 180, |
| | | fixed: 'right' as const, |
| | | render: (row: any) => { |
| | | // 根据状态判断按钮 |
| | | // 待接单(1)、待出发(2)、待到达预约地(3)、待乘客上车(4): 取消订单、改派、详情、删除 (注: 设计图里待接单没有改派,但为简化先通用渲染) |
| | | // 服务中(6): 关闭订单、详情、删除 |
| | | // 待支付(8): 改价、详情、删除 |
| | | // 待评价(9): 详情、删除 |
| | | |
| | | const actions = [] |
| | | |
| | | if ([1, 2, 3, 4].includes(row.status)) { |
| | | actions.push(<span style={{ color: '#0068FF', cursor: 'pointer' }}>取消订单</span>) |
| | | if (row.status !== 1) { |
| | | actions.push(<span style={{ color: '#0068FF', cursor: 'pointer' }}>改派</span>) |
| | | } |
| | | } |
| | | else if (row.status === 6) { |
| | | actions.push(<span style={{ color: '#0068FF', cursor: 'pointer' }}>关闭订单</span>) |
| | | } |
| | | else if (row.status === 8) { |
| | | actions.push(<span style={{ color: '#0068FF', cursor: 'pointer' }}>改价</span>) |
| | | } |
| | | |
| | | actions.push(<span style={{ color: '#0068FF', cursor: 'pointer' }}>详情</span>) |
| | | actions.push(<span style={{ color: '#0068FF', cursor: 'pointer' }}>删除</span>) |
| | | |
| | | width: 80, |
| | | fixed: 'right', |
| | | render: (row) => { |
| | | return ( |
| | | <div class="flex justify-start gap-3"> |
| | | {actions} |
| | | </div> |
| | | <NButton |
| | | text |
| | | v-permission="60111" |
| | | type="primary" |
| | | onClick={() => { |
| | | router.push({ |
| | | name: 'orderDetail', |
| | | query: { |
| | | orderId: row.id!, |
| | | }, |
| | | }) |
| | | }} |
| | | > |
| | | 详情 |
| | | </NButton> |
| | | ) |
| | | }, |
| | | }, |
| | | ] |
| | | ]) |
| | | |
| | | // 订单搜索操作 |
| | | function handleSearchOrder() { |
| | |
| | | function handleResetOrder() { |
| | | orderSearchModel.value = { |
| | | orderNumber: '', |
| | | userPhone: '', |
| | | vehicleNumber: '', |
| | | nickName: '', |
| | | plateNumber: '', |
| | | fleetId: null, |
| | | channel: null, |
| | | cityId: null, |
| | | orderSource: null, |
| | | cityCode: null, |
| | | status: null, |
| | | orderTime: null, |
| | | } |
| | | } |
| | | |
| | |
| | | |
| | | <template> |
| | | <div class="user-detail-page roboTaxi w-full h-full"> |
| | | <div class="flex flex-col gap-12px flex-1"> |
| | | <!-- 页面标题区 --> |
| | | <NCard :bordered="false" class="page-header-card"> |
| | | <div class="page-header"> |
| | | <div class="flex flex-col gap-12px flex-1 h-full"> |
| | | <!-- 基础信息区 --> |
| | | <div class="page-header-card" style="flex: none; padding-bottom: 24px; background: white; padding: 24px;"> |
| | | <div class="page-header" style="margin-bottom: 24px;"> |
| | | <div class="title-left"> |
| | | <div class="blue-bar" /> |
| | | <span class="title-text">用户详情</span> |
| | | <span class="title-text">基础信息</span> |
| | | </div> |
| | | <!-- <NButton @click="handleBack">返回</NButton> --> |
| | | </div> |
| | | <NTabs v-model:value="activeTab" type="line" animated class="info-list flex-1"> |
| | | <!-- 基础信息 --> |
| | | <NTabPane name="basicInfo" tab="基础信息" class="h-full flex-1"> |
| | | <div class="flex flex-col h-full basic-info-container"> |
| | | <div class="info-row"> |
| | | <div class="info-item"> |
| | | <span class="info-label">用户名称:</span> |
| | | <span class="info-value">{{ basicInfoData.userName || '-' }}</span> |
| | | </div> |
| | | <div class="info-item"> |
| | | <span class="info-label">白名单账号:</span> |
| | | <span class="info-value">{{ basicInfoData.whitelistAccount || '-' }}</span> |
| | | </div> |
| | | <div class="info-item"> |
| | | <span class="info-label">状态:</span> |
| | | <span class="info-value">{{ basicInfoData.status || '-' }}</span> |
| | | </div> |
| | | </div> |
| | | <div class="info-row"> |
| | | <div class="info-item"> |
| | | <span class="info-label">是否优先派单:</span> |
| | | <span class="info-value">{{ basicInfoData.isPriorityDispatch || '-' }}</span> |
| | | </div> |
| | | <div class="info-item"> |
| | | <span class="info-label">有效期:</span> |
| | | <span class="info-value">{{ basicInfoData.expireDate || '-' }}</span> |
| | | </div> |
| | | <div class="info-item"> |
| | | <span class="info-label">最大下单数量:</span> |
| | | <span class="info-value">{{ basicInfoData.maxOrderCount || '-' }}</span> |
| | | </div> |
| | | </div> |
| | | <div class="basic-info-container"> |
| | | <div class="info-row"> |
| | | <div class="info-item"> |
| | | <span class="info-label">用户名称:</span> |
| | | <span class="info-value">{{ basicInfoData.userName || '-' }}</span> |
| | | </div> |
| | | </NTabPane> |
| | | <div class="info-item"> |
| | | <span class="info-label">白名单账号:</span> |
| | | <span class="info-value">{{ basicInfoData.whitelistAccount || '-' }}</span> |
| | | </div> |
| | | <div class="info-item"> |
| | | <span class="info-label">状态:</span> |
| | | <span class="info-value"> |
| | | <NTag v-if="basicInfoData.status === '正常'" type="info" :bordered="false"> |
| | | {{ basicInfoData.status || '-' }} |
| | | </NTag> |
| | | <span v-else>{{ basicInfoData.status || '-' }}</span> |
| | | </span> |
| | | </div> |
| | | </div> |
| | | <div class="info-row"> |
| | | <div class="info-item"> |
| | | <span class="info-label">是否优先派单:</span> |
| | | <span class="info-value">{{ basicInfoData.isPriorityDispatch || '-' }}</span> |
| | | </div> |
| | | <div class="info-item"> |
| | | <span class="info-label">有效期:</span> |
| | | <span class="info-value">{{ basicInfoData.expireDate || '-' }}</span> |
| | | </div> |
| | | <div class="info-item"> |
| | | <span class="info-label">最大下单数量:</span> |
| | | <span class="info-value">{{ basicInfoData.maxOrderCount || '-' }}</span> |
| | | </div> |
| | | </div> |
| | | <div class="info-row"> |
| | | <div class="info-item"> |
| | | <span class="info-label">创建人:</span> |
| | | <span class="info-value">{{ basicInfoData.creator || '-' }}</span> |
| | | </div> |
| | | <div class="info-item"> |
| | | <span class="info-label">创建时间:</span> |
| | | <span class="info-value">{{ basicInfoData.createTime || '-' }}</span> |
| | | </div> |
| | | <div class="info-item" /> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- Tab 标签页和内容区 --> |
| | | <div class="info-list flex-1"> |
| | | <NTabs v-model:value="activeTab" type="line" animated class="flex-1 h-full"> |
| | | <!-- 活动参与记录 --> |
| | | <NTabPane name="activityRecord" tab="活动参与记录" class="h-full flex-1"> |
| | | <div class="flex flex-col h-full"> |
| | | <div class="flex flex-col h-full px-12px"> |
| | | <!-- 搜索区域 --> |
| | | <div class="search-form-area"> |
| | | <NForm |
| | | inline |
| | | label-placement="left" |
| | | :show-feedback="false" |
| | | style="width: 100%" |
| | | > |
| | | <div class="search-wrapper"> |
| | | <div class="search-fields"> |
| | | <NFormItem label="领取时间:"> |
| | | <NDatePicker |
| | | v-model:value="activitySearchModel.timeRange" |
| | | type="daterange" |
| | | clearable |
| | | start-placeholder="开始时间" |
| | | end-placeholder="结束时间" |
| | | style="width: 260px" |
| | | :actions="[]" |
| | | :close-on-select="true" |
| | | > |
| | | <template #separator> |
| | | <span>至</span> |
| | | </template> |
| | | </NDatePicker> |
| | | </NFormItem> |
| | | <NFormItem label="所属活动:"> |
| | | <NInput |
| | | v-model:value="activitySearchModel.activityName" |
| | | placeholder="请输入活动名称" |
| | | style="width: 220px" |
| | | /> |
| | | </NFormItem> |
| | | <NFormItem label="使用状态:"> |
| | | <NSelect |
| | | v-model:value="activitySearchModel.useStatus" |
| | | :options="useStatusOptions" |
| | | clearable |
| | | placeholder="请选择使用状态" |
| | | style="width: 200px" |
| | | @update:show="(value: boolean) => isUseStatusSelectOpen = value" |
| | | > |
| | | <template #arrow> |
| | | <img |
| | | src="https://huishou-1323682843.cos.ap-nanjing.myqcloud.com/images/e120f81c-9f15-4e1a-9938-22252898edf9.png" |
| | | class="arrow-icon" |
| | | :class="{ 'arrow-icon--open': isUseStatusSelectOpen }" |
| | | style="width: 16px; height: 16px" |
| | | alt="arrow" |
| | | > |
| | | </template> |
| | | <template #empty> |
| | | <div class="flex-center"> |
| | | <img |
| | | src="/src/assets/element-img/no-data.png" |
| | | style="width: 50%" |
| | | > |
| | | </div> |
| | | </template> |
| | | </NSelect> |
| | | </NFormItem> |
| | | </div> |
| | | <div class="search-actions"> |
| | | <NButton |
| | | class="action-btn" |
| | | @click="handleResetActivity" |
| | | > |
| | | 重置 |
| | | </NButton> |
| | | <NButton |
| | | type="primary" |
| | | class="action-btn" |
| | | @click="handleSearchActivity" |
| | | > |
| | | 查询 |
| | | </NButton> |
| | | </div> |
| | | </div> |
| | | </NForm> |
| | | </div> |
| | | |
| | | <!-- 批量操作区 --> |
| | | <div class="table-actions"> |
| | | <NButton @click="handleBatchDelete"> |
| | | 批量删除 |
| | | </NButton> |
| | | </div> |
| | | |
| | | <!-- 数据表格 --> |
| | | <NDataTable |
| | | v-model:checked-row-keys="selectedRowKeys" |
| | | :columns="activityColumns" |
| | | :data="activityRecords" |
| | | :row-key="(row) => row.id" |
| | | class="flex-1" |
| | | flex-height |
| | | striped |
| | | :bordered="false" |
| | | > |
| | | <template #empty> |
| | | <div class="flex-center"> |
| | | <img |
| | | src="/src/assets/element-img/no-data.png" |
| | | style="width: 123px; height: 125px" |
| | | > |
| | | </div> |
| | | </template> |
| | | </NDataTable> |
| | | |
| | | <!-- 分页器 --> |
| | | <Pagination |
| | | style="margin-top: 16px; margin-bottom: 16px;" |
| | | :count="1000" |
| | | :page="activityPage.pageNum" |
| | | :page-size="activityPage.pageSize" |
| | | @change="handleActivityPageChange" |
| | | /> |
| | | </div> |
| | | </NTabPane> |
| | | |
| | | <!-- 订单记录 --> |
| | | <NTabPane name="orderRecord" tab="订单记录" class="h-full flex-1"> |
| | | <div class="flex flex-col h-full"> |
| | | <!-- 搜索区域 --> |
| | | <div class="search-form-area"> |
| | | <NForm |
| | | inline |
| | | label-placement="left" |
| | | :show-feedback="false" |
| | | style="width: 100%" |
| | | > |
| | | <NForm inline label-placement="left" :show-feedback="false" style="width: 100%"> |
| | | <SearchPanel> |
| | | <NGi v-for="item in orderFilterItems" :key="item.path"> |
| | | <NGi v-for="item in activityFilterItems" :key="item.path" class="n-grid-item"> |
| | | <NFormItem :label="item.label" :path="item.path"> |
| | | <component :is="item.render" /> |
| | | </NFormItem> |
| | | </NGi> |
| | | <template #action> |
| | | <NButton |
| | | class="action-btn" |
| | | @click="handleResetOrder" |
| | | > |
| | | <NButton @click="handleResetActivity"> |
| | | 重置 |
| | | </NButton> |
| | | <NButton |
| | | type="primary" |
| | | class="action-btn ml-12px" |
| | | @click="handleSearchOrder" |
| | | > |
| | | <NButton type="primary" @click="handleSearchActivity"> |
| | | 查询 |
| | | </NButton> |
| | | </template> |
| | |
| | | </div> |
| | | |
| | | <!-- 批量操作区 --> |
| | | <div class="table-actions"> |
| | | <div class="gap-2 items-center table-actions"> |
| | | <span v-if="selectedRowKeys.length > 0" style="color: rgba(23, 31, 38, 0.6); font-weight: 400"> |
| | | 已选择 {{ selectedRowKeys.length }} 条记录 |
| | | </span> |
| | | <NButton v-permission="30121" :disabled="selectedRowKeys.length === 0" @click="handleBatchDelete"> |
| | | 批量删除 |
| | | </NButton> |
| | | </div> |
| | | <!-- 数据表格 --> |
| | | <NDataTable |
| | | v-model:checked-row-keys="selectedRowKeys" :columns="activityColumns" :data="activityRecords" |
| | | :row-key="(row) => row.id" class="flex-1" flex-height striped :bordered="false" |
| | | > |
| | | <template #empty> |
| | | <div class="flex-center"> |
| | | <img src="/src/assets/element-img/no-data.png" style="width: 123px; height: 125px"> |
| | | </div> |
| | | </template> |
| | | </NDataTable> |
| | | |
| | | <!-- 分页器 --> |
| | | <Pagination |
| | | style="margin-top: 16px;" :count="1000" :page="activityPage.pageNum" |
| | | :page-size="activityPage.pageSize" @change="handleActivityPageChange" |
| | | /> |
| | | </div> |
| | | </NTabPane> |
| | | |
| | | <!-- 订单记录 --> |
| | | <NTabPane name="orderRecord" tab="订单记录" class="h-full flex-1"> |
| | | <div class="flex flex-col h-full px-12px"> |
| | | <!-- 搜索区域 --> |
| | | <div class="search-form-area"> |
| | | <NForm inline label-placement="left" :show-feedback="false" style="width: 100%"> |
| | | <SearchPanel> |
| | | <NGi v-for="item in orderFilterItems" :key="item.path" class="n-grid-item"> |
| | | <NFormItem :label="item.label" :path="item.path"> |
| | | <component :is="item.render" /> |
| | | </NFormItem> |
| | | </NGi> |
| | | <template #action> |
| | | <NButton @click="handleResetOrder"> |
| | | 重置 |
| | | </NButton> |
| | | <NButton type="primary" @click="handleSearchOrder"> |
| | | 查询 |
| | | </NButton> |
| | | </template> |
| | | </SearchPanel> |
| | | </NForm> |
| | | </div> |
| | | |
| | | <!-- 批量操作区 --> |
| | | <div class="gap-2 items-center table-actions"> |
| | | <span v-if="selectedOrderRowKeys.length > 0" style="color: rgba(23, 31, 38, 0.6);font-weight: 400;"> |
| | | {{ $t('accountManagement.messages.selectedRecords', { count: selectedOrderRowKeys.length }) }} |
| | | </span> |
| | | <NButton @click="handleBatchExportOrder"> |
| | | 批量导出订单 |
| | | </NButton> |
| | |
| | | |
| | | <!-- 数据表格 --> |
| | | <NDataTable |
| | | v-model:checked-row-keys="selectedOrderRowKeys" |
| | | :columns="orderColumns" |
| | | :data="orderRecords" |
| | | :row-key="(row) => row.id" |
| | | class="flex-1" |
| | | flex-height |
| | | striped |
| | | :scroll-x="1600" |
| | | :bordered="false" |
| | | v-model:checked-row-keys="selectedOrderRowKeys" :columns="orderColumns" :data="orderRecords" |
| | | :row-key="(row) => row.id" class="flex-1" flex-height striped :scroll-x="1160" :bordered="false" |
| | | > |
| | | <template #empty> |
| | | <div class="flex-center"> |
| | | <img |
| | | src="/src/assets/element-img/no-data.png" |
| | | style="width: 123px; height: 125px" |
| | | > |
| | | <img src="/src/assets/element-img/no-data.png" style="width: 123px; height: 125px"> |
| | | </div> |
| | | </template> |
| | | </NDataTable> |
| | | |
| | | <!-- 分页器 --> |
| | | <Pagination |
| | | style="margin-top: 16px; margin-bottom: 16px;" |
| | | :count="1000" |
| | | :page="orderPage.pageNum" |
| | | :page-size="orderPage.pageSize" |
| | | @change="handleOrderPageChange" |
| | | style="margin-top: 16px;" :count="1000" :page="orderPage.pageNum" |
| | | :page-size="orderPage.pageSize" @change="handleOrderPageChange" |
| | | /> |
| | | </div> |
| | | </NTabPane> |
| | | </NTabs> |
| | | </NCard> |
| | | |
| | | <!-- Tab 标签页和内容区 --> |
| | | <!-- <NCard :bordered="false" class="info-list flex-1"> |
| | | |
| | | </NCard> --> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </template> |
| | |
| | | } |
| | | |
| | | .page-header-card { |
| | | border-radius: 4px; |
| | | flex: 1; |
| | | :deep(.n-card__content) { |
| | | padding: 16px 24px; |
| | | display: flex; |
| | | flex-direction: column; |
| | | } |
| | | border-radius: 6px; |
| | | background-color: #fff; |
| | | padding: 24px; |
| | | box-sizing: border-box; |
| | | } |
| | | |
| | | .page-header { |
| | |
| | | align-items: center; |
| | | |
| | | .blue-bar { |
| | | width: 4px; |
| | | height: 16px; |
| | | width: 3px; |
| | | height: 14px; |
| | | background-color: #0068ff; |
| | | border-radius: 2px; |
| | | margin-right: 8px; |
| | |
| | | } |
| | | |
| | | .info-list { |
| | | border-radius: 4px; |
| | | border-radius: 6px; |
| | | display: flex; |
| | | flex-direction: column; |
| | | |
| | | :deep(.n-card__content) { |
| | | padding: 0 24px; |
| | | display: flex; |
| | | flex-direction: column; |
| | | flex: 1; |
| | | min-height: 0; |
| | | } |
| | | background-color: #fff; |
| | | padding-bottom: 12px; |
| | | box-sizing: border-box; |
| | | |
| | | :deep(.n-tabs) { |
| | | display: flex; |
| | | flex-direction: column; |
| | | flex: 1; |
| | | |
| | | .n-tabs-nav-scroll-content { |
| | | box-sizing: border-box; |
| | | padding-left: 16px; |
| | | } |
| | | |
| | | .n-tabs-nav.n-tabs-nav--line-type.n-tabs-nav--top .n-tabs-nav-scroll-content { |
| | | border-bottom: 1px solid rgba(23, 31, 38, 0.1); |
| | | } |
| | | |
| | | .n-tabs-nav { |
| | | padding: 12px 0 0; |
| | | border-bottom: 1px solid rgba(23, 31, 38, 0.08); |
| | | |
| | | .n-tabs-tab { |
| | | padding-bottom: 12px; |
| | |
| | | flex: 1; |
| | | min-height: 0; |
| | | } |
| | | |
| | | .n-tabs-tab__label { |
| | | font-weight: 600; |
| | | } |
| | | |
| | | .n-tabs-bar { |
| | | margin-left: 16px; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .search-form-area { |
| | | padding: 12px 0 12px; |
| | | |
| | | .search-wrapper { |
| | | display: flex; |
| | | width: 100%; |
| | | justify-content: space-between; |
| | | align-items: flex-start; |
| | | |
| | | .search-fields { |
| | | display: flex; |
| | | flex-wrap: wrap; |
| | | gap: 0 24px; |
| | | } |
| | | |
| | | .search-actions { |
| | | display: flex; |
| | | gap: 8px; |
| | | white-space: nowrap; |
| | | } |
| | | } |
| | | width: 100%; |
| | | } |
| | | |
| | | .table-actions { |
| | | margin-bottom: 16px; |
| | | margin-bottom: 12px; |
| | | display: flex; |
| | | justify-content: flex-start; |
| | | } |
| | | |
| | | .basic-info-container { |
| | | padding: 10px 0 24px; |
| | | |
| | | .info-row { |
| | | display: flex; |
| | | margin-bottom: 24px; |
| | | margin-bottom: 16px; |
| | | |
| | | &:last-child { |
| | | margin-bottom: 0; |
| | |
| | | |
| | | .info-label { |
| | | color: rgba(23, 31, 38, 0.6); |
| | | // width: 100px; /* 根据设计图和长文本保持标签宽度一致 */ |
| | | flex-shrink: 0; |
| | | } |
| | | |
| | |
| | | } |
| | | } |
| | | |
| | | :deep(.n-tabs-pane-wrapper){ |
| | | flex:1; |
| | | :deep(.n-tabs-pane-wrapper) { |
| | | flex: 1; |
| | | } |
| | | |
| | | :deep(.n-data-table) { |
| | | .n-data-table-th { |
| | | background-color: #f7f8f9 !important; |
| | | font-weight: 500; |
| | | color: rgba(23, 31, 38, 0.8); |
| | | } |
| | | .n-data-table-td { |
| | | color: #171f26; |
| | | } |
| | | } |
| | | |
| | | .action-btn { |
| | | padding: 7px 20px; |
| | | border-radius: 4px; |
| | | } |
| | | |
| | | /* .expand-btn { |
| | | display: inline-flex; |
| | | align-items: center; |
| | | color: #0068FF; |
| | | cursor: pointer; |
| | | font-size: 14px; |
| | | user-select: none; |
| | | } */ |
| | | </style> |