admin_new P0+P1 全批次合并 (84 commits) #2
@ -52,7 +52,7 @@ export const MENU_LABELS: MenuLabel[] = [
|
|||||||
{ url: 'user/user', label: '用户列表', icon: 'User', path: '/user/user', sort: 100 },
|
{ url: 'user/user', label: '用户列表', icon: 'User', path: '/user/user', sort: 100 },
|
||||||
{ url: 'user/enterprise/enterprise', label: '企业列表', icon: 'OfficeBuilding', path: '/user/enterprise', sort: 101 },
|
{ url: 'user/enterprise/enterprise', label: '企业列表', icon: 'OfficeBuilding', path: '/user/enterprise', sort: 101 },
|
||||||
{ url: 'user/enterprise/enterprise-rating', label: '评级', icon: 'Star', path: '/user/enterprise/rating', sort: 102 },
|
{ url: 'user/enterprise/enterprise-rating', label: '评级', icon: 'Star', path: '/user/enterprise/rating', sort: 102 },
|
||||||
{ url: 'user/enterprise/enterprise-level', label: '企业等级管理', icon: 'Medal', path: '/user/enterprise/level', sort: 103 },
|
{ url: 'user/enterprise/enterprise-level', label: '企业等级管理', icon: 'Medal', path: '/user/enterprise/level', sort: 103 }, // 已接真 view W16
|
||||||
{ url: 'user/enterprise/enterprise-score', label: '评分', icon: 'DataAnalysis', path: '/user/enterprise/score', sort: 104 },
|
{ url: 'user/enterprise/enterprise-score', label: '评分', icon: 'DataAnalysis', path: '/user/enterprise/score', sort: 104 },
|
||||||
{ url: 'user/finance/recharge', label: '充值管理', icon: 'Wallet', path: '/user/finance/recharge', sort: 110 },
|
{ url: 'user/finance/recharge', label: '充值管理', icon: 'Wallet', path: '/user/finance/recharge', sort: 110 },
|
||||||
{ url: 'user/finance/recharge-tx', label: '提现管理', icon: 'CreditCard', path: '/user/finance/recharge-tx', sort: 111 },
|
{ url: 'user/finance/recharge-tx', label: '提现管理', icon: 'CreditCard', path: '/user/finance/recharge-tx', sort: 111 },
|
||||||
@ -80,8 +80,12 @@ export const MENU_LABELS: MenuLabel[] = [
|
|||||||
{ url: 'supplier/servicegood', label: '服务商品', icon: 'Box', path: '/supplier/servicegood', sort: 301 },
|
{ url: 'supplier/servicegood', label: '服务商品', icon: 'Box', path: '/supplier/servicegood', sort: 301 },
|
||||||
{ url: 'supplier/activityzone', label: '活动专区', icon: 'Promotion', path: '/supplier/activity', sort: 302 },
|
{ url: 'supplier/activityzone', label: '活动专区', icon: 'Promotion', path: '/supplier/activity', sort: 302 },
|
||||||
{ url: 'supplier/coupon', label: '优惠券', icon: 'Ticket', path: '/supplier/coupon', sort: 303 },
|
{ url: 'supplier/coupon', label: '优惠券', icon: 'Ticket', path: '/supplier/coupon', sort: 303 },
|
||||||
{ url: 'supplier/promoter', label: '推广返佣', icon: 'UserFilled', path: '/supplier/promoter', sort: 304 },
|
{ url: 'supplier/promoter', label: '推广员', icon: 'UserFilled', path: '/supplier/promoter', sort: 304 },
|
||||||
{ url: 'supplier/inquiry/inquiry', label: '询价列表', icon: 'ChatLineSquare', path: '/supplier/inquiry', sort: 305 },
|
{ url: 'supplier/inquiry/inquiry', label: '询价列表', icon: 'ChatLineSquare', path: '/supplier/inquiry', sort: 305 },
|
||||||
|
{ url: 'supplier/ordercomment', label: '订单评价', icon: 'Star', path: '/supplier/ordercomment', sort: 306 },
|
||||||
|
{ url: 'supplier/historylog', label: '浏览足迹', icon: 'View', path: '/supplier/historylog', sort: 307 },
|
||||||
|
{ url: 'supplier/logistics', label: '物流方案', icon: 'Van', path: '/supplier/logistics', sort: 308 },
|
||||||
|
{ url: 'supplier/coupon-scope', label: '优惠券适用范围',icon: 'Filter', path: '/supplier/coupon-scope',sort: 309 },
|
||||||
|
|
||||||
// ===== 系统模块 =====
|
// ===== 系统模块 =====
|
||||||
{ url: 'sys/user', label: '用户管理[后台用户]', icon: 'UserFilled', path: '/sys/user', sort: 400 },
|
{ url: 'sys/user', label: '用户管理[后台用户]', icon: 'UserFilled', path: '/sys/user', sort: 400 },
|
||||||
|
|||||||
117
src/modules/supplier/couponscope/CouponScopeListView.vue
Normal file
117
src/modules/supplier/couponscope/CouponScopeListView.vue
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
/**
|
||||||
|
* F006 优惠券适用范围 - W16
|
||||||
|
*
|
||||||
|
* 沿用老 vue supplier/couponscope.vue 字段
|
||||||
|
*
|
||||||
|
* 端点: 4 URL 探测 (couponscope/page, /list, /search, /queryPage); 404 → FALLBACK seed
|
||||||
|
*/
|
||||||
|
import { ref, reactive, onMounted, computed } from 'vue'
|
||||||
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
|
import { httpGet } from '@/utils/request'
|
||||||
|
import PageHeader from '@/components/PageHeader/PageHeader.vue'
|
||||||
|
import SearchBar from '@/components/SearchBar/SearchBar.vue'
|
||||||
|
import DataTable from '@/components/DataTable/DataTable.vue'
|
||||||
|
|
||||||
|
interface CouponScopeRow { id:string; couponId:string; supplierGoodId:string; sourceType:number; createTime:string }
|
||||||
|
const ENDPOINTS = [
|
||||||
|
'/supplier/couponscope/page',
|
||||||
|
'/supplier/couponscope/list',
|
||||||
|
'/supplier/couponscope/search',
|
||||||
|
'/supplier/couponscope/queryPage'
|
||||||
|
]
|
||||||
|
const FALLBACK: CouponScopeRow[] = [
|
||||||
|
{ id:'CS001', couponId:'C202501', supplierGoodId:'SG101', sourceType:1, createTime:'2026-04-15 09:30:00' },
|
||||||
|
{ id:'CS002', couponId:'C202502', supplierGoodId:'SG102', sourceType:1, createTime:'2026-04-22 14:22:00' },
|
||||||
|
{ id:'CS003', couponId:'C202503', supplierGoodId:'', sourceType:2, createTime:'2026-05-08 11:08:30' },
|
||||||
|
{ id:'CS004', couponId:'C202504', supplierGoodId:'SG104', sourceType:1, createTime:'2026-05-15 16:48:00' },
|
||||||
|
{ id:'CS005', couponId:'C202505', supplierGoodId:'', sourceType:2, createTime:'2026-05-22 09:15:00' },
|
||||||
|
{ id:'CS006', couponId:'C202506', supplierGoodId:'SG106', sourceType:1, createTime:'2026-06-03 10:42:00' },
|
||||||
|
{ id:'CS007', couponId:'C202507', supplierGoodId:'SG107', sourceType:1, createTime:'2026-06-12 13:18:00' },
|
||||||
|
{ id:'CS008', couponId:'C202508', supplierGoodId:'', sourceType:2, createTime:'2026-06-20 11:35:00' },
|
||||||
|
{ id:'CS009', couponId:'C202509', supplierGoodId:'SG109', sourceType:1, createTime:'2026-06-28 14:55:00' },
|
||||||
|
{ id:'CS010', couponId:'C202510', supplierGoodId:'SG110', sourceType:1, createTime:'2026-07-04 09:50:00' },
|
||||||
|
{ id:'CS011', couponId:'C202511', supplierGoodId:'', sourceType:2, createTime:'2026-07-08 16:08:00' },
|
||||||
|
{ id:'CS012', couponId:'C202512', supplierGoodId:'SG112', sourceType:1, createTime:'2026-07-12 11:42:00' }
|
||||||
|
]
|
||||||
|
type Col = { type?: 'status'|'text'|'selection'; prop?: string; label: string; width?: number; slot?: string; fixed?: 'right'|true }
|
||||||
|
const columns: Col[] = [
|
||||||
|
{ type:'selection', label:'', width:50 },
|
||||||
|
{ prop:'id', label:'范围ID', width:100, fixed:true },
|
||||||
|
{ prop:'couponId', label:'优惠券ID', width:130, slot:'couponId' },
|
||||||
|
{ prop:'supplierGoodId', label:'适用商品ID', width:140, slot:'goodId' },
|
||||||
|
{ prop:'sourceType', label:'卷来源', width:100, slot:'src' },
|
||||||
|
{ prop:'createTime', label:'创建时间', width:160, slot:'time' },
|
||||||
|
{ prop:'actions', label:'操作', width:200, slot:'actions', fixed:'right' }
|
||||||
|
]
|
||||||
|
const STATUS_DEFAULTS = [
|
||||||
|
{ value:'', label:'全部' },
|
||||||
|
{ value:'1', label:'供应商来源' },
|
||||||
|
{ value:'2', label:'平台来源' }
|
||||||
|
]
|
||||||
|
const list = ref<CouponScopeRow[]>([]); const total = ref(0); const loading = ref(false); const page = ref(1); const limit = ref(10); const filterKw = ref(''); const statusTab = ref('')
|
||||||
|
const dataSource = ref<'api'|'mock'|'loading'>('loading'); const apiError = ref<string>('')
|
||||||
|
async function load() {
|
||||||
|
loading.value = true; dataSource.value = 'loading'
|
||||||
|
for (const ep of ENDPOINTS) {
|
||||||
|
try {
|
||||||
|
const data: any = await httpGet<any>(ep, { page: page.value, limit: limit.value })
|
||||||
|
const rows = (data?.list || []).map((it: any) => ({ ...it, raw: it }))
|
||||||
|
list.value = rows; total.value = Number(data?.total || rows.length); dataSource.value = 'api'; apiError.value = ''; loading.value = false; return
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
list.value = [...FALLBACK]; total.value = FALLBACK.length; dataSource.value = 'mock'; apiError.value = '后端 API 暂未实装, 已加载前端示例数据'; loading.value = false
|
||||||
|
}
|
||||||
|
const filtered = computed(() => {
|
||||||
|
const kw = filterKw.value.trim().toLowerCase(); let rs = list.value
|
||||||
|
if (statusTab.value) rs = rs.filter(r => String(r.sourceType) === statusTab.value)
|
||||||
|
if (kw) rs = rs.filter(r => String(r.id).includes(kw) || String(r.couponId).includes(kw) || String(r.supplierGoodId).includes(kw))
|
||||||
|
return rs
|
||||||
|
})
|
||||||
|
const searchFields: Array<{ key: string; label: string; type: 'text' }> = [{ key:'id', label:'范围ID', type:'text' },{ key:'couponId', label:'优惠券ID', type:'text' },{ key:'supplierGoodId', label:'商品ID', type:'text' }]
|
||||||
|
const filters = reactive<Record<string, any>>({})
|
||||||
|
function onSearch() { filterKw.value = String(filters.id || filters.couponId || filters.supplierGoodId || ''); page.value = 1 }
|
||||||
|
function onStatusTab(v: string) { statusTab.value = v; page.value = 1 }
|
||||||
|
async function onDelete(row: CouponScopeRow) {
|
||||||
|
try { await ElMessageBox.confirm(`确认删除适用范围「${row.id}」?`, '提示', { type: 'warning' }) } catch { return }
|
||||||
|
ElMessage.success('已删除 (前端示例)')
|
||||||
|
}
|
||||||
|
function onView(row: CouponScopeRow) { ElMessage.info(`详情 ${row.id}`) }
|
||||||
|
function onExport() { ElMessage.success('已发起导出 (前端示例, 共 ' + filtered.value.length + ' 条)') }
|
||||||
|
onMounted(load)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="rr-page">
|
||||||
|
<PageHeader title="优惠券适用范围" sub="优惠券可使用商品范围配置" :data-source="dataSource" :api-error="apiError">
|
||||||
|
<el-button type="primary" @click="onExport">导出明细</el-button>
|
||||||
|
</PageHeader>
|
||||||
|
<el-tabs v-model="statusTab" @tab-change="onStatusTab" class="rr-tabs">
|
||||||
|
<el-tab-pane v-for="t in STATUS_DEFAULTS" :key="t.value" :name="t.value" :label="t.label" />
|
||||||
|
</el-tabs>
|
||||||
|
<SearchBar v-model:filters="filters" :fields="searchFields" @search="onSearch" />
|
||||||
|
<DataTable :columns="columns" :data="filtered" :loading="loading" :page="page" :limit="limit" :total="total" row-key="id"
|
||||||
|
@page-change="(p: number) => { page = p; load() }" @limit-change="(l: number) => { limit = l; page = 1; load() }">
|
||||||
|
<template #couponId="{ row }">
|
||||||
|
<el-link v-if="row.couponId" type="primary" :underline="false" @click="onView(row)">{{ row.couponId }}</el-link>
|
||||||
|
<span v-else class="rr-muted">通用券</span>
|
||||||
|
</template>
|
||||||
|
<template #goodId="{ row }">
|
||||||
|
<span v-if="row.supplierGoodId">{{ row.supplierGoodId }}</span>
|
||||||
|
<span v-else class="rr-muted">全场通用</span>
|
||||||
|
</template>
|
||||||
|
<template #src="{ row }">
|
||||||
|
<el-tag :type="row.sourceType === 1 ? 'primary' : 'success'" size="small">{{ row.sourceType === 1 ? '供应商' : '平台' }}</el-tag>
|
||||||
|
</template>
|
||||||
|
<template #time="{ row }">{{ row.createTime }}</template>
|
||||||
|
<template #actions="{ row }">
|
||||||
|
<el-button type="primary" link @click="onView(row)">详情</el-button>
|
||||||
|
<el-button type="danger" link @click="onDelete(row)">删除</el-button>
|
||||||
|
</template>
|
||||||
|
</DataTable>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<style scoped>
|
||||||
|
.rr-page { padding: 16px; }
|
||||||
|
.rr-tabs { margin-bottom: 12px; }
|
||||||
|
</style>
|
||||||
115
src/modules/supplier/historylog/HistorylogListView.vue
Normal file
115
src/modules/supplier/historylog/HistorylogListView.vue
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
/**
|
||||||
|
* F006 浏览足迹 - W16
|
||||||
|
*
|
||||||
|
* 沿用老 vue supplier/historylog.vue 字段
|
||||||
|
*
|
||||||
|
* 端点: 4 URL 探测 (historylog/page, /list, /search, /queryPage); 404 → FALLBACK seed
|
||||||
|
*/
|
||||||
|
import { ref, reactive, onMounted, computed } from 'vue'
|
||||||
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
|
import { httpGet } from '@/utils/request'
|
||||||
|
import PageHeader from '@/components/PageHeader/PageHeader.vue'
|
||||||
|
import SearchBar from '@/components/SearchBar/SearchBar.vue'
|
||||||
|
import DataTable from '@/components/DataTable/DataTable.vue'
|
||||||
|
|
||||||
|
interface HistoryRow { id:string; userId:string; userType:number; goodType:number; supplierGoodId:string; createTime:string }
|
||||||
|
const ENDPOINTS = [
|
||||||
|
'/supplier/historylog/page',
|
||||||
|
'/supplier/historylog/list',
|
||||||
|
'/supplier/historylog/search',
|
||||||
|
'/supplier/historylog/queryPage'
|
||||||
|
]
|
||||||
|
const FALLBACK: HistoryRow[] = [
|
||||||
|
{ id:'H001', userId:'u10086', userType:1, goodType:1, supplierGoodId:'SG1001', createTime:'2026-07-04 14:22:11' },
|
||||||
|
{ id:'H002', userId:'u10112', userType:1, goodType:2, supplierGoodId:'SG1002', createTime:'2026-07-05 09:45:30' },
|
||||||
|
{ id:'H003', userId:'u10133', userType:2, goodType:1, supplierGoodId:'SG1003', createTime:'2026-07-05 11:18:42' },
|
||||||
|
{ id:'H004', userId:'u10144', userType:1, goodType:1, supplierGoodId:'SG1004', createTime:'2026-07-05 16:02:55' },
|
||||||
|
{ id:'H005', userId:'u10155', userType:2, goodType:2, supplierGoodId:'SG1005', createTime:'2026-07-06 08:30:18' },
|
||||||
|
{ id:'H006', userId:'u10166', userType:1, goodType:1, supplierGoodId:'SG1006', createTime:'2026-07-06 10:55:08' },
|
||||||
|
{ id:'H007', userId:'u10177', userType:1, goodType:2, supplierGoodId:'SG1007', createTime:'2026-07-06 13:42:35' },
|
||||||
|
{ id:'H008', userId:'u10188', userType:2, goodType:1, supplierGoodId:'SG1008', createTime:'2026-07-06 15:18:42' },
|
||||||
|
{ id:'H009', userId:'u10199', userType:1, goodType:1, supplierGoodId:'SG1009', createTime:'2026-07-06 17:33:21' },
|
||||||
|
{ id:'H010', userId:'u10210', userType:1, goodType:2, supplierGoodId:'SG1010', createTime:'2026-07-06 19:08:55' },
|
||||||
|
{ id:'H011', userId:'u10221', userType:2, goodType:1, supplierGoodId:'SG1011', createTime:'2026-07-07 08:22:11' },
|
||||||
|
{ id:'H012', userId:'u10232', userType:1, goodType:1, supplierGoodId:'SG1012', createTime:'2026-07-07 09:48:30' },
|
||||||
|
{ id:'H013', userId:'u10243', userType:1, goodType:2, supplierGoodId:'SG1013', createTime:'2026-07-07 11:25:48' },
|
||||||
|
{ id:'H014', userId:'u10254', userType:2, goodType:1, supplierGoodId:'SG1014', createTime:'2026-07-07 13:42:11' }
|
||||||
|
]
|
||||||
|
type Col = { type?: 'status'|'text'|'selection'; prop?: string; label: string; width?: number; slot?: string; fixed?: 'right'|true }
|
||||||
|
const columns: Col[] = [
|
||||||
|
{ type:'selection', label:'', width:50 },
|
||||||
|
{ prop:'id', label:'ID', width:90, fixed:true },
|
||||||
|
{ prop:'userId', label:'用户ID', width:110 },
|
||||||
|
{ prop:'userType', label:'用户类型', width:100, slot:'userType' },
|
||||||
|
{ prop:'goodType', label:'商品类型', width:100, slot:'goodType' },
|
||||||
|
{ prop:'supplierGoodId', label:'商品ID', width:130 },
|
||||||
|
{ prop:'createTime', label:'浏览时间', width:180, slot:'time' },
|
||||||
|
{ prop:'actions', label:'操作', width:150, slot:'actions', fixed:'right' }
|
||||||
|
]
|
||||||
|
const STATUS_DEFAULTS = [
|
||||||
|
{ value:'', label:'全部' },
|
||||||
|
{ value:'1', label:'C端用户' },
|
||||||
|
{ value:'2', label:'供应商' }
|
||||||
|
]
|
||||||
|
const list = ref<HistoryRow[]>([]); const total = ref(0); const loading = ref(false); const page = ref(1); const limit = ref(10); const filterKw = ref(''); const statusTab = ref('')
|
||||||
|
const dataSource = ref<'api'|'mock'|'loading'>('loading'); const apiError = ref<string>('')
|
||||||
|
async function load() {
|
||||||
|
loading.value = true; dataSource.value = 'loading'
|
||||||
|
for (const ep of ENDPOINTS) {
|
||||||
|
try {
|
||||||
|
const data: any = await httpGet<any>(ep, { page: page.value, limit: limit.value })
|
||||||
|
const rows = (data?.list || []).map((it: any) => ({ ...it, raw: it }))
|
||||||
|
list.value = rows; total.value = Number(data?.total || rows.length); dataSource.value = 'api'; apiError.value = ''; loading.value = false; return
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
list.value = [...FALLBACK]; total.value = FALLBACK.length; dataSource.value = 'mock'; apiError.value = '后端 API 暂未实装, 已加载前端示例数据'; loading.value = false
|
||||||
|
}
|
||||||
|
const filtered = computed(() => {
|
||||||
|
const kw = filterKw.value.trim().toLowerCase(); let rs = list.value
|
||||||
|
if (statusTab.value) rs = rs.filter(r => String(r.userType) === statusTab.value)
|
||||||
|
if (kw) rs = rs.filter(r => String(r.id).includes(kw) || String(r.userId).includes(kw) || String(r.supplierGoodId).includes(kw))
|
||||||
|
return rs
|
||||||
|
})
|
||||||
|
const searchFields: Array<{ key: string; label: string; type: 'text' }> = [{ key:'id', label:'ID', type:'text' },{ key:'userId', label:'用户ID', type:'text' },{ key:'supplierGoodId', label:'商品ID', type:'text' }]
|
||||||
|
const filters = reactive<Record<string, any>>({})
|
||||||
|
function onSearch() { filterKw.value = String(filters.id || filters.userId || filters.supplierGoodId || ''); page.value = 1 }
|
||||||
|
function onStatusTab(v: string) { statusTab.value = v; page.value = 1 }
|
||||||
|
async function onDelete(row: HistoryRow) {
|
||||||
|
try { await ElMessageBox.confirm(`确认删除「${row.id}」?`, '提示', { type: 'warning' }) } catch { return }
|
||||||
|
ElMessage.success('已删除 (前端示例)')
|
||||||
|
}
|
||||||
|
function onView(row: HistoryRow) { ElMessage.info(`详情 ${row.id}`) }
|
||||||
|
function onExport() { ElMessage.success('已发起导出 (前端示例, 共 ' + filtered.value.length + ' 条)') }
|
||||||
|
onMounted(load)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="rr-page">
|
||||||
|
<PageHeader title="浏览足迹" sub="用户浏览商品行为记录" :data-source="dataSource" :api-error="apiError">
|
||||||
|
<el-button type="primary" @click="onExport">导出明细</el-button>
|
||||||
|
</PageHeader>
|
||||||
|
<el-tabs v-model="statusTab" @tab-change="onStatusTab" class="rr-tabs">
|
||||||
|
<el-tab-pane v-for="t in STATUS_DEFAULTS" :key="t.value" :name="t.value" :label="t.label" />
|
||||||
|
</el-tabs>
|
||||||
|
<SearchBar v-model:filters="filters" :fields="searchFields" @search="onSearch" />
|
||||||
|
<DataTable :columns="columns" :data="filtered" :loading="loading" :page="page" :limit="limit" :total="total" row-key="id"
|
||||||
|
@page-change="(p: number) => { page = p; load() }" @limit-change="(l: number) => { limit = l; page = 1; load() }">
|
||||||
|
<template #userType="{ row }">
|
||||||
|
<el-tag :type="row.userType === 1 ? 'primary' : 'success'" size="small">{{ row.userType === 1 ? 'C端用户' : '供应商' }}</el-tag>
|
||||||
|
</template>
|
||||||
|
<template #goodType="{ row }">
|
||||||
|
<el-tag :type="row.goodType === 1 ? 'warning' : 'info'" size="small">{{ row.goodType === 1 ? '实物' : '服务' }}</el-tag>
|
||||||
|
</template>
|
||||||
|
<template #time="{ row }">{{ row.createTime }}</template>
|
||||||
|
<template #actions="{ row }">
|
||||||
|
<el-button type="primary" link @click="onView(row)">详情</el-button>
|
||||||
|
<el-button type="danger" link @click="onDelete(row)">删除</el-button>
|
||||||
|
</template>
|
||||||
|
</DataTable>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<style scoped>
|
||||||
|
.rr-page { padding: 16px; }
|
||||||
|
.rr-tabs { margin-bottom: 12px; }
|
||||||
|
</style>
|
||||||
116
src/modules/supplier/logistics/LogisticsListView.vue
Normal file
116
src/modules/supplier/logistics/LogisticsListView.vue
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
/**
|
||||||
|
* F006 物流方案管理 - W16
|
||||||
|
*
|
||||||
|
* 沿用老 vue supplier/logistics.vue 字段:
|
||||||
|
* - 选择 + id + enterpriseId + isDefault + logisticsName + chargeType + isOpen + createDate
|
||||||
|
*
|
||||||
|
* 端点: /supplier/logistics/page 探测; 404 → FALLBACK seed (12 条)
|
||||||
|
*
|
||||||
|
* isDefault 0否1是 / chargeType 1=按件数 2=整单 / isOpen 0否1是
|
||||||
|
*/
|
||||||
|
import { ref, reactive, onMounted, computed } from 'vue'
|
||||||
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
|
import { httpGet } from '@/utils/request'
|
||||||
|
import PageHeader from '@/components/PageHeader/PageHeader.vue'
|
||||||
|
import SearchBar from '@/components/SearchBar/SearchBar.vue'
|
||||||
|
import DataTable from '@/components/DataTable/DataTable.vue'
|
||||||
|
|
||||||
|
interface LogisticsRow {
|
||||||
|
id: string; enterpriseId: string; isDefault: number; logisticsName: string; chargeType: number; isOpen: number; createDate: string
|
||||||
|
}
|
||||||
|
const DEFAULT_L: Record<number, string> = { 0: '否', 1: '是' }
|
||||||
|
const CHARGE_L: Record<number, string> = { 1: '按件数', 2: '整单' }
|
||||||
|
const ENDPOINTS = ['/supplier/logistics/page', '/supplier/logistics/list', '/supplier/logistics/search', '/supplier/logistics/queryPage']
|
||||||
|
const FALLBACK: LogisticsRow[] = [
|
||||||
|
{ id:'L001', enterpriseId:'s20015', isDefault:1, logisticsName:'标准配送 (同城48h)', chargeType:1, isOpen:1, createDate:'2026-04-12 09:30:00' },
|
||||||
|
{ id:'L002', enterpriseId:'s20016', isDefault:1, logisticsName:'经济配送 (跨省3-5天)', chargeType:2, isOpen:1, createDate:'2026-04-15 14:22:00' },
|
||||||
|
{ id:'L003', enterpriseId:'s20017', isDefault:0, logisticsName:'加急配送 (同城当日达)', chargeType:1, isOpen:1, createDate:'2026-04-20 16:48:00' },
|
||||||
|
{ id:'L004', enterpriseId:'s20018', isDefault:1, logisticsName:'大宗货运专线', chargeType:2, isOpen:1, createDate:'2026-05-02 10:15:00' },
|
||||||
|
{ id:'L005', enterpriseId:'s20019', isDefault:0, logisticsName:'仓配一体化 (本地仓发货)', chargeType:1, isOpen:0, createDate:'2026-05-08 11:35:00' },
|
||||||
|
{ id:'L006', enterpriseId:'s20020', isDefault:1, logisticsName:'冷链配送 (8-15°C)', chargeType:2, isOpen:1, createDate:'2026-05-14 08:55:00' },
|
||||||
|
{ id:'L007', enterpriseId:'s20021', isDefault:0, logisticsName:'大件专线 (超长件)', chargeType:2, isOpen:1, createDate:'2026-05-20 13:18:00' },
|
||||||
|
{ id:'L008', enterpriseId:'s20022', isDefault:1, logisticsName:'厂家直发 (48-72h)', chargeType:1, isOpen:1, createDate:'2026-06-02 09:42:00' },
|
||||||
|
{ id:'L009', enterpriseId:'s20023', isDefault:0, logisticsName:'节假日专送', chargeType:1, isOpen:0, createDate:'2026-06-10 16:08:00' },
|
||||||
|
{ id:'L010', enterpriseId:'s20024', isDefault:1, logisticsName:'海外集运', chargeType:2, isOpen:1, createDate:'2026-06-18 11:25:00' },
|
||||||
|
{ id:'L011', enterpriseId:'s20025', isDefault:0, logisticsName:'安装配送一体', chargeType:1, isOpen:1, createDate:'2026-06-28 14:38:00' },
|
||||||
|
{ id:'L012', enterpriseId:'s20026', isDefault:0, logisticsName:'试用物流 (10km内免费)', chargeType:1, isOpen:0, createDate:'2026-07-01 09:50:00' }
|
||||||
|
]
|
||||||
|
type Col = { type?: 'status' | 'text' | 'selection'; prop?: string; label: string; width?: number; slot?: string; fixed?: 'right' | true }
|
||||||
|
const columns: Col[] = [
|
||||||
|
{ type: 'selection', label: '', width: 50 },
|
||||||
|
{ prop: 'id', label: '方案ID', width: 100, fixed: true },
|
||||||
|
{ prop: 'enterpriseId', label: '所属企业ID', width: 130 },
|
||||||
|
{ type: 'status', prop: 'isDefault', label: '是否默认', width: 90 },
|
||||||
|
{ prop: 'logisticsName', label: '方案名称', width: 220 },
|
||||||
|
{ type: 'status', prop: 'chargeType', label: '计费方式', width: 110 },
|
||||||
|
{ type: 'status', prop: 'isOpen', label: '是否启用', width: 90 },
|
||||||
|
{ prop: 'createDate', label: '创建时间', width: 160, slot: 'time' },
|
||||||
|
{ prop: 'actions', label: '操作', width: 200, slot: 'actions', fixed: 'right' }
|
||||||
|
]
|
||||||
|
const STATUS_DEFAULTS = [
|
||||||
|
{ value: '', label: '全部' },
|
||||||
|
{ value: '1', label: '默认' },
|
||||||
|
{ value: '0', label: '非默认' }
|
||||||
|
]
|
||||||
|
const list = ref<LogisticsRow[]>([]); const total = ref(0); const loading = ref(false); const page = ref(1); const limit = ref(10); const filterKw = ref(''); const statusTab = ref('')
|
||||||
|
const dataSource = ref<'api' | 'mock' | 'loading'>('loading'); const apiError = ref<string>('')
|
||||||
|
async function load() {
|
||||||
|
loading.value = true; dataSource.value = 'loading'
|
||||||
|
for (const ep of ENDPOINTS) {
|
||||||
|
try {
|
||||||
|
const data: any = await httpGet<any>(ep, { page: page.value, limit: limit.value })
|
||||||
|
const rows = (data?.list || []).map((it: any) => ({ ...it, raw: it }))
|
||||||
|
list.value = rows; total.value = Number(data?.total || rows.length); dataSource.value = 'api'; apiError.value = ''; loading.value = false; return
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
list.value = [...FALLBACK]; total.value = FALLBACK.length; dataSource.value = 'mock'; apiError.value = '后端 API 暂未实装, 已加载前端示例数据'; loading.value = false
|
||||||
|
}
|
||||||
|
const filtered = computed(() => {
|
||||||
|
const kw = filterKw.value.trim().toLowerCase(); let rs = list.value
|
||||||
|
if (statusTab.value) rs = rs.filter(r => String(r.isDefault) === statusTab.value)
|
||||||
|
if (kw) rs = rs.filter(r => String(r.id).toLowerCase().includes(kw) || String(r.logisticsName).toLowerCase().includes(kw) || String(r.enterpriseId).toLowerCase().includes(kw))
|
||||||
|
return rs
|
||||||
|
})
|
||||||
|
const searchFields: Array<{ key: string; label: string; type: 'text' }> = [
|
||||||
|
{ key: 'id', label: '方案ID', type: 'text' },
|
||||||
|
{ key: 'logisticsName', label: '方案名称', type: 'text' },
|
||||||
|
{ key: 'enterpriseId', label: '所属企业ID', type: 'text' }
|
||||||
|
]
|
||||||
|
const filters = reactive<Record<string, any>>({})
|
||||||
|
function onSearch() {
|
||||||
|
filterKw.value = String(filters.id || filters.logisticsName || filters.enterpriseId || ''); page.value = 1
|
||||||
|
}
|
||||||
|
function onStatusTab(v: string) { statusTab.value = v; page.value = 1 }
|
||||||
|
async function onDelete(row: LogisticsRow) {
|
||||||
|
try { await ElMessageBox.confirm(`确认删除「${row.logisticsName}」?`, '提示', { type: 'warning' }) } catch { return }
|
||||||
|
ElMessage.success('已删除 (前端示例)')
|
||||||
|
}
|
||||||
|
function onView(row: LogisticsRow) { ElMessage.info(`打开详情 ${row.id}`) }
|
||||||
|
function onExport() { ElMessage.success('已发起导出 (前端示例, 共 ' + filtered.value.length + ' 条)') }
|
||||||
|
onMounted(load)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="rr-page">
|
||||||
|
<PageHeader title="物流方案管理" sub="供应商物流配送方案配置" :data-source="dataSource" :api-error="apiError">
|
||||||
|
<el-button type="primary" @click="onExport">导出明细</el-button>
|
||||||
|
</PageHeader>
|
||||||
|
<el-tabs v-model="statusTab" @tab-change="onStatusTab" class="rr-tabs">
|
||||||
|
<el-tab-pane v-for="t in STATUS_DEFAULTS" :key="t.value" :name="t.value" :label="t.label" />
|
||||||
|
</el-tabs>
|
||||||
|
<SearchBar v-model:filters="filters" :fields="searchFields" @search="onSearch" />
|
||||||
|
<DataTable :columns="columns" :data="filtered" :loading="loading" :page="page" :limit="limit" :total="total" row-key="id"
|
||||||
|
@page-change="(p: number) => { page = p; load() }" @limit-change="(l: number) => { limit = l; page = 1; load() }">
|
||||||
|
<template #time="{ row }">{{ row.createDate }}</template>
|
||||||
|
<template #actions="{ row }">
|
||||||
|
<el-button type="primary" link @click="onView(row)">查看</el-button>
|
||||||
|
<el-button type="danger" link @click="onDelete(row)">删除</el-button>
|
||||||
|
</template>
|
||||||
|
</DataTable>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<style scoped>
|
||||||
|
.rr-page { padding: 16px; }
|
||||||
|
.rr-tabs { margin-bottom: 12px; }
|
||||||
|
</style>
|
||||||
121
src/modules/supplier/ordercomment/OrderCommentListView.vue
Normal file
121
src/modules/supplier/ordercomment/OrderCommentListView.vue
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
/**
|
||||||
|
* F006 订单评价管理 - W16
|
||||||
|
*
|
||||||
|
* 沿用老 vue supplier/ordercomment.vue 字段
|
||||||
|
*
|
||||||
|
* 端点: 4 URL 探测 (ordercomment/page, /list, /search, /queryPage); 404 → FALLBACK seed
|
||||||
|
*/
|
||||||
|
import { ref, reactive, onMounted, computed } from 'vue'
|
||||||
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
|
import { httpGet } from '@/utils/request'
|
||||||
|
import PageHeader from '@/components/PageHeader/PageHeader.vue'
|
||||||
|
import SearchBar from '@/components/SearchBar/SearchBar.vue'
|
||||||
|
import DataTable from '@/components/DataTable/DataTable.vue'
|
||||||
|
|
||||||
|
interface CommentRow { id:string; userId:string; parentId:string; goodType:number; skuDetailId:string; orderGoodId:string; supplierId:string; supplierGoodId:string; star:number; content:string; createTime:string }
|
||||||
|
const ENDPOINTS = [
|
||||||
|
'/supplier/ordercomment/page',
|
||||||
|
'/supplier/ordercomment/list',
|
||||||
|
'/supplier/ordercomment/search',
|
||||||
|
'/supplier/ordercomment/queryPage'
|
||||||
|
]
|
||||||
|
const FALLBACK: CommentRow[] = [
|
||||||
|
{ id:'OC001', userId:'u10086', parentId:'', goodType:1, skuDetailId:'SKU101', orderGoodId:'OG201', supplierId:'s20015', supplierGoodId:'SG101', star:5, content:'商品质量很好, 配送也快, 满意!', createTime:'2026-07-01 14:32:11' },
|
||||||
|
{ id:'OC002', userId:'u10112', parentId:'', goodType:1, skuDetailId:'SKU102', orderGoodId:'OG202', supplierId:'s20016', supplierGoodId:'SG102', star:4, content:'包装一般, 商品本身没问题', createTime:'2026-07-02 09:18:42' },
|
||||||
|
{ id:'OC003', userId:'u10133', parentId:'OC001', goodType:1, skuDetailId:'SKU103', orderGoodId:'OG203', supplierId:'s20017', supplierGoodId:'SG103', star:5, content:'感谢您的好评!', createTime:'2026-07-02 16:45:00' },
|
||||||
|
{ id:'OC004', userId:'u10144', parentId:'', goodType:2, skuDetailId:'SKU104', orderGoodId:'OG204', supplierId:'s20018', supplierGoodId:'SG104', star:5, content:'安装师傅态度很好', createTime:'2026-07-03 11:02:18' },
|
||||||
|
{ id:'OC005', userId:'u10155', parentId:'', goodType:1, skuDetailId:'SKU105', orderGoodId:'OG205', supplierId:'s20019', supplierGoodId:'SG105', star:4, content:'规格与描述相符', createTime:'2026-07-03 15:25:30' },
|
||||||
|
{ id:'OC006', userId:'u10166', parentId:'OC002', goodType:1, skuDetailId:'SKU106', orderGoodId:'OG206', supplierId:'s20016', supplierGoodId:'SG102', star:5, content:'已改进包装, 感谢建议', createTime:'2026-07-04 09:12:55' },
|
||||||
|
{ id:'OC007', userId:'u10177', parentId:'', goodType:1, skuDetailId:'SKU107', orderGoodId:'OG207', supplierId:'s20020', supplierGoodId:'SG107', star:4, content:'整体满意', createTime:'2026-07-04 16:48:22' },
|
||||||
|
{ id:'OC008', userId:'u10188', parentId:'', goodType:2, skuDetailId:'SKU108', orderGoodId:'OG208', supplierId:'s20021', supplierGoodId:'SG108', star:5, content:'量房很专业', createTime:'2026-07-05 08:30:18' },
|
||||||
|
{ id:'OC009', userId:'u10199', parentId:'', goodType:1, skuDetailId:'SKU109', orderGoodId:'OG209', supplierId:'s20022', supplierGoodId:'SG109', star:3, content:'颜色有偏差, 退换顺利', createTime:'2026-07-05 13:25:48' },
|
||||||
|
{ id:'OC010', userId:'u10210', parentId:'', goodType:1, skuDetailId:'SKU110', orderGoodId:'OG210', supplierId:'s20023', supplierGoodId:'SG110', star:5, content:'送货快', createTime:'2026-07-05 17:33:21' },
|
||||||
|
{ id:'OC011', userId:'u10221', parentId:'OC009', goodType:1, skuDetailId:'SKU111', orderGoodId:'OG211', supplierId:'s20022', supplierGoodId:'SG109', star:5, content:'补发正确颜色', createTime:'2026-07-06 09:42:18' },
|
||||||
|
{ id:'OC012', userId:'u10232', parentId:'', goodType:1, skuDetailId:'SKU112', orderGoodId:'OG212', supplierId:'s20024', supplierGoodId:'SG112', star:4, content:'批量优惠可以, 物流稍慢', createTime:'2026-07-06 14:22:11' },
|
||||||
|
{ id:'OC013', userId:'u10243', parentId:'', goodType:2, skuDetailId:'SKU113', orderGoodId:'OG213', supplierId:'s20025', supplierGoodId:'SG113', star:5, content:'自助下单良好', createTime:'2026-07-06 19:08:55' },
|
||||||
|
{ id:'OC014', userId:'u10254', parentId:'', goodType:1, skuDetailId:'SKU114', orderGoodId:'OG214', supplierId:'s20026', supplierGoodId:'SG114', star:5, content:'商品不错, 配送好', createTime:'2026-07-07 09:25:48' },
|
||||||
|
{ id:'OC015', userId:'u10265', parentId:'', goodType:1, skuDetailId:'SKU115', orderGoodId:'OG215', supplierId:'s20015', supplierGoodId:'SG101', star:3, content:'便宜但慢', createTime:'2026-07-07 13:42:11' }
|
||||||
|
]
|
||||||
|
type Col = { type?: 'status'|'text'|'selection'; prop?: string; label: string; width?: number; slot?: string; fixed?: 'right'|true }
|
||||||
|
const columns: Col[] = [
|
||||||
|
{ type:'selection', label:'', width:50 },
|
||||||
|
{ prop:'id', label:'评价ID', width:100, fixed:true },
|
||||||
|
{ prop:'orderGoodId', label:'小订单ID', width:130 },
|
||||||
|
{ prop:'supplierGoodId', label:'商品ID', width:130 },
|
||||||
|
{ prop:'userId', label:'评价人', width:110 },
|
||||||
|
{ prop:'star', label:'评分', width:80, slot:'star' },
|
||||||
|
{ prop:'content', label:'内容', width:280, slot:'content' },
|
||||||
|
{ prop:'createTime', label:'时间', width:160, slot:'time' },
|
||||||
|
{ prop:'actions', label:'操作', width:200, slot:'actions', fixed:'right' }
|
||||||
|
]
|
||||||
|
const STATUS_DEFAULTS = [
|
||||||
|
{ value:'', label:'全部' },
|
||||||
|
{ value:'5', label:'5星' },
|
||||||
|
{ value:'4', label:'4星' },
|
||||||
|
{ value:'3', label:'3星' },
|
||||||
|
{ value:'1', label:'≤2星' }
|
||||||
|
]
|
||||||
|
const list = ref<CommentRow[]>([]); const total = ref(0); const loading = ref(false); const page = ref(1); const limit = ref(10); const filterKw = ref(''); const statusTab = ref('')
|
||||||
|
const dataSource = ref<'api'|'mock'|'loading'>('loading'); const apiError = ref<string>('')
|
||||||
|
async function load() {
|
||||||
|
loading.value = true; dataSource.value = 'loading'
|
||||||
|
for (const ep of ENDPOINTS) {
|
||||||
|
try {
|
||||||
|
const data: any = await httpGet<any>(ep, { page: page.value, limit: limit.value })
|
||||||
|
const rows = (data?.list || []).map((it: any) => ({ ...it, raw: it }))
|
||||||
|
list.value = rows; total.value = Number(data?.total || rows.length); dataSource.value = 'api'; apiError.value = ''; loading.value = false; return
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
list.value = [...FALLBACK]; total.value = FALLBACK.length; dataSource.value = 'mock'; apiError.value = '后端 API 暂未实装, 已加载前端示例数据'; loading.value = false
|
||||||
|
}
|
||||||
|
const filtered = computed(() => {
|
||||||
|
const kw = filterKw.value.trim().toLowerCase(); let rs = list.value
|
||||||
|
if (statusTab.value) { if (statusTab.value === '1') rs = rs.filter(r => r.star < 3); else rs = rs.filter(r => String(r.star) === statusTab.value) }
|
||||||
|
if (kw) rs = rs.filter(r => String(r.id).includes(kw) || String(r.content).includes(kw) || String(r.userId).includes(kw))
|
||||||
|
return rs
|
||||||
|
})
|
||||||
|
const searchFields: Array<{ key: string; label: string; type: 'text' }> = [{ key:'id', label:'评价ID', type:'text' },{ key:'userId', label:'评价人ID', type:'text' },{ key:'content', label:'内容关键词', type:'text' }]
|
||||||
|
const filters = reactive<Record<string, any>>({})
|
||||||
|
function onSearch() { filterKw.value = String(filters.id || filters.userId || filters.content || ''); page.value = 1 }
|
||||||
|
function onStatusTab(v: string) { statusTab.value = v; page.value = 1 }
|
||||||
|
async function onDelete(row: CommentRow) {
|
||||||
|
try { await ElMessageBox.confirm(`确认删除评价「${row.id}」?`, '提示', { type: 'warning' }) } catch { return }
|
||||||
|
ElMessage.success('已删除 (前端示例)')
|
||||||
|
}
|
||||||
|
function onReply(row: CommentRow) { ElMessage.info(`回复 ${row.id}`) }
|
||||||
|
function onView(row: CommentRow) { ElMessage.info(`详情 ${row.id}`) }
|
||||||
|
function onExport() { ElMessage.success('已发起导出 (前端示例, 共 ' + filtered.value.length + ' 条)') }
|
||||||
|
onMounted(load)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="rr-page">
|
||||||
|
<PageHeader title="订单评价管理" sub="订单商品的用户评价管理" :data-source="dataSource" :api-error="apiError">
|
||||||
|
<el-button type="primary" @click="onExport">导出明细</el-button>
|
||||||
|
</PageHeader>
|
||||||
|
<el-tabs v-model="statusTab" @tab-change="onStatusTab" class="rr-tabs">
|
||||||
|
<el-tab-pane v-for="t in STATUS_DEFAULTS" :key="t.value" :name="t.value" :label="t.label" />
|
||||||
|
</el-tabs>
|
||||||
|
<SearchBar v-model:filters="filters" :fields="searchFields" @search="onSearch" />
|
||||||
|
<DataTable :columns="columns" :data="filtered" :loading="loading" :page="page" :limit="limit" :total="total" row-key="id"
|
||||||
|
@page-change="(p: number) => { page = p; load() }" @limit-change="(l: number) => { limit = l; page = 1; load() }">
|
||||||
|
<template #star="{ row }">
|
||||||
|
<el-rate v-model="row.star" :max="5" disabled />
|
||||||
|
</template>
|
||||||
|
<template #content="{ row }">
|
||||||
|
<span :title="row.content">{{ (row.content || '').slice(0, 28) }}{{ (row.content || '').length > 28 ? '...' : '' }}</span>
|
||||||
|
</template>
|
||||||
|
<template #time="{ row }">{{ row.createTime }}</template>
|
||||||
|
<template #actions="{ row }">
|
||||||
|
<el-button type="primary" link @click="onView(row)">详情</el-button>
|
||||||
|
<el-button v-if="!row.parentId" type="success" link @click="onReply(row)">回复</el-button>
|
||||||
|
<el-button type="danger" link @click="onDelete(row)">删除</el-button>
|
||||||
|
</template>
|
||||||
|
</DataTable>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<style scoped>
|
||||||
|
.rr-page { padding: 16px; }
|
||||||
|
.rr-tabs { margin-bottom: 12px; }
|
||||||
|
</style>
|
||||||
115
src/modules/supplier/promoter/PromoterListView.vue
Normal file
115
src/modules/supplier/promoter/PromoterListView.vue
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
/**
|
||||||
|
* F006 推广员管理 - W16
|
||||||
|
*
|
||||||
|
* 沿用老 vue supplier/promoter.vue 字段
|
||||||
|
*
|
||||||
|
* 端点: 4 URL 探测 (promoter/page, /list, /search, /queryPage); 404 → FALLBACK seed
|
||||||
|
*/
|
||||||
|
import { ref, reactive, onMounted, computed } from 'vue'
|
||||||
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
|
import { httpGet } from '@/utils/request'
|
||||||
|
import PageHeader from '@/components/PageHeader/PageHeader.vue'
|
||||||
|
import SearchBar from '@/components/SearchBar/SearchBar.vue'
|
||||||
|
import DataTable from '@/components/DataTable/DataTable.vue'
|
||||||
|
|
||||||
|
interface PromoterRow { id:string; enterpriseId:string; enterpriseName:string; promoterName:string; promoterRadio:number; promoterGood:string; beginTime:string; endTime:string; createTime:string }
|
||||||
|
const ENDPOINTS = [
|
||||||
|
'/supplier/promoter/page',
|
||||||
|
'/supplier/promoter/list',
|
||||||
|
'/supplier/promoter/search',
|
||||||
|
'/supplier/promoter/queryPage'
|
||||||
|
]
|
||||||
|
const FALLBACK: PromoterRow[] = [
|
||||||
|
{ id:'P001', enterpriseId:'s20015', enterpriseName:'坚美建筑材料有限公司', promoterName:'张推广', promoterRadio:0.05, promoterGood:'国标槽钢/角钢系列', beginTime:'2026-04-01', endTime:'2026-12-31', createTime:'2026-04-01 09:30:00' },
|
||||||
|
{ id:'P002', enterpriseId:'s20016', enterpriseName:'华建门窗厂', promoterName:'李经纪人', promoterRadio:0.08, promoterGood:'断桥铝门窗', beginTime:'2026-04-15', endTime:'2026-10-30', createTime:'2026-04-15 14:22:00' },
|
||||||
|
{ id:'P003', enterpriseId:'s20017', enterpriseName:'融鑫管业', promoterName:'王工长', promoterRadio:0.06, promoterGood:'PPR给水管', beginTime:'2026-05-01', endTime:'2026-12-31', createTime:'2026-05-01 11:08:30' },
|
||||||
|
{ id:'P004', enterpriseId:'s20018', enterpriseName:'东方五金机械', promoterName:'赵渠道', promoterRadio:0.10, promoterGood:'电动工具', beginTime:'2026-05-10', endTime:'2026-09-30', createTime:'2026-05-10 16:48:00' },
|
||||||
|
{ id:'P005', enterpriseId:'s20019', enterpriseName:'美洁涂料', promoterName:'陈渠道', promoterRadio:0.07, promoterGood:'环保涂料', beginTime:'2026-05-20', endTime:'2026-12-31', createTime:'2026-05-20 09:15:00' },
|
||||||
|
{ id:'P006', enterpriseId:'s20020', enterpriseName:'万家灯具', promoterName:'周照明师', promoterRadio:0.12, promoterGood:'LED灯具', beginTime:'2026-06-01', endTime:'2026-12-31', createTime:'2026-06-01 10:42:00' },
|
||||||
|
{ id:'P007', enterpriseId:'s20021', enterpriseName:'顺达物流', promoterName:'吴同城', promoterRadio:0.05, promoterGood:'同城配送服务', beginTime:'2026-06-10', endTime:'2026-12-31', createTime:'2026-06-10 13:18:00' },
|
||||||
|
{ id:'P008', enterpriseId:'s20022', enterpriseName:'华建门窗厂', promoterName:'林工长', promoterRadio:0.08, promoterGood:'铝合金门窗', beginTime:'2026-06-20', endTime:'2026-12-31', createTime:'2026-06-20 11:35:00' },
|
||||||
|
{ id:'P009', enterpriseId:'s20023', enterpriseName:'众成厨具', promoterName:'黄销售', promoterRadio:0.06, promoterGood:'厨房用品', beginTime:'2026-07-01', endTime:'2026-12-31', createTime:'2026-07-01 09:50:00' },
|
||||||
|
{ id:'P010', enterpriseId:'s20024', enterpriseName:'东方五金机械', promoterName:'何专业', promoterRadio:0.09, promoterGood:'角磨机', beginTime:'2026-07-05', endTime:'2026-12-31', createTime:'2026-07-05 14:25:00' },
|
||||||
|
{ id:'P011', enterpriseId:'s20025', enterpriseName:'坚美建筑材料', promoterName:'钱工长', promoterRadio:0.07, promoterGood:'国标钢材', beginTime:'2026-07-08', endTime:'2026-12-31', createTime:'2026-07-08 11:42:00' },
|
||||||
|
{ id:'P012', enterpriseId:'s20026', enterpriseName:'美洁涂料', promoterName:'孙工长', promoterRadio:0.06, promoterGood:'外墙腻子粉', beginTime:'2026-07-12', endTime:'2026-12-31', createTime:'2026-07-12 16:08:00' }
|
||||||
|
]
|
||||||
|
type Col = { type?: 'status'|'text'|'selection'; prop?: string; label: string; width?: number; slot?: string; fixed?: 'right'|true }
|
||||||
|
const columns: Col[] = [
|
||||||
|
{ type:'selection', label:'', width:50 },
|
||||||
|
{ prop:'id', label:'推广员ID', width:100, fixed:true },
|
||||||
|
{ prop:'enterpriseName', label:'所属供应商', width:200 },
|
||||||
|
{ prop:'promoterName', label:'推广员姓名', width:120 },
|
||||||
|
{ prop:'promoterRadio', label:'返佣比例', width:110, slot:'radio' },
|
||||||
|
{ prop:'promoterGood', label:'推广商品', width:200 },
|
||||||
|
{ prop:'beginTime', label:'开始时间', width:120, slot:'date1' },
|
||||||
|
{ prop:'endTime', label:'结束时间', width:120, slot:'date2' },
|
||||||
|
{ prop:'createTime', label:'创建时间', width:160, slot:'time' },
|
||||||
|
{ prop:'actions', label:'操作', width:200, slot:'actions', fixed:'right' }
|
||||||
|
]
|
||||||
|
const STATUS_DEFAULTS = [
|
||||||
|
{ value:'', label:'全部' },
|
||||||
|
{ value:'1', label:'推广中' },
|
||||||
|
{ value:'0', label:'已结束' }
|
||||||
|
]
|
||||||
|
const list = ref<PromoterRow[]>([]); const total = ref(0); const loading = ref(false); const page = ref(1); const limit = ref(10); const filterKw = ref(''); const statusTab = ref('')
|
||||||
|
const dataSource = ref<'api'|'mock'|'loading'>('loading'); const apiError = ref<string>('')
|
||||||
|
async function load() {
|
||||||
|
loading.value = true; dataSource.value = 'loading'
|
||||||
|
for (const ep of ENDPOINTS) {
|
||||||
|
try {
|
||||||
|
const data: any = await httpGet<any>(ep, { page: page.value, limit: limit.value })
|
||||||
|
const rows = (data?.list || []).map((it: any) => ({ ...it, raw: it }))
|
||||||
|
list.value = rows; total.value = Number(data?.total || rows.length); dataSource.value = 'api'; apiError.value = ''; loading.value = false; return
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
list.value = [...FALLBACK]; total.value = FALLBACK.length; dataSource.value = 'mock'; apiError.value = '后端 API 暂未实装, 已加载前端示例数据'; loading.value = false
|
||||||
|
}
|
||||||
|
const filtered = computed(() => {
|
||||||
|
const kw = filterKw.value.trim().toLowerCase(); let rs = list.value
|
||||||
|
if (statusTab.value === '1') rs = rs.filter(r => new Date(r.endTime) >= new Date('2026-07-07'))
|
||||||
|
if (kw) rs = rs.filter(r => String(r.promoterName).includes(kw) || String(r.enterpriseName).includes(kw) || String(r.promoterGood).includes(kw))
|
||||||
|
return rs
|
||||||
|
})
|
||||||
|
const searchFields: Array<{ key: string; label: string; type: 'text' }> = [{ key:'promoterName', label:'推广员姓名', type:'text' },{ key:'enterpriseName', label:'供应商', type:'text' },{ key:'promoterGood', label:'推广商品', type:'text' }]
|
||||||
|
|
||||||
|
const filters = reactive<Record<string, any>>({})
|
||||||
|
function onSearch() { filterKw.value = String(filters.promoterName || filters.enterpriseName || filters.promoterGood || ''); page.value = 1 }
|
||||||
|
function onStatusTab(v: string) { statusTab.value = v; page.value = 1 }
|
||||||
|
async function onDelete(row: PromoterRow) {
|
||||||
|
try { await ElMessageBox.confirm(`确认删除推广员「${row.promoterName}」?`, '提示', { type: 'warning' }) } catch { return }
|
||||||
|
ElMessage.success('已删除 (前端示例)')
|
||||||
|
}
|
||||||
|
function onEdit(row: PromoterRow) { ElMessage.info(`编辑 ${row.id}`) }
|
||||||
|
function onView(row: PromoterRow) { ElMessage.info(`详情 ${row.id}`) }
|
||||||
|
function onExport() { ElMessage.success('已发起导出 (前端示例, 共 ' + filtered.value.length + ' 条)') }
|
||||||
|
onMounted(load)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="rr-page">
|
||||||
|
<PageHeader title="推广员管理" sub="商品推广返佣管理" :data-source="dataSource" :api-error="apiError">
|
||||||
|
<el-button type="primary" @click="onExport">导出明细</el-button>
|
||||||
|
</PageHeader>
|
||||||
|
<el-tabs v-model="statusTab" @tab-change="onStatusTab" class="rr-tabs">
|
||||||
|
<el-tab-pane v-for="t in STATUS_DEFAULTS" :key="t.value" :name="t.value" :label="t.label" />
|
||||||
|
</el-tabs>
|
||||||
|
<SearchBar v-model:filters="filters" :fields="searchFields" @search="onSearch" />
|
||||||
|
<DataTable :columns="columns" :data="filtered" :loading="loading" :page="page" :limit="limit" :total="total" row-key="id"
|
||||||
|
@page-change="(p: number) => { page = p; load() }" @limit-change="(l: number) => { limit = l; page = 1; load() }">
|
||||||
|
<template #radio="{ row }">{{ (row.promoterRadio * 100).toFixed(1) }}%</template>
|
||||||
|
<template #date1="{ row }">{{ row.beginTime }}</template>
|
||||||
|
<template #date2="{ row }">{{ row.endTime }}</template>
|
||||||
|
<template #time="{ row }">{{ row.createTime }}</template>
|
||||||
|
<template #actions="{ row }">
|
||||||
|
<el-button type="primary" link @click="onView(row)">详情</el-button>
|
||||||
|
<el-button type="success" link @click="onEdit(row)">编辑</el-button>
|
||||||
|
<el-button type="danger" link @click="onDelete(row)">删除</el-button>
|
||||||
|
</template>
|
||||||
|
</DataTable>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<style scoped>
|
||||||
|
.rr-page { padding: 16px; }
|
||||||
|
.rr-tabs { margin-bottom: 12px; }
|
||||||
|
</style>
|
||||||
101
src/modules/user/enterpriselevel/EnterpriselevelListView.vue
Normal file
101
src/modules/user/enterpriselevel/EnterpriselevelListView.vue
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
/**
|
||||||
|
* F006 企业等级管理 - W16
|
||||||
|
*
|
||||||
|
* 沿用老 vue user/enterprise/enterprise-level.vue 字段
|
||||||
|
*
|
||||||
|
* 端点: 4 URL 探测 (enterpriselevel/page, /list, /search, /queryPage); 404 → FALLBACK seed
|
||||||
|
*/
|
||||||
|
import { ref, reactive, onMounted, computed } from 'vue'
|
||||||
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
|
import { httpGet } from '@/utils/request'
|
||||||
|
import PageHeader from '@/components/PageHeader/PageHeader.vue'
|
||||||
|
import SearchBar from '@/components/SearchBar/SearchBar.vue'
|
||||||
|
import DataTable from '@/components/DataTable/DataTable.vue'
|
||||||
|
|
||||||
|
interface LevelRow { id:string; enterpriseLevel:string; scoreFrom:number; scoreTo:number; createTime:string }
|
||||||
|
const ENDPOINTS = [
|
||||||
|
'/user/enterpriselevel/page',
|
||||||
|
'/user/enterpriselevel/list',
|
||||||
|
'/user/enterpriselevel/search',
|
||||||
|
'/user/enterpriselevel/queryPage'
|
||||||
|
]
|
||||||
|
const FALLBACK: LevelRow[] = [
|
||||||
|
{ id:'L001', enterpriseLevel:'A (战略级)', scoreFrom:90, scoreTo:100, createTime:'2026-01-05 10:00:00' },
|
||||||
|
{ id:'L002', enterpriseLevel:'B (核心级)', scoreFrom:80, scoreTo:89, createTime:'2026-01-05 10:05:00' },
|
||||||
|
{ id:'L003', enterpriseLevel:'C (成长级)', scoreFrom:70, scoreTo:79, createTime:'2026-01-05 10:10:00' },
|
||||||
|
{ id:'L004', enterpriseLevel:'D (入门级)', scoreFrom:60, scoreTo:69, createTime:'2026-01-05 10:15:00' },
|
||||||
|
{ id:'L005', enterpriseLevel:'E (观察级)', scoreFrom:0, scoreTo:59, createTime:'2026-01-05 10:20:00' }
|
||||||
|
]
|
||||||
|
type Col = { type?: 'status'|'text'|'selection'; prop?: string; label: string; width?: number; slot?: string; fixed?: 'right'|true }
|
||||||
|
const columns: Col[] = [
|
||||||
|
{ type:'selection', label:'', width:50 },
|
||||||
|
{ prop:'id', label:'ID', width:80, fixed:true },
|
||||||
|
{ prop:'enterpriseLevel', label:'等级', width:140 },
|
||||||
|
{ prop:'scoreFrom', label:'起始分', width:100 },
|
||||||
|
{ prop:'scoreTo', label:'截止分', width:100 },
|
||||||
|
{ prop:'actions', label:'操作', width:200, slot:'actions', fixed:'right' }
|
||||||
|
]
|
||||||
|
const STATUS_DEFAULTS = [
|
||||||
|
{ value:'', label:'全部' }
|
||||||
|
]
|
||||||
|
const list = ref<LevelRow[]>([]); const total = ref(0); const loading = ref(false); const page = ref(1); const limit = ref(10); const filterKw = ref(''); const statusTab = ref('')
|
||||||
|
const dataSource = ref<'api'|'mock'|'loading'>('loading'); const apiError = ref<string>('')
|
||||||
|
async function load() {
|
||||||
|
loading.value = true; dataSource.value = 'loading'
|
||||||
|
for (const ep of ENDPOINTS) {
|
||||||
|
try {
|
||||||
|
const data: any = await httpGet<any>(ep, { page: page.value, limit: limit.value })
|
||||||
|
const rows = (data?.list || []).map((it: any) => ({ ...it, raw: it }))
|
||||||
|
list.value = rows; total.value = Number(data?.total || rows.length); dataSource.value = 'api'; apiError.value = ''; loading.value = false; return
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
list.value = [...FALLBACK]; total.value = FALLBACK.length; dataSource.value = 'mock'; apiError.value = '后端 API 暂未实装, 已加载前端示例数据'; loading.value = false
|
||||||
|
}
|
||||||
|
const filtered = computed(() => {
|
||||||
|
const kw = filterKw.value.trim().toLowerCase(); let rs = list.value
|
||||||
|
if (kw) rs = rs.filter(r => String(r.id).includes(kw) || String(r.enterpriseLevel).includes(kw))
|
||||||
|
return rs
|
||||||
|
})
|
||||||
|
const searchFields: Array<{ key: string; label: string; type: 'text' }> = [
|
||||||
|
{ key:'id', label:'ID', type:'text' },
|
||||||
|
{ key:'enterpriseLevel', label:'等级', type:'text' }
|
||||||
|
]
|
||||||
|
const filters = reactive<Record<string, any>>({})
|
||||||
|
function onSearch() { filterKw.value = String(filters.id || filters.enterpriseLevel || ''); page.value = 1 }
|
||||||
|
function onStatusTab(v: string) { statusTab.value = v; page.value = 1 }
|
||||||
|
function onView(row: LevelRow) { ElMessage.info(`查看 ${row.id}`) }
|
||||||
|
async function onEdit(row: LevelRow) {
|
||||||
|
ElMessage.success('编辑成功 (前端示例)')
|
||||||
|
}
|
||||||
|
async function onDelete(row: LevelRow) {
|
||||||
|
try { await ElMessageBox.confirm(`确认删除等级「${row.enterpriseLevel}」?`, '提示', { type: 'warning' }) } catch { return }
|
||||||
|
ElMessage.success('已删除 (前端示例)')
|
||||||
|
}
|
||||||
|
function onExport() { ElMessage.success('已发起导出 (前端示例, 共 ' + filtered.value.length + ' 条)') }
|
||||||
|
onMounted(load)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="rr-page">
|
||||||
|
<PageHeader title="企业等级管理" sub="供应商等级与对应分数段配置" :data-source="dataSource" :api-error="apiError">
|
||||||
|
<el-button type="primary" @click="onExport">导出明细</el-button>
|
||||||
|
</PageHeader>
|
||||||
|
<el-tabs v-model="statusTab" @tab-change="onStatusTab" class="rr-tabs">
|
||||||
|
<el-tab-pane v-for="t in STATUS_DEFAULTS" :key="t.value" :name="t.value" :label="t.label" />
|
||||||
|
</el-tabs>
|
||||||
|
<SearchBar v-model:filters="filters" :fields="searchFields" @search="onSearch" />
|
||||||
|
<DataTable :columns="columns" :data="filtered" :loading="loading" :page="page" :limit="limit" :total="total" row-key="id"
|
||||||
|
@page-change="(p: number) => { page = p; load() }" @limit-change="(l: number) => { limit = l; page = 1; load() }">
|
||||||
|
<template #actions="{ row }">
|
||||||
|
<el-button type="primary" link @click="onView(row)">查看</el-button>
|
||||||
|
<el-button type="success" link @click="onEdit(row)">编辑</el-button>
|
||||||
|
<el-button type="danger" link @click="onDelete(row)">删除</el-button>
|
||||||
|
</template>
|
||||||
|
</DataTable>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<style scoped>
|
||||||
|
.rr-page { padding: 16px; }
|
||||||
|
.rr-tabs { margin-bottom: 12px; }
|
||||||
|
</style>
|
||||||
118
src/modules/user/enterpriserating/EnterpriseratingListView.vue
Normal file
118
src/modules/user/enterpriserating/EnterpriseratingListView.vue
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
/**
|
||||||
|
* F006 供应商评级管理 - W16
|
||||||
|
*
|
||||||
|
* 沿用老 vue user/enterprise/enterprise-rating.vue 字段
|
||||||
|
*
|
||||||
|
* 端点: 4 URL 探测 (enterpriserating/page, /list, /search, /queryPage); 404 → FALLBACK seed
|
||||||
|
*/
|
||||||
|
import { ref, reactive, onMounted, computed } from 'vue'
|
||||||
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
|
import { httpGet } from '@/utils/request'
|
||||||
|
import PageHeader from '@/components/PageHeader/PageHeader.vue'
|
||||||
|
import SearchBar from '@/components/SearchBar/SearchBar.vue'
|
||||||
|
import DataTable from '@/components/DataTable/DataTable.vue'
|
||||||
|
|
||||||
|
interface RatingRow { id:string; enterpriseId:string; enterpriseName:string; supplierLevel:string; clientScore:number; performanceScore:number; awardScore:number; finalScore:number; deadline:string }
|
||||||
|
const ENDPOINTS = [
|
||||||
|
'/user/enterpriserating/page',
|
||||||
|
'/user/enterpriserating/list',
|
||||||
|
'/user/enterpriserating/search',
|
||||||
|
'/user/enterpriserating/queryPage'
|
||||||
|
]
|
||||||
|
const FALLBACK: RatingRow[] = [
|
||||||
|
{ id:'R001', enterpriseId:'s20015', enterpriseName:'坚美建筑材料有限公司', supplierLevel:'A 战略级', clientScore:96, performanceScore:92, awardScore:5, finalScore:193, deadline:'2026-06-30 23:59:59' },
|
||||||
|
{ id:'R002', enterpriseId:'s20016', enterpriseName:'华建门窗厂', supplierLevel:'B 核心级', clientScore:88, performanceScore:85, awardScore:3, finalScore:176, deadline:'2026-06-30 23:59:59' },
|
||||||
|
{ id:'R003', enterpriseId:'s20017', enterpriseName:'融鑫管业', supplierLevel:'B 核心级', clientScore:84, performanceScore:88, awardScore:0, finalScore:172, deadline:'2026-06-30 23:59:59' },
|
||||||
|
{ id:'R004', enterpriseId:'s20018', enterpriseName:'东方五金机械', supplierLevel:'A 战略级', clientScore:92, performanceScore:90, awardScore:8, finalScore:190, deadline:'2026-06-30 23:59:59' },
|
||||||
|
{ id:'R005', enterpriseId:'s20019', enterpriseName:'美洁涂料', supplierLevel:'C 成长级', clientScore:78, performanceScore:76, awardScore:-2, finalScore:152, deadline:'2026-06-30 23:59:59' },
|
||||||
|
{ id:'R006', enterpriseId:'s20020', enterpriseName:'万家灯具', supplierLevel:'B 核心级', clientScore:86, performanceScore:82, awardScore:2, finalScore:170, deadline:'2026-06-30 23:59:59' },
|
||||||
|
{ id:'R007', enterpriseId:'s20021', enterpriseName:'顺达物流', supplierLevel:'D 入门级', clientScore:65, performanceScore:68, awardScore:0, finalScore:133, deadline:'2026-06-30 23:59:59' },
|
||||||
|
{ id:'R008', enterpriseId:'s20022', enterpriseName:'众成厨具', supplierLevel:'C 成长级', clientScore:75, performanceScore:72, awardScore:-3, finalScore:144, deadline:'2026-06-30 23:59:59' },
|
||||||
|
{ id:'R009', enterpriseId:'s20023', enterpriseName:'坚美建筑材料', supplierLevel:'A 战略级', clientScore:94, performanceScore:91, awardScore:6, finalScore:191, deadline:'2026-06-30 23:59:59' },
|
||||||
|
{ id:'R010', enterpriseId:'s20024', enterpriseName:'华建门窗厂', supplierLevel:'A 战略级', clientScore:90, performanceScore:89, awardScore:4, finalScore:183, deadline:'2026-06-30 23:59:59' },
|
||||||
|
{ id:'R011', enterpriseId:'s20025', enterpriseName:'融鑫管业', supplierLevel:'C 成长级', clientScore:74, performanceScore:78, awardScore:0, finalScore:152, deadline:'2026-06-30 23:59:59' },
|
||||||
|
{ id:'R012', enterpriseId:'s20026', enterpriseName:'美洁涂料', supplierLevel:'D 入门级', clientScore:62, performanceScore:65, awardScore:-1, finalScore:126, deadline:'2026-06-30 23:59:59' }
|
||||||
|
]
|
||||||
|
type Col = { type?: 'status'|'text'|'selection'; prop?: string; label: string; width?: number; slot?: string; fixed?: 'right'|true }
|
||||||
|
const columns: Col[] = [
|
||||||
|
{ type:'selection', label:'', width:50 },
|
||||||
|
{ prop:'id', label:'评级ID', width:90, fixed:true },
|
||||||
|
{ prop:'enterpriseName', label:'供应商', width:200 },
|
||||||
|
{ prop:'supplierLevel', label:'等级', width:120, slot:'level' },
|
||||||
|
{ prop:'clientScore', label:'客户评价', width:100 },
|
||||||
|
{ prop:'performanceScore', label:'履约评价', width:100 },
|
||||||
|
{ prop:'awardScore', label:'奖惩分', width:90, slot:'award' },
|
||||||
|
{ prop:'finalScore', label:'最终得分', width:100, slot:'final' },
|
||||||
|
{ prop:'deadline', label:'评级时间', width:170, slot:'deadline' },
|
||||||
|
{ prop:'actions', label:'操作', width:180, slot:'actions', fixed:'right' }
|
||||||
|
]
|
||||||
|
const STATUS_DEFAULTS = [
|
||||||
|
{ value:'', label:'全部' }
|
||||||
|
]
|
||||||
|
const list = ref<RatingRow[]>([]); const total = ref(0); const loading = ref(false); const page = ref(1); const limit = ref(10); const filterKw = ref(''); const statusTab = ref('')
|
||||||
|
const dataSource = ref<'api'|'mock'|'loading'>('loading'); const apiError = ref<string>('')
|
||||||
|
async function load() {
|
||||||
|
loading.value = true; dataSource.value = 'loading'
|
||||||
|
for (const ep of ENDPOINTS) {
|
||||||
|
try {
|
||||||
|
const data: any = await httpGet<any>(ep, { page: page.value, limit: limit.value })
|
||||||
|
const rows = (data?.list || []).map((it: any) => ({ ...it, raw: it }))
|
||||||
|
list.value = rows; total.value = Number(data?.total || rows.length); dataSource.value = 'api'; apiError.value = ''; loading.value = false; return
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
list.value = [...FALLBACK]; total.value = FALLBACK.length; dataSource.value = 'mock'; apiError.value = '后端 API 暂未实装, 已加载前端示例数据'; loading.value = false
|
||||||
|
}
|
||||||
|
const filtered = computed(() => {
|
||||||
|
const kw = filterKw.value.trim().toLowerCase(); let rs = list.value
|
||||||
|
if (kw) rs = rs.filter(r => String(r.enterpriseName).includes(kw))
|
||||||
|
return rs
|
||||||
|
})
|
||||||
|
const searchFields: Array<{ key: string; label: string; type: 'text' }> = [
|
||||||
|
{ key:'enterpriseName', label:'供应商', type:'text' }
|
||||||
|
]
|
||||||
|
const filters = reactive<Record<string, any>>({})
|
||||||
|
function onSearch() { filterKw.value = String(filters.enterpriseName || ''); page.value = 1 }
|
||||||
|
function onStatusTab(v: string) { statusTab.value = v; page.value = 1 }
|
||||||
|
function onView(row: RatingRow) { ElMessage.info(`详情 ${row.id}`) }
|
||||||
|
async function onEdit(row: RatingRow) {
|
||||||
|
ElMessage.success('已发起调整 (前端示例)')
|
||||||
|
}
|
||||||
|
function onExport() { ElMessage.success('已发起导出 (前端示例, 共 ' + filtered.value.length + ' 条)') }
|
||||||
|
onMounted(load)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="rr-page">
|
||||||
|
<PageHeader title="供应商评级管理" sub="供应商基于多维度打分的最终评级" :data-source="dataSource" :api-error="apiError">
|
||||||
|
<el-button type="primary" @click="onExport">导出明细</el-button>
|
||||||
|
</PageHeader>
|
||||||
|
<el-tabs v-model="statusTab" @tab-change="onStatusTab" class="rr-tabs">
|
||||||
|
<el-tab-pane v-for="t in STATUS_DEFAULTS" :key="t.value" :name="t.value" :label="t.label" />
|
||||||
|
</el-tabs>
|
||||||
|
<SearchBar v-model:filters="filters" :fields="searchFields" @search="onSearch" />
|
||||||
|
<DataTable :columns="columns" :data="filtered" :loading="loading" :page="page" :limit="limit" :total="total" row-key="id"
|
||||||
|
@page-change="(p: number) => { page = p; load() }" @limit-change="(l: number) => { limit = l; page = 1; load() }">
|
||||||
|
<template #level="{ row }">
|
||||||
|
<el-tag :type="row.supplierLevel.startsWith('A') ? 'success' : row.supplierLevel.startsWith('B') ? 'primary' : row.supplierLevel.startsWith('C') ? 'warning' : 'info'" size="small">{{ row.supplierLevel }}</el-tag>
|
||||||
|
</template>
|
||||||
|
<template #award="{ row }">
|
||||||
|
<span :style="{ color: row.awardScore > 0 ? '#67c23a' : row.awardScore < 0 ? '#f56c6c' : '#999' }">
|
||||||
|
{{ row.awardScore > 0 ? '+' : '' }}{{ row.awardScore }}
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
<template #final="{ row }">
|
||||||
|
<span style="font-weight: 600;">{{ row.finalScore }}</span>
|
||||||
|
</template>
|
||||||
|
<template #deadline="{ row }">{{ row.deadline }}</template>
|
||||||
|
<template #actions="{ row }">
|
||||||
|
<el-button type="primary" link @click="onView(row)">详情</el-button>
|
||||||
|
<el-button type="success" link @click="onEdit(row)">调整分</el-button>
|
||||||
|
</template>
|
||||||
|
</DataTable>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<style scoped>
|
||||||
|
.rr-page { padding: 16px; }
|
||||||
|
.rr-tabs { margin-bottom: 12px; }
|
||||||
|
</style>
|
||||||
113
src/modules/user/enterprisescore/EnterprisescoreListView.vue
Normal file
113
src/modules/user/enterprisescore/EnterprisescoreListView.vue
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
/**
|
||||||
|
* F006 供应商评分管理 - W16
|
||||||
|
*
|
||||||
|
* 沿用老 vue user/enterprise/enterprise-score.vue 字段
|
||||||
|
*
|
||||||
|
* 端点: 4 URL 探测 (enterprisescore/page, /list, /search, /queryPage); 404 → FALLBACK seed
|
||||||
|
*/
|
||||||
|
import { ref, reactive, onMounted, computed } from 'vue'
|
||||||
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
|
import { httpGet } from '@/utils/request'
|
||||||
|
import PageHeader from '@/components/PageHeader/PageHeader.vue'
|
||||||
|
import SearchBar from '@/components/SearchBar/SearchBar.vue'
|
||||||
|
import DataTable from '@/components/DataTable/DataTable.vue'
|
||||||
|
|
||||||
|
interface ScoreRow { id:string; enterpriseId:string; enterpriseName:string; scoreContent:string; evaluator:string; score:number; deadline:string; status:number }
|
||||||
|
const ENDPOINTS = [
|
||||||
|
'/user/enterprisescore/page',
|
||||||
|
'/user/enterprisescore/list',
|
||||||
|
'/user/enterprisescore/search',
|
||||||
|
'/user/enterprisescore/queryPage'
|
||||||
|
]
|
||||||
|
const FALLBACK: ScoreRow[] = [
|
||||||
|
{ id:'SC001', enterpriseId:'s20015', enterpriseName:'坚美建筑材料有限公司', scoreContent:'商品质量', evaluator:'张工长', score:96, deadline:'2026-07-10 23:59:59', status:1 },
|
||||||
|
{ id:'SC002', enterpriseId:'s20015', enterpriseName:'坚美建筑材料有限公司', scoreContent:'配送时效', evaluator:'李采购', score:90, deadline:'2026-07-10 23:59:59', status:1 },
|
||||||
|
{ id:'SC003', enterpriseId:'s20016', enterpriseName:'华建门窗厂', scoreContent:'商品质量', evaluator:'王工长', score:88, deadline:'2026-07-10 23:59:59', status:1 },
|
||||||
|
{ id:'SC004', enterpriseId:'s20016', enterpriseName:'华建门窗厂', scoreContent:'安装服务', evaluator:'赵监理', score:92, deadline:'2026-07-10 23:59:59', status:1 },
|
||||||
|
{ id:'SC005', enterpriseId:'s20017', enterpriseName:'融鑫管业', scoreContent:'商品质量', evaluator:'钱工', score:85, deadline:'2026-07-15 23:59:59', status:0 },
|
||||||
|
{ id:'SC006', enterpriseId:'s20018', enterpriseName:'东方五金机械', scoreContent:'商品质量', evaluator:'孙经理', score:94, deadline:'2026-07-15 23:59:59', status:0 },
|
||||||
|
{ id:'SC007', enterpriseId:'s20019', enterpriseName:'美洁涂料', scoreContent:'商品质量', evaluator:'周工', score:78, deadline:'2026-07-15 23:59:59', status:0 },
|
||||||
|
{ id:'SC008', enterpriseId:'s20020', enterpriseName:'万家灯具', scoreContent:'商品质量', evaluator:'吴总', score:88, deadline:'2026-07-15 23:59:59', status:0 },
|
||||||
|
{ id:'SC009', enterpriseId:'s20021', enterpriseName:'顺达物流', scoreContent:'配送时效', evaluator:'郑采购', score:65, deadline:'2026-07-20 23:59:59', status:0 },
|
||||||
|
{ id:'SC010', enterpriseId:'s20022', enterpriseName:'众成厨具', scoreContent:'商品质量', evaluator:'冯工', score:76, deadline:'2026-07-20 23:59:59', status:0 },
|
||||||
|
{ id:'SC011', enterpriseId:'s20023', enterpriseName:'坚美建筑材料', scoreContent:'商品质量', evaluator:'陈工长', score:95, deadline:'2026-07-20 23:59:59', status:0 },
|
||||||
|
{ id:'SC012', enterpriseId:'s20024', enterpriseName:'华建门窗厂', scoreContent:'商品质量', evaluator:'褚采购', score:90, deadline:'2026-07-20 23:59:59', status:0 }
|
||||||
|
]
|
||||||
|
type Col = { type?: 'status'|'text'|'selection'; prop?: string; label: string; width?: number; slot?: string; fixed?: 'right'|true }
|
||||||
|
const columns: Col[] = [
|
||||||
|
{ type:'selection', label:'', width:50 },
|
||||||
|
{ prop:'id', label:'评分ID', width:90, fixed:true },
|
||||||
|
{ prop:'enterpriseName', label:'评分对象', width:200 },
|
||||||
|
{ prop:'scoreContent', label:'评分内容', width:140 },
|
||||||
|
{ prop:'evaluator', label:'评分员', width:120 },
|
||||||
|
{ prop:'score', label:'分值', width:90, slot:'score' },
|
||||||
|
{ prop:'status', label:'状态', width:90, slot:'status' },
|
||||||
|
{ prop:'deadline', label:'截止时间', width:170, slot:'deadline' },
|
||||||
|
{ prop:'actions', label:'操作', width:180, slot:'actions', fixed:'right' }
|
||||||
|
]
|
||||||
|
const STATUS_DEFAULTS = [
|
||||||
|
{ value:'', label:'全部' }
|
||||||
|
]
|
||||||
|
const list = ref<ScoreRow[]>([]); const total = ref(0); const loading = ref(false); const page = ref(1); const limit = ref(10); const filterKw = ref(''); const statusTab = ref('')
|
||||||
|
const dataSource = ref<'api'|'mock'|'loading'>('loading'); const apiError = ref<string>('')
|
||||||
|
async function load() {
|
||||||
|
loading.value = true; dataSource.value = 'loading'
|
||||||
|
for (const ep of ENDPOINTS) {
|
||||||
|
try {
|
||||||
|
const data: any = await httpGet<any>(ep, { page: page.value, limit: limit.value })
|
||||||
|
const rows = (data?.list || []).map((it: any) => ({ ...it, raw: it }))
|
||||||
|
list.value = rows; total.value = Number(data?.total || rows.length); dataSource.value = 'api'; apiError.value = ''; loading.value = false; return
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
list.value = [...FALLBACK]; total.value = FALLBACK.length; dataSource.value = 'mock'; apiError.value = '后端 API 暂未实装, 已加载前端示例数据'; loading.value = false
|
||||||
|
}
|
||||||
|
const filtered = computed(() => {
|
||||||
|
const kw = filterKw.value.trim().toLowerCase(); let rs = list.value
|
||||||
|
if (kw) rs = rs.filter(r => String(r.enterpriseName).includes(kw) || String(r.evaluator).includes(kw))
|
||||||
|
return rs
|
||||||
|
})
|
||||||
|
const searchFields: Array<{ key: string; label: string; type: 'text' }> = [
|
||||||
|
{ key:'enterpriseName', label:'评分对象', type:'text' },
|
||||||
|
{ key:'evaluator', label:'评分员', type:'text' }
|
||||||
|
]
|
||||||
|
const filters = reactive<Record<string, any>>({})
|
||||||
|
function onSearch() { filterKw.value = String(filters.enterpriseName || filters.evaluator || ''); page.value = 1 }
|
||||||
|
function onStatusTab(v: string) { statusTab.value = v; page.value = 1 }
|
||||||
|
function onView(row: ScoreRow) { ElMessage.info(`详情 ${row.id}`) }
|
||||||
|
async function onScore(row: ScoreRow) {
|
||||||
|
ElMessage.success('已跳转打分 (前端示例)')
|
||||||
|
}
|
||||||
|
function onExport() { ElMessage.success('已发起导出 (前端示例, 共 ' + filtered.value.length + ' 条)') }
|
||||||
|
onMounted(load)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="rr-page">
|
||||||
|
<PageHeader title="供应商评分管理" sub="供应商评分记录和评分员管理" :data-source="dataSource" :api-error="apiError">
|
||||||
|
<el-button type="primary" @click="onExport">导出明细</el-button>
|
||||||
|
</PageHeader>
|
||||||
|
<el-tabs v-model="statusTab" @tab-change="onStatusTab" class="rr-tabs">
|
||||||
|
<el-tab-pane v-for="t in STATUS_DEFAULTS" :key="t.value" :name="t.value" :label="t.label" />
|
||||||
|
</el-tabs>
|
||||||
|
<SearchBar v-model:filters="filters" :fields="searchFields" @search="onSearch" />
|
||||||
|
<DataTable :columns="columns" :data="filtered" :loading="loading" :page="page" :limit="limit" :total="total" row-key="id"
|
||||||
|
@page-change="(p: number) => { page = p; load() }" @limit-change="(l: number) => { limit = l; page = 1; load() }">
|
||||||
|
<template #score="{ row }">
|
||||||
|
<span style="font-weight: 600; color: var(--primary);">{{ row.score }}</span>
|
||||||
|
</template>
|
||||||
|
<template #status="{ row }">
|
||||||
|
<el-tag :type="row.status === 1 ? 'success' : 'warning'" size="small">{{ row.status === 1 ? '已评分' : '未评分' }}</el-tag>
|
||||||
|
</template>
|
||||||
|
<template #deadline="{ row }">{{ row.deadline }}</template>
|
||||||
|
<template #actions="{ row }">
|
||||||
|
<el-button type="primary" link @click="onView(row)">详情</el-button>
|
||||||
|
<el-button v-if="row.status === 0" type="success" link @click="onScore(row)">去打分</el-button>
|
||||||
|
</template>
|
||||||
|
</DataTable>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<style scoped>
|
||||||
|
.rr-page { padding: 16px; }
|
||||||
|
.rr-tabs { margin-bottom: 12px; }
|
||||||
|
</style>
|
||||||
@ -239,7 +239,11 @@ const routes: RouteRecordRaw[] = [
|
|||||||
{ path: '/other/seckill', component: () => import('@/modules/generic/MockTableView.vue'), props: () => ({ configKey: 'other-seckill' }), meta: { title: '秒杀活动' } },
|
{ path: '/other/seckill', component: () => import('@/modules/generic/MockTableView.vue'), props: () => ({ configKey: 'other-seckill' }), meta: { title: '秒杀活动' } },
|
||||||
// supplier 2 个
|
// supplier 2 个
|
||||||
{ path: '/supplier/coupon', component: () => import('@/modules/supplier/coupon/CouponListView.vue'), meta: { title: '优惠券' } }, // W7: 独立 view, 走 /supplier/coupon/page 真 API
|
{ path: '/supplier/coupon', component: () => import('@/modules/supplier/coupon/CouponListView.vue'), meta: { title: '优惠券' } }, // W7: 独立 view, 走 /supplier/coupon/page 真 API
|
||||||
{ path: '/supplier/promoter', component: () => import('@/modules/generic/MockTableView.vue'), props: () => ({ configKey: 'supplier-promoter' }), meta: { title: '推广返佣' } },
|
{ path: '/supplier/promoter', component: () => import('@/modules/supplier/promoter/PromoterListView.vue'), meta: { title: '推广员管理' } },
|
||||||
|
{ path: '/supplier/historylog', component: () => import('@/modules/supplier/historylog/HistorylogListView.vue'), meta: { title: '浏览足迹' } },
|
||||||
|
{ path: '/supplier/ordercomment', component: () => import('@/modules/supplier/ordercomment/OrderCommentListView.vue'), meta: { title: '订单评价' } },
|
||||||
|
{ path: '/supplier/logistics', component: () => import('@/modules/supplier/logistics/LogisticsListView.vue'), meta: { title: '物流方案' } },
|
||||||
|
{ path: '/supplier/coupon-scope', component: () => import('@/modules/supplier/couponscope/CouponScopeListView.vue'), meta: { title: '优惠券适用范围' } },
|
||||||
// sys 11 个
|
// sys 11 个
|
||||||
{ path: '/sys/dict', component: () => import('@/modules/generic/MockTableView.vue'), props: () => ({ configKey: 'sys-dict' }), meta: { title: '字典类型' } },
|
{ path: '/sys/dict', component: () => import('@/modules/generic/MockTableView.vue'), props: () => ({ configKey: 'sys-dict' }), meta: { title: '字典类型' } },
|
||||||
{ path: '/sys/log/login', component: () => import('@/modules/sys/LogLoginView.vue'), meta: { title: '登录日志' } },
|
{ path: '/sys/log/login', component: () => import('@/modules/sys/LogLoginView.vue'), meta: { title: '登录日志' } },
|
||||||
@ -250,9 +254,9 @@ const routes: RouteRecordRaw[] = [
|
|||||||
{ path: '/sys/notice-user', component: () => import('@/modules/generic/MockTableView.vue'), props: () => ({ configKey: 'sys-notice-user' }), meta: { title: '用户通知' } },
|
{ path: '/sys/notice-user', component: () => import('@/modules/generic/MockTableView.vue'), props: () => ({ configKey: 'sys-notice-user' }), meta: { title: '用户通知' } },
|
||||||
{ path: '/sys/userManagement', component: () => import('@/modules/generic/MockTableView.vue'), props: () => ({ configKey: 'sys-userManagement' }), meta: { title: '全国运营中心' } },
|
{ path: '/sys/userManagement', component: () => import('@/modules/generic/MockTableView.vue'), props: () => ({ configKey: 'sys-userManagement' }), meta: { title: '全国运营中心' } },
|
||||||
// user 9 个
|
// user 9 个
|
||||||
{ path: '/user/enterprise/level', component: () => import('@/modules/generic/MockTableView.vue'), props: () => ({ configKey: 'user-enterprise-level' }), meta: { title: '企业等级管理' } },
|
{ path: '/user/enterprise/level', component: () => import('@/modules/user/enterpriselevel/EnterpriselevelListView.vue'), meta: { title: '企业等级管理' } },
|
||||||
{ path: '/user/enterprise/rating', component: () => import('@/modules/generic/MockTableView.vue'), props: () => ({ configKey: 'user-enterprise-rating' }), meta: { title: '评级' } },
|
{ path: '/user/enterprise/rating', component: () => import('@/modules/user/enterpriserating/EnterpriseratingListView.vue'), meta: { title: '供应商评级' } },
|
||||||
{ path: '/user/enterprise/score', component: () => import('@/modules/generic/MockTableView.vue'), props: () => ({ configKey: 'user-enterprise-score' }), meta: { title: '评分' } },
|
{ path: '/user/enterprise/score', component: () => import('@/modules/user/enterprisescore/EnterprisescoreListView.vue'), meta: { title: '供应商评分' } },
|
||||||
{ path: '/user/finance/recharge', component: () => import('@/modules/generic/MockTableView.vue'), props: () => ({ configKey: 'user-finance-recharge', queryParams: { type: 1 } }), meta: { title: '充值管理' } },
|
{ path: '/user/finance/recharge', component: () => import('@/modules/generic/MockTableView.vue'), props: () => ({ configKey: 'user-finance-recharge', queryParams: { type: 1 } }), meta: { title: '充值管理' } },
|
||||||
{ path: '/user/finance/recharge-tx', component: () => import('@/modules/generic/MockTableView.vue'), props: () => ({ configKey: 'user-finance-recharge-tx', queryParams: { type: 2 } }), meta: { title: '提现管理' } },
|
{ path: '/user/finance/recharge-tx', component: () => import('@/modules/generic/MockTableView.vue'), props: () => ({ configKey: 'user-finance-recharge-tx', queryParams: { type: 2 } }), meta: { title: '提现管理' } },
|
||||||
{ path: '/user/finance/revenue', component: () => import('@/modules/generic/MockTableView.vue'), props: () => ({ configKey: 'user-finance-revenue', queryParams: { type: 3 } }), meta: { title: '分账记录管理' } },
|
{ path: '/user/finance/revenue', component: () => import('@/modules/generic/MockTableView.vue'), props: () => ({ configKey: 'user-finance-revenue', queryParams: { type: 3 } }), meta: { title: '分账记录管理' } },
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user