Files
medical-mall/pages/mall/admin/distribution/division/agent.uvue
2026-02-16 15:19:17 +08:00

335 lines
13 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<view class="admin-page">
<view class="filter-card">
<view class="filter-row">
<view class="filter-item">
<text class="label">代理商查询:</text>
<input class="filter-input" placeholder="请输入姓名或UID" v-model="searchQuery" @confirm="onSearch" />
</view>
<view class="filter-btns">
<button class="btn primary" @click="onSearch">查询</button>
<button class="btn" @click="onReset">重置</button>
</view>
</view>
</view>
<view class="content-card">
<view class="action-bar">
<button class="btn primary small" @click="onAdd">添加代理商</button>
</view>
<view class="table-container">
<view v-if="isLoading" class="loading-mask">
<text class="loading-text">数据加载中...</text>
</view>
<view class="table-header">
<view class="col col-uid"><text>用户UID</text></view>
<view class="col col-avatar"><text>头像</text></view>
<view class="col col-name"><text>名称</text></view>
<view class="col col-ratio"><text>分佣比例</text></view>
<view class="col col-dept"><text>所属事业部</text></view>
<view class="col col-count"><text>员工数量</text></view>
<view class="col col-time"><text>过期时间</text></view>
<view class="col col-status"><text>状态</text></view>
<view class="col col-ops"><text>操作</text></view>
</view>
<view class="table-body">
<view v-if="agentList.length === 0 && !isLoading" class="empty-row">
<text>暂无代理商数据</text>
</view>
<view v-for="item in agentList" :key="item.uid" class="table-row">
<view class="col col-uid"><text class="td-txt-small">{{ item.uid }}</text></view>
<view class="col col-avatar">
<image class="avatar-img" src="/static/logo.png" mode="aspectFill" />
</view>
<view class="col col-name"><text>{{ item.name }}</text></view>
<view class="col col-ratio"><text>{{ item.commission_ratio }}%</text></view>
<view class="col col-dept"><text>{{ item.division_name || '-' }}</text></view>
<view class="col col-count"><text>{{ item.staffCount }}</text></view>
<view class="col col-time"><text>{{ item.end_time ? item.end_time.substring(0, 10) : '-' }}</text></view>
<view class="col col-status">
<switch :checked="item.is_enabled" color="#1890ff" scale="0.8" @change="() => onToggleStatus(item)" />
</view>
<view class="col col-ops">
<text class="op-link" @click="onEdit(item)">编辑</text>
<text class="op-divider">|</text>
<text class="op-link danger" @click="onDelete(item.uid)">删除</text>
</view>
</view>
</view>
</view>
<view class="pagination">
<view class="pager-btns">
<button class="btn small" :disabled="page <= 1" @click="onPrevPage">上一页</button>
<text class="page-num">第 {{ page }} 页</text>
<button class="btn small" :disabled="agentList.length < pageSize" @click="onNextPage">下一页</button>
</view>
<text class="page-info">共 {{ agentList.length }} 条记录</text>
</view>
</view>
<!-- 添加/编辑 弹窗 -->
<view v-if="editPopupVisible" class="popup-mask" @click="closeEditModal">
<view class="popup-card" @click.stop>
<view class="popup-header">
<text class="popup-title">{{ isEdit ? '编辑代理商' : '添加代理商' }}</text>
<text class="popup-close" @click="closeEditModal">×</text>
</view>
<view class="popup-body">
<view class="popup-item" v-if="!isEdit">
<text class="popup-label">用户 UID</text>
<input v-model="editForm.uid" class="popup-input" placeholder="请输入代理商 UID" />
</view>
<view class="popup-item">
<text class="popup-label">所属事业部</text>
<picker :value="divisionIndex" :range="divisionOptions" range-key="name" @change="onDivisionChange">
<view class="select-box">
<text>{{ divisionOptions[divisionIndex]?.name || '请选择事业部' }}</text>
<text class="arrow">▼</text>
</view>
</picker>
</view>
<view class="popup-item">
<text class="popup-label">代理商名称</text>
<input v-model="editForm.name" class="popup-input" placeholder="请输入名称" />
</view>
<view class="popup-item">
<text class="popup-label">分佣比例 (%)</text>
<input v-model="editForm.commission_ratio" type="digit" class="popup-input" placeholder="0 - 100" />
</view>
<view class="popup-item">
<text class="popup-label">过期时间</text>
<input v-model="editForm.end_time" class="popup-input" placeholder="YYYY-MM-DD" />
</view>
<view class="popup-item popup-row">
<text class="popup-label">启用状态</text>
<switch :checked="editForm.is_enabled" color="#1890ff" scale="0.8" @change="(e : any) => editForm.is_enabled = e.detail.value" />
</view>
</view>
<view class="popup-footer">
<button class="btn" @click="closeEditModal">取消</button>
<button class="btn primary" @click="handleSave">保存</button>
</view>
</view>
</view>
</view>
</template>
<script setup lang="uts">
import { ref, onMounted, reactive } from 'vue'
import { getAgentList, saveAgent, deleteAgent, getDivisionList, type Agent, type Division } from '@/services/admin/distributionService.uts'
const agentList = ref<Agent[]>([])
const isLoading = ref(false)
const searchQuery = ref('')
const page = ref(1)
const pageSize = 20
// 事业部选项 (供选择器使用)
const divisionOptions = ref<Division[]>([])
const divisionIndex = ref(0)
// 弹窗状态
const editPopupVisible = ref(false)
const isEdit = ref(false)
const editForm = reactive({
uid: '',
division_uid: '',
name: '',
commission_ratio: 0,
is_enabled: true,
end_time: ''
})
onMounted(() => {
loadAgents()
loadDivisions()
})
async function loadAgents() {
isLoading.value = true
try {
const res = await getAgentList(searchQuery.value || null, page.value, pageSize)
agentList.value = res
} catch (e) {
uni.showToast({ title: '加载失败', icon: 'none' })
} finally {
isLoading.value = false
}
}
async function loadDivisions() {
const res = await getDivisionList(null, 1, 100)
divisionOptions.value = res
}
function onSearch() {
page.value = 1
loadAgents()
}
function onReset() {
searchQuery.value = ''
page.value = 1
loadAgents()
}
function onAdd() {
isEdit.value = false
Object.assign(editForm, {
uid: '',
division_uid: '',
name: '',
commission_ratio: 0,
is_enabled: true,
end_time: ''
})
divisionIndex.value = 0
editPopupVisible.value = true
}
function onEdit(item : Agent) {
isEdit.value = true
Object.assign(editForm, {
uid: item.uid,
division_uid: item.division_uid,
name: item.name,
commission_ratio: item.commission_ratio,
is_enabled: item.is_enabled,
end_time: item.end_time || ''
})
const idx = divisionOptions.value.findIndex(d => d.uid === item.division_uid)
divisionIndex.value = idx > -1 ? idx : 0
editPopupVisible.value = true
}
function closeEditModal() {
editPopupVisible.value = false
}
function onDivisionChange(e : any) {
divisionIndex.value = e.detail.value as number
editForm.division_uid = divisionOptions.value[divisionIndex.value].uid
}
async function handleSave() {
if (!editForm.uid || !editForm.division_uid || !editForm.name) {
uni.showToast({ title: '请完善信息', icon: 'none' })
return
}
isLoading.value = true
try {
const success = await saveAgent(editForm)
if (success) {
uni.showToast({ title: '保存成功', icon: 'success' })
editPopupVisible.value = false
loadAgents()
}
} finally {
isLoading.value = false
}
}
async function onDelete(uid : string) {
uni.showModal({
title: '确认删除',
content: '确定要删除该代理商吗?',
success: async (res) => {
if (res.confirm) {
isLoading.value = true
try {
const success = await deleteAgent(uid)
if (success) {
uni.showToast({ title: '删除成功' })
loadAgents()
}
} finally {
isLoading.value = false
}
}
}
})
}
async function onToggleStatus(item : Agent) {
const updated = { ...item, is_enabled: !item.is_enabled }
const success = await saveAgent(updated)
if (success) {
item.is_enabled = !item.is_enabled
uni.showToast({ title: '状态已更新' })
}
}
function onPrevPage() {
if (page.value > 1) {
page.value--
loadAgents()
}
}
function onNextPage() {
if (agentList.value.length < pageSize) return
page.value++
loadAgents()
}
</script>
<style scoped lang="scss">
.admin-page { padding: 0; }
.filter-card { background: #fff; padding: 24px; margin-bottom: 16px; border-radius: 4px; }
.filter-row { display: flex; flex-direction: row; align-items: center; gap: 24px; }
.label { font-size: 14px; color: #333; }
.filter-input { border: 1px solid #d9d9d9; height: 32px; width: 220px; padding: 0 12px; font-size: 14px; border-radius: 4px; }
.btn { height: 32px; padding: 0 16px; font-size: 14px; border: 1px solid #d9d9d9; background: #fff; display: flex; align-items: center; justify-content: center; cursor: pointer; border-radius: 4px; }
.btn.primary { background: #1890ff; border-color: #1890ff; color: #fff; }
.btn.small { height: 28px; padding: 0 12px; font-size: 13px; }
.content-card { background: #fff; border-radius: 4px; position: relative; }
.action-bar { padding: 16px 24px; }
.table-container { padding: 0 24px 24px; min-height: 200px; }
.table-header { display: flex; flex-direction: row; background: #f8faff; border-bottom: 1px solid #f0f0f0; padding: 12px 0; }
.table-row { display: flex; flex-direction: row; border-bottom: 1px solid #f0f0f0; padding: 12px 0; align-items: center; &:hover { background: #fafafa; } }
.col { padding: 0 8px; font-size: 14px; color: #333; display: flex; align-items: center; }
.col-uid { width: 80px; } .col-avatar { width: 80px; justify-content: center; } .col-name { width: 120px; } .col-ratio { width: 100px; justify-content: center; } .col-dept { width: 140px; } .col-count { width: 100px; justify-content: center; } .col-time { width: 120px; justify-content: center; } .col-status { width: 80px; justify-content: center; } .col-ops { flex: 1; justify-content: flex-end; }
.avatar-img { width: 32px; height: 32px; border-radius: 2px; }
.op-link { color: #1890ff; cursor: pointer; &.danger { color: #ff4d4f; } }
.op-divider { color: #e8e8e8; margin: 0 8px; }
.pagination { padding: 16px 24px; border-top: 1px solid #f0f0f0; display: flex; flex-direction: row; align-items: center; justify-content: space-between; }
.pager-btns { display: flex; flex-direction: row; align-items: center; gap: 12px; }
.page-num { font-size: 14px; color: #333; }
.page-info { font-size: 14px; color: #999; }
.loading-mask { position: absolute; top: 0; left: 0; right: 0; bottom: 0; background: rgba(255,255,255,0.7); display: flex; align-items: center; justify-content: center; z-index: 10; }
.loading-text { color: #1890ff; font-size: 14px; }
.empty-row { padding: 40px 0; text-align: center; color: #999; font-size: 14px; }
/* 弹窗样式 */
.popup-mask { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background-color: rgba(0, 0, 0, 0.5); display: flex; align-items: center; justify-content: center; z-index: 999; }
.popup-card { width: 500px; background-color: #fff; border-radius: 8px; display: flex; flex-direction: column; overflow: hidden; }
.popup-header { display: flex; flex-direction: row; justify-content: space-between; align-items: center; padding: 16px 20px; border-bottom: 1px solid #f0f0f0; }
.popup-title { font-size: 16px; font-weight: bold; color: #333; }
.popup-close { font-size: 20px; color: #999; cursor: pointer; padding: 4px; }
.popup-body { padding: 24px; display: flex; flex-direction: column; gap: 16px; }
.popup-item { display: flex; flex-direction: column; gap: 8px; }
.popup-row { flex-direction: row; align-items: center; justify-content: space-between; }
.popup-label { font-size: 14px; color: #666; }
.popup-input { border: 1px solid #d9d9d9; border-radius: 4px; height: 36px; padding: 0 12px; font-size: 14px; width: 100%; }
.select-box { border: 1px solid #d9d9d9; border-radius: 4px; height: 36px; padding: 0 12px; display: flex; flex-direction: row; align-items: center; justify-content: space-between; text { font-size: 14px; color: #333; } .arrow { font-size: 10px; color: #bfbfbf; } }
.popup-footer { padding: 16px 24px; border-top: 1px solid #f0f0f0; display: flex; flex-direction: row; justify-content: flex-end; gap: 12px; }
</style>