|
|
@@ -1,9 +1,6 @@
|
|
|
<template>
|
|
|
<div class="page-container">
|
|
|
- <el-tabs v-model="queryParams.tab" class="customer-tabs" @tab-change="handleTabChange">
|
|
|
- <el-tab-pane label="本品牌所属用户" :name="0" />
|
|
|
- <el-tab-pane label="订单关联用户" :name="1" />
|
|
|
- </el-tabs>
|
|
|
+
|
|
|
<el-card shadow="never">
|
|
|
<template #header>
|
|
|
<div class="card-header">
|
|
|
@@ -45,12 +42,15 @@
|
|
|
<el-tag v-for="tag in scope.row.tags" :key="tag.id" :type="tag.colorType || 'info'" effect="light" size="small" style="margin-right: 5px;">{{ tag.name }}</el-tag>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
- <el-table-column label="录入信息" width="200">
|
|
|
- <template #default="scope">
|
|
|
- <div><el-tag size="small" effect="plain" :type="scope.row.source && scope.row.source.includes('平台') ? '' : 'warning'">{{ scope.row.source || '-' }}</el-tag></div>
|
|
|
- <div style="font-size: 12px; color: #999; margin-top: 4px;">{{ scope.row.createTime }}</div>
|
|
|
- </template>
|
|
|
- </el-table-column>
|
|
|
+<!-- <el-table-column label="录入信息" width="200">-->
|
|
|
+<!-- <template #default="scope">-->
|
|
|
+<!-- <div v-if="scope.row.tenantName" style="margin-bottom: 4px;">-->
|
|
|
+<!-- <el-tag size="small" effect="light">{{ scope.row.tenantName }}</el-tag>-->
|
|
|
+<!-- </div>-->
|
|
|
+<!--<!– <div><el-tag size="small" effect="plain" :type="scope.row.source && scope.row.source.includes('平台') ? '' : 'warning'">{{ scope.row.source || '-' }}</el-tag></div>–>-->
|
|
|
+<!-- <div style="font-size: 12px; color: #999; margin-top: 4px;">{{ scope.row.createTime }}</div>-->
|
|
|
+<!-- </template>-->
|
|
|
+<!-- </el-table-column>-->
|
|
|
<el-table-column label="订单数量" width="120" align="center" sortable prop="orderCount">
|
|
|
<template #default="scope">
|
|
|
<div>{{ scope.row.orderCount }}单</div>
|
|
|
@@ -107,116 +107,17 @@
|
|
|
</el-card>
|
|
|
|
|
|
<!-- User Detail Drawer -->
|
|
|
- <el-drawer v-model="drawerVisible" title="用户档案详情" size="60%" destroy-on-close>
|
|
|
- <div class="profile-header">
|
|
|
- <el-avatar :size="80" :src="currentUser.avatarUrl" />
|
|
|
- <div class="profile-basic">
|
|
|
- <div class="name-row">
|
|
|
- <span class="name">{{ currentUser.name }}</span>
|
|
|
- <dict-tag :options="sys_user_sex" :value="currentUser.gender" />
|
|
|
- <span class="phone">{{ currentUser.phone }}</span>
|
|
|
- </div>
|
|
|
- <div class="tags-row" style="margin-top: 8px">
|
|
|
- <el-tag v-for="tag in currentUser.tags" :key="tag.id" :type="tag.colorType || 'info'" effect="light" size="small" style="margin-right: 5px">
|
|
|
- {{ tag.name }}
|
|
|
- </el-tag>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
-
|
|
|
- <el-tabs v-model="detailActiveTab" class="profile-tabs">
|
|
|
- <el-tab-pane label="档案信息" name="info">
|
|
|
- <div class="section-title">基本信息</div>
|
|
|
- <el-descriptions :column="2" border>
|
|
|
- <el-descriptions-item label="姓名">{{ currentUser.name }}</el-descriptions-item>
|
|
|
- <el-descriptions-item label="电话">{{ currentUser.phone }}</el-descriptions-item>
|
|
|
- <el-descriptions-item label="所属区域">{{ currentUser.areaName || '-' }}</el-descriptions-item>
|
|
|
- <el-descriptions-item label="所属站点">{{ currentUser.stationName || '-' }}</el-descriptions-item>
|
|
|
- <el-descriptions-item label="录入来源">{{ currentUser.source || '-' }}</el-descriptions-item>
|
|
|
- <el-descriptions-item label="录入时间">{{ currentUser.createTime || '-' }}</el-descriptions-item>
|
|
|
- </el-descriptions>
|
|
|
-
|
|
|
- <div class="section-title" style="margin-top: 20px">居住信息</div>
|
|
|
- <el-descriptions :column="2" border>
|
|
|
- <el-descriptions-item label="详细住址" :span="2">{{ currentUser.address }}</el-descriptions-item>
|
|
|
- <el-descriptions-item label="房屋类型">
|
|
|
- <dict-tag :options="sys_house_type" :value="currentUser.houseType" />
|
|
|
- </el-descriptions-item>
|
|
|
- <el-descriptions-item label="入门方式">
|
|
|
- <dict-tag :options="sys_entry_method" :value="currentUser.entryMethod" />
|
|
|
- </el-descriptions-item>
|
|
|
- <el-descriptions-item label="开门详情" :span="2">
|
|
|
- {{ currentUser.entryMethod === 'password' ? currentUser.entryPassword : currentUser.keyLocation }}
|
|
|
- </el-descriptions-item>
|
|
|
- </el-descriptions>
|
|
|
- </el-tab-pane>
|
|
|
-
|
|
|
- <el-tab-pane label="宠物列表" name="pets">
|
|
|
- <div style="margin-bottom: 15px;">
|
|
|
- <el-button type="primary" size="small" icon="Plus" @click="openAddPet">新增宠物</el-button>
|
|
|
- </div>
|
|
|
- <el-table :data="currentPets" border style="width: 100%">
|
|
|
- <el-table-column label="宠物信息" width="200">
|
|
|
- <template #default="scope">
|
|
|
- <div style="display: flex; align-items: center;">
|
|
|
- <el-avatar :size="30" src="https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png" style="margin-right: 8px;" />
|
|
|
- {{ scope.row.name }}
|
|
|
- </div>
|
|
|
- </template>
|
|
|
- </el-table-column>
|
|
|
- <el-table-column prop="breed" label="品种" />
|
|
|
- <el-table-column prop="gender" label="性别" width="60" />
|
|
|
- <el-table-column prop="age" label="年龄" width="60" />
|
|
|
- <el-table-column prop="status" label="健康状态">
|
|
|
- <template #default="scope">
|
|
|
- <el-tag :type="scope.row.status === '健康' ? 'success' : 'warning'" size="small">{{ scope.row.status }}</el-tag>
|
|
|
- </template>
|
|
|
- </el-table-column>
|
|
|
- <el-table-column label="疫苗接种" width="120" align="center">
|
|
|
- <template #default="scope">
|
|
|
- {{ scope.row.vaccine || '-' }}
|
|
|
- </template>
|
|
|
- </el-table-column>
|
|
|
- <el-table-column label="操作" width="200" align="center">
|
|
|
- <template #default="scope">
|
|
|
- <el-button link type="primary" @click="handlePetDetail(scope.row)">详情</el-button>
|
|
|
- <el-button link type="primary" @click="handlePetEdit(scope.row)">编辑</el-button>
|
|
|
- <el-button link type="primary" @click="handlePetRemark(scope.row)">备注</el-button>
|
|
|
- <el-button link type="danger" @click="handlePetDelete(scope.row)">删除</el-button>
|
|
|
- </template>
|
|
|
- </el-table-column>
|
|
|
- </el-table>
|
|
|
- </el-tab-pane>
|
|
|
-
|
|
|
- <el-tab-pane label="历史订单" name="orders">
|
|
|
- <el-table :data="mockOrders" border style="width: 100%">
|
|
|
- <el-table-column prop="orderNo" label="订单编号" width="180" />
|
|
|
- <el-table-column prop="service" label="服务项目" />
|
|
|
- <el-table-column prop="pets" label="服务宠物" />
|
|
|
- <el-table-column prop="time" label="服务时间" width="180" />
|
|
|
- <el-table-column prop="status" label="状态" width="100">
|
|
|
- <template #default="scope">
|
|
|
- <el-tag type="success" size="small">完成</el-tag>
|
|
|
- </template>
|
|
|
- </el-table-column>
|
|
|
- </el-table>
|
|
|
- </el-tab-pane>
|
|
|
-
|
|
|
- <el-tab-pane label="档案日志" name="logs">
|
|
|
- <el-timeline style="margin-top: 10px; padding-left: 5px;">
|
|
|
- <el-timeline-item
|
|
|
- v-for="(log, index) in changeLogs"
|
|
|
- :key="index"
|
|
|
- :timestamp="log.createTime"
|
|
|
- type="primary"
|
|
|
- >
|
|
|
- [{{ log.changeType }}] {{ log.content }}
|
|
|
- <div style="font-size: 12px; color: #999; margin-top: 4px">操作人: {{ log.operatorName }}</div>
|
|
|
- </el-timeline-item>
|
|
|
- </el-timeline>
|
|
|
- </el-tab-pane>
|
|
|
- </el-tabs>
|
|
|
- </el-drawer>
|
|
|
+ <CustomerDetailDrawer
|
|
|
+ ref="customerDetailRef"
|
|
|
+ v-model:visible="drawerVisible"
|
|
|
+ :customer-id="currentCustomerId"
|
|
|
+ editable
|
|
|
+ @add-pet="openAddPet"
|
|
|
+ @pet-detail="handlePetDetail"
|
|
|
+ @pet-edit="handlePetEdit"
|
|
|
+ @pet-remark="handlePetRemark"
|
|
|
+ @pet-delete="handlePetDelete"
|
|
|
+ />
|
|
|
|
|
|
<!-- Add/Edit User Dialog -->
|
|
|
<el-dialog v-model="dialogVisible" :title="isEdit ? '编辑用户' : '新增用户'" width="700px" destroy-on-close>
|
|
|
@@ -230,15 +131,15 @@
|
|
|
</el-col>
|
|
|
|
|
|
<el-col :span="24"><div class="form-section-header">基本资料</div></el-col>
|
|
|
- <el-col :span="12">
|
|
|
- <el-form-item label="录入来源">
|
|
|
- <PageSelect v-model="form.source"
|
|
|
- :options="brandList.map(item => ({ value: item.name, label: item.name }))"
|
|
|
- :total="brandTotal" :pageSize="10" placeholder="请选择所属品牌"
|
|
|
- @page-change="handleBrandPageChange"
|
|
|
- @visible-change="handleBrandVisibleChange" />
|
|
|
- </el-form-item>
|
|
|
- </el-col>
|
|
|
+<!-- <el-col :span="12">-->
|
|
|
+<!-- <el-form-item label="录入来源">-->
|
|
|
+<!-- <PageSelect v-model="form.tenantId"-->
|
|
|
+<!-- :options="brandList.map(item => ({ value: item.id, label: item.name }))"-->
|
|
|
+<!-- :total="brandTotal" :pageSize="10" placeholder="请选择所属品牌"-->
|
|
|
+<!-- @page-change="handleBrandPageChange"-->
|
|
|
+<!-- @visible-change="handleBrandVisibleChange" />-->
|
|
|
+<!-- </el-form-item>-->
|
|
|
+<!-- </el-col>-->
|
|
|
<el-col :span="12">
|
|
|
<el-form-item label="所属区域">
|
|
|
<el-cascader v-model="formAreaValue" :options="areaTreeOptions" placeholder="请选择区域"
|
|
|
@@ -488,7 +389,10 @@ import { listOnStore } from '@/api/system/areaStation'
|
|
|
import { listOnStore as listBrandOnStore } from '@/api/system/tenant'
|
|
|
import { regionData, codeToText } from 'element-china-area-data'
|
|
|
import PageSelect from '@/components/PageSelect/index.vue'
|
|
|
+import CustomerDetailDrawer from '@/components/CustomerDetailDrawer/index.vue'
|
|
|
+import { useUserStore } from '@/store/modules/user'
|
|
|
|
|
|
+const userStore = useUserStore()
|
|
|
const { proxy } = getCurrentInstance()
|
|
|
const { sys_user_sex, sys_customer_status, sys_house_type, sys_entry_method, sys_pet_gender, sys_pet_size, sys_pet_type, sys_pet_breed } = toRefs(
|
|
|
proxy?.useDict('sys_user_sex', 'sys_customer_status', 'sys_house_type', 'sys_entry_method', 'sys_pet_gender', 'sys_pet_size', 'sys_pet_type', 'sys_pet_breed')
|
|
|
@@ -534,15 +438,10 @@ const queryParams = reactive({
|
|
|
keyword: '',
|
|
|
areaId: undefined,
|
|
|
stationId: undefined,
|
|
|
- status: undefined,
|
|
|
- tab: 0
|
|
|
+ status: undefined
|
|
|
})
|
|
|
|
|
|
-/** 处理标签页切换 */
|
|
|
-const handleTabChange = () => {
|
|
|
- queryParams.pageNum = 1
|
|
|
- getList()
|
|
|
-}
|
|
|
+
|
|
|
|
|
|
const searchForm = reactive({
|
|
|
keyword: '',
|
|
|
@@ -560,12 +459,12 @@ const petDialogActiveTab = ref('basic')
|
|
|
|
|
|
const selectedTagIds = ref([])
|
|
|
const currentUser = ref({})
|
|
|
-const currentPets = ref([])
|
|
|
const tableData = ref([])
|
|
|
|
|
|
const allUserTags = ref([])
|
|
|
const allPetTags = ref([])
|
|
|
-const changeLogs = ref([])
|
|
|
+const currentCustomerId = ref(null)
|
|
|
+const customerDetailRef = ref(null)
|
|
|
const userAvatarDisplayUrl = ref('')
|
|
|
const petAvatarDisplayUrl = ref('')
|
|
|
const petVaccineCertDisplayUrl = ref('')
|
|
|
@@ -576,10 +475,7 @@ const formAreaValue = ref([])
|
|
|
const regionCascaderValue = ref([])
|
|
|
|
|
|
|
|
|
-const mockOrders = ref([
|
|
|
- { orderNo: 'DD20231001001', service: '上门喂养 (标准版)', pets: '旺财', time: '2023-10-01 10:00', amount: '88.00', status: 'completed' },
|
|
|
- { orderNo: 'DD20230915002', service: '深度洗护套餐', pets: '旺财, 咪咪', time: '2023-09-15 14:00', amount: '158.00', status: 'completed' }
|
|
|
-])
|
|
|
+// 移除 mockOrders
|
|
|
|
|
|
const form = reactive({
|
|
|
id: undefined,
|
|
|
@@ -598,7 +494,7 @@ const form = reactive({
|
|
|
entryMethod: '',
|
|
|
entryPassword: '',
|
|
|
keyLocation: '',
|
|
|
- source: '',
|
|
|
+ tenantId: undefined,
|
|
|
emergencyContact: '',
|
|
|
emergencyPhone: '',
|
|
|
memberLevel: 0,
|
|
|
@@ -700,7 +596,7 @@ const handleSearch = () => {
|
|
|
}
|
|
|
|
|
|
const loadTags = () => {
|
|
|
- listAllTag({ category: 'user', status: 0 }).then((res) => {
|
|
|
+ listAllTag({ category: 'customer', status: 0 }).then((res) => {
|
|
|
allUserTags.value = res.data || []
|
|
|
}).catch((err) => {
|
|
|
console.error('加载用户标签失败', err)
|
|
|
@@ -718,7 +614,7 @@ const handleAdd = () => {
|
|
|
Object.assign(form, {
|
|
|
id: undefined, name: '', phone: '', avatar: undefined, gender: undefined, birthday: '', idCard: '',
|
|
|
areaId: undefined, stationId: undefined, regionCode: '', region: [], address: '',
|
|
|
- houseType: '', entryMethod: '', entryPassword: '', keyLocation: '', source: '',
|
|
|
+ houseType: '', entryMethod: '', entryPassword: '', keyLocation: '', tenantId: userStore.tenantId,
|
|
|
emergencyContact: '', emergencyPhone: '', memberLevel: 0, status: 0, remark: '', tagIds: []
|
|
|
})
|
|
|
userAvatarDisplayUrl.value = ''
|
|
|
@@ -736,7 +632,7 @@ const handleEdit = (row) => {
|
|
|
birthday: data.birthday, idCard: data.idCard, areaId: data.areaId, stationId: data.stationId,
|
|
|
regionCode: data.regionCode, region: data.regionCode ? data.regionCode.split('/') : [],
|
|
|
address: data.address, houseType: data.houseType, entryMethod: data.entryMethod,
|
|
|
- entryPassword: data.entryPassword, keyLocation: data.keyLocation, source: data.source,
|
|
|
+ entryPassword: data.entryPassword, keyLocation: data.keyLocation, tenantId: data.tenantId,
|
|
|
emergencyContact: data.emergencyContact, emergencyPhone: data.emergencyPhone,
|
|
|
memberLevel: data.memberLevel, status: data.status, remark: data.remark, tagIds: []
|
|
|
})
|
|
|
@@ -768,41 +664,11 @@ const handleEdit = (row) => {
|
|
|
}
|
|
|
|
|
|
const handleDetail = (row) => {
|
|
|
- getCustomer(row.id).then((res) => {
|
|
|
- const data = res.data
|
|
|
- // Convert areaId to area name
|
|
|
- if (data.areaId) {
|
|
|
- const area = allNodes.value.find(n => n.id === data.areaId)
|
|
|
- data.areaName = area ? area.name : '-'
|
|
|
- } else {
|
|
|
- data.areaName = '-'
|
|
|
- }
|
|
|
- // Convert stationId to station name
|
|
|
- if (data.stationId) {
|
|
|
- const station = allNodes.value.find(n => n.id === data.stationId)
|
|
|
- data.stationName = station ? station.name : '-'
|
|
|
- } else {
|
|
|
- data.stationName = '-'
|
|
|
- }
|
|
|
- currentUser.value = data
|
|
|
- detailActiveTab.value = 'info'
|
|
|
- loadDetailPets(row.id)
|
|
|
- loadDetailLogs(row.id, 'customer')
|
|
|
- drawerVisible.value = true
|
|
|
- })
|
|
|
+ currentCustomerId.value = row.id
|
|
|
+ drawerVisible.value = true
|
|
|
}
|
|
|
|
|
|
-const loadDetailPets = (userId) => {
|
|
|
- listPetByUser(userId).then((res) => {
|
|
|
- currentPets.value = res.data || []
|
|
|
- })
|
|
|
-}
|
|
|
-
|
|
|
-const loadDetailLogs = (targetId, targetType) => {
|
|
|
- listAllChangeLog(targetId, targetType).then((res) => {
|
|
|
- changeLogs.value = res.data || []
|
|
|
- })
|
|
|
-}
|
|
|
+// 移除不需要的方法
|
|
|
|
|
|
const handleRemark = (row) => {
|
|
|
currentUser.value = row
|
|
|
@@ -954,7 +820,7 @@ const handlePetUploadVaccineCert = async (file) => {
|
|
|
const openAddPet = () => {
|
|
|
petDialogActiveTab.value = 'basic'
|
|
|
Object.assign(petForm, {
|
|
|
- id: undefined, userId: currentUser.value.id, avatar: undefined, name: '', type: 0, gender: undefined,
|
|
|
+ id: undefined, userId: currentCustomerId.value, avatar: undefined, name: '', type: 0, gender: undefined,
|
|
|
breed: '', birthday: '', age: 1, weight: 5, size: 'small', isSterilized: 0,
|
|
|
arrivalTime: '', houseType: '', entryMethod: '', entryPassword: '', keyLocation: '',
|
|
|
personality: '', cutePersonality: '', healthStatus: '健康', aggression: 0,
|
|
|
@@ -997,7 +863,7 @@ const handlePetDelete = (row) => {
|
|
|
ElMessageBox.confirm(`确认删除宠物 [${row.name}] 吗?`, '提示', { type: 'warning' }).then(() => {
|
|
|
delPet(row.id).then(() => {
|
|
|
ElMessage.success('宠物删除成功')
|
|
|
- loadDetailPets(currentUser.value.id)
|
|
|
+ customerDetailRef.value.refresh()
|
|
|
getList()
|
|
|
})
|
|
|
})
|
|
|
@@ -1011,7 +877,7 @@ const savePet = () => {
|
|
|
api.then(() => {
|
|
|
ElMessage.success('宠物档案保存成功')
|
|
|
petDialogVisible.value = false
|
|
|
- loadDetailPets(currentUser.value.id)
|
|
|
+ customerDetailRef.value.refresh()
|
|
|
getList()
|
|
|
}).finally(() => {
|
|
|
submitLoading.value = false
|