| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368 |
- <template>
- <div class="app-page">
- <section class="glass-card section-card">
- <el-form :model="filters" inline class="filter-form">
- <el-form-item label="物流商名称">
- <el-input v-model="filters.name" placeholder="物流商名称" clearable style="width:160px" @keyup.enter="loadData" />
- </el-form-item>
- <el-form-item label="承运渠道">
- <el-select v-model="filters.channel" placeholder="全部" clearable style="width:140px">
- <el-option label="DHL" value="DHL" />
- <el-option label="FedEx" value="FedEx" />
- <el-option label="UPS" value="UPS" />
- <el-option label="顺丰" value="顺丰" />
- <el-option label="云途" value="云途" />
- </el-select>
- </el-form-item>
- <el-form-item label="状态">
- <el-select v-model="filters.status" placeholder="全部" clearable style="width:120px">
- <el-option label="启用" value="启用" />
- <el-option label="停用" value="停用" />
- </el-select>
- </el-form-item>
- <el-form-item>
- <el-button type="primary" @click="loadData">查询</el-button>
- <el-button @click="resetFilters">重置</el-button>
- </el-form-item>
- </el-form>
- </section>
- <section class="glass-card section-card" style="padding:12px 24px">
- <div class="table-toolbar" style="margin-bottom:0">
- <div class="chip-list">
- <el-button type="primary" @click="openDialog()">新建物流商</el-button>
- </div>
- <el-button @click="loadData">刷新</el-button>
- </div>
- </section>
- <section class="glass-card section-card">
- <el-table :data="filteredItems" stripe style="width:100%" v-loading="loading">
- <el-table-column prop="name" label="物流商名称" min-width="140" />
- <el-table-column prop="code" label="代码" width="100" />
- <el-table-column prop="channels" label="承运渠道" min-width="180">
- <template #default="{ row }">
- <el-tag v-for="c in row.channels" :key="c" size="small" style="margin-right:4px">{{ c }}</el-tag>
- </template>
- </el-table-column>
- <el-table-column prop="settlementType" label="结算方式" width="100" />
- <el-table-column prop="avgDays" label="平均时效(天)" width="110" />
- <el-table-column prop="trackingUrl" label="追踪URL" min-width="200" show-overflow-tooltip />
- <el-table-column prop="status" label="状态" width="80">
- <template #default="{ row }">
- <el-tag :type="row.status === '启用' ? 'success' : 'danger'" size="small">{{ row.status }}</el-tag>
- </template>
- </el-table-column>
- <el-table-column prop="updatedAt" label="更新时间" width="160" />
- <el-table-column label="操作" width="200" fixed="right">
- <template #default="{ row }">
- <el-button link type="primary" @click="openDialog(row)">编辑</el-button>
- <el-button link type="primary" @click="openTemplate(row)">运费模板</el-button>
- <el-button link :type="row.status === '启用' ? 'danger' : 'primary'" @click="toggleStatus(row)">{{ row.status === '启用' ? '停用' : '启用' }}</el-button>
- </template>
- </el-table-column>
- <template #empty>
- <el-empty description="暂无数据" />
- </template>
- </el-table>
- </section>
- <el-dialog v-model="dialogVisible" :title="editingItem ? '编辑物流商' : '新建物流商'" width="560px" destroy-on-close>
- <el-form :model="formData" label-width="110px">
- <el-form-item label="物流商名称" required>
- <el-input v-model="formData.name" placeholder="请输入物流商名称" />
- </el-form-item>
- <el-form-item label="物流商代码" required>
- <el-input v-model="formData.code" placeholder="如 DHL/FedEx" style="width:100%" />
- </el-form-item>
- <el-form-item label="承运渠道" required>
- <el-checkbox-group v-model="formData.channels">
- <el-checkbox label="DHL" />
- <el-checkbox label="FedEx" />
- <el-checkbox label="UPS" />
- <el-checkbox label="顺丰" />
- <el-checkbox label="云途" />
- <el-checkbox label="燕文" />
- <el-checkbox label="4PX" />
- </el-checkbox-group>
- </el-form-item>
- <el-form-item label="结算方式" required>
- <el-select v-model="formData.settlementType" placeholder="选择结算方式" style="width:100%">
- <el-option label="按重量" value="按重量" />
- <el-option label="按件数" value="按件数" />
- <el-option label="按体积" value="按体积" />
- <el-option label="包月" value="包月" />
- </el-select>
- </el-form-item>
- <el-form-item label="平均时效(天)">
- <el-input-number v-model="formData.avgDays" :min="1" :max="60" style="width:100%" />
- </el-form-item>
- <el-form-item label="追踪URL模板">
- <el-input v-model="formData.trackingUrl" placeholder="https://track.xxx.com/{trackingNo}" />
- </el-form-item>
- <el-form-item label="联系人">
- <el-input v-model="formData.contact" placeholder="联系人" />
- </el-form-item>
- <el-form-item label="联系电话">
- <el-input v-model="formData.phone" placeholder="电话" />
- </el-form-item>
- <el-form-item label="备注">
- <el-input v-model="formData.remark" type="textarea" :rows="2" placeholder="备注信息" />
- </el-form-item>
- </el-form>
- <template #footer>
- <el-button @click="dialogVisible = false">取消</el-button>
- <el-button type="primary" @click="saveProvider">保存</el-button>
- </template>
- </el-dialog>
- <el-dialog v-model="templateDialogVisible" :title="`运费模板 - ${currentProvider?.name}`" width="700px" destroy-on-close>
- <div style="margin-bottom:16px">
- <el-button type="primary" size="small" @click="openTemplateDialog()">新建模板</el-button>
- </div>
- <el-table :data="templateList" stripe size="small">
- <el-table-column prop="name" label="模板名称" min-width="140" />
- <el-table-column prop="calcType" label="计费方式" width="100">
- <template #default="{ row }">
- <el-tag size="small">{{ row.calcType }}</el-tag>
- </template>
- </el-table-column>
- <el-table-column prop="firstWeight" label="首重" width="80" />
- <el-table-column prop="firstPrice" label="首费" width="80" />
- <el-table-column prop="continueWeight" label="续重" width="80" />
- <el-table-column prop="continuePrice" label="续费" width="80" />
- <el-table-column prop="regions" label="适用地区" min-width="120" show-overflow-tooltip />
- <el-table-column prop="status" label="状态" width="70">
- <template #default="{ row }">
- <el-tag :type="row.status === '启用' ? 'success' : 'danger'" size="small">{{ row.status }}</el-tag>
- </template>
- </el-table-column>
- <el-table-column label="操作" width="100" fixed="right">
- <template #default="{ row }">
- <el-button link type="primary" size="small" @click="editTemplate(row)">编辑</el-button>
- <el-button link type="danger" size="small" @click="deleteTemplate(row)">删除</el-button>
- </template>
- </el-table-column>
- </el-table>
- </el-dialog>
- <el-dialog v-model="templateFormVisible" :title="editingTemplate ? '编辑模板' : '新建模板'" width="500px" destroy-on-close>
- <el-form :model="templateForm" label-width="100px">
- <el-form-item label="模板名称" required>
- <el-input v-model="templateForm.name" placeholder="模板名称" />
- </el-form-item>
- <el-form-item label="计费方式" required>
- <el-select v-model="templateForm.calcType" placeholder="选择计费方式" style="width:100%">
- <el-option label="按重量" value="按重量" />
- <el-option label="按件数" value="按件数" />
- <el-option label="按体积" value="按体积" />
- </el-select>
- </el-form-item>
- <el-form-item label="首重/首件">
- <el-input-number v-model="templateForm.firstWeight" :min="0" style="width:48%" />
- <span style="margin:0 8px">kg/件</span>
- </el-form-item>
- <el-form-item label="首费">
- <el-input-number v-model="templateForm.firstPrice" :min="0" :precision="2" style="width:100%" />
- </el-form-item>
- <el-form-item label="续重/续件">
- <el-input-number v-model="templateForm.continueWeight" :min="0" style="width:48%" />
- <span style="margin:0 8px">kg/件</span>
- </el-form-item>
- <el-form-item label="续费">
- <el-input-number v-model="templateForm.continuePrice" :min="0" :precision="2" style="width:100%" />
- </el-form-item>
- <el-form-item label="适用地区">
- <el-input v-model="templateForm.regions" type="textarea" :rows="2" placeholder="美国,加拿大,英国" />
- </el-form-item>
- </el-form>
- <template #footer>
- <el-button @click="templateFormVisible = false">取消</el-button>
- <el-button type="primary" @click="saveTemplate">保存</el-button>
- </template>
- </el-dialog>
- </div>
- </template>
- <script setup lang="ts">
- import { computed, onMounted, reactive, ref } from 'vue';
- import { ElMessage, ElMessageBox } from 'element-plus';
- interface LogisticsProvider {
- id: string;
- name: string;
- code: string;
- channels: string[];
- settlementType: string;
- avgDays: number;
- trackingUrl: string;
- contact: string;
- phone: string;
- status: string;
- updatedAt: string;
- remark?: string;
- }
- interface ShippingTemplate {
- id: string;
- name: string;
- calcType: string;
- firstWeight: number;
- firstPrice: number;
- continueWeight: number;
- continuePrice: number;
- regions: string;
- status: string;
- }
- const items = ref<LogisticsProvider[]>([
- { id: 'LP001', name: 'DHL国际快递', code: 'DHL', channels: ['DHL'], settlementType: '按重量', avgDays: 5, trackingUrl: 'https://www.dhl.com/track?tracking-id={trackingNo}', contact: 'John', phone: '+1-800-225-5345', status: '启用', updatedAt: '2026-04-15 10:30' },
- { id: 'LP002', name: 'FedEx联邦快递', code: 'FedEx', channels: ['FedEx'], settlementType: '按重量', avgDays: 6, trackingUrl: 'https://www.fedex.com/fedextrack/?trknbr={trackingNo}', contact: 'Mary', phone: '+1-800-463-3339', status: '启用', updatedAt: '2026-04-14 14:20' },
- { id: 'LP003', name: '顺丰速运', code: 'SF', channels: ['顺丰'], settlementType: '按重量', avgDays: 3, trackingUrl: 'https://www.sf-express.com/sf-service-owf/web/en/querytools/track/{trackingNo}', contact: '王强', phone: '95338', status: '启用', updatedAt: '2026-04-10 09:00' },
- { id: 'LP004', name: '云途物流', code: 'YTO', channels: ['云途'], settlementType: '按重量', avgDays: 8, trackingUrl: 'https://www.yuntrack.com/track?trackingNo={trackingNo}', contact: 'Tom', phone: '400-800-6060', status: '停用', updatedAt: '2026-03-28 16:45' }
- ]);
- const templates = ref<ShippingTemplate[]>([
- { id: 'T001', name: 'DHL-美国专线', calcType: '按重量', firstWeight: 0.5, firstPrice: 120, continueWeight: 0.5, continuePrice: 35, regions: '美国,加拿大', status: '启用' },
- { id: 'T002', name: 'DHL-欧洲专线', calcType: '按重量', firstWeight: 0.5, firstPrice: 150, continueWeight: 0.5, continuePrice: 45, regions: '英国,德国,法国,意大利', status: '启用' },
- { id: 'T003', name: 'FedEx-全球优先', calcType: '按重量', firstWeight: 1, firstPrice: 200, continueWeight: 1, continuePrice: 50, regions: '全球', status: '启用' }
- ]);
- const loading = ref(false);
- const dialogVisible = ref(false);
- const templateDialogVisible = ref(false);
- const templateFormVisible = ref(false);
- const editingItem = ref<LogisticsProvider | null>(null);
- const currentProvider = ref<LogisticsProvider | null>(null);
- const editingTemplate = ref<ShippingTemplate | null>(null);
- const filters = ref({ name: '', channel: '', status: '' });
- const formData = reactive({
- name: '',
- code: '',
- channels: [] as string[],
- settlementType: '',
- avgDays: 5,
- trackingUrl: '',
- contact: '',
- phone: '',
- remark: ''
- });
- const templateForm = reactive({
- name: '',
- calcType: '按重量',
- firstWeight: 0.5,
- firstPrice: 0,
- continueWeight: 0.5,
- continuePrice: 0,
- regions: ''
- });
- const templateList = computed(() => {
- if (!currentProvider.value) return [];
- return templates.value;
- });
- const filteredItems = computed(() => {
- return items.value.filter(item => {
- if (filters.value.name && !item.name.includes(filters.value.name)) return false;
- if (filters.value.channel && !item.channels.includes(filters.value.channel)) return false;
- if (filters.value.status && item.status !== filters.value.status) return false;
- return true;
- });
- });
- const loadData = () => {
- loading.value = true;
- setTimeout(() => { loading.value = false; }, 300);
- };
- const resetFilters = () => {
- filters.value = { name: '', channel: '', status: '' };
- };
- const openDialog = (item?: LogisticsProvider) => {
- editingItem.value = item || null;
- if (item) {
- Object.assign(formData, { name: item.name, code: item.code, channels: [...item.channels], settlementType: item.settlementType, avgDays: item.avgDays, trackingUrl: item.trackingUrl, contact: item.contact, phone: item.phone, remark: item.remark || '' });
- } else {
- Object.assign(formData, { name: '', code: '', channels: [], settlementType: '', avgDays: 5, trackingUrl: '', contact: '', phone: '', remark: '' });
- }
- dialogVisible.value = true;
- };
- const saveProvider = () => {
- if (!formData.name || !formData.code || !formData.channels.length) {
- ElMessage.warning('请填写必填项');
- return;
- }
- if (editingItem.value) {
- const idx = items.value.findIndex(i => i.id === editingItem.value!.id);
- if (idx !== -1) items.value[idx] = { ...items.value[idx], ...formData, updatedAt: new Date().toLocaleString() };
- ElMessage.success('物流商已更新');
- } else {
- items.value.push({ id: `LP${String(items.value.length + 1).padStart(3, '0')}`, ...formData, status: '启用', updatedAt: new Date().toLocaleString() });
- ElMessage.success('物流商已创建');
- }
- dialogVisible.value = false;
- };
- const toggleStatus = async (row: LogisticsProvider) => {
- const action = row.status === '启用' ? '停用' : '启用';
- await ElMessageBox.confirm(`确认${action}「${row.name}」?`, `${action}确认`);
- const idx = items.value.findIndex(i => i.id === row.id);
- if (idx !== -1) {
- items.value[idx].status = row.status === '启用' ? '停用' : '启用';
- items.value[idx].updatedAt = new Date().toLocaleString();
- }
- ElMessage.success(`物流商已${action}`);
- };
- const openTemplate = (row: LogisticsProvider) => {
- currentProvider.value = row;
- templateDialogVisible.value = true;
- };
- const openTemplateDialog = () => {
- editingTemplate.value = null;
- Object.assign(templateForm, { name: '', calcType: '按重量', firstWeight: 0.5, firstPrice: 0, continueWeight: 0.5, continuePrice: 0, regions: '' });
- templateFormVisible.value = true;
- };
- const editTemplate = (row: ShippingTemplate) => {
- editingTemplate.value = row;
- Object.assign(templateForm, { ...row });
- templateFormVisible.value = true;
- };
- const saveTemplate = () => {
- if (!templateForm.name) {
- ElMessage.warning('请填写模板名称');
- return;
- }
- if (editingTemplate.value) {
- const idx = templates.value.findIndex(t => t.id === editingTemplate.value!.id);
- if (idx !== -1) templates.value[idx] = { ...templates.value[idx], ...templateForm };
- ElMessage.success('模板已更新');
- } else {
- templates.value.push({ id: `T${String(templates.value.length + 1).padStart(3, '0')}`, ...templateForm, status: '启用' });
- ElMessage.success('模板已创建');
- }
- templateFormVisible.value = false;
- };
- const deleteTemplate = async (row: ShippingTemplate) => {
- await ElMessageBox.confirm('确认删除此模板?', '删除确认');
- const idx = templates.value.findIndex(t => t.id === row.id);
- if (idx !== -1) templates.value.splice(idx, 1);
- ElMessage.success('模板已删除');
- };
- onMounted(loadData);
- </script>
- <style scoped>
- .filter-form :deep(.el-form-item) { margin-bottom: 0; }
- </style>
|