提交
This commit is contained in:
1 parent
833544def3
commit
16be6fa22d
204 files changed
+455089
-46491
No files matched your search
@@ -148,10 +148,16 @@ export async function checkConvert(id: number | string) {
|
||||
return { data: unwrapData(res) }
|
||||
}
|
||||
|
||||
export function exportClue(query?: ClueQuery) {
|
||||
export function exportClue(data: Record<string, any> = {}) {
|
||||
const { ids, columns, ...rest } = data
|
||||
const body: Record<string, any> = {
|
||||
...mapListQuery(rest as ClueQuery),
|
||||
...(ids ? { ids } : {}),
|
||||
...(columns ? { columns } : {})
|
||||
}
|
||||
return request.post({
|
||||
url: `${BASE}/export`,
|
||||
data: mapListQuery(query),
|
||||
data: body,
|
||||
responseType: 'blob',
|
||||
showErrorMessage: false
|
||||
})
|
||||
|
||||
@@ -77,10 +77,16 @@ export function approveTransfer(data: {
|
||||
})
|
||||
}
|
||||
|
||||
export async function exportApproval(query: ApprovalQuery) {
|
||||
export async function exportApproval(data: Record<string, any> = {}) {
|
||||
const { ids, columns, ...rest } = data
|
||||
const body: Record<string, any> = {
|
||||
...mapListQuery(rest as ApprovalQuery),
|
||||
...(ids ? { ids } : {}),
|
||||
...(columns ? { columns } : {})
|
||||
}
|
||||
return request.post({
|
||||
url: `${BASE}/export`,
|
||||
data: mapListQuery(query),
|
||||
data: body,
|
||||
responseType: 'blob'
|
||||
})
|
||||
}
|
||||
|
||||
@@ -81,10 +81,16 @@ export function executeChange(id: number | string) {
|
||||
return request.put({ url: `${BASE}/execute/${String(id)}` })
|
||||
}
|
||||
|
||||
export async function exportChange(query: ChangeQuery) {
|
||||
export async function exportChange(data: Record<string, any> = {}) {
|
||||
const { ids, columns, ...rest } = data
|
||||
const body: Record<string, any> = {
|
||||
...mapListQuery(rest as ChangeQuery),
|
||||
...(ids ? { ids } : {}),
|
||||
...(columns ? { columns } : {})
|
||||
}
|
||||
return request.post({
|
||||
url: `${BASE}/export`,
|
||||
data: mapListQuery(query),
|
||||
data: body,
|
||||
responseType: 'blob'
|
||||
})
|
||||
}
|
||||
@@ -194,10 +194,16 @@ export function uploadAttachments(data: {
|
||||
})
|
||||
}
|
||||
|
||||
export async function exportContract(query?: ContractQuery) {
|
||||
export async function exportContract(data: Record<string, any> = {}) {
|
||||
const { ids, columns, ...rest } = data
|
||||
const body: Record<string, any> = {
|
||||
...mapListQuery(rest as ContractQuery),
|
||||
...(ids ? { ids } : {}),
|
||||
...(columns ? { columns } : {})
|
||||
}
|
||||
return request.post({
|
||||
url: `${BASE}/export`,
|
||||
data: mapListQuery(query),
|
||||
data: body,
|
||||
responseType: 'blob'
|
||||
})
|
||||
}
|
||||
|
||||
@@ -60,10 +60,16 @@ export function disableTemplate(id: number | string) {
|
||||
return request.put({ url: `${BASE}/disable/${String(id)}` })
|
||||
}
|
||||
|
||||
export async function exportTemplate(query: TemplateQuery) {
|
||||
export async function exportTemplate(data: Record<string, any> = {}) {
|
||||
const { ids, columns, ...rest } = data
|
||||
const body: Record<string, any> = {
|
||||
...mapListQuery(rest as TemplateQuery),
|
||||
...(ids ? { ids } : {}),
|
||||
...(columns ? { columns } : {})
|
||||
}
|
||||
return request.post({
|
||||
url: `${BASE}/export`,
|
||||
data: mapListQuery(query),
|
||||
data: body,
|
||||
responseType: 'blob'
|
||||
})
|
||||
}
|
||||
@@ -119,10 +119,16 @@ export function markLost(data: OpportunityMarkLostParams) {
|
||||
})
|
||||
}
|
||||
|
||||
export async function exportOpportunity(query?: OpportunityQuery) {
|
||||
export async function exportOpportunity(data: Record<string, any> = {}) {
|
||||
const { ids, columns, ...rest } = data
|
||||
const body: Record<string, any> = {
|
||||
...mapListQuery(rest as OpportunityQuery),
|
||||
...(ids ? { ids } : {}),
|
||||
...(columns ? { columns } : {})
|
||||
}
|
||||
return request.post({
|
||||
url: `${BASE}/export`,
|
||||
data: mapListQuery(query),
|
||||
data: body,
|
||||
responseType: 'blob'
|
||||
})
|
||||
}
|
||||
|
||||
@@ -26,6 +26,15 @@ export function fetchPoolRecordList(data?: Record<string, any>) {
|
||||
return request.post({ url: '/web/crm/customer/pool/record/list', data })
|
||||
}
|
||||
|
||||
/** 导出公海客户 */
|
||||
export function fetchExportPoolCustomers(data: Record<string, any> = {}) {
|
||||
return request.post({
|
||||
url: '/web/crm/customer/pool/export',
|
||||
data,
|
||||
responseType: 'blob'
|
||||
})
|
||||
}
|
||||
|
||||
// ── 公海池规则 ──
|
||||
|
||||
/** 公海池规则列表 */
|
||||
@@ -62,3 +71,13 @@ export function fetchTogglePoolRule(data: { id: string; status: string }) {
|
||||
export function fetchExecutePoolRules(data?: Record<string, any>) {
|
||||
return request.post({ url: '/web/crm/customer/pool/rule/execute', data })
|
||||
}
|
||||
|
||||
/** 导出公海规则 */
|
||||
export function fetchExportPoolRules(data: Record<string, any> = {}) {
|
||||
return request.post({
|
||||
url: '/web/crm/customer/pool/rule/export',
|
||||
data,
|
||||
responseType: 'blob'
|
||||
})
|
||||
}
|
||||
|
||||
@@ -161,10 +161,16 @@ export function invalidQuote(id: string | number, remark?: string) {
|
||||
return request.post({ url: `${BASE}/invalid`, data: { id: String(id), remark } })
|
||||
}
|
||||
|
||||
export async function exportQuote(query?: QuoteQuery) {
|
||||
export async function exportQuote(data: Record<string, any> = {}) {
|
||||
const { ids, columns, ...rest } = data
|
||||
const body: Record<string, any> = {
|
||||
...mapListQuery(rest as QuoteQuery),
|
||||
...(ids ? { ids } : {}),
|
||||
...(columns ? { columns } : {})
|
||||
}
|
||||
return request.post({
|
||||
url: `${BASE}/export`,
|
||||
data: mapListQuery(query),
|
||||
data: body,
|
||||
responseType: 'blob'
|
||||
})
|
||||
}
|
||||
|
||||
@@ -104,6 +104,9 @@ export const inventoryApi = {
|
||||
list(params?: InventoryListParams) {
|
||||
return dbRequest.get<InventoryRecord[]>('/inventory', { params, timeout: 60000 })
|
||||
},
|
||||
export(data: Record<string, any>) {
|
||||
return dbRequest.post<Blob>('/inventory/export', data, { responseType: 'blob' })
|
||||
},
|
||||
get(id: string) {
|
||||
return dbRequest.get<InventoryRecord>(`/inventory/${id}`)
|
||||
},
|
||||
|
||||
@@ -131,6 +131,9 @@ export const planApi = {
|
||||
list(params?: PlanListParams) {
|
||||
return dbRequest.get<Plan[]>('/plans', { params })
|
||||
},
|
||||
export(data: Record<string, any>) {
|
||||
return dbRequest.post<Blob>('/plans/export', data, { responseType: 'blob' })
|
||||
},
|
||||
get(id: string) {
|
||||
return dbRequest.get<Plan>(`/plans/${id}`)
|
||||
},
|
||||
|
||||
@@ -104,6 +104,9 @@ export const weighingApi = {
|
||||
list(params?: WeighingListParams) {
|
||||
return dbRequest.get<WeighingRecord[]>('/weighing', { params })
|
||||
},
|
||||
export(data: Record<string, any>) {
|
||||
return dbRequest.post<Blob>('/weighing/export', data, { responseType: 'blob' })
|
||||
},
|
||||
get(id: string) {
|
||||
return dbRequest.get<WeighingRecord>(`/weighing/${id}`)
|
||||
},
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
demoUnits
|
||||
} from './mock-data'
|
||||
import { DEFAULT_LIST_PAGE_SIZE } from '@/utils/table/tableConfig'
|
||||
import request from '@/utils/http'
|
||||
|
||||
type PageParams = { page?: number; pageSize?: number; keyword?: string }
|
||||
|
||||
@@ -387,3 +388,113 @@ export async function fetchAttachmentDownloadUrl(_data: { fileUrl: string; fileN
|
||||
await delay()
|
||||
return { downloadUrl: '#' }
|
||||
}
|
||||
|
||||
|
||||
const DEMO_PRODUCT_EXPORT_COLS = [
|
||||
{ field: 'id', title: 'id' },
|
||||
{ field: 'name', title: '商品名称' },
|
||||
{ field: 'skuCode', title: 'SKU编码' },
|
||||
{ field: 'categoryName', title: '分类' },
|
||||
{ field: 'measureValue', title: '计量' },
|
||||
{ field: 'unitName', title: '单位' },
|
||||
{ field: 'imageUrl', title: '图片URL' },
|
||||
{ field: 'isActive', title: '状态' },
|
||||
{ field: 'createAt', title: '创建时间' }
|
||||
]
|
||||
|
||||
const DEMO_CATEGORY_EXPORT_COLS = [
|
||||
{ field: 'id', title: 'id' },
|
||||
{ field: 'name', title: '分类名称' },
|
||||
{ field: 'parentId', title: '父分类ID' },
|
||||
{ field: 'parentName', title: '父分类名称' },
|
||||
{ field: 'sortOrder', title: '排序' },
|
||||
{ field: 'priceCoefficient', title: '价格系数(%)' },
|
||||
{ field: 'isActive', title: '状态' },
|
||||
{ field: 'imageUrl', title: '图片URL' }
|
||||
]
|
||||
|
||||
function pickExportColumns(
|
||||
all: { field: string; title: string }[],
|
||||
visible?: string[]
|
||||
) {
|
||||
if (!visible?.length) return all
|
||||
const set = new Set(visible)
|
||||
const picked = all.filter((c) => set.has(c.field))
|
||||
return picked.length ? picked : all
|
||||
}
|
||||
|
||||
/** Demo 商品列表导出(Mock 筛行 + 后端 xlsx) */
|
||||
export async function fetchExportDemoProducts(payload: Record<string, any> = {}) {
|
||||
await delay()
|
||||
let list = [...products]
|
||||
if (payload.categoryId) list = list.filter((p) => p.categoryId === payload.categoryId)
|
||||
if (payload.isActive === true || payload.isActive === false) {
|
||||
list = list.filter((p) => p.isActive === payload.isActive)
|
||||
}
|
||||
list = filterKeyword(list, ['name', 'skuCode'], payload.keyword)
|
||||
if (payload.ids?.length) {
|
||||
const idSet = new Set(payload.ids.map(String))
|
||||
list = list.filter((p) => idSet.has(String(p.id)))
|
||||
}
|
||||
const cols = pickExportColumns(DEMO_PRODUCT_EXPORT_COLS, payload.columns)
|
||||
const rows = list.map((row) => ({
|
||||
id: row.id,
|
||||
name: row.name,
|
||||
skuCode: (row as any).skuCode || 'SKU-DEMO',
|
||||
categoryName: row.categoryName,
|
||||
measureValue: (row as any).measureValue ?? 1,
|
||||
unitName: row.unitName,
|
||||
imageUrl: row.imageUrl,
|
||||
isActive: row.isActive ? '启用' : '禁用',
|
||||
createAt: row.createAt
|
||||
}))
|
||||
return request.post({
|
||||
url: '/web/admin/demo/xlsx-export',
|
||||
data: {
|
||||
sheetTitle: '商品数据',
|
||||
filenameStem: '商品数据',
|
||||
columns: cols,
|
||||
rows
|
||||
},
|
||||
responseType: 'blob'
|
||||
})
|
||||
}
|
||||
|
||||
/** Demo 分类列表导出(Mock 筛行 + 后端 xlsx) */
|
||||
export async function fetchExportDemoCategories(payload: Record<string, any> = {}) {
|
||||
await delay()
|
||||
let list = [...categories]
|
||||
if (payload.isActive === true || payload.isActive === false) {
|
||||
list = list.filter((c) => c.isActive === payload.isActive)
|
||||
}
|
||||
list = filterKeyword(list, ['name'], payload.keyword)
|
||||
if (payload.ids?.length) {
|
||||
const idSet = new Set(payload.ids.map(String))
|
||||
list = list.filter((c) => idSet.has(String(c.id)))
|
||||
}
|
||||
const idToName = new Map(list.map((c) => [c.id, c.name]))
|
||||
// parent may be outside filtered set — use full categories map
|
||||
const allNames = new Map(categories.map((c) => [c.id, c.name]))
|
||||
const cols = pickExportColumns(DEMO_CATEGORY_EXPORT_COLS, payload.columns)
|
||||
const rows = list.map((row) => ({
|
||||
id: row.id,
|
||||
name: row.name,
|
||||
parentId: row.parentId || '',
|
||||
parentName: row.parentId ? allNames.get(row.parentId) || '' : '',
|
||||
sortOrder: row.sortOrder,
|
||||
priceCoefficient: row.priceCoefficient ?? 0,
|
||||
isActive: row.isActive ? '启用' : '禁用',
|
||||
imageUrl: row.imageUrl || ''
|
||||
}))
|
||||
return request.post({
|
||||
url: '/web/admin/demo/xlsx-export',
|
||||
data: {
|
||||
sheetTitle: '分类数据',
|
||||
filenameStem: '分类数据',
|
||||
columns: cols,
|
||||
rows
|
||||
},
|
||||
responseType: 'blob'
|
||||
})
|
||||
}
|
||||
|
||||
@@ -55,6 +55,14 @@ export interface PublicIoOrderPage {
|
||||
items: PublicIoOrderItem[]
|
||||
}
|
||||
|
||||
export interface PublicIoOrderHistoryProduct {
|
||||
productName: string
|
||||
model?: string
|
||||
packageSpec?: string
|
||||
quantity?: number | null
|
||||
unit?: string
|
||||
}
|
||||
|
||||
export interface PublicIoOrderHistoryRecord {
|
||||
kind: 'inbound' | 'outbound' | string
|
||||
orderId: string
|
||||
@@ -66,6 +74,11 @@ export interface PublicIoOrderHistoryRecord {
|
||||
supplierName?: string | null
|
||||
customerName?: string | null
|
||||
vehicleNo?: string | null
|
||||
pickupTime?: string | null
|
||||
deliveryTime?: string | null
|
||||
pickupAddress?: string | null
|
||||
destinationAddress?: string | null
|
||||
products?: PublicIoOrderHistoryProduct[]
|
||||
siteAccessToken?: string | null
|
||||
siteUrl?: string | null
|
||||
status?: string | null
|
||||
|
||||
@@ -64,6 +64,14 @@ export interface PublicUploadResult {
|
||||
fileName?: string
|
||||
}
|
||||
|
||||
export interface PublicTransportHistoryProduct {
|
||||
productName: string
|
||||
model?: string
|
||||
packageSpec?: string
|
||||
quantity?: number | null
|
||||
unit?: string
|
||||
}
|
||||
|
||||
export interface PublicTransportHistoryRecord {
|
||||
planId: string
|
||||
planNo: string
|
||||
@@ -75,6 +83,7 @@ export interface PublicTransportHistoryRecord {
|
||||
deliveryTime?: string
|
||||
pickupAddress?: string
|
||||
destinationAddress?: string
|
||||
products?: PublicTransportHistoryProduct[]
|
||||
planVoided?: boolean
|
||||
planArchived?: boolean
|
||||
statusLabel?: string
|
||||
|
||||
@@ -1,26 +1,20 @@
|
||||
<template>
|
||||
<div v-if="hasSelection" class="list-export-dropdown">
|
||||
<el-button
|
||||
class="btn-ghost list-export-dropdown__main"
|
||||
:disabled="disabled"
|
||||
:loading="exporting"
|
||||
@click="handleExportFiltered"
|
||||
>
|
||||
<el-icon><Download /></el-icon>
|
||||
导出筛选结果
|
||||
</el-button>
|
||||
<el-dropdown
|
||||
trigger="click"
|
||||
:disabled="disabled || exporting"
|
||||
@command="handleCommand"
|
||||
>
|
||||
<el-button class="btn-ghost list-export-dropdown__caret" :disabled="disabled || exporting">
|
||||
<el-icon><ArrowDown /></el-icon>
|
||||
<el-button class="btn-ghost" :disabled="disabled" :loading="exporting">
|
||||
<el-icon><Download /></el-icon>
|
||||
导出
|
||||
<el-icon class="list-export-dropdown__caret-icon"><ArrowDown /></el-icon>
|
||||
</el-button>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item command="selected" :disabled="!selectedIds?.length">
|
||||
导出已选
|
||||
<el-dropdown-item command="filtered">导出筛选结果</el-dropdown-item>
|
||||
<el-dropdown-item command="selected" :disabled="!selectedCount">
|
||||
{{ selectedCount ? `导出已选(${selectedCount})` : '导出已选' }}
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
@@ -40,7 +34,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { computed, ref } from 'vue'
|
||||
import { ArrowDown, Download } from '@element-plus/icons-vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import {
|
||||
@@ -77,6 +71,8 @@
|
||||
|
||||
const exporting = ref(false)
|
||||
|
||||
const selectedCount = computed(() => props.selectedIds?.length || 0)
|
||||
|
||||
function buildPayload(withIds: boolean): ListExportPayload {
|
||||
const payload: ListExportPayload = { ...props.getFilterParams() }
|
||||
if (props.visibleColumns?.length) {
|
||||
@@ -111,6 +107,7 @@
|
||||
}
|
||||
|
||||
function handleCommand(cmd: string) {
|
||||
if (cmd === 'filtered') void runExport(false)
|
||||
if (cmd === 'selected') void runExport(true)
|
||||
}
|
||||
</script>
|
||||
@@ -118,7 +115,7 @@
|
||||
<style scoped lang="scss">
|
||||
.list-export-dropdown {
|
||||
display: inline-flex;
|
||||
align-items: stretch;
|
||||
align-items: center;
|
||||
vertical-align: middle;
|
||||
|
||||
:deep(.el-button) {
|
||||
@@ -129,16 +126,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
.list-export-dropdown__main {
|
||||
border-top-right-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
margin-right: -1px;
|
||||
}
|
||||
|
||||
.list-export-dropdown__caret {
|
||||
border-top-left-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
min-width: 36px;
|
||||
padding: 0 10px;
|
||||
.list-export-dropdown__caret-icon {
|
||||
margin-left: 4px;
|
||||
}
|
||||
</style>
|
||||
@@ -36,6 +36,7 @@
|
||||
value-format="YYYY-MM-DD"
|
||||
clearable
|
||||
style="width: 100%"
|
||||
@change="handlePlanDateChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="归属公司" prop="belongsCompany">
|
||||
@@ -383,14 +384,18 @@
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="pl-plan-logistics-block">
|
||||
<div class="pl-plan-logistics-block-head">
|
||||
<div class="pl-plan-logistics-block-wrap">
|
||||
<el-button
|
||||
class="pl-plan-logistics-swap-btn"
|
||||
text
|
||||
type="primary"
|
||||
@click="handleSwapPickupDelivery"
|
||||
>
|
||||
<el-icon><Sort /></el-icon>取送货互换
|
||||
</el-button>
|
||||
<div class="pl-plan-logistics-block">
|
||||
<h4 class="pl-plan-subtitle">送货信息</h4>
|
||||
<el-button text type="primary" @click="handleSwapPickupDelivery">
|
||||
<el-icon><Sort /></el-icon>取送货互换
|
||||
</el-button>
|
||||
</div>
|
||||
<div class="form-grid-2">
|
||||
<div class="form-grid-2">
|
||||
<el-form-item label="送货地址" class="form-col-span-2">
|
||||
<PlAddressWithLibrary
|
||||
v-model="formData.destinationAddress"
|
||||
@@ -415,6 +420,7 @@
|
||||
<el-form-item label="注意事项" class="form-col-span-2">
|
||||
<el-input v-model="formData.destinationNotes" placeholder="送货注意事项" />
|
||||
</el-form-item>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1339,21 +1345,12 @@ function resetForm() {
|
||||
basicFormRef.value?.clearValidate()
|
||||
}
|
||||
|
||||
/** 新增时:取货/送达时间默认跟随计划日期;已手改则不覆盖 */
|
||||
watch(
|
||||
() => formData.planDate,
|
||||
(next, prev) => {
|
||||
if (isEdit.value || isCopy.value) return
|
||||
const nextVal = next || ''
|
||||
const prevVal = prev || ''
|
||||
if (!formData.pickupTime || formData.pickupTime === prevVal) {
|
||||
formData.pickupTime = nextVal
|
||||
}
|
||||
if (!formData.deliveryTime || formData.deliveryTime === prevVal) {
|
||||
formData.deliveryTime = nextVal
|
||||
}
|
||||
}
|
||||
)
|
||||
/** 计划日期变更时,取货/送货时间始终跟随;二者仍可单独调整 */
|
||||
function handlePlanDateChange(val: string | null) {
|
||||
const nextVal = val || ''
|
||||
formData.pickupTime = nextVal
|
||||
formData.deliveryTime = nextVal
|
||||
}
|
||||
|
||||
function handleOpened() {
|
||||
loadMasterData().then(async () => {
|
||||
@@ -1792,17 +1789,6 @@ async function handleSubmit(mode: 'save' | 'saveAndArrange' = 'save') {
|
||||
margin: 0 0 10px;
|
||||
}
|
||||
|
||||
.pl-plan-logistics-block-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.pl-plan-logistics-block-head .pl-plan-subtitle {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.pl-plan-party-select-row {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
@@ -1873,12 +1859,30 @@ async function handleSubmit(mode: 'save' | 'saveAndArrange' = 'save') {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.pl-plan-logistics-row {
|
||||
margin-top: 34px;
|
||||
}
|
||||
|
||||
.pl-plan-party-row .pl-plan-party-block,
|
||||
.pl-plan-logistics-row .pl-plan-logistics-block {
|
||||
.pl-plan-logistics-row .pl-plan-logistics-block,
|
||||
.pl-plan-logistics-row .pl-plan-logistics-block-wrap {
|
||||
flex: 1 1 0;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.pl-plan-logistics-block-wrap {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.pl-plan-logistics-swap-btn {
|
||||
position: absolute;
|
||||
top: -32px;
|
||||
right: 0;
|
||||
z-index: 1;
|
||||
height: 26px;
|
||||
padding: 0 4px;
|
||||
}
|
||||
|
||||
.pl-plan-flow-bridge {
|
||||
flex: 0 0 88px;
|
||||
display: flex;
|
||||
@@ -1934,10 +1938,6 @@ async function handleSubmit(mode: 'save' | 'saveAndArrange' = 'save') {
|
||||
margin: 0 0 8px;
|
||||
}
|
||||
|
||||
.pl-plan-logistics-block-head {
|
||||
margin: 0 0 8px;
|
||||
}
|
||||
|
||||
:deep(.el-form-item) {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
@@ -88,7 +88,27 @@ dbAxios.interceptors.request.use(async (config: InternalAxiosRequestConfig) => {
|
||||
})
|
||||
|
||||
dbAxios.interceptors.response.use(
|
||||
(res) => res.data,
|
||||
async (res) => {
|
||||
if (res.config.responseType === 'blob') {
|
||||
const contentType = String(res.headers['content-type'] || '')
|
||||
if (contentType.includes('application/json')) {
|
||||
const text = await (res.data as Blob).text()
|
||||
try {
|
||||
const json = JSON.parse(text) as { detail?: string; msg?: string }
|
||||
const message =
|
||||
typeof json.detail === 'string'
|
||||
? json.detail
|
||||
: json.msg || '导出失败'
|
||||
ElMessage.error(message)
|
||||
return Promise.reject(new Error(message))
|
||||
} catch {
|
||||
/* 非 JSON blob 直出 */
|
||||
}
|
||||
}
|
||||
return res.data
|
||||
}
|
||||
return res.data
|
||||
},
|
||||
async (err) => {
|
||||
const status = err.response?.status
|
||||
const config = err.config as RetryableConfig | undefined
|
||||
@@ -142,16 +162,36 @@ dbAxios.interceptors.response.use(
|
||||
|
||||
const showErrorMessage = config?.showErrorMessage !== false
|
||||
if (showErrorMessage) {
|
||||
const detail = err.response?.data?.detail
|
||||
const message =
|
||||
typeof detail === 'string'
|
||||
? detail
|
||||
: Array.isArray(detail)
|
||||
? detail.map((x: { msg?: string }) => x.msg).filter(Boolean).join(';')
|
||||
: err.response?.data?.msg
|
||||
let message: string | undefined
|
||||
const data = err.response?.data
|
||||
if (data instanceof Blob) {
|
||||
try {
|
||||
const text = await data.text()
|
||||
const json = JSON.parse(text) as { detail?: string; msg?: string }
|
||||
message = typeof json.detail === 'string' ? json.detail : json.msg
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
} else {
|
||||
const detail = data?.detail
|
||||
message =
|
||||
typeof detail === 'string'
|
||||
? detail
|
||||
: Array.isArray(detail)
|
||||
? detail.map((x: { msg?: string }) => x.msg).filter(Boolean).join(';')
|
||||
: data?.msg
|
||||
}
|
||||
ElMessage.error(message || err.message || '网络异常')
|
||||
}
|
||||
return Promise.reject(err)
|
||||
return Promise.reject(
|
||||
err instanceof Error && err.message
|
||||
? err
|
||||
: new Error(
|
||||
(typeof err.response?.data?.detail === 'string'
|
||||
? err.response.data.detail
|
||||
: err.message) || '网络异常'
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -185,6 +185,14 @@
|
||||
<el-icon><CircleClose /></el-icon>标为无效
|
||||
</el-button>
|
||||
|
||||
<ListExportDropdown
|
||||
:export-fn="exportClue"
|
||||
:get-filter-params="getClueExportFilterParams"
|
||||
:has-selection="true"
|
||||
:selected-ids="ids.map(String)"
|
||||
:visible-columns="clueExportColumns"
|
||||
filename="线索列表.xlsx"
|
||||
/>
|
||||
<ListToolbarEndActions
|
||||
:loading="loading"
|
||||
:column-defs="listColumnDefs"
|
||||
@@ -504,6 +512,7 @@
|
||||
import AssignForm from './assignForm.vue'
|
||||
import InvalidForm from './invalidForm.vue'
|
||||
import DetailDrawer from './detailDrawer.vue'
|
||||
import ListExportDropdown from '@/components/business/list-export-dropdown/index.vue'
|
||||
import ListToolbarEndActions from '@/components/business/list-toolbar-end-actions/index.vue'
|
||||
import ListQuickSearchInput from '@/components/business/list-quick-search-input/index.vue'
|
||||
import { usePersistedListColumns } from '@/composables/usePersistedListColumns'
|
||||
@@ -579,12 +588,29 @@
|
||||
]
|
||||
})
|
||||
|
||||
const clueExportColumns = computed(() => {
|
||||
const cols = [...visibleColumns.value]
|
||||
if (!cols.includes('company')) cols.unshift('company')
|
||||
return cols
|
||||
})
|
||||
|
||||
function getClueExportFilterParams() {
|
||||
const params = buildQueryParams()
|
||||
delete params.pageNum
|
||||
delete params.pageSize
|
||||
if (activeView.value === 'mine') params.viewMode = 'mine'
|
||||
else if (activeView.value === 'toFollow') params.viewType = 'toFollow'
|
||||
else if (activeView.value === 'today') params.viewType = 'todayNew'
|
||||
else if (activeView.value === 'converted') params.viewType = 'converted'
|
||||
return params
|
||||
}
|
||||
|
||||
|
||||
const importDialogVisible = ref(false)
|
||||
const importFileList = ref<UploadFile[]>([])
|
||||
const importLoading = ref(false)
|
||||
const importUpdateSupport = ref(false)
|
||||
const templateLoading = ref(false)
|
||||
const exportLoading = ref(false)
|
||||
|
||||
const canConvertSelected = computed(() => {
|
||||
if (selectedRows.value.length !== 1) return false
|
||||
@@ -852,29 +878,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function handleExport() {
|
||||
exportLoading.value = true
|
||||
try {
|
||||
const params = buildQueryParams()
|
||||
const res = await exportClue(params)
|
||||
const blob =
|
||||
res instanceof Blob
|
||||
? res
|
||||
: new Blob([res as BlobPart], {
|
||||
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
||||
})
|
||||
const url = URL.createObjectURL(blob)
|
||||
const link = document.createElement('a')
|
||||
link.href = url
|
||||
link.download = `线索列表_${new Date().getTime()}.xlsx`
|
||||
link.click()
|
||||
URL.revokeObjectURL(url)
|
||||
} catch {
|
||||
ElMessage.error('导出失败')
|
||||
} finally {
|
||||
exportLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
watchDebounced(
|
||||
() => ({ ...searchForm, dateRange: dateRange.value }),
|
||||
|
||||
@@ -130,9 +130,14 @@
|
||||
>
|
||||
<el-icon><Upload /></el-icon>上传扫描件
|
||||
</el-button>
|
||||
<el-button class="btn-ghost" :loading="exportLoading" @click="handleExport">
|
||||
<el-icon><Download /></el-icon>导出
|
||||
</el-button>
|
||||
<ListExportDropdown
|
||||
:export-fn="exportContract"
|
||||
:get-filter-params="getContractExportFilterParams"
|
||||
:has-selection="true"
|
||||
:selected-ids="ids.map(String)"
|
||||
:visible-columns="contractExportColumns"
|
||||
filename="合同列表.xlsx"
|
||||
/>
|
||||
|
||||
<ListToolbarEndActions
|
||||
:loading="loading"
|
||||
@@ -562,6 +567,7 @@ import {
|
||||
openCrmAttachmentPreviewInNewTab
|
||||
} from '@/utils/crm-attachment'
|
||||
import ListToolbarDateShortcuts from '@/components/core/list-toolbar-date-shortcuts/index.vue'
|
||||
import ListExportDropdown from '@/components/business/list-export-dropdown/index.vue'
|
||||
import ListToolbarEndActions from '@/components/business/list-toolbar-end-actions/index.vue'
|
||||
import ListColumnStatsRow from '@/components/business/list-column-stats-row/index.vue'
|
||||
import { usePersistedListColumns } from '@/composables/usePersistedListColumns'
|
||||
@@ -578,7 +584,6 @@ const isManageMode = computed(() => props.mode === 'manage')
|
||||
const pageTitle = computed(() => (isManageMode.value ? '合同管理' : '合同审批'))
|
||||
|
||||
const userStore = useUserStore();
|
||||
const exportLoading = ref(false);
|
||||
|
||||
/** 当前登录用户 ID(后端 current-user 返回 id,兼容历史 userId 字段) */
|
||||
const resolveCurrentUserId = (): string => {
|
||||
@@ -1108,29 +1113,26 @@ const handleView = (row?: ContractVO) => {
|
||||
contractDetailRef.value?.open(targetRow.id);
|
||||
};
|
||||
|
||||
/** 导出按钮操作 */
|
||||
const handleExport = async () => {
|
||||
exportLoading.value = true;
|
||||
try {
|
||||
const res = await exportContract({ ...queryParams.value });
|
||||
const blob =
|
||||
res instanceof Blob
|
||||
? res
|
||||
: new Blob([res as any], {
|
||||
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
||||
});
|
||||
const url = URL.createObjectURL(blob);
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.download = `contract_${new Date().getTime()}.xlsx`;
|
||||
link.click();
|
||||
URL.revokeObjectURL(url);
|
||||
} catch (error) {
|
||||
console.error('导出失败:', error);
|
||||
} finally {
|
||||
exportLoading.value = false;
|
||||
const contractExportColumns = computed(() => {
|
||||
const cols = [...visibleColumns.value]
|
||||
if (!cols.includes('contractNo')) cols.unshift('contractNo')
|
||||
if (!cols.includes('contractName')) {
|
||||
const idx = cols.indexOf('contractNo')
|
||||
cols.splice(idx + 1, 0, 'contractName')
|
||||
}
|
||||
};
|
||||
return cols
|
||||
})
|
||||
|
||||
function getContractExportFilterParams() {
|
||||
const params: Record<string, any> = { ...queryParams.value }
|
||||
if (dateRange.value && dateRange.value.length === 2) {
|
||||
params.signDateBegin = dateRange.value[0]
|
||||
params.signDateEnd = dateRange.value[1]
|
||||
}
|
||||
delete params.pageNum
|
||||
delete params.pageSize
|
||||
return params
|
||||
}
|
||||
|
||||
/** 提交审批(单条) */
|
||||
const handleSubmit = (row?: ContractVO) => {
|
||||
|
||||
@@ -70,13 +70,14 @@
|
||||
@search="handleQuickSearch"
|
||||
@clear="handleQuickSearchClear"
|
||||
/>
|
||||
<el-button
|
||||
class="btn-ghost"
|
||||
:loading="exportLoading"
|
||||
@click="handleExport"
|
||||
>
|
||||
<el-icon><Download /></el-icon>导出
|
||||
</el-button>
|
||||
<ListExportDropdown
|
||||
:export-fn="exportApproval"
|
||||
:get-filter-params="getApprovalExportFilterParams"
|
||||
:has-selection="true"
|
||||
:selected-ids="selectedRows.map((r) => String(r.id))"
|
||||
:visible-columns="approvalExportColumns"
|
||||
filename="合同审批记录.xlsx"
|
||||
/>
|
||||
|
||||
<ListToolbarEndActions
|
||||
:loading="loading"
|
||||
@@ -266,6 +267,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="ContractApproval">
|
||||
import ListExportDropdown from '@/components/business/list-export-dropdown/index.vue'
|
||||
import ListToolbarEndActions from '@/components/business/list-toolbar-end-actions/index.vue'
|
||||
import { usePersistedListColumns } from '@/composables/usePersistedListColumns'
|
||||
import { ref, reactive, computed, onMounted, watch } from 'vue'
|
||||
@@ -319,6 +321,24 @@
|
||||
resetToDefault
|
||||
} = usePersistedListColumns('crm_contract_approval_visible_columns', listColumnDefs)
|
||||
|
||||
const approvalExportColumns = computed(() => {
|
||||
const cols = [...visibleColumns.value]
|
||||
if (!cols.includes('contractNo')) cols.unshift('contractNo')
|
||||
return cols
|
||||
})
|
||||
|
||||
function getApprovalExportFilterParams() {
|
||||
const params: Record<string, any> = { ...queryParams.value }
|
||||
if (dateRange.value && dateRange.value.length === 2) {
|
||||
params.beginTime = dateRange.value[0]
|
||||
params.endTime = dateRange.value[1]
|
||||
}
|
||||
delete params.pageNum
|
||||
delete params.pageSize
|
||||
return params
|
||||
}
|
||||
|
||||
|
||||
const queryParams = ref<ApprovalQuery>({
|
||||
pageNum: 1,
|
||||
pageSize: 20,
|
||||
@@ -356,7 +376,6 @@
|
||||
const loading = ref(true)
|
||||
const selectedRows = ref<ApprovalVO[]>([])
|
||||
const submitLoading = ref(false)
|
||||
const exportLoading = ref(false)
|
||||
const total = ref(0)
|
||||
const dateRange = ref<[string, string]>()
|
||||
|
||||
@@ -538,36 +557,6 @@
|
||||
viewDialog.data = res.data
|
||||
}
|
||||
|
||||
const handleExport = async () => {
|
||||
if (dateRange.value && dateRange.value.length === 2) {
|
||||
queryParams.value.beginTime = dateRange.value[0]
|
||||
queryParams.value.endTime = dateRange.value[1]
|
||||
} else {
|
||||
queryParams.value.beginTime = undefined
|
||||
queryParams.value.endTime = undefined
|
||||
}
|
||||
exportLoading.value = true
|
||||
try {
|
||||
const res = await exportApproval(queryParams.value)
|
||||
const blob =
|
||||
res instanceof Blob
|
||||
? res
|
||||
: new Blob([res as BlobPart], {
|
||||
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
||||
})
|
||||
const url = URL.createObjectURL(blob)
|
||||
const link = document.createElement('a')
|
||||
link.href = url
|
||||
link.download = `contract_approval_${new Date().getTime()}.xlsx`
|
||||
link.click()
|
||||
URL.revokeObjectURL(url)
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
$modal.msgError('导出失败')
|
||||
} finally {
|
||||
exportLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getList()
|
||||
|
||||
@@ -119,13 +119,14 @@
|
||||
>
|
||||
<el-icon><RefreshLeft /></el-icon>撤回
|
||||
</el-button>
|
||||
<el-button
|
||||
class="btn-ghost"
|
||||
:loading="exportLoading"
|
||||
@click="handleExport"
|
||||
>
|
||||
<el-icon><Download /></el-icon>导出
|
||||
</el-button>
|
||||
<ListExportDropdown
|
||||
:export-fn="exportChange"
|
||||
:get-filter-params="getChangeExportFilterParams"
|
||||
:has-selection="true"
|
||||
:selected-ids="selectedRows.map((r) => String(r.id))"
|
||||
:visible-columns="changeExportColumns"
|
||||
filename="合同变更.xlsx"
|
||||
/>
|
||||
|
||||
<ListToolbarEndActions
|
||||
:loading="loading"
|
||||
@@ -402,6 +403,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="ContractChange">
|
||||
import ListExportDropdown from '@/components/business/list-export-dropdown/index.vue'
|
||||
import ListToolbarEndActions from '@/components/business/list-toolbar-end-actions/index.vue'
|
||||
import { usePersistedListColumns } from '@/composables/usePersistedListColumns'
|
||||
import { ref, reactive, computed, onMounted } from 'vue'
|
||||
@@ -472,6 +474,24 @@
|
||||
resetToDefault
|
||||
} = usePersistedListColumns('crm_contract_change_visible_columns', listColumnDefs)
|
||||
|
||||
const changeExportColumns = computed(() => {
|
||||
const cols = [...visibleColumns.value]
|
||||
if (!cols.includes('contractNo')) cols.unshift('contractNo')
|
||||
return cols
|
||||
})
|
||||
|
||||
function getChangeExportFilterParams() {
|
||||
const params: Record<string, any> = { ...queryParams.value }
|
||||
if (dateRange.value && dateRange.value.length === 2) {
|
||||
params.beginTime = dateRange.value[0]
|
||||
params.endTime = dateRange.value[1]
|
||||
}
|
||||
delete params.pageNum
|
||||
delete params.pageSize
|
||||
return params
|
||||
}
|
||||
|
||||
|
||||
const queryParams = ref<ChangeQuery>({
|
||||
pageNum: 1,
|
||||
pageSize: 20,
|
||||
@@ -505,7 +525,6 @@
|
||||
const selectedRows = ref<ChangeVO[]>([])
|
||||
const loading = ref(true)
|
||||
const submitLoading = ref(false)
|
||||
const exportLoading = ref(false)
|
||||
const total = ref(0)
|
||||
const ids = ref<number[]>([])
|
||||
const dateRange = ref<[string, string]>()
|
||||
@@ -757,36 +776,6 @@
|
||||
await refreshViewDialogIfOpen()
|
||||
}
|
||||
|
||||
const handleExport = async () => {
|
||||
if (dateRange.value && dateRange.value.length === 2) {
|
||||
queryParams.value.beginTime = dateRange.value[0]
|
||||
queryParams.value.endTime = dateRange.value[1]
|
||||
} else {
|
||||
queryParams.value.beginTime = undefined
|
||||
queryParams.value.endTime = undefined
|
||||
}
|
||||
exportLoading.value = true
|
||||
try {
|
||||
const res = await exportChange(queryParams.value)
|
||||
const blob =
|
||||
res instanceof Blob
|
||||
? res
|
||||
: new Blob([res as BlobPart], {
|
||||
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
||||
})
|
||||
const url = URL.createObjectURL(blob)
|
||||
const link = document.createElement('a')
|
||||
link.href = url
|
||||
link.download = `contract_change_${new Date().getTime()}.xlsx`
|
||||
link.click()
|
||||
URL.revokeObjectURL(url)
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
$modal.msgError('导出失败')
|
||||
} finally {
|
||||
exportLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const cancel = () => {
|
||||
dialog.visible = false
|
||||
|
||||
@@ -75,13 +75,14 @@
|
||||
>
|
||||
<el-icon><Delete /></el-icon>删除
|
||||
</el-button>
|
||||
<el-button
|
||||
class="btn-ghost"
|
||||
:loading="exportLoading"
|
||||
@click="handleExport"
|
||||
>
|
||||
<el-icon><Download /></el-icon>导出
|
||||
</el-button>
|
||||
<ListExportDropdown
|
||||
:export-fn="exportTemplate"
|
||||
:get-filter-params="getTemplateExportFilterParams"
|
||||
:has-selection="true"
|
||||
:selected-ids="selectedRows.map((r) => String(r.id))"
|
||||
:visible-columns="templateExportColumns"
|
||||
filename="合同模板.xlsx"
|
||||
/>
|
||||
|
||||
<ListToolbarEndActions
|
||||
:loading="loading"
|
||||
@@ -332,6 +333,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="ContractTemplate">
|
||||
import ListExportDropdown from '@/components/business/list-export-dropdown/index.vue'
|
||||
import ListToolbarEndActions from '@/components/business/list-toolbar-end-actions/index.vue'
|
||||
import { usePersistedListColumns } from '@/composables/usePersistedListColumns'
|
||||
import { ref, reactive, computed, onMounted } from 'vue'
|
||||
@@ -397,6 +399,20 @@
|
||||
resetToDefault
|
||||
} = usePersistedListColumns('crm_contract_template_visible_columns', listColumnDefs)
|
||||
|
||||
const templateExportColumns = computed(() => {
|
||||
const cols = [...visibleColumns.value]
|
||||
if (!cols.includes('templateName')) cols.unshift('templateName')
|
||||
return cols
|
||||
})
|
||||
|
||||
function getTemplateExportFilterParams() {
|
||||
const params: Record<string, any> = { ...queryParams.value }
|
||||
delete params.pageNum
|
||||
delete params.pageSize
|
||||
return params
|
||||
}
|
||||
|
||||
|
||||
const queryParams = ref<TemplateQuery>({
|
||||
pageNum: 1,
|
||||
pageSize: 20,
|
||||
@@ -428,7 +444,6 @@
|
||||
const selectedRows = ref<TemplateVO[]>([])
|
||||
const loading = ref(true)
|
||||
const submitLoading = ref(false)
|
||||
const exportLoading = ref(false)
|
||||
const total = ref(0)
|
||||
const ids = ref<number[]>([])
|
||||
|
||||
@@ -618,29 +633,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
const handleExport = async () => {
|
||||
exportLoading.value = true
|
||||
try {
|
||||
const res = await exportTemplate(queryParams.value)
|
||||
const blob =
|
||||
res instanceof Blob
|
||||
? res
|
||||
: new Blob([res as BlobPart], {
|
||||
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
||||
})
|
||||
const url = URL.createObjectURL(blob)
|
||||
const link = document.createElement('a')
|
||||
link.href = url
|
||||
link.download = `contract_template_${new Date().getTime()}.xlsx`
|
||||
link.click()
|
||||
URL.revokeObjectURL(url)
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
$modal.msgError('导出失败')
|
||||
} finally {
|
||||
exportLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const cancel = () => {
|
||||
dialog.visible = false
|
||||
|
||||
@@ -161,6 +161,14 @@
|
||||
放入公海
|
||||
</el-button>
|
||||
|
||||
<ListExportDropdown
|
||||
:export-fn="fetchExportCustomer"
|
||||
:get-filter-params="getCustomerExportFilterParams"
|
||||
:has-selection="true"
|
||||
:selected-ids="selectedRows.map((r) => String(r.id))"
|
||||
:visible-columns="customerExportColumns"
|
||||
filename="客户列表.xlsx"
|
||||
/>
|
||||
<ListToolbarEndActions
|
||||
:loading="loading"
|
||||
:column-defs="listColumnDefs"
|
||||
@@ -681,6 +689,7 @@ import TransferForm from './TransferDialog.vue'
|
||||
import IntoPoolForm from './IntoPoolDialog.vue'
|
||||
import FollowForm from './FollowFormDialog.vue'
|
||||
import DealForm from './DealDialog.vue'
|
||||
import ListExportDropdown from '@/components/business/list-export-dropdown/index.vue'
|
||||
import ListToolbarEndActions from '@/components/business/list-toolbar-end-actions/index.vue'
|
||||
import ListQuickSearchInput from '@/components/business/list-quick-search-input/index.vue'
|
||||
import { usePersistedListColumns } from '@/composables/usePersistedListColumns'
|
||||
@@ -780,6 +789,23 @@ const {
|
||||
]
|
||||
})
|
||||
|
||||
const customerExportColumns = computed(() => {
|
||||
const cols = [...visibleColumns.value]
|
||||
if (!cols.includes('customerName')) cols.unshift('customerName')
|
||||
return cols
|
||||
})
|
||||
|
||||
function getCustomerExportFilterParams() {
|
||||
const params = buildQueryParams()
|
||||
delete params.page
|
||||
delete params.pageSize
|
||||
if (activeView.value && activeView.value !== 'all') {
|
||||
params.viewMode = activeView.value
|
||||
}
|
||||
return params
|
||||
}
|
||||
|
||||
|
||||
// 子组件引用
|
||||
const customerFormRef = ref<InstanceType<typeof CustomerForm>>()
|
||||
const customerDetailRef = ref<InstanceType<typeof CustomerDetail>>()
|
||||
@@ -986,24 +1012,6 @@ async function handleDelete(row?: any) {
|
||||
await getList()
|
||||
}
|
||||
|
||||
/** 导出按钮操作 */
|
||||
async function handleExport() {
|
||||
try {
|
||||
const params = buildQueryParams()
|
||||
const res = await fetchExportCustomer(params)
|
||||
const blob = new Blob([res as any], {
|
||||
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||
})
|
||||
const url = URL.createObjectURL(blob)
|
||||
const link = document.createElement('a')
|
||||
link.href = url
|
||||
link.download = `客户列表_${new Date().getTime()}.xlsx`
|
||||
link.click()
|
||||
URL.revokeObjectURL(url)
|
||||
} catch {
|
||||
ElMessage.error('导出失败')
|
||||
}
|
||||
}
|
||||
|
||||
watchDebounced(
|
||||
() => ({ ...searchForm, dateRange: dateRange.value }),
|
||||
|
||||
@@ -92,6 +92,14 @@
|
||||
手动执行
|
||||
</el-button>
|
||||
|
||||
<ListExportDropdown
|
||||
:export-fn="fetchExportPoolRules"
|
||||
:get-filter-params="getPoolRuleExportFilterParams"
|
||||
:has-selection="true"
|
||||
:selected-ids="selectedRows.map((r) => String(r.id))"
|
||||
:visible-columns="poolRuleExportColumns"
|
||||
filename="公海规则.xlsx"
|
||||
/>
|
||||
<ListToolbarEndActions
|
||||
:loading="loading"
|
||||
:column-defs="listColumnDefs"
|
||||
@@ -227,6 +235,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { watchDebounced } from '@vueuse/core'
|
||||
import ListExportDropdown from '@/components/business/list-export-dropdown/index.vue'
|
||||
import ListToolbarEndActions from '@/components/business/list-toolbar-end-actions/index.vue'
|
||||
import ListQuickSearchInput from '@/components/business/list-quick-search-input/index.vue'
|
||||
import { usePersistedListColumns } from '@/composables/usePersistedListColumns'
|
||||
@@ -247,6 +256,7 @@ import {
|
||||
fetchDeletePoolRule,
|
||||
fetchTogglePoolRule,
|
||||
fetchExecutePoolRules,
|
||||
fetchExportPoolRules,
|
||||
} from '@/api/crm/pool'
|
||||
import RuleDialog from './components/RuleDialog.vue'
|
||||
import { canDeleteByCreator, filterDeletableByCreator, allRowsDeletableByCreator, CREATOR_DELETE_TIP } from '@/utils/creator-delete'
|
||||
@@ -276,6 +286,22 @@ const {
|
||||
resetToDefault
|
||||
} = usePersistedListColumns('crm_pool_rule_visible_columns', listColumnDefs)
|
||||
|
||||
const poolRuleExportColumns = computed(() => {
|
||||
const cols = [...visibleColumns.value]
|
||||
if (!cols.includes('poolName')) cols.unshift('poolName')
|
||||
return cols
|
||||
})
|
||||
|
||||
function getPoolRuleExportFilterParams() {
|
||||
const quickKeyword = quickSearchKeyword.value?.trim()
|
||||
return {
|
||||
poolName: quickKeyword || searchForm.poolName || undefined,
|
||||
ruleType: searchForm.ruleType || undefined,
|
||||
status: searchForm.status || undefined
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 数据列表
|
||||
const ruleList = ref<any[]>([])
|
||||
const loading = ref(false)
|
||||
|
||||
@@ -71,6 +71,14 @@
|
||||
分配客户
|
||||
</el-button>
|
||||
|
||||
<ListExportDropdown
|
||||
:export-fn="fetchExportPoolCustomers"
|
||||
:get-filter-params="getPoolExportFilterParams"
|
||||
:has-selection="true"
|
||||
:selected-ids="ids"
|
||||
:visible-columns="poolExportColumns"
|
||||
filename="公海客户.xlsx"
|
||||
/>
|
||||
<ListToolbarEndActions
|
||||
:loading="loading"
|
||||
:column-defs="listColumnDefs"
|
||||
@@ -536,7 +544,8 @@ import {
|
||||
More,
|
||||
} from '@element-plus/icons-vue'
|
||||
import { useDictStore } from '@/store/modules/dict'
|
||||
import { fetchPoolCustomerList } from '@/api/crm/pool'
|
||||
import { fetchPoolCustomerList, fetchExportPoolCustomers } from '@/api/crm/pool'
|
||||
import ListExportDropdown from '@/components/business/list-export-dropdown/index.vue'
|
||||
import ListToolbarEndActions from '@/components/business/list-toolbar-end-actions/index.vue'
|
||||
import ListQuickSearchInput from '@/components/business/list-quick-search-input/index.vue'
|
||||
import { usePersistedListColumns } from '@/composables/usePersistedListColumns'
|
||||
@@ -623,6 +632,24 @@ const {
|
||||
]
|
||||
})
|
||||
|
||||
const poolExportColumns = computed(() => {
|
||||
const cols = [...visibleColumns.value]
|
||||
if (!cols.includes('customerName')) cols.unshift('customerName')
|
||||
if (!cols.includes('customerNo')) cols.unshift('customerNo')
|
||||
return cols
|
||||
})
|
||||
|
||||
function getPoolExportFilterParams() {
|
||||
const quickKeyword = quickSearchKeyword.value?.trim()
|
||||
const filterKeyword =
|
||||
searchForm.customerName?.trim() || searchForm.customerNo?.trim() || undefined
|
||||
return {
|
||||
keyword: quickKeyword || filterKeyword,
|
||||
level: searchForm.level || undefined
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const claimDialogRef = ref()
|
||||
const assignDialogRef = ref()
|
||||
const customerDetailRef = ref()
|
||||
|
||||
@@ -135,6 +135,14 @@
|
||||
<el-icon><User /></el-icon>批量分配
|
||||
</el-button>
|
||||
|
||||
<ListExportDropdown
|
||||
:export-fn="exportOpportunity"
|
||||
:get-filter-params="getOpportunityExportFilterParams"
|
||||
:has-selection="true"
|
||||
:selected-ids="ids.map(String)"
|
||||
:visible-columns="opportunityExportColumns"
|
||||
filename="商机列表.xlsx"
|
||||
/>
|
||||
<ListToolbarEndActions
|
||||
:loading="loading"
|
||||
:column-defs="listColumnDefs"
|
||||
@@ -587,6 +595,7 @@
|
||||
import BatchUpdateStageDialog from './components/BatchUpdateStageDialog.vue'
|
||||
import BatchUpdateDateDialog from './components/BatchUpdateDateDialog.vue'
|
||||
import OpportunityDetailDrawer from './components/OpportunityDetailDrawer.vue'
|
||||
import ListExportDropdown from '@/components/business/list-export-dropdown/index.vue'
|
||||
import ListToolbarEndActions from '@/components/business/list-toolbar-end-actions/index.vue'
|
||||
import { usePersistedListColumns } from '@/composables/usePersistedListColumns'
|
||||
|
||||
@@ -594,7 +603,6 @@
|
||||
const router = useRouter()
|
||||
|
||||
const selectedRows = ref<OpportunityVO[]>([])
|
||||
const exportLoading = ref(false)
|
||||
const assignLoading = ref(false)
|
||||
|
||||
// 列配置
|
||||
@@ -841,23 +849,6 @@
|
||||
await getList()
|
||||
}
|
||||
|
||||
/** 导出按钮操作 */
|
||||
const handleExport = async () => {
|
||||
exportLoading.value = true
|
||||
try {
|
||||
const blob = await exportOpportunity({ ...queryParams.value })
|
||||
const url = window.URL.createObjectURL(blob as Blob)
|
||||
const link = document.createElement('a')
|
||||
link.href = url
|
||||
link.download = `商机数据_${Date.now()}.xlsx`
|
||||
link.click()
|
||||
window.URL.revokeObjectURL(url)
|
||||
} catch {
|
||||
ElMessage.error('导出失败')
|
||||
} finally {
|
||||
exportLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 变更阶段 */
|
||||
const handleChangeStage = (row?: OpportunityVO) => {
|
||||
|
||||
@@ -111,6 +111,14 @@
|
||||
<el-icon><CircleClose /></el-icon>作废
|
||||
</el-button>
|
||||
|
||||
<ListExportDropdown
|
||||
:export-fn="exportQuote"
|
||||
:get-filter-params="getQuoteExportFilterParams"
|
||||
:has-selection="true"
|
||||
:selected-ids="ids.map(String)"
|
||||
:visible-columns="quoteExportColumns"
|
||||
filename="报价单列表.xlsx"
|
||||
/>
|
||||
<ListToolbarEndActions
|
||||
:loading="loading"
|
||||
:column-defs="listColumnDefs"
|
||||
@@ -867,6 +875,7 @@ import { fitRichTextTablesToMaxWidth } from '@/utils/tinymce-table-excel-resize'
|
||||
import QuoteFormDialog from './components/QuoteFormDialog.vue'
|
||||
import QuoteDetailDialog from './components/QuoteDetailDialog.vue'
|
||||
import ListToolbarDateShortcuts from '@/components/core/list-toolbar-date-shortcuts/index.vue'
|
||||
import ListExportDropdown from '@/components/business/list-export-dropdown/index.vue'
|
||||
import ListToolbarEndActions from '@/components/business/list-toolbar-end-actions/index.vue'
|
||||
import { usePersistedListColumns } from '@/composables/usePersistedListColumns'
|
||||
import {
|
||||
@@ -1001,6 +1010,20 @@ const {
|
||||
]
|
||||
})
|
||||
|
||||
const quoteExportColumns = computed(() => {
|
||||
const cols = [...visibleColumns.value]
|
||||
if (!cols.includes('quoteNo')) cols.unshift('quoteNo')
|
||||
return cols
|
||||
})
|
||||
|
||||
function getQuoteExportFilterParams() {
|
||||
const params = { ...buildListQueryParams() }
|
||||
delete params.pageNum
|
||||
delete params.pageSize
|
||||
return params
|
||||
}
|
||||
|
||||
|
||||
type QuoteViewName = 'all' | 'submitted' | 'accepted'
|
||||
|
||||
interface TabFilterState {
|
||||
@@ -1054,7 +1077,6 @@ const loading = ref(true);
|
||||
const total = ref(0);
|
||||
const expandLoadingMap = reactive<Record<string, boolean>>({});
|
||||
const selectedRows = ref<QuoteVO[]>([]);
|
||||
const exportLoading = ref(false);
|
||||
const single = ref(true);
|
||||
const multiple = ref(true);
|
||||
const ids = ref<(string | number)[]>([]);
|
||||
@@ -1463,29 +1485,6 @@ const handleQuickSearchClear = () => {
|
||||
handleQuickSearch();
|
||||
};
|
||||
|
||||
/** 导出按钮操作 */
|
||||
const handleExport = async () => {
|
||||
exportLoading.value = true;
|
||||
try {
|
||||
const res = await exportQuote(buildListQueryParams())
|
||||
const blob =
|
||||
res instanceof Blob
|
||||
? res
|
||||
: new Blob([res as any], {
|
||||
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
||||
})
|
||||
const url = URL.createObjectURL(blob)
|
||||
const link = document.createElement('a')
|
||||
link.href = url
|
||||
link.download = `quote_${new Date().getTime()}.xlsx`
|
||||
link.click()
|
||||
URL.revokeObjectURL(url)
|
||||
} catch (error) {
|
||||
console.error('导出失败:', error)
|
||||
} finally {
|
||||
exportLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
/** 报价分析 */
|
||||
const handleStatistics = () => {
|
||||
|
||||
@@ -112,10 +112,14 @@
|
||||
<el-icon><Delete /></el-icon>
|
||||
删除
|
||||
</el-button>
|
||||
<el-button class="btn-ghost" :loading="exportLoading" @click="handleExport">
|
||||
<el-icon><Download /></el-icon>
|
||||
导出 Excel
|
||||
</el-button>
|
||||
<ListExportDropdown
|
||||
:export-fn="(payload) => inventoryApi.export(payload)"
|
||||
:get-filter-params="getInventoryExportFilterParams"
|
||||
:has-selection="true"
|
||||
:selected-ids="selectedRows.map((r) => String(r.id))"
|
||||
:visible-columns="inventoryExportColumns"
|
||||
filename="出入库列表.xlsx"
|
||||
/>
|
||||
|
||||
<ListToolbarEndActions
|
||||
:loading="loading"
|
||||
@@ -536,6 +540,7 @@
|
||||
import DbCrmCustomerSelect from '@/components/business/db/DbCrmCustomerSelect.vue'
|
||||
import ListToolbarDateShortcuts from '@/components/core/list-toolbar-date-shortcuts/index.vue'
|
||||
import ListQuickSearchInput from '@/components/business/list-quick-search-input/index.vue'
|
||||
import ListExportDropdown from '@/components/business/list-export-dropdown/index.vue'
|
||||
import ListToolbarEndActions from '@/components/business/list-toolbar-end-actions/index.vue'
|
||||
import ListColumnStatsRow from '@/components/business/list-column-stats-row/index.vue'
|
||||
import type { StatsRowColumn } from '@/utils/list-column-stats'
|
||||
@@ -592,6 +597,17 @@
|
||||
resetToDefault
|
||||
} = usePersistedListColumns('db-inventory-list-columns', listColumnDefs)
|
||||
|
||||
const inventoryExportColumns = computed(() => {
|
||||
const cols = [...visibleColumns.value]
|
||||
if (!cols.includes('planNo')) cols.unshift('planNo')
|
||||
return cols
|
||||
})
|
||||
|
||||
function getInventoryExportFilterParams() {
|
||||
return { ...buildListQueryParams() }
|
||||
}
|
||||
|
||||
|
||||
const inventoryTableWrapRef = ref<HTMLElement | null>(null)
|
||||
const inventoryStatsRowRef = ref<{ resync?: () => void } | null>(null)
|
||||
|
||||
@@ -631,7 +647,6 @@
|
||||
return (row as any)?.[prop]
|
||||
}
|
||||
|
||||
const exportLoading = ref(false)
|
||||
const saving = ref(false)
|
||||
const tableData = ref<InventoryRecord[]>([])
|
||||
const allRecords = ref<InventoryRecord[]>([])
|
||||
@@ -1114,52 +1129,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
const handleExport = async () => {
|
||||
exportLoading.value = true
|
||||
try {
|
||||
let records = await inventoryApi.list(buildListQueryParams())
|
||||
if (records.length === 0) {
|
||||
$modal.msgWarning('没有数据可导出')
|
||||
return
|
||||
}
|
||||
|
||||
let list = [...records]
|
||||
if (sortProp.value && sortOrder.value) {
|
||||
list.sort((a, b) => compare(a, b, sortProp.value, sortOrder.value!))
|
||||
}
|
||||
|
||||
const headers = ['日期', '往来单位', '业务类型', '产品', '数量(吨)', '车牌号', '计划号', '备注']
|
||||
const csvContent = [
|
||||
headers.join(','),
|
||||
...list.map((r) =>
|
||||
[
|
||||
r.date,
|
||||
r.partnerCompany,
|
||||
r.businessType,
|
||||
r.product,
|
||||
r.quantity,
|
||||
r.vehicleNo,
|
||||
r.planNo || '',
|
||||
r.remarks || ''
|
||||
].join(',')
|
||||
)
|
||||
].join('\n')
|
||||
const blob = new Blob(['\uFEFF' + csvContent], { type: 'text/csv;charset=utf-8' })
|
||||
const url = URL.createObjectURL(blob)
|
||||
const a = document.createElement('a')
|
||||
a.href = url
|
||||
const now = new Date()
|
||||
const stamp = `${now.getFullYear()}${pad2(now.getMonth() + 1)}${pad2(now.getDate())}_${pad2(now.getHours())}${pad2(now.getMinutes())}${pad2(now.getSeconds())}`
|
||||
a.download = `出入库列表_${stamp}.csv`
|
||||
a.click()
|
||||
URL.revokeObjectURL(url)
|
||||
$modal.msgSuccess('导出成功')
|
||||
} catch (error) {
|
||||
console.error('导出失败', error)
|
||||
} finally {
|
||||
exportLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
useDbInstantSearch(
|
||||
() => ({
|
||||
|
||||
@@ -112,10 +112,14 @@
|
||||
<el-icon><Delete /></el-icon>
|
||||
删除
|
||||
</el-button>
|
||||
<el-button class="btn-ghost" :loading="exportLoading" @click="handleExport">
|
||||
<el-icon><Download /></el-icon>
|
||||
导出 Excel
|
||||
</el-button>
|
||||
<ListExportDropdown
|
||||
:export-fn="(payload) => planApi.export(payload)"
|
||||
:get-filter-params="getPlanExportFilterParams"
|
||||
:has-selection="true"
|
||||
:selected-ids="selectedRows.map((r) => String(r.id))"
|
||||
:visible-columns="planExportColumns"
|
||||
filename="计划列表.xlsx"
|
||||
/>
|
||||
|
||||
<ListToolbarEndActions
|
||||
:loading="loading"
|
||||
@@ -488,6 +492,7 @@
|
||||
import DbCrmCustomerSelect from '@/components/business/db/DbCrmCustomerSelect.vue'
|
||||
import ListToolbarDateShortcuts from '@/components/core/list-toolbar-date-shortcuts/index.vue'
|
||||
import ListQuickSearchInput from '@/components/business/list-quick-search-input/index.vue'
|
||||
import ListExportDropdown from '@/components/business/list-export-dropdown/index.vue'
|
||||
import ListToolbarEndActions from '@/components/business/list-toolbar-end-actions/index.vue'
|
||||
import ListColumnStatsRow from '@/components/business/list-column-stats-row/index.vue'
|
||||
import type { StatsRowColumn } from '@/utils/list-column-stats'
|
||||
@@ -534,6 +539,17 @@
|
||||
resetToDefault
|
||||
} = usePersistedListColumns('db-plans-list-columns', listColumnDefs)
|
||||
|
||||
const planExportColumns = computed(() => {
|
||||
const cols = [...visibleColumns.value]
|
||||
if (!cols.includes('planNo')) cols.unshift('planNo')
|
||||
return cols
|
||||
})
|
||||
|
||||
function getPlanExportFilterParams() {
|
||||
return { ...buildListQueryParams() }
|
||||
}
|
||||
|
||||
|
||||
const plansTableWrapRef = ref<HTMLElement | null>(null)
|
||||
const plansStatsRowRef = ref<{ resync?: () => void } | null>(null)
|
||||
|
||||
@@ -565,7 +581,6 @@
|
||||
}
|
||||
|
||||
const loading = ref(false)
|
||||
const exportLoading = ref(false)
|
||||
const saving = ref(false)
|
||||
const tableData = ref<Plan[]>([])
|
||||
const total = ref(0)
|
||||
@@ -993,64 +1008,6 @@
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
|
||||
const handleExport = async () => {
|
||||
exportLoading.value = true
|
||||
try {
|
||||
const records = await fetchFilteredPlans()
|
||||
if (records.length === 0) {
|
||||
$modal.msgWarning('没有数据可导出')
|
||||
return
|
||||
}
|
||||
|
||||
const headers = [
|
||||
'计划号',
|
||||
'往来单位',
|
||||
'计划日期',
|
||||
'起始日期',
|
||||
'截止日期',
|
||||
'产品',
|
||||
'规格',
|
||||
'车牌号',
|
||||
'计划量',
|
||||
'已执行量',
|
||||
'执行状态',
|
||||
'备注'
|
||||
]
|
||||
const csvContent = [
|
||||
headers.join(','),
|
||||
...records.map((r) =>
|
||||
[
|
||||
r.planNo,
|
||||
r.customerName,
|
||||
r.planDate,
|
||||
r.startDate,
|
||||
r.endDate,
|
||||
r.product,
|
||||
r.specification,
|
||||
r.vehicleNo,
|
||||
r.plannedQuantity,
|
||||
r.executedQuantity || 0,
|
||||
r.executionStatus,
|
||||
r.remarks || ''
|
||||
].join(',')
|
||||
)
|
||||
].join('\n')
|
||||
const blob = new Blob(['\uFEFF' + csvContent], { type: 'text/csv;charset=utf-8' })
|
||||
const url = URL.createObjectURL(blob)
|
||||
const a = document.createElement('a')
|
||||
a.href = url
|
||||
const now = new Date()
|
||||
const stamp = `${now.getFullYear()}${pad2(now.getMonth() + 1)}${pad2(now.getDate())}_${pad2(now.getHours())}${pad2(now.getMinutes())}${pad2(now.getSeconds())}`
|
||||
a.download = `计划列表_${stamp}.csv`
|
||||
a.click()
|
||||
URL.revokeObjectURL(url)
|
||||
$modal.msgSuccess('导出成功')
|
||||
} catch (error) {
|
||||
console.error('导出失败', error)
|
||||
} finally {
|
||||
exportLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
useDbInstantSearch(
|
||||
() => ({
|
||||
|
||||
@@ -91,10 +91,14 @@
|
||||
<el-icon><Close /></el-icon>
|
||||
作废
|
||||
</el-button>
|
||||
<el-button class="btn-ghost" :loading="exportLoading" @click="handleExport">
|
||||
<el-icon><Download /></el-icon>
|
||||
导出 Excel
|
||||
</el-button>
|
||||
<ListExportDropdown
|
||||
:export-fn="(payload) => weighingApi.export(payload)"
|
||||
:get-filter-params="getWeighingExportFilterParams"
|
||||
:has-selection="true"
|
||||
:selected-ids="selectedRows.map((r) => String(r.id))"
|
||||
:visible-columns="weighingExportColumns"
|
||||
filename="地磅单列表.xlsx"
|
||||
/>
|
||||
|
||||
<ListToolbarEndActions
|
||||
:loading="loading"
|
||||
@@ -527,6 +531,7 @@
|
||||
import DbCrmCustomerSelect from '@/components/business/db/DbCrmCustomerSelect.vue'
|
||||
import ListToolbarDateShortcuts from '@/components/core/list-toolbar-date-shortcuts/index.vue'
|
||||
import ListQuickSearchInput from '@/components/business/list-quick-search-input/index.vue'
|
||||
import ListExportDropdown from '@/components/business/list-export-dropdown/index.vue'
|
||||
import ListToolbarEndActions from '@/components/business/list-toolbar-end-actions/index.vue'
|
||||
import ListColumnStatsRow from '@/components/business/list-column-stats-row/index.vue'
|
||||
import type { StatsRowColumn } from '@/utils/list-column-stats'
|
||||
@@ -581,6 +586,17 @@
|
||||
resetToDefault
|
||||
} = usePersistedListColumns('db-weighing-list-columns', listColumnDefs)
|
||||
|
||||
const weighingExportColumns = computed(() => {
|
||||
const cols = [...visibleColumns.value]
|
||||
if (!cols.includes('planNo')) cols.unshift('planNo')
|
||||
return cols
|
||||
})
|
||||
|
||||
function getWeighingExportFilterParams() {
|
||||
return { ...buildListQueryParams() }
|
||||
}
|
||||
|
||||
|
||||
const weighingTableWrapRef = ref<HTMLElement | null>(null)
|
||||
const weighingStatsRowRef = ref<{ resync?: () => void } | null>(null)
|
||||
|
||||
@@ -609,7 +625,6 @@
|
||||
})
|
||||
|
||||
const loading = ref(false)
|
||||
const exportLoading = ref(false)
|
||||
const saving = ref(false)
|
||||
const allData = ref<WeighingRecord[]>([])
|
||||
const total = ref(0)
|
||||
@@ -1075,60 +1090,6 @@
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
|
||||
const handleExport = async () => {
|
||||
exportLoading.value = true
|
||||
try {
|
||||
const exportRecords = await weighingApi.list(buildListQueryParams())
|
||||
if (exportRecords.length === 0) {
|
||||
$modal.msgWarning('没有数据可导出')
|
||||
return
|
||||
}
|
||||
|
||||
const headers = [
|
||||
'车牌号',
|
||||
'货物名称',
|
||||
'业务类型',
|
||||
'毛重(kg)',
|
||||
'皮重(kg)',
|
||||
'净重(kg)',
|
||||
'毛重时间',
|
||||
'皮重时间',
|
||||
'供货单位',
|
||||
'收货单位'
|
||||
]
|
||||
const csvContent = [
|
||||
headers.join(','),
|
||||
...exportRecords.map((r) =>
|
||||
[
|
||||
r.vehicleNo,
|
||||
r.cargoName,
|
||||
r.businessType,
|
||||
r.grossWeight,
|
||||
r.tareWeight,
|
||||
r.netWeight,
|
||||
r.grossWeightTime,
|
||||
r.tareWeightTime,
|
||||
r.supplier,
|
||||
r.consignee
|
||||
].join(',')
|
||||
)
|
||||
].join('\n')
|
||||
const blob = new Blob(['\uFEFF' + csvContent], { type: 'text/csv;charset=utf-8' })
|
||||
const url = URL.createObjectURL(blob)
|
||||
const a = document.createElement('a')
|
||||
a.href = url
|
||||
const now = new Date()
|
||||
const stamp = `${now.getFullYear()}${pad2(now.getMonth() + 1)}${pad2(now.getDate())}_${pad2(now.getHours())}${pad2(now.getMinutes())}${pad2(now.getSeconds())}`
|
||||
a.download = `地磅单列表_${stamp}.csv`
|
||||
a.click()
|
||||
URL.revokeObjectURL(url)
|
||||
$modal.msgSuccess('导出成功')
|
||||
} catch (error) {
|
||||
console.error('导出失败', error)
|
||||
} finally {
|
||||
exportLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const clearInventoryRouteQuery = () => {
|
||||
if (!route.query.inboundOutboundId) return
|
||||
|
||||
@@ -47,16 +47,14 @@
|
||||
批量删除
|
||||
</el-button>
|
||||
|
||||
<ArtExcelExport
|
||||
:data="batchExportData"
|
||||
:list-total="pagination.total"
|
||||
:load-all-data="loadAllCategoriesForExport"
|
||||
:filename="categoryExportFilename"
|
||||
sheetName="分类数据"
|
||||
:columns="categoryExportColumns"
|
||||
>
|
||||
批量导出
|
||||
</ArtExcelExport>
|
||||
<ListExportDropdown
|
||||
:export-fn="fetchExportDemoCategories"
|
||||
:get-filter-params="getDemoCategoryExportFilterParams"
|
||||
:has-selection="true"
|
||||
:selected-ids="selectedRows.map((r) => String(r.id))"
|
||||
:visible-columns="demoCategoryExportVisibleColumns"
|
||||
filename="分类数据.xlsx"
|
||||
/>
|
||||
|
||||
<ArtExcelExport
|
||||
:data="categoryImportTemplateData"
|
||||
@@ -510,7 +508,8 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import ListToolbarEndActions from '@/components/business/list-toolbar-end-actions/index.vue'
|
||||
import ListExportDropdown from '@/components/business/list-export-dropdown/index.vue'
|
||||
import ListToolbarEndActions from '@/components/business/list-toolbar-end-actions/index.vue'
|
||||
import AttachmentUploadField from '@/components/business/attachment-upload-field/index.vue'
|
||||
import { useListMediaActive } from '@/composables/useListMediaActive'
|
||||
/* eslint-disable vue/no-v-model-argument */
|
||||
@@ -533,6 +532,7 @@ import ListToolbarEndActions from '@/components/business/list-toolbar-end-action
|
||||
import SystemViewDialog from '@/components/business/system-view-dialog/index.vue'
|
||||
import {
|
||||
fetchCategoryList,
|
||||
fetchExportDemoCategories,
|
||||
fetchCreateCategory,
|
||||
fetchUpdateCategory,
|
||||
fetchDeleteCategory,
|
||||
@@ -595,6 +595,24 @@ import ListToolbarEndActions from '@/components/business/list-toolbar-end-action
|
||||
selectedRows.value = rows
|
||||
}
|
||||
|
||||
const demoCategoryExportVisibleColumns = [
|
||||
'id',
|
||||
'name',
|
||||
'parentId',
|
||||
'parentName',
|
||||
'sortOrder',
|
||||
'priceCoefficient',
|
||||
'isActive',
|
||||
'imageUrl'
|
||||
]
|
||||
|
||||
function getDemoCategoryExportFilterParams() {
|
||||
return {
|
||||
keyword: searchForm.keyword || undefined,
|
||||
isActive: searchForm.isActive === '' ? undefined : searchForm.isActive
|
||||
}
|
||||
}
|
||||
|
||||
const categoryExportFilename = 'base_category_batch_export'
|
||||
const categoryImportTemplateFilename = 'base_category_import_template'
|
||||
|
||||
|
||||
@@ -62,16 +62,14 @@
|
||||
批量删除
|
||||
</el-button>
|
||||
|
||||
<ArtExcelExport
|
||||
:data="batchExportData"
|
||||
:list-total="pagination.total"
|
||||
:load-all-data="loadAllProductsForExport"
|
||||
:filename="productExportFilename"
|
||||
sheetName="商品数据"
|
||||
:columns="productExportColumns"
|
||||
>
|
||||
批量导出
|
||||
</ArtExcelExport>
|
||||
<ListExportDropdown
|
||||
:export-fn="fetchExportDemoProducts"
|
||||
:get-filter-params="getDemoProductExportFilterParams"
|
||||
:has-selection="true"
|
||||
:selected-ids="selectedRows.map((r) => String(r.id))"
|
||||
:visible-columns="demoProductExportVisibleColumns"
|
||||
filename="商品数据.xlsx"
|
||||
/>
|
||||
|
||||
<ListToolbarEndActions :loading="loading" @refresh="loadData" />
|
||||
</div>
|
||||
@@ -925,9 +923,11 @@ import ListToolbarEndActions from '@/components/business/list-toolbar-end-action
|
||||
} from '@element-plus/icons-vue'
|
||||
import { VueDraggable as draggable } from 'vue-draggable-plus'
|
||||
import ProductViewDialog from '@/components/business/product-view-dialog/index.vue'
|
||||
import ListExportDropdown from '@/components/business/list-export-dropdown/index.vue'
|
||||
import AttachmentUploadField from '@/components/business/attachment-upload-field/index.vue'
|
||||
import {
|
||||
fetchProductList,
|
||||
fetchExportDemoProducts,
|
||||
fetchProductDetail,
|
||||
fetchCreateProduct,
|
||||
fetchUpdateProduct,
|
||||
@@ -973,6 +973,26 @@ import ListToolbarEndActions from '@/components/business/list-toolbar-end-action
|
||||
selectedRows.value = rows
|
||||
}
|
||||
|
||||
const demoProductExportVisibleColumns = [
|
||||
'id',
|
||||
'name',
|
||||
'skuCode',
|
||||
'categoryName',
|
||||
'measureValue',
|
||||
'unitName',
|
||||
'imageUrl',
|
||||
'isActive',
|
||||
'createAt'
|
||||
]
|
||||
|
||||
function getDemoProductExportFilterParams() {
|
||||
return {
|
||||
keyword: searchForm.keyword || undefined,
|
||||
categoryId: searchForm.categoryId || undefined,
|
||||
isActive: searchForm.isActive === '' ? undefined : searchForm.isActive
|
||||
}
|
||||
}
|
||||
|
||||
const productExportFilename = 'base_product_batch_export'
|
||||
const formatMeasureCell = (v: unknown) => {
|
||||
if (v == null || v === '') return '-'
|
||||
|
||||
@@ -10,48 +10,29 @@
|
||||
/>
|
||||
|
||||
<div class="filters">
|
||||
<div class="filter-row">
|
||||
<van-field
|
||||
class="date-field"
|
||||
readonly
|
||||
is-link
|
||||
clickable
|
||||
:model-value="pickDate"
|
||||
placeholder="选择日期"
|
||||
@click="openDatePicker"
|
||||
/>
|
||||
<input
|
||||
v-model="keyword"
|
||||
type="search"
|
||||
class="search-input"
|
||||
placeholder="供方/需方"
|
||||
enterkeyhint="search"
|
||||
/>
|
||||
</div>
|
||||
<div class="chips">
|
||||
<button
|
||||
v-for="q in quickOptions"
|
||||
:key="q.offset"
|
||||
type="button"
|
||||
class="chip"
|
||||
:class="{ on: pickDate === q.ymd }"
|
||||
@click="setQuick(q.offset)"
|
||||
>
|
||||
{{ q.label }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<van-popup v-model:show="showDatePicker" position="bottom" round>
|
||||
<van-date-picker
|
||||
v-model="datePickerValue"
|
||||
title="选择日期"
|
||||
<van-calendar
|
||||
class="history-calendar"
|
||||
:poppable="false"
|
||||
:show-confirm="false"
|
||||
:show-title="false"
|
||||
:show-mark="false"
|
||||
:safe-area-inset-bottom="false"
|
||||
switch-mode="month"
|
||||
color="#059669"
|
||||
:row-height="28"
|
||||
:default-date="calendarDate"
|
||||
:min-date="minDate"
|
||||
:max-date="maxDate"
|
||||
@confirm="onDateConfirm"
|
||||
@cancel="showDatePicker = false"
|
||||
@select="onCalendarSelect"
|
||||
/>
|
||||
</van-popup>
|
||||
<input
|
||||
v-model="keyword"
|
||||
type="search"
|
||||
class="search-input"
|
||||
placeholder="车牌号/产品名称"
|
||||
enterkeyhint="search"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<van-empty v-if="errorMessage" :description="errorMessage" />
|
||||
<van-list
|
||||
@@ -72,16 +53,33 @@
|
||||
>
|
||||
<span class="index-tag">{{ index + 1 }}</span>
|
||||
<div class="record-top">
|
||||
<span class="plan-no">{{ row.orderNo || row.planNo || '—' }}</span>
|
||||
<span class="card-title">{{ cardTitle(row) }}</span>
|
||||
<span class="tag" :class="tagClass(row)">{{ row.statusLabel || '进行中' }}</span>
|
||||
</div>
|
||||
<div class="meta">类型:{{ kindLabel(row.kind) }} · 计划:{{ row.planNo || '—' }}</div>
|
||||
<div class="meta">
|
||||
往来:{{ row.partnerCompany || '—' }} · 车牌:{{ row.vehicleNo || '—' }}
|
||||
车牌:<span class="plate-hl">{{ row.vehicleNo || '—' }}</span>
|
||||
</div>
|
||||
<div class="meta">
|
||||
供方:{{ row.supplierName || '—' }} · 需方:{{ row.customerName || '—' }}
|
||||
取货:{{ formatDay(row.pickupTime) }} · 送货:{{ formatDay(row.deliveryTime) }}
|
||||
</div>
|
||||
<div class="meta">
|
||||
路线:{{ row.pickupAddress || '—' }} → {{ row.destinationAddress || '—' }}
|
||||
</div>
|
||||
<template v-if="!(row.products || []).length">
|
||||
<div class="meta">产品:—</div>
|
||||
<div class="meta">规格型号:—</div>
|
||||
<div class="meta">包装规格:—</div>
|
||||
<div class="meta">数量:— · 单位:—</div>
|
||||
</template>
|
||||
<template v-for="(prod, pIndex) in row.products || []" :key="pIndex">
|
||||
<div class="meta">产品:{{ prod.productName || '—' }}</div>
|
||||
<div class="meta">规格型号:{{ prod.model || '—' }}</div>
|
||||
<div class="meta">包装规格:{{ prod.packageSpec || '—' }}</div>
|
||||
<div class="meta">
|
||||
数量:{{ formatQty(prod.quantity) }} · 单位:{{ prod.unit || '—' }}
|
||||
</div>
|
||||
</template>
|
||||
<div class="meta">运输计划号:{{ row.planNo || '—' }}</div>
|
||||
</div>
|
||||
</van-list>
|
||||
</div>
|
||||
@@ -99,9 +97,7 @@
|
||||
|
||||
import 'vant/es/toast/style'
|
||||
import 'vant/es/list/style'
|
||||
import 'vant/es/field/style'
|
||||
import 'vant/es/popup/style'
|
||||
import 'vant/es/date-picker/style'
|
||||
import 'vant/es/calendar/style'
|
||||
import '@/styles/public-mobile-vant.scss'
|
||||
|
||||
const PAGE_SIZE = 20
|
||||
@@ -118,19 +114,18 @@
|
||||
const records = ref<PublicIoOrderHistoryRecord[]>([])
|
||||
const total = ref(0)
|
||||
const page = ref(1)
|
||||
const pickDate = ref(addDays(0))
|
||||
const pickDate = ref(formatYmd(startOfToday()))
|
||||
const calendarDate = ref(startOfToday())
|
||||
const keyword = ref('')
|
||||
const showDatePicker = ref(false)
|
||||
const datePickerValue = ref<string[]>(ymdToParts(pickDate.value))
|
||||
const minDate = new Date(new Date().getFullYear() - 2, 0, 1)
|
||||
const maxDate = new Date(new Date().getFullYear() + 2, 11, 31)
|
||||
let requestSeq = 0
|
||||
|
||||
const quickOptions = [
|
||||
{ label: '今日', offset: 0, ymd: addDays(0) },
|
||||
{ label: '明日', offset: 1, ymd: addDays(1) },
|
||||
{ label: '后天', offset: 2, ymd: addDays(2) }
|
||||
]
|
||||
function startOfToday() {
|
||||
const d = new Date()
|
||||
d.setHours(0, 0, 0, 0)
|
||||
return d
|
||||
}
|
||||
|
||||
function formatYmd(d: Date) {
|
||||
const y = d.getFullYear()
|
||||
@@ -139,34 +134,33 @@
|
||||
return `${y}-${m}-${day}`
|
||||
}
|
||||
|
||||
function addDays(n: number) {
|
||||
const d = new Date()
|
||||
d.setHours(0, 0, 0, 0)
|
||||
d.setDate(d.getDate() + n)
|
||||
return formatYmd(d)
|
||||
function formatDay(value?: string | null) {
|
||||
if (!value) return '—'
|
||||
const normalized = String(value).replace('T', ' ')
|
||||
return normalized.length >= 10 ? normalized.slice(0, 10) : normalized
|
||||
}
|
||||
|
||||
function ymdToParts(ymd: string): string[] {
|
||||
const [y, m, d] = String(ymd || '').split('-')
|
||||
if (!y || !m || !d) return ymdToParts(addDays(0))
|
||||
return [y, m, d]
|
||||
function formatQty(value?: number | null) {
|
||||
if (value == null) return '—'
|
||||
return String(value)
|
||||
}
|
||||
|
||||
function openDatePicker() {
|
||||
datePickerValue.value = ymdToParts(pickDate.value)
|
||||
showDatePicker.value = true
|
||||
function cardTitleDate(row: PublicIoOrderHistoryRecord) {
|
||||
const fromPlan = formatDay(row.planDate)
|
||||
if (fromPlan !== '—') return fromPlan
|
||||
const fromPickup = formatDay(row.pickupTime)
|
||||
if (fromPickup !== '—') return fromPickup
|
||||
const fromDelivery = formatDay(row.deliveryTime)
|
||||
if (fromDelivery !== '—') return fromDelivery
|
||||
return pickDate.value
|
||||
}
|
||||
|
||||
function onDateConfirm({ selectedValues }: { selectedValues: string[] }) {
|
||||
pickDate.value = selectedValues.join('-')
|
||||
showDatePicker.value = false
|
||||
reload()
|
||||
}
|
||||
|
||||
function kindLabel(kind?: string) {
|
||||
if (kind === 'inbound') return '入库'
|
||||
if (kind === 'outbound') return '出库'
|
||||
return '—'
|
||||
function cardTitle(row: PublicIoOrderHistoryRecord) {
|
||||
const names = (row.products || [])
|
||||
.map((p) => (p.productName || '').trim())
|
||||
.filter(Boolean)
|
||||
const productLabel = names.length ? names.join('、') : '—'
|
||||
return `${cardTitleDate(row)} ${productLabel}`
|
||||
}
|
||||
|
||||
function tagClass(row: PublicIoOrderHistoryRecord) {
|
||||
@@ -183,9 +177,11 @@
|
||||
router.push({ name: detailRouteName(), params: { token: token.value } })
|
||||
}
|
||||
|
||||
function setQuick(offset: number) {
|
||||
pickDate.value = addDays(offset)
|
||||
datePickerValue.value = ymdToParts(pickDate.value)
|
||||
function onCalendarSelect(date: Date) {
|
||||
const next = formatYmd(date)
|
||||
if (next === pickDate.value) return
|
||||
pickDate.value = next
|
||||
calendarDate.value = date
|
||||
reload()
|
||||
}
|
||||
|
||||
@@ -283,7 +279,7 @@
|
||||
|
||||
.filters {
|
||||
margin-bottom: 12px;
|
||||
padding: 12px 14px;
|
||||
padding: 4px 4px 10px;
|
||||
border-radius: 12px;
|
||||
background: #fff;
|
||||
border: 1px solid #e2e8f0;
|
||||
@@ -292,78 +288,45 @@
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.filter-row {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1.1fr) minmax(0, 1fr);
|
||||
gap: 8px;
|
||||
max-width: 100%;
|
||||
min-width: 0;
|
||||
}
|
||||
.history-calendar {
|
||||
height: auto;
|
||||
--van-calendar-background: #fff;
|
||||
--van-calendar-header-shadow: none;
|
||||
--van-calendar-header-title-height: 32px;
|
||||
--van-calendar-header-subtitle-font-size: 13px;
|
||||
--van-calendar-weekdays-height: 24px;
|
||||
--van-calendar-weekdays-font-size: 11px;
|
||||
--van-calendar-day-height: 28px;
|
||||
--van-calendar-day-font-size: 12px;
|
||||
--van-calendar-day-margin-bottom: 0;
|
||||
--van-calendar-selected-day-size: 26px;
|
||||
--van-primary-color: #059669;
|
||||
|
||||
.date-field {
|
||||
padding: 0 12px;
|
||||
min-height: 40px;
|
||||
min-width: 0;
|
||||
width: 100%;
|
||||
border: 1px solid #cbd5e1;
|
||||
border-radius: 8px;
|
||||
align-items: center;
|
||||
background: #fff;
|
||||
box-sizing: border-box;
|
||||
|
||||
:deep(.van-field__control) {
|
||||
font-size: 14px;
|
||||
color: #1e293b;
|
||||
height: 40px;
|
||||
line-height: 40px;
|
||||
:deep(.van-calendar__footer) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
:deep(.van-field__right-icon) {
|
||||
margin-right: 0;
|
||||
color: #64748b;
|
||||
:deep(.van-calendar__body) {
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.search-input {
|
||||
display: block;
|
||||
width: 100%;
|
||||
width: calc(100% - 8px);
|
||||
max-width: 100%;
|
||||
min-width: 0;
|
||||
height: 40px;
|
||||
height: 36px;
|
||||
margin: 8px 4px 0;
|
||||
padding: 0 12px;
|
||||
border: 1px solid #cbd5e1;
|
||||
border-radius: 8px;
|
||||
font-size: 14px;
|
||||
color: #1e293b;
|
||||
color: #0f172a;
|
||||
background: #fff;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.chips {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.chip {
|
||||
height: 30px;
|
||||
padding: 0 12px;
|
||||
border-radius: 999px;
|
||||
border: 1px solid #d1fae5;
|
||||
background: #ecfdf5;
|
||||
color: #047857;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.chip.on {
|
||||
background: #059669;
|
||||
border-color: #059669;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.record-card {
|
||||
position: relative;
|
||||
margin-bottom: 10px;
|
||||
@@ -402,7 +365,7 @@
|
||||
padding-left: 4px;
|
||||
}
|
||||
|
||||
.plan-no {
|
||||
.card-title {
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
color: #0f172a;
|
||||
@@ -435,7 +398,7 @@
|
||||
|
||||
.meta {
|
||||
font-size: 13px;
|
||||
color: #64748b;
|
||||
color: #0f172a;
|
||||
line-height: 1.45;
|
||||
word-break: break-all;
|
||||
|
||||
@@ -443,4 +406,10 @@
|
||||
margin-top: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.plate-hl {
|
||||
color: #059669;
|
||||
font-weight: 700;
|
||||
font-size: 15px;
|
||||
}
|
||||
</style>
|
||||
@@ -9,66 +9,68 @@
|
||||
@click-left="goBack"
|
||||
/>
|
||||
|
||||
<van-loading v-if="loading" vertical class="page-loading">加载中...</van-loading>
|
||||
<van-empty v-else-if="errorMessage" :description="errorMessage" />
|
||||
<van-loading v-if="bootLoading" vertical class="page-loading">加载中...</van-loading>
|
||||
<van-empty v-else-if="fatalError" :description="fatalError" />
|
||||
<template v-else>
|
||||
<div class="filters">
|
||||
<div class="filter-row">
|
||||
<van-field
|
||||
class="date-field"
|
||||
readonly
|
||||
is-link
|
||||
clickable
|
||||
:model-value="pickDate"
|
||||
placeholder="选择日期"
|
||||
@click="openDatePicker"
|
||||
/>
|
||||
<div class="chips">
|
||||
<button
|
||||
v-for="q in quickOptions"
|
||||
:key="q.offset"
|
||||
type="button"
|
||||
class="chip"
|
||||
:class="{ on: pickDate === q.ymd }"
|
||||
@click="setQuick(q.offset)"
|
||||
>
|
||||
{{ q.label }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<van-popup v-model:show="showDatePicker" position="bottom" round>
|
||||
<van-date-picker
|
||||
v-model="datePickerValue"
|
||||
title="选择日期"
|
||||
<van-calendar
|
||||
class="history-calendar"
|
||||
:poppable="false"
|
||||
:show-confirm="false"
|
||||
:show-title="false"
|
||||
:show-mark="false"
|
||||
:safe-area-inset-bottom="false"
|
||||
switch-mode="month"
|
||||
color="#059669"
|
||||
:row-height="28"
|
||||
:default-date="calendarDate"
|
||||
:min-date="minDate"
|
||||
:max-date="maxDate"
|
||||
@confirm="onDateConfirm"
|
||||
@cancel="showDatePicker = false"
|
||||
@select="onCalendarSelect"
|
||||
/>
|
||||
</van-popup>
|
||||
|
||||
<van-empty v-if="!records.length" description="暂无运输记录" />
|
||||
<div
|
||||
v-for="(row, index) in records"
|
||||
:key="row.planId"
|
||||
class="record-card"
|
||||
role="button"
|
||||
@click="openRecord(row)"
|
||||
>
|
||||
<span class="index-tag">{{ index + 1 }}</span>
|
||||
<div class="record-top">
|
||||
<span class="plan-no">{{ row.planNo }}</span>
|
||||
<span class="tag" :class="tagClass(row)">{{ row.statusLabel || '进行中' }}</span>
|
||||
</div>
|
||||
<div class="meta">
|
||||
取货:{{ formatDay(row.pickupTime) }} · 送货:{{ formatDay(row.deliveryTime) }}
|
||||
</div>
|
||||
<div class="meta">
|
||||
线路:{{ row.pickupAddress || '—' }} → {{ row.destinationAddress || '—' }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<van-loading v-if="loading" vertical class="list-loading">加载中...</van-loading>
|
||||
<van-empty v-else-if="!records.length" description="暂无运输记录" />
|
||||
<template v-else>
|
||||
<div
|
||||
v-for="(row, index) in records"
|
||||
:key="row.planId"
|
||||
class="record-card"
|
||||
role="button"
|
||||
@click="openRecord(row)"
|
||||
>
|
||||
<span class="index-tag">{{ index + 1 }}</span>
|
||||
<div class="record-top">
|
||||
<span class="card-title">{{ cardTitle(row) }}</span>
|
||||
<span class="tag" :class="tagClass(row)">{{ row.statusLabel || '进行中' }}</span>
|
||||
</div>
|
||||
<div class="meta">
|
||||
车牌:<span class="plate-hl">{{ row.licensePlate || '—' }}</span>
|
||||
</div>
|
||||
<div class="meta">
|
||||
取货:{{ formatDay(row.pickupTime) }} · 送货:{{ formatDay(row.deliveryTime) }}
|
||||
</div>
|
||||
<div class="meta">
|
||||
路线:{{ row.pickupAddress || '—' }} → {{ row.destinationAddress || '—' }}
|
||||
</div>
|
||||
<template v-if="!(row.products || []).length">
|
||||
<div class="meta">产品:—</div>
|
||||
<div class="meta">规格型号:—</div>
|
||||
<div class="meta">包装规格:—</div>
|
||||
<div class="meta">数量:— · 单位:—</div>
|
||||
</template>
|
||||
<template v-for="(prod, pIndex) in row.products || []" :key="pIndex">
|
||||
<div class="meta">产品:{{ prod.productName || '—' }}</div>
|
||||
<div class="meta">规格型号:{{ prod.model || '—' }}</div>
|
||||
<div class="meta">包装规格:{{ prod.packageSpec || '—' }}</div>
|
||||
<div class="meta">
|
||||
数量:{{ formatQty(prod.quantity) }} · 单位:{{ prod.unit || '—' }}
|
||||
</div>
|
||||
</template>
|
||||
<div class="meta">运输计划号:{{ row.planNo || '—' }}</div>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
</div>
|
||||
</PublicMobileShell>
|
||||
@@ -83,29 +85,27 @@
|
||||
import PublicMobileShell from '@/layouts/PublicMobileShell.vue'
|
||||
|
||||
import 'vant/es/toast/style'
|
||||
import 'vant/es/field/style'
|
||||
import 'vant/es/popup/style'
|
||||
import 'vant/es/date-picker/style'
|
||||
import 'vant/es/calendar/style'
|
||||
import '@/styles/public-mobile-vant.scss'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const token = computed(() => String(route.params.token || ''))
|
||||
|
||||
const loading = ref(true)
|
||||
const errorMessage = ref('')
|
||||
const bootLoading = ref(true)
|
||||
const loading = ref(false)
|
||||
const fatalError = ref('')
|
||||
const records = ref<PublicTransportHistoryRecord[]>([])
|
||||
const pickDate = ref(addDays(0))
|
||||
const showDatePicker = ref(false)
|
||||
const datePickerValue = ref<string[]>(ymdToParts(pickDate.value))
|
||||
const pickDate = ref(formatYmd(startOfToday()))
|
||||
const calendarDate = ref(startOfToday())
|
||||
const minDate = new Date(new Date().getFullYear() - 2, 0, 1)
|
||||
const maxDate = new Date(new Date().getFullYear() + 2, 11, 31)
|
||||
|
||||
const quickOptions = [
|
||||
{ label: '今日', offset: 0, ymd: addDays(0) },
|
||||
{ label: '明日', offset: 1, ymd: addDays(1) },
|
||||
{ label: '后天', offset: 2, ymd: addDays(2) }
|
||||
]
|
||||
function startOfToday() {
|
||||
const d = new Date()
|
||||
d.setHours(0, 0, 0, 0)
|
||||
return d
|
||||
}
|
||||
|
||||
function formatYmd(d: Date) {
|
||||
const y = d.getFullYear()
|
||||
@@ -114,36 +114,33 @@
|
||||
return `${y}-${m}-${day}`
|
||||
}
|
||||
|
||||
function addDays(n: number) {
|
||||
const d = new Date()
|
||||
d.setHours(0, 0, 0, 0)
|
||||
d.setDate(d.getDate() + n)
|
||||
return formatYmd(d)
|
||||
}
|
||||
|
||||
function ymdToParts(ymd: string): string[] {
|
||||
const [y, m, d] = String(ymd || '').split('-')
|
||||
if (!y || !m || !d) return ymdToParts(addDays(0))
|
||||
return [y, m, d]
|
||||
}
|
||||
|
||||
function openDatePicker() {
|
||||
datePickerValue.value = ymdToParts(pickDate.value)
|
||||
showDatePicker.value = true
|
||||
}
|
||||
|
||||
function onDateConfirm({ selectedValues }: { selectedValues: string[] }) {
|
||||
pickDate.value = selectedValues.join('-')
|
||||
showDatePicker.value = false
|
||||
load()
|
||||
}
|
||||
|
||||
function formatDay(value?: string) {
|
||||
if (!value) return '—'
|
||||
const normalized = value.replace('T', ' ')
|
||||
return normalized.length >= 10 ? normalized.slice(0, 10) : normalized
|
||||
}
|
||||
|
||||
function formatQty(value?: number | null) {
|
||||
if (value == null) return '—'
|
||||
return String(value)
|
||||
}
|
||||
|
||||
function cardTitleDate(row: PublicTransportHistoryRecord) {
|
||||
const fromPickup = formatDay(row.pickupTime)
|
||||
if (fromPickup !== '—') return fromPickup
|
||||
const fromDelivery = formatDay(row.deliveryTime)
|
||||
if (fromDelivery !== '—') return fromDelivery
|
||||
return pickDate.value
|
||||
}
|
||||
|
||||
function cardTitle(row: PublicTransportHistoryRecord) {
|
||||
const names = (row.products || [])
|
||||
.map((p) => (p.productName || '').trim())
|
||||
.filter(Boolean)
|
||||
const productLabel = names.length ? names.join('、') : '—'
|
||||
return `${cardTitleDate(row)} ${productLabel}`
|
||||
}
|
||||
|
||||
function tagClass(row: PublicTransportHistoryRecord) {
|
||||
if (row.planVoided || row.statusLabel === '已作废') return 'tag--danger'
|
||||
if (row.planArchived || row.statusLabel === '已归档') return 'tag--muted'
|
||||
@@ -154,10 +151,12 @@
|
||||
router.push({ name: 'PublicPlTransportDriver', params: { token: token.value } })
|
||||
}
|
||||
|
||||
function setQuick(offset: number) {
|
||||
pickDate.value = addDays(offset)
|
||||
datePickerValue.value = ymdToParts(pickDate.value)
|
||||
load()
|
||||
function onCalendarSelect(date: Date) {
|
||||
const next = formatYmd(date)
|
||||
if (next === pickDate.value) return
|
||||
pickDate.value = next
|
||||
calendarDate.value = date
|
||||
load({ silentBoot: true })
|
||||
}
|
||||
|
||||
function openRecord(row: PublicTransportHistoryRecord) {
|
||||
@@ -169,30 +168,44 @@
|
||||
router.push({ name: 'PublicPlTransportDriver', params: { token: accessToken } })
|
||||
}
|
||||
|
||||
async function load() {
|
||||
async function load(options?: { silentBoot?: boolean }) {
|
||||
loading.value = true
|
||||
errorMessage.value = ''
|
||||
if (!options?.silentBoot) {
|
||||
bootLoading.value = true
|
||||
fatalError.value = ''
|
||||
}
|
||||
try {
|
||||
const data = await publicTransportApi.getHistory(token.value, pickDate.value)
|
||||
records.value = data?.records || []
|
||||
fatalError.value = ''
|
||||
} catch (e: any) {
|
||||
errorMessage.value = e?.message || '链接无效或已失效'
|
||||
records.value = []
|
||||
if (!options?.silentBoot) {
|
||||
fatalError.value = e?.message || '链接无效或已失效'
|
||||
} else {
|
||||
showFailToast(e?.message || '加载失败')
|
||||
}
|
||||
} finally {
|
||||
loading.value = false
|
||||
bootLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(load)
|
||||
onMounted(() => load())
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.page-loading {
|
||||
.page-loading,
|
||||
.list-loading {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 48px 0;
|
||||
}
|
||||
|
||||
.list-loading {
|
||||
padding: 32px 0;
|
||||
}
|
||||
|
||||
.transport-nav-bar {
|
||||
margin: -12px -16px 12px;
|
||||
border-radius: 0 0 12px 12px;
|
||||
@@ -214,7 +227,7 @@
|
||||
|
||||
.filters {
|
||||
margin-bottom: 12px;
|
||||
padding: 12px 14px;
|
||||
padding: 4px 4px 2px;
|
||||
border-radius: 12px;
|
||||
background: #fff;
|
||||
border: 1px solid #e2e8f0;
|
||||
@@ -223,69 +236,29 @@
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.filter-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
min-width: 0;
|
||||
max-width: 100%;
|
||||
}
|
||||
.history-calendar {
|
||||
height: auto;
|
||||
--van-calendar-background: #fff;
|
||||
--van-calendar-header-shadow: none;
|
||||
--van-calendar-header-title-height: 32px;
|
||||
--van-calendar-header-subtitle-font-size: 13px;
|
||||
--van-calendar-weekdays-height: 24px;
|
||||
--van-calendar-weekdays-font-size: 11px;
|
||||
--van-calendar-day-height: 28px;
|
||||
--van-calendar-day-font-size: 12px;
|
||||
--van-calendar-day-margin-bottom: 0;
|
||||
--van-calendar-selected-day-size: 26px;
|
||||
--van-primary-color: #059669;
|
||||
|
||||
.date-field {
|
||||
flex: 0 1 11.5em;
|
||||
min-width: 0;
|
||||
padding: 0 10px;
|
||||
min-height: 40px;
|
||||
border: 1px solid #cbd5e1;
|
||||
border-radius: 8px;
|
||||
align-items: center;
|
||||
background: #fff;
|
||||
box-sizing: border-box;
|
||||
|
||||
:deep(.van-field__control) {
|
||||
font-size: 14px;
|
||||
color: #1e293b;
|
||||
height: 40px;
|
||||
line-height: 40px;
|
||||
:deep(.van-calendar__footer) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
:deep(.van-field__right-icon) {
|
||||
margin-right: 0;
|
||||
color: #64748b;
|
||||
:deep(.van-calendar__body) {
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.chips {
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
margin-top: 0;
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.chip {
|
||||
height: 30px;
|
||||
padding: 0 10px;
|
||||
border-radius: 999px;
|
||||
border: 1px solid #d1fae5;
|
||||
background: #ecfdf5;
|
||||
color: #047857;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
flex-shrink: 0;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.chip.on {
|
||||
background: #059669;
|
||||
border-color: #059669;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.record-card {
|
||||
position: relative;
|
||||
margin-bottom: 10px;
|
||||
@@ -324,7 +297,7 @@
|
||||
padding-left: 4px;
|
||||
}
|
||||
|
||||
.plan-no {
|
||||
.card-title {
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
color: #0f172a;
|
||||
@@ -357,7 +330,7 @@
|
||||
|
||||
.meta {
|
||||
font-size: 13px;
|
||||
color: #64748b;
|
||||
color: #0f172a;
|
||||
line-height: 1.45;
|
||||
word-break: break-all;
|
||||
|
||||
@@ -365,4 +338,10 @@
|
||||
margin-top: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.plate-hl {
|
||||
color: #059669;
|
||||
font-weight: 700;
|
||||
font-size: 15px;
|
||||
}
|
||||
</style>
|
||||
Reference in new issue
Block a user