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

309 lines
12 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">
<!-- Loading 遮罩 -->
<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-code"><text>邀请码</text></view>
<view class="col col-ratio"><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="divisionList.length === 0 && !isLoading" class="empty-row">
<text>暂无事业部数据</text>
</view>
<view v-for="item in divisionList" :key="item.uid" class="table-row">
<view class="col col-uid"><text>{{ 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-code"><text>{{ item.invite_code }}</text></view>
<view class="col col-ratio"><text>{{ item.commission_ratio }}%</text></view>
<view class="col col-count"><text>{{ item.agentCount }}</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="divisionList.length < pageSize" @click="onNextPage">下一页</button>
</view>
<text class="page-info">共 {{ divisionList.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>
<input v-model="editForm.name" class="popup-input" placeholder="如:华东事业部" />
</view>
<view class="popup-item">
<text class="popup-label">邀请码</text>
<input v-model="editForm.invite_code" 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 { getDivisionList, saveDivision, deleteDivision, type Division } from '@/services/admin/distributionService.uts'
const divisionList = ref<Division[]>([])
const isLoading = ref(false)
const searchQuery = ref('')
const page = ref(1)
const pageSize = 20
// 弹窗表单状态
const editPopupVisible = ref(false)
const isEdit = ref(false)
const editForm = reactive({
uid: '',
name: '',
invite_code: '',
commission_ratio: 0,
is_enabled: true,
end_time: ''
})
onMounted(() => {
loadDivisions()
})
async function loadDivisions() {
isLoading.value = true
try {
const res = await getDivisionList(searchQuery.value || null, page.value, pageSize)
divisionList.value = res
} catch (e) {
uni.showToast({ title: '加载失败', icon: 'none' })
} finally {
isLoading.value = false
}
}
function onSearch() {
page.value = 1
loadDivisions()
}
function onReset() {
searchQuery.value = ''
page.value = 1
loadDivisions()
}
function onAdd() {
isEdit.value = false
Object.assign(editForm, {
uid: '',
name: '',
invite_code: Math.random().toString(36).slice(2, 10).toUpperCase(),
commission_ratio: 0,
is_enabled: true,
end_time: ''
})
editPopupVisible.value = true
}
function onEdit(item : Division) {
isEdit.value = true
Object.assign(editForm, {
uid: item.uid,
name: item.name,
invite_code: item.invite_code,
commission_ratio: item.commission_ratio,
is_enabled: item.is_enabled,
end_time: item.end_time || ''
})
editPopupVisible.value = true
}
function closeEditModal() {
editPopupVisible.value = false
}
async function handleSave() {
if (!editForm.uid || !editForm.name) {
uni.showToast({ title: '请填写必要信息', icon: 'none' })
return
}
isLoading.value = true
try {
const success = await saveDivision(editForm)
if (success) {
uni.showToast({ title: '保存成功', icon: 'success' })
editPopupVisible.value = false
loadDivisions()
} else {
uni.showToast({ title: '保存失败', icon: 'none' })
}
} 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 deleteDivision(uid)
if (success) {
uni.showToast({ title: '删除成功' })
loadDivisions()
}
} finally {
isLoading.value = false
}
}
}
})
}
async function onToggleStatus(item : Division) {
const updated = { ...item, is_enabled: !item.is_enabled }
const success = await saveDivision(updated)
if (success) {
item.is_enabled = !item.is_enabled
uni.showToast({ title: '状态已更新' })
}
}
function onPrevPage() {
if (page.value > 1) {
page.value--
loadDivisions()
}
}
function onNextPage() {
if (divisionList.value.length < pageSize) return
page.value++
loadDivisions()
}
</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-code { width: 100px; justify-content: center; } .col-ratio { width: 100px; justify-content: center; } .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%; }
.popup-footer { padding: 16px 24px; border-top: 1px solid #f0f0f0; display: flex; flex-direction: row; justify-content: flex-end; gap: 12px; }
</style>