|
@@ -5,22 +5,18 @@
|
|
|
<div class="card-header">
|
|
<div class="card-header">
|
|
|
<span class="title">用户管理</span>
|
|
<span class="title">用户管理</span>
|
|
|
<div class="header-actions">
|
|
<div class="header-actions">
|
|
|
- <el-cascader
|
|
|
|
|
- v-model="searchAreaValue"
|
|
|
|
|
- :options="areaTreeOptions"
|
|
|
|
|
- :props="{ value: 'id', label: 'name' }"
|
|
|
|
|
- placeholder="所属站点"
|
|
|
|
|
- style="width: 240px; margin-right: 10px"
|
|
|
|
|
- clearable
|
|
|
|
|
- @change="onSearchAreaChange"
|
|
|
|
|
- />
|
|
|
|
|
- <el-input v-model="searchForm.keyword" placeholder="搜索姓名/手机号" style="width: 200px; margin-right: 10px;" clearable @keyup.enter="handleSearch" @clear="handleSearch" />
|
|
|
|
|
- <el-button type="primary" icon="Plus" @click="handleAdd" v-hasPermi="['archieves:customer:add']">新增用户</el-button>
|
|
|
|
|
|
|
+ <el-cascader v-model="searchAreaValue" :options="areaTreeOptions" :props="{ value: 'id', label: 'name' }"
|
|
|
|
|
+ placeholder="所属站点" style="width: 240px; margin-right: 10px" clearable @change="onSearchAreaChange" />
|
|
|
|
|
+ <el-input v-model="searchForm.keyword" placeholder="搜索姓名/手机号" style="width: 200px; margin-right: 10px;"
|
|
|
|
|
+ clearable @keyup.enter="handleSearch" @clear="handleSearch" />
|
|
|
|
|
+ <el-button type="primary" icon="Plus" @click="handleAdd"
|
|
|
|
|
+ v-hasPermi="['archieves:customer:add']">新增用户</el-button>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
</template>
|
|
</template>
|
|
|
|
|
|
|
|
- <el-table :data="tableData" v-loading="loading" style="width: 100%" :header-cell-style="{ background: '#f5f7fa' }">
|
|
|
|
|
|
|
+ <el-table :data="tableData" v-loading="loading" style="width: 100%"
|
|
|
|
|
+ :header-cell-style="{ background: '#f5f7fa' }">
|
|
|
<el-table-column label="用户基本信息" width="250">
|
|
<el-table-column label="用户基本信息" width="250">
|
|
|
<template #default="scope">
|
|
<template #default="scope">
|
|
|
<div style="display: flex; align-items: center;">
|
|
<div style="display: flex; align-items: center;">
|
|
@@ -36,17 +32,20 @@
|
|
|
</el-table-column>
|
|
</el-table-column>
|
|
|
<el-table-column label="住址" show-overflow-tooltip min-width="150">
|
|
<el-table-column label="住址" show-overflow-tooltip min-width="150">
|
|
|
<template #default="scope">
|
|
<template #default="scope">
|
|
|
- {{ [scope.row.regionCode ? scope.row.regionCode.split('/').map(c => codeToName(c)).filter(Boolean).join(' ') : '', scope.row.address].filter(Boolean).join(' ') || '-' }}
|
|
|
|
|
|
|
+ {{[scope.row.regionCode ? scope.row.regionCode.split('/').map(c => codeToName(c)).filter(Boolean).join(' ')
|
|
|
|
|
+ : '', scope.row.address].filter(Boolean).join(' ') || '-'}}
|
|
|
</template>
|
|
</template>
|
|
|
</el-table-column>
|
|
</el-table-column>
|
|
|
<el-table-column label="用户标签" width="200">
|
|
<el-table-column label="用户标签" width="200">
|
|
|
<template #default="scope">
|
|
<template #default="scope">
|
|
|
- <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>
|
|
|
|
|
|
|
+ <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>
|
|
</template>
|
|
|
</el-table-column>
|
|
</el-table-column>
|
|
|
<el-table-column label="录入信息" width="200">
|
|
<el-table-column label="录入信息" width="200">
|
|
|
<template #default="scope">
|
|
<template #default="scope">
|
|
|
- <div><el-tag size="small" effect="plain" :type="scope.row.tenantName ? '' : 'warning'">{{ scope.row.tenantName || '-' }}</el-tag></div>
|
|
|
|
|
|
|
+ <div><el-tag size="small" effect="plain" :type="scope.row.tenantName ? '' : 'warning'">{{
|
|
|
|
|
+ scope.row.tenantName || '-' }}</el-tag></div>
|
|
|
<div style="font-size: 12px; color: #999; margin-top: 4px;">{{ scope.row.createTime }}</div>
|
|
<div style="font-size: 12px; color: #999; margin-top: 4px;">{{ scope.row.createTime }}</div>
|
|
|
</template>
|
|
</template>
|
|
|
</el-table-column>
|
|
</el-table-column>
|
|
@@ -62,30 +61,27 @@
|
|
|
</el-table-column>
|
|
</el-table-column>
|
|
|
<el-table-column label="状态" width="100" align="center">
|
|
<el-table-column label="状态" width="100" align="center">
|
|
|
<template #default="scope">
|
|
<template #default="scope">
|
|
|
- <el-switch
|
|
|
|
|
- v-model="scope.row.status"
|
|
|
|
|
- :active-value="0"
|
|
|
|
|
- :inactive-value="1"
|
|
|
|
|
- inline-prompt
|
|
|
|
|
- active-text="正常"
|
|
|
|
|
- inactive-text="停用"
|
|
|
|
|
- @change="handleStatusChange(scope.row)"
|
|
|
|
|
- />
|
|
|
|
|
|
|
+ <el-switch v-model="scope.row.status" :active-value="0" :inactive-value="1" inline-prompt active-text="正常"
|
|
|
|
|
+ inactive-text="停用" @change="handleStatusChange(scope.row)" />
|
|
|
</template>
|
|
</template>
|
|
|
</el-table-column>
|
|
</el-table-column>
|
|
|
<el-table-column prop="remark" label="备注" show-overflow-tooltip min-width="150" />
|
|
<el-table-column prop="remark" label="备注" show-overflow-tooltip min-width="150" />
|
|
|
<el-table-column label="操作" width="200" align="center">
|
|
<el-table-column label="操作" width="200" align="center">
|
|
|
<template #default="scope">
|
|
<template #default="scope">
|
|
|
- <el-button link type="primary" size="small" @click="handleDetail(scope.row)" v-hasPermi="['archieves:customer:query']">详情</el-button>
|
|
|
|
|
- <el-button link type="primary" size="small" @click="handleEdit(scope.row)" v-hasPermi="['archieves:customer:edit']">编辑</el-button>
|
|
|
|
|
- <el-dropdown trigger="click" @command="(cmd) => handleCommand(cmd, scope.row)" style="margin-left: 10px; vertical-align: middle">
|
|
|
|
|
|
|
+ <el-button link type="primary" size="small" @click="handleDetail(scope.row)"
|
|
|
|
|
+ v-hasPermi="['archieves:customer:query']">详情</el-button>
|
|
|
|
|
+ <el-button link type="primary" size="small" @click="handleEdit(scope.row)"
|
|
|
|
|
+ v-hasPermi="['archieves:customer:edit']">编辑</el-button>
|
|
|
|
|
+ <el-dropdown trigger="click" @command="(cmd) => handleCommand(cmd, scope.row)"
|
|
|
|
|
+ style="margin-left: 10px; vertical-align: middle">
|
|
|
<el-button link type="primary" size="small">
|
|
<el-button link type="primary" size="small">
|
|
|
更多<el-icon class="el-icon--right"><arrow-down /></el-icon>
|
|
更多<el-icon class="el-icon--right"><arrow-down /></el-icon>
|
|
|
</el-button>
|
|
</el-button>
|
|
|
<template #dropdown>
|
|
<template #dropdown>
|
|
|
<el-dropdown-menu>
|
|
<el-dropdown-menu>
|
|
|
<el-dropdown-item command="remark" v-hasPermi="['archieves:customer:remark']">添加备注</el-dropdown-item>
|
|
<el-dropdown-item command="remark" v-hasPermi="['archieves:customer:remark']">添加备注</el-dropdown-item>
|
|
|
- <el-dropdown-item command="delete" style="color: #F56C6C" v-hasPermi="['archieves:customer:remove']">删除用户</el-dropdown-item>
|
|
|
|
|
|
|
+ <el-dropdown-item command="delete" style="color: #F56C6C"
|
|
|
|
|
+ v-hasPermi="['archieves:customer:remove']">删除用户</el-dropdown-item>
|
|
|
</el-dropdown-menu>
|
|
</el-dropdown-menu>
|
|
|
</template>
|
|
</template>
|
|
|
</el-dropdown>
|
|
</el-dropdown>
|
|
@@ -93,145 +89,33 @@
|
|
|
</el-table-column>
|
|
</el-table-column>
|
|
|
</el-table>
|
|
</el-table>
|
|
|
<div class="pagination-container">
|
|
<div class="pagination-container">
|
|
|
- <el-pagination
|
|
|
|
|
- v-model:current-page="queryParams.pageNum"
|
|
|
|
|
- v-model:page-size="queryParams.pageSize"
|
|
|
|
|
- :page-sizes="[10, 20, 50, 100]"
|
|
|
|
|
- layout="total, sizes, prev, pager, next, jumper"
|
|
|
|
|
- :total="total"
|
|
|
|
|
- @size-change="getList"
|
|
|
|
|
- @current-change="getList"
|
|
|
|
|
- />
|
|
|
|
|
|
|
+ <el-pagination v-model:current-page="queryParams.pageNum" v-model:page-size="queryParams.pageSize"
|
|
|
|
|
+ :page-sizes="[10, 20, 50, 100]" layout="total, sizes, prev, pager, next, jumper" :total="total"
|
|
|
|
|
+ @size-change="getList" @current-change="getList" />
|
|
|
</div>
|
|
</div>
|
|
|
</el-card>
|
|
</el-card>
|
|
|
|
|
|
|
|
<!-- User Detail Drawer -->
|
|
<!-- User Detail Drawer -->
|
|
|
- <CustomerDetailDrawer
|
|
|
|
|
- ref="customerDetailRef"
|
|
|
|
|
- v-model:visible="drawerVisible"
|
|
|
|
|
- :customer-id="currentUser.id"
|
|
|
|
|
- editable
|
|
|
|
|
- :service-list="serviceList"
|
|
|
|
|
- :area-station-list="allNodes"
|
|
|
|
|
- @add-pet="openAddPet"
|
|
|
|
|
- @pet-detail="handlePetDetail"
|
|
|
|
|
- @pet-edit="handlePetEdit"
|
|
|
|
|
- @pet-remark="handlePetRemark"
|
|
|
|
|
- @pet-delete="handlePetDelete"
|
|
|
|
|
- />
|
|
|
|
|
|
|
+ <CustomerDetailDrawer ref="customerDetailRef" v-model:visible="drawerVisible" :customer-id="currentUser.id" editable
|
|
|
|
|
+ :service-list="serviceList" :area-station-list="allNodes" @add-pet="openAddPet" @pet-detail="handlePetDetail"
|
|
|
|
|
+ @pet-edit="handlePetEdit" @pet-remark="handlePetRemark" @pet-delete="handlePetDelete" />
|
|
|
|
|
|
|
|
<!-- Pet Detail Drawer -->
|
|
<!-- Pet Detail Drawer -->
|
|
|
- <PetDetailDrawer
|
|
|
|
|
- v-model:visible="petDrawerVisible"
|
|
|
|
|
- :pet-id="currentPet.id"
|
|
|
|
|
- :service-list="serviceList"
|
|
|
|
|
- @remark-saved="getList"
|
|
|
|
|
- />
|
|
|
|
|
|
|
+ <PetDetailDrawer v-model:visible="petDrawerVisible" :pet-id="currentPet.id" :service-list="serviceList"
|
|
|
|
|
+ @remark-saved="getList" />
|
|
|
|
|
|
|
|
<!-- Add/Edit User Dialog -->
|
|
<!-- Add/Edit User Dialog -->
|
|
|
- <el-dialog v-model="dialogVisible" :title="isEdit ? '编辑用户' : '新增用户'" width="700px" destroy-on-close>
|
|
|
|
|
- <el-form :model="form" label-width="90px" class="user-form">
|
|
|
|
|
- <el-row :gutter="20">
|
|
|
|
|
- <el-col :span="24" style="text-align: center; margin-bottom: 25px;">
|
|
|
|
|
- <el-upload action="#" :show-file-list="false" :auto-upload="false" :on-change="handleUserUploadFile">
|
|
|
|
|
- <el-avatar :size="80" :src="userAvatarDisplayUrl || 'https://cube.elemecdn.com/3/7c/3ea6beec64369c2642b92c6726f1epng.png'" class="upload-avatar" />
|
|
|
|
|
- <div style="margin-top: 8px; font-size: 12px; color: #409EFF;">点击修改头像</div>
|
|
|
|
|
- </el-upload>
|
|
|
|
|
- </el-col>
|
|
|
|
|
-
|
|
|
|
|
- <el-col :span="24"><div class="form-section-header">基本资料</div></el-col>
|
|
|
|
|
- <el-col :span="12">
|
|
|
|
|
- <el-form-item label="所属品牌" required>
|
|
|
|
|
- <PageSelect v-model="form.tenantId"
|
|
|
|
|
- :options="brandList.map(item => ({ value: item.tenantId, 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="姓名" required><el-input v-model="form.name" placeholder="请输入姓名" /></el-form-item>
|
|
|
|
|
- </el-col>
|
|
|
|
|
- <el-col :span="12">
|
|
|
|
|
- <el-form-item label="电话" required><el-input v-model="form.phone" placeholder="请输入电话" /></el-form-item>
|
|
|
|
|
- </el-col>
|
|
|
|
|
- <el-col :span="12">
|
|
|
|
|
- <el-form-item label="性别">
|
|
|
|
|
- <el-select v-model="form.gender" placeholder="请选择">
|
|
|
|
|
- <el-option v-for="dict in sys_user_sex" :key="dict.value" :label="dict.label" :value="parseInt(dict.value)" />
|
|
|
|
|
- </el-select>
|
|
|
|
|
- </el-form-item>
|
|
|
|
|
- </el-col>
|
|
|
|
|
- <el-col :span="24">
|
|
|
|
|
- <el-form-item label="所属站点" required>
|
|
|
|
|
- <el-cascader v-model="formAreaValue" :options="areaTreeOptions" :props="{ value: 'id', label: 'name' }" placeholder="请选择站点"
|
|
|
|
|
- style="width: 100%" clearable @change="handleFormAreaChange" />
|
|
|
|
|
- </el-form-item>
|
|
|
|
|
- </el-col>
|
|
|
|
|
-
|
|
|
|
|
- <el-col :span="24"><div class="form-section-header">居住信息</div></el-col>
|
|
|
|
|
- <el-col :span="24">
|
|
|
|
|
- <el-form-item label="所在地区">
|
|
|
|
|
- <RegionCascader v-model="regionCascaderValue" />
|
|
|
|
|
- </el-form-item>
|
|
|
|
|
- </el-col>
|
|
|
|
|
- <el-col :span="24">
|
|
|
|
|
- <el-form-item label="详细住址" required><el-input v-model="form.address" placeholder="请输入街道/门牌号" /></el-form-item>
|
|
|
|
|
- </el-col>
|
|
|
|
|
- <el-col :span="12">
|
|
|
|
|
- <el-form-item label="房屋类型">
|
|
|
|
|
- <el-radio-group v-model="form.houseType">
|
|
|
|
|
- <el-radio v-for="dict in sys_house_type" :key="dict.value" :value="dict.value">{{ dict.label }}</el-radio>
|
|
|
|
|
- </el-radio-group>
|
|
|
|
|
- </el-form-item>
|
|
|
|
|
- </el-col>
|
|
|
|
|
- <el-col :span="12">
|
|
|
|
|
- <el-form-item label="入门方式" required>
|
|
|
|
|
- <el-radio-group v-model="form.entryMethod">
|
|
|
|
|
- <el-radio v-for="dict in sys_entry_method" :key="dict.value" :value="dict.value">{{ dict.label }}</el-radio>
|
|
|
|
|
- </el-radio-group>
|
|
|
|
|
- </el-form-item>
|
|
|
|
|
- </el-col>
|
|
|
|
|
- <el-col :span="12" v-if="form.entryMethod === 'password'">
|
|
|
|
|
- <el-form-item label="开门密码" required>
|
|
|
|
|
- <el-input v-model="form.entryPassword" placeholder="请输入密码" />
|
|
|
|
|
- </el-form-item>
|
|
|
|
|
- </el-col>
|
|
|
|
|
- <el-col :span="12" v-if="form.entryMethod === 'key'">
|
|
|
|
|
- <el-form-item label="钥匙位置" required>
|
|
|
|
|
- <el-input v-model="form.keyLocation" placeholder="如:地毯下" />
|
|
|
|
|
- </el-form-item>
|
|
|
|
|
- </el-col>
|
|
|
|
|
-
|
|
|
|
|
- <el-col :span="24"><div class="form-section-header">其他</div></el-col>
|
|
|
|
|
- <el-col :span="24">
|
|
|
|
|
- <el-form-item label="用户标签">
|
|
|
|
|
- <el-select v-model="selectedTagIds" multiple placeholder="选择标签" style="width: 100%">
|
|
|
|
|
- <el-option v-for="tag in allUserTags" :key="tag.id" :label="tag.name" :value="tag.id">
|
|
|
|
|
- <el-tag :type="tag.colorType || 'info'" effect="light" size="small">{{ tag.name }}</el-tag>
|
|
|
|
|
- </el-option>
|
|
|
|
|
- </el-select>
|
|
|
|
|
- </el-form-item>
|
|
|
|
|
- </el-col>
|
|
|
|
|
- <el-col :span="24">
|
|
|
|
|
- <el-form-item label="备注说明"><el-input type="textarea" v-model="form.remark" rows="3" /></el-form-item>
|
|
|
|
|
- </el-col>
|
|
|
|
|
- </el-row>
|
|
|
|
|
- </el-form>
|
|
|
|
|
- <div style="text-align: center; margin-top: 20px;">
|
|
|
|
|
- <el-button @click="dialogVisible = false" size="large" style="width: 120px;">取消</el-button>
|
|
|
|
|
- <el-button type="primary" :loading="submitLoading" @click="saveUser" size="large" style="width: 120px;">保存</el-button>
|
|
|
|
|
- </div>
|
|
|
|
|
- </el-dialog>
|
|
|
|
|
|
|
+ <AddCustomerDialog v-model:visible="customerDialogVisible" :customer-data="customerEditData" :show-brand="true"
|
|
|
|
|
+ @success="onCustomerSaved" />
|
|
|
|
|
|
|
|
<!-- Remark Dialog -->
|
|
<!-- Remark Dialog -->
|
|
|
<el-dialog v-model="remarkDialogVisible" title="添加备注" width="400px">
|
|
<el-dialog v-model="remarkDialogVisible" title="添加备注" width="400px">
|
|
|
<el-input v-model="remarkForm.content" type="textarea" :rows="3" placeholder="请输入备注内容..." />
|
|
<el-input v-model="remarkForm.content" type="textarea" :rows="3" placeholder="请输入备注内容..." />
|
|
|
<template #footer>
|
|
<template #footer>
|
|
|
- <span class="dialog-footer">
|
|
|
|
|
- <el-button @click="remarkDialogVisible = false">取消</el-button>
|
|
|
|
|
- <el-button type="primary" @click="saveRemark">保存</el-button>
|
|
|
|
|
- </span>
|
|
|
|
|
|
|
+ <span class="dialog-footer">
|
|
|
|
|
+ <el-button @click="remarkDialogVisible = false">取消</el-button>
|
|
|
|
|
+ <el-button type="primary" @click="saveRemark">保存</el-button>
|
|
|
|
|
+ </span>
|
|
|
</template>
|
|
</template>
|
|
|
</el-dialog>
|
|
</el-dialog>
|
|
|
|
|
|
|
@@ -239,179 +123,44 @@
|
|
|
<el-dialog v-model="petRemarkDialogVisible" title="添加宠物备注" width="400px" append-to-body>
|
|
<el-dialog v-model="petRemarkDialogVisible" title="添加宠物备注" width="400px" append-to-body>
|
|
|
<el-input v-model="remarkForm.content" type="textarea" :rows="3" placeholder="请输入宠物备注内容..." />
|
|
<el-input v-model="remarkForm.content" type="textarea" :rows="3" placeholder="请输入宠物备注内容..." />
|
|
|
<template #footer>
|
|
<template #footer>
|
|
|
- <span class="dialog-footer">
|
|
|
|
|
- <el-button @click="petRemarkDialogVisible = false">取消</el-button>
|
|
|
|
|
- <el-button type="primary" @click="savePetRemark">保存</el-button>
|
|
|
|
|
- </span>
|
|
|
|
|
|
|
+ <span class="dialog-footer">
|
|
|
|
|
+ <el-button @click="petRemarkDialogVisible = false">取消</el-button>
|
|
|
|
|
+ <el-button type="primary" @click="savePetRemark">保存</el-button>
|
|
|
|
|
+ </span>
|
|
|
</template>
|
|
</template>
|
|
|
</el-dialog>
|
|
</el-dialog>
|
|
|
|
|
|
|
|
- <!-- Full Add/Edit Pet Dialog -->
|
|
|
|
|
- <el-dialog v-model="petDialogVisible" :title="petForm.id ? '编辑宠物' : '新增宠物'" width="800px">
|
|
|
|
|
- <el-tabs v-model="petDialogActiveTab">
|
|
|
|
|
- <el-tab-pane label="基本信息" name="basic">
|
|
|
|
|
- <el-form :model="petForm" label-width="100px">
|
|
|
|
|
- <el-row>
|
|
|
|
|
- <el-col :span="24" style="display: flex; justify-content: center; margin-bottom: 20px;">
|
|
|
|
|
- <el-upload
|
|
|
|
|
- class="avatar-uploader"
|
|
|
|
|
- action="#"
|
|
|
|
|
- :show-file-list="false"
|
|
|
|
|
- :auto-upload="false"
|
|
|
|
|
- :on-change="handlePetUploadFile"
|
|
|
|
|
- >
|
|
|
|
|
- <el-avatar v-if="petAvatarDisplayUrl" :src="petAvatarDisplayUrl" :size="80" />
|
|
|
|
|
- <el-icon v-else class="avatar-uploader-icon"><Plus /></el-icon>
|
|
|
|
|
- </el-upload>
|
|
|
|
|
- </el-col>
|
|
|
|
|
- <el-col :span="12">
|
|
|
|
|
- <el-form-item label="宠物姓名" required><el-input v-model="petForm.name" /></el-form-item>
|
|
|
|
|
- </el-col>
|
|
|
|
|
- <el-col :span="12">
|
|
|
|
|
- <el-form-item label="性别">
|
|
|
|
|
- <el-select v-model="petForm.gender" placeholder="请选择">
|
|
|
|
|
- <el-option v-for="dict in sys_pet_gender" :key="dict.value" :label="dict.label" :value="parseInt(dict.value)" />
|
|
|
|
|
- </el-select>
|
|
|
|
|
- </el-form-item>
|
|
|
|
|
- </el-col>
|
|
|
|
|
- <el-col :span="12">
|
|
|
|
|
- <el-form-item label="品种" required>
|
|
|
|
|
- <el-select v-model="petForm.breed" placeholder="请选择品种" style="width: 100%" filterable allow-create default-first-option>
|
|
|
|
|
- <el-option v-for="dict in sys_pet_breed" :key="dict.value" :label="dict.label" :value="dict.value" />
|
|
|
|
|
- </el-select>
|
|
|
|
|
- </el-form-item>
|
|
|
|
|
- </el-col>
|
|
|
|
|
- <el-col :span="12">
|
|
|
|
|
- <el-form-item label="体型" required>
|
|
|
|
|
- <el-select v-model="petForm.size" style="width: 100%">
|
|
|
|
|
- <el-option v-for="dict in sys_pet_size" :key="dict.value" :label="dict.label" :value="dict.value" />
|
|
|
|
|
- </el-select>
|
|
|
|
|
- </el-form-item>
|
|
|
|
|
- </el-col>
|
|
|
|
|
- <el-col :span="12">
|
|
|
|
|
- <el-form-item label="体重(kg)" required><el-input-number v-model="petForm.weight" :min="0" :precision="1" style="width: 100%" /></el-form-item>
|
|
|
|
|
- </el-col>
|
|
|
|
|
- <el-col :span="12">
|
|
|
|
|
- <el-form-item label="年龄(岁)" required><el-input-number v-model="petForm.age" :min="0" style="width: 100%" /></el-form-item>
|
|
|
|
|
- </el-col>
|
|
|
|
|
- <el-col :span="24">
|
|
|
|
|
- <el-form-item label="性格关键词"><el-input v-model="petForm.personality" placeholder="如:活泼、粘人" /></el-form-item>
|
|
|
|
|
- </el-col>
|
|
|
|
|
- <el-col :span="24">
|
|
|
|
|
- <el-form-item label="萌宠性格"><el-input v-model="petForm.cutePersonality" type="textarea" placeholder="详细描述" /></el-form-item>
|
|
|
|
|
- </el-col>
|
|
|
|
|
- <el-col :span="24">
|
|
|
|
|
- <el-form-item label="宠物标签">
|
|
|
|
|
- <el-select v-model="petForm.tagIds" multiple placeholder="选择标签" style="width: 100%">
|
|
|
|
|
- <el-option v-for="tag in allPetTags" :key="tag.id" :label="tag.name" :value="tag.id">
|
|
|
|
|
- <el-tag :type="tag.colorType || 'info'" effect="light" size="small">{{ tag.name }}</el-tag>
|
|
|
|
|
- </el-option>
|
|
|
|
|
- </el-select>
|
|
|
|
|
- </el-form-item>
|
|
|
|
|
- </el-col>
|
|
|
|
|
- </el-row>
|
|
|
|
|
- </el-form>
|
|
|
|
|
- </el-tab-pane>
|
|
|
|
|
- <el-tab-pane label="家庭信息" name="family">
|
|
|
|
|
- <el-form :model="petForm" label-width="120px">
|
|
|
|
|
- <el-form-item label="新来家庭时间">
|
|
|
|
|
- <el-date-picker v-model="petForm.arrivalTime" type="date" placeholder="选择日期" style="width: 100%" />
|
|
|
|
|
- </el-form-item>
|
|
|
|
|
- <el-form-item label="家庭房屋类型" required>
|
|
|
|
|
- <el-radio-group v-model="petForm.houseType">
|
|
|
|
|
- <el-radio v-for="dict in sys_house_type" :key="dict.value" :value="dict.value">{{ dict.label }}</el-radio>
|
|
|
|
|
- </el-radio-group>
|
|
|
|
|
- </el-form-item>
|
|
|
|
|
- <el-form-item label="入门方式" required>
|
|
|
|
|
- <el-radio-group v-model="petForm.entryMethod">
|
|
|
|
|
- <el-radio v-for="dict in sys_entry_method" :key="dict.value" :value="dict.value">{{ dict.label }}</el-radio>
|
|
|
|
|
- </el-radio-group>
|
|
|
|
|
- </el-form-item>
|
|
|
|
|
- <el-form-item label="密码" v-if="petForm.entryMethod === 'password'" required>
|
|
|
|
|
- <el-input v-model="petForm.entryPassword" placeholder="请输入门锁密码" />
|
|
|
|
|
- </el-form-item>
|
|
|
|
|
- <el-form-item label="钥匙位置" v-if="petForm.entryMethod === 'key'" required>
|
|
|
|
|
- <el-input v-model="petForm.keyLocation" placeholder="请输入钥匙存放位置" />
|
|
|
|
|
- </el-form-item>
|
|
|
|
|
- </el-form>
|
|
|
|
|
- </el-tab-pane>
|
|
|
|
|
- <el-tab-pane label="健康状况" name="health">
|
|
|
|
|
- <el-form :model="petForm" label-width="120px">
|
|
|
|
|
- <el-form-item label="健康状态" required>
|
|
|
|
|
- <el-radio-group v-model="petForm.healthStatus">
|
|
|
|
|
- <el-radio label="健康">健康</el-radio>
|
|
|
|
|
- <el-radio label="亚健康">亚健康</el-radio>
|
|
|
|
|
- <el-radio label="疾病">疾病</el-radio>
|
|
|
|
|
- </el-radio-group>
|
|
|
|
|
- </el-form-item>
|
|
|
|
|
- <el-form-item label="是否有攻击倾向" required>
|
|
|
|
|
- <el-switch v-model="petForm.aggression" active-text="是" inactive-text="否" :active-value="1" :inactive-value="0" />
|
|
|
|
|
- </el-form-item>
|
|
|
|
|
- <el-form-item label="疫苗情况" required>
|
|
|
|
|
- <el-radio-group v-model="petForm.vaccineStatus">
|
|
|
|
|
- <el-radio label="无">无</el-radio>
|
|
|
|
|
- <el-radio label="已打1次">已打1次</el-radio>
|
|
|
|
|
- <el-radio label="已打2次">已打2次</el-radio>
|
|
|
|
|
- <el-radio label="已打3次">已打3次</el-radio>
|
|
|
|
|
- </el-radio-group>
|
|
|
|
|
- </el-form-item>
|
|
|
|
|
- <el-form-item label="疫苗凭证">
|
|
|
|
|
- <el-upload class="avatar-uploader" action="#" :show-file-list="false" :auto-upload="false" :on-change="handlePetUploadVaccineCert">
|
|
|
|
|
- <img v-if="petVaccineCertDisplayUrl" :src="petVaccineCertDisplayUrl" class="avatar" style="width: 100px; height: 100px; object-fit: cover;" />
|
|
|
|
|
- <el-icon v-else class="avatar-uploader-icon" style="width: 100px; height: 100px; line-height: 100px;"><Plus /></el-icon>
|
|
|
|
|
- </el-upload>
|
|
|
|
|
- </el-form-item>
|
|
|
|
|
- <el-form-item label="既往病史" required>
|
|
|
|
|
- <el-input v-model="petForm.medicalHistory" type="textarea" placeholder="如有病史请记录" />
|
|
|
|
|
- </el-form-item>
|
|
|
|
|
- <el-form-item label="过敏史" required>
|
|
|
|
|
- <el-input v-model="petForm.allergies" type="textarea" placeholder="如有过敏源请记录" />
|
|
|
|
|
- </el-form-item>
|
|
|
|
|
- </el-form>
|
|
|
|
|
- </el-tab-pane>
|
|
|
|
|
- </el-tabs>
|
|
|
|
|
- <template #footer>
|
|
|
|
|
- <span class="dialog-footer">
|
|
|
|
|
- <el-button @click="petDialogVisible = false">取消</el-button>
|
|
|
|
|
- <el-button type="primary" :loading="submitLoading" @click="savePet">保存</el-button>
|
|
|
|
|
- </span>
|
|
|
|
|
- </template>
|
|
|
|
|
- </el-dialog>
|
|
|
|
|
|
|
+ <AddPetDialog v-model:visible="petDialogVisible" :user-id="currentUser.id"
|
|
|
|
|
+ :user-options="[{ id: currentUser.id, name: currentUser.name, phone: currentUser.phone }]" :locked-owner="true"
|
|
|
|
|
+ :pet-data="petEditData" @success="onPetSaved" />
|
|
|
</div>
|
|
</div>
|
|
|
</template>
|
|
</template>
|
|
|
|
|
|
|
|
<script setup>
|
|
<script setup>
|
|
|
import { ref, reactive, computed, onMounted, getCurrentInstance, toRefs } from 'vue'
|
|
import { ref, reactive, computed, onMounted, getCurrentInstance, toRefs } from 'vue'
|
|
|
-import { globalHeaders } from '@/utils/request'
|
|
|
|
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
|
|
-import { listCustomer, getCustomer, addCustomer, updateCustomer, delCustomer, changeCustomerStatus, updateCustomerRemark } from '@/api/archieves/customer'
|
|
|
|
|
-import { listAllTag } from '@/api/archieves/tag'
|
|
|
|
|
-import { listPetByUser, addPet, updatePet, delPet, updatePetRemark } from '@/api/archieves/pet'
|
|
|
|
|
|
|
+import { listCustomer, getCustomer, delCustomer, changeCustomerStatus, updateCustomerRemark } from '@/api/archieves/customer'
|
|
|
|
|
+import { listPetByUser, delPet, updatePetRemark } from '@/api/archieves/pet'
|
|
|
import { listAllChangeLog } from '@/api/archieves/changeLog'
|
|
import { listAllChangeLog } from '@/api/archieves/changeLog'
|
|
|
import { listAreaStation } from '@/api/system/areaStation'
|
|
import { listAreaStation } from '@/api/system/areaStation'
|
|
|
-import { listOnStore as listBrandOnStore } from '@/api/system/tenant'
|
|
|
|
|
import CustomerDetailDrawer from '@/components/CustomerDetailDrawer/index.vue'
|
|
import CustomerDetailDrawer from '@/components/CustomerDetailDrawer/index.vue'
|
|
|
import PetDetailDrawer from '@/components/PetDetailDrawer/index.vue'
|
|
import PetDetailDrawer from '@/components/PetDetailDrawer/index.vue'
|
|
|
|
|
+import AddCustomerDialog from '@/components/AddCustomerDialog/index.vue'
|
|
|
|
|
+import AddPetDialog from '@/components/AddPetDialog/index.vue'
|
|
|
import { listAllService } from '@/api/service/list/index'
|
|
import { listAllService } from '@/api/service/list/index'
|
|
|
-import RegionCascader from '@/components/RegionCascader/index.vue'
|
|
|
|
|
import { useRegionData } from '@/hooks/useRegionData'
|
|
import { useRegionData } from '@/hooks/useRegionData'
|
|
|
-import PageSelect from '@/components/PageSelect/index.vue'
|
|
|
|
|
-import { useUserStore } from '@/store/modules/user'
|
|
|
|
|
|
|
|
|
|
-const userStore = useUserStore()
|
|
|
|
|
const { codeToName, loadRegionData } = useRegionData()
|
|
const { codeToName, loadRegionData } = useRegionData()
|
|
|
|
|
|
|
|
const { proxy } = getCurrentInstance()
|
|
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')
|
|
|
|
|
|
|
+const { sys_user_sex, sys_customer_status } = toRefs(
|
|
|
|
|
+ proxy?.useDict('sys_user_sex', 'sys_customer_status')
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
const loading = ref(false)
|
|
const loading = ref(false)
|
|
|
-const submitLoading = ref(false)
|
|
|
|
|
const total = ref(0)
|
|
const total = ref(0)
|
|
|
|
|
|
|
|
const allNodes = ref([])
|
|
const allNodes = ref([])
|
|
|
-// 废弃变量已清理,统一使用级联选择器展示全量树结构
|
|
|
|
|
|
|
|
|
|
const loadAreaStation = () => {
|
|
const loadAreaStation = () => {
|
|
|
listAreaStation().then((res) => {
|
|
listAreaStation().then((res) => {
|
|
@@ -448,33 +197,22 @@ const searchForm = reactive({
|
|
|
stationId: undefined
|
|
stationId: undefined
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
-const dialogVisible = ref(false)
|
|
|
|
|
|
|
+const customerDialogVisible = ref(false)
|
|
|
const drawerVisible = ref(false)
|
|
const drawerVisible = ref(false)
|
|
|
const petDrawerVisible = ref(false)
|
|
const petDrawerVisible = ref(false)
|
|
|
const remarkDialogVisible = ref(false)
|
|
const remarkDialogVisible = ref(false)
|
|
|
const petRemarkDialogVisible = ref(false)
|
|
const petRemarkDialogVisible = ref(false)
|
|
|
const petDialogVisible = ref(false)
|
|
const petDialogVisible = ref(false)
|
|
|
-const isEdit = ref(false)
|
|
|
|
|
const detailActiveTab = ref('info')
|
|
const detailActiveTab = ref('info')
|
|
|
-const petDialogActiveTab = ref('basic')
|
|
|
|
|
|
|
|
|
|
-const selectedTagIds = ref([])
|
|
|
|
|
|
|
+const customerEditData = ref(null)
|
|
|
|
|
+const petEditData = ref(null)
|
|
|
const currentUser = ref({})
|
|
const currentUser = ref({})
|
|
|
const currentPet = ref({})
|
|
const currentPet = ref({})
|
|
|
const currentPets = ref([])
|
|
const currentPets = ref([])
|
|
|
const tableData = ref([])
|
|
const tableData = ref([])
|
|
|
|
|
|
|
|
-const allUserTags = ref([])
|
|
|
|
|
-const allPetTags = ref([])
|
|
|
|
|
const changeLogs = ref([])
|
|
const changeLogs = ref([])
|
|
|
-const userAvatarDisplayUrl = ref('')
|
|
|
|
|
-const petAvatarDisplayUrl = ref('')
|
|
|
|
|
-const petVaccineCertDisplayUrl = ref('')
|
|
|
|
|
-
|
|
|
|
|
-const brandList = ref([])
|
|
|
|
|
-const brandTotal = ref(0)
|
|
|
|
|
-const formAreaValue = ref([])
|
|
|
|
|
-const regionCascaderValue = ref([])
|
|
|
|
|
|
|
|
|
|
const customerDetailRef = ref(null)
|
|
const customerDetailRef = ref(null)
|
|
|
const serviceList = ref([])
|
|
const serviceList = ref([])
|
|
@@ -485,63 +223,6 @@ const getServiceList = () => {
|
|
|
})
|
|
})
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-const form = reactive({
|
|
|
|
|
- id: undefined,
|
|
|
|
|
- name: '',
|
|
|
|
|
- phone: '',
|
|
|
|
|
- avatar: undefined,
|
|
|
|
|
- gender: undefined,
|
|
|
|
|
- birthday: '',
|
|
|
|
|
- idCard: '',
|
|
|
|
|
- areaId: undefined,
|
|
|
|
|
- stationId: undefined,
|
|
|
|
|
- regionCode: '',
|
|
|
|
|
- region: [],
|
|
|
|
|
- address: '',
|
|
|
|
|
- houseType: '',
|
|
|
|
|
- entryMethod: '',
|
|
|
|
|
- entryPassword: '',
|
|
|
|
|
- keyLocation: '',
|
|
|
|
|
- source: '',
|
|
|
|
|
- emergencyContact: '',
|
|
|
|
|
- emergencyPhone: '',
|
|
|
|
|
- memberLevel: 0,
|
|
|
|
|
- status: 0,
|
|
|
|
|
- remark: '',
|
|
|
|
|
- tagIds: [],
|
|
|
|
|
- tenantId: undefined
|
|
|
|
|
-})
|
|
|
|
|
-
|
|
|
|
|
-const petForm = reactive({
|
|
|
|
|
- id: undefined,
|
|
|
|
|
- userId: undefined,
|
|
|
|
|
- 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,
|
|
|
|
|
- vaccineStatus: '',
|
|
|
|
|
- vaccineCert: undefined,
|
|
|
|
|
- medicalHistory: '',
|
|
|
|
|
- allergies: '',
|
|
|
|
|
- remark: '',
|
|
|
|
|
- tagIds: []
|
|
|
|
|
-})
|
|
|
|
|
-
|
|
|
|
|
const remarkForm = reactive({ content: '' })
|
|
const remarkForm = reactive({ content: '' })
|
|
|
|
|
|
|
|
const areaTreeOptions = computed(() => {
|
|
const areaTreeOptions = computed(() => {
|
|
@@ -550,8 +231,8 @@ const areaTreeOptions = computed(() => {
|
|
|
.filter(item => String(item.parentId) === String(parentId))
|
|
.filter(item => String(item.parentId) === String(parentId))
|
|
|
.map(item => {
|
|
.map(item => {
|
|
|
const children = buildTree(data, item.id)
|
|
const children = buildTree(data, item.id)
|
|
|
- const node = {
|
|
|
|
|
- id: item.id,
|
|
|
|
|
|
|
+ const node = {
|
|
|
|
|
+ id: item.id,
|
|
|
name: item.name,
|
|
name: item.name,
|
|
|
// 如果不是站点且没有下级了,则不可选(防止误选区域叶子点)
|
|
// 如果不是站点且没有下级了,则不可选(防止误选区域叶子点)
|
|
|
disabled: Number(item.type) !== 2 && (!children || children.length === 0)
|
|
disabled: Number(item.type) !== 2 && (!children || children.length === 0)
|
|
@@ -563,36 +244,6 @@ const areaTreeOptions = computed(() => {
|
|
|
return buildTree(allNodes.value, 0)
|
|
return buildTree(allNodes.value, 0)
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
-const handleFormAreaChange = (value) => {
|
|
|
|
|
- if (value && value.length > 0) {
|
|
|
|
|
- const lastId = value[value.length - 1];
|
|
|
|
|
- const node = allNodes.value.find(item => item.id === lastId);
|
|
|
|
|
- form.stationId = lastId;
|
|
|
|
|
- form.areaId = node ? node.parentId : undefined;
|
|
|
|
|
- } else {
|
|
|
|
|
- form.stationId = undefined;
|
|
|
|
|
- form.areaId = undefined;
|
|
|
|
|
- }
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-const getBrandList = async (pageNum = 1) => {
|
|
|
|
|
- const res = await listBrandOnStore({ pageNum, pageSize: 10 })
|
|
|
|
|
- if (res.code === 200) {
|
|
|
|
|
- brandList.value = res.rows || []
|
|
|
|
|
- brandTotal.value = res.total || 0
|
|
|
|
|
- }
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-const handleBrandPageChange = (page) => {
|
|
|
|
|
- getBrandList(Number(page))
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-const handleBrandVisibleChange = (visible) => {
|
|
|
|
|
- if (visible) {
|
|
|
|
|
- getBrandList(1)
|
|
|
|
|
- }
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
const getList = () => {
|
|
const getList = () => {
|
|
|
loading.value = true
|
|
loading.value = true
|
|
|
queryParams.keyword = searchForm.keyword
|
|
queryParams.keyword = searchForm.keyword
|
|
@@ -611,75 +262,42 @@ const handleSearch = () => {
|
|
|
getList()
|
|
getList()
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-const loadTags = () => {
|
|
|
|
|
- listAllTag({ category: 'customer', status: 0 }).then((res) => {
|
|
|
|
|
- allUserTags.value = res.data || []
|
|
|
|
|
- }).catch((err) => {
|
|
|
|
|
- console.error('加载用户标签失败', err)
|
|
|
|
|
- })
|
|
|
|
|
- listAllTag({ category: 'pet', status: 0 }).then((res) => {
|
|
|
|
|
- allPetTags.value = res.data || []
|
|
|
|
|
- }).catch((err) => {
|
|
|
|
|
- console.error('加载宠物标签失败', err)
|
|
|
|
|
- })
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
const handleAdd = () => {
|
|
const handleAdd = () => {
|
|
|
- isEdit.value = false
|
|
|
|
|
- selectedTagIds.value = []
|
|
|
|
|
- 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: '',
|
|
|
|
|
- emergencyContact: '', emergencyPhone: '', memberLevel: 0, status: 0, remark: '', tagIds: [],
|
|
|
|
|
- tenantId: undefined
|
|
|
|
|
- })
|
|
|
|
|
- userAvatarDisplayUrl.value = ''
|
|
|
|
|
- formAreaValue.value = []
|
|
|
|
|
- regionCascaderValue.value = []
|
|
|
|
|
- dialogVisible.value = true
|
|
|
|
|
|
|
+ customerEditData.value = null
|
|
|
|
|
+ customerDialogVisible.value = true
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
const handleEdit = (row) => {
|
|
const handleEdit = (row) => {
|
|
|
- isEdit.value = true
|
|
|
|
|
getCustomer(row.id).then((res) => {
|
|
getCustomer(row.id).then((res) => {
|
|
|
- const data = res.data
|
|
|
|
|
- Object.assign(form, {
|
|
|
|
|
- id: data.id, name: data.name, phone: data.phone, avatar: data.avatar, gender: data.gender,
|
|
|
|
|
- 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,
|
|
|
|
|
- emergencyContact: data.emergencyContact, emergencyPhone: data.emergencyPhone,
|
|
|
|
|
- memberLevel: data.memberLevel, status: data.status, remark: data.remark, tagIds: [],
|
|
|
|
|
- tenantId: data.tenantId
|
|
|
|
|
- })
|
|
|
|
|
- userAvatarDisplayUrl.value = data.avatarUrl || ''
|
|
|
|
|
- // Restore area cascader value path
|
|
|
|
|
- if (data.stationId || data.areaId) {
|
|
|
|
|
- const targetId = data.stationId || data.areaId;
|
|
|
|
|
- const path = [];
|
|
|
|
|
- let currentId = targetId;
|
|
|
|
|
- while (currentId && String(currentId) !== '0') {
|
|
|
|
|
- path.unshift(currentId);
|
|
|
|
|
- const currentArea = allNodes.value.find((item) => String(item.id) === String(currentId));
|
|
|
|
|
- if (currentArea) {
|
|
|
|
|
- currentId = currentArea.parentId;
|
|
|
|
|
- } else {
|
|
|
|
|
- break;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- formAreaValue.value = path;
|
|
|
|
|
- } else {
|
|
|
|
|
- formAreaValue.value = []
|
|
|
|
|
- }
|
|
|
|
|
- // Restore region cascader value
|
|
|
|
|
- regionCascaderValue.value = data.regionCode ? data.regionCode.split('/') : []
|
|
|
|
|
- selectedTagIds.value = data.tags ? data.tags.map(t => t.id) : []
|
|
|
|
|
- dialogVisible.value = true
|
|
|
|
|
|
|
+ customerEditData.value = res.data
|
|
|
|
|
+ customerDialogVisible.value = true
|
|
|
})
|
|
})
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+const onCustomerSaved = () => {
|
|
|
|
|
+ customerDialogVisible.value = false
|
|
|
|
|
+ getList()
|
|
|
|
|
+ if (drawerVisible.value) {
|
|
|
|
|
+ loadDetailLogs(currentUser.value.id, 'customer')
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const openAddPet = () => {
|
|
|
|
|
+ petEditData.value = null
|
|
|
|
|
+ petDialogVisible.value = true
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const handlePetEdit = (row) => {
|
|
|
|
|
+ petEditData.value = row
|
|
|
|
|
+ petDialogVisible.value = true
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const onPetSaved = () => {
|
|
|
|
|
+ petDialogVisible.value = false
|
|
|
|
|
+ customerDetailRef.value?.refresh()
|
|
|
|
|
+ getList()
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
const handleDetail = (row) => {
|
|
const handleDetail = (row) => {
|
|
|
getCustomer(row.id).then((res) => {
|
|
getCustomer(row.id).then((res) => {
|
|
|
const data = res.data
|
|
const data = res.data
|
|
@@ -755,32 +373,6 @@ const savePetRemark = () => {
|
|
|
})
|
|
})
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-const saveUser = () => {
|
|
|
|
|
- if (!form.tenantId) return ElMessage.warning('请选择所属品牌')
|
|
|
|
|
- if (!form.name) return ElMessage.warning('请输入姓名')
|
|
|
|
|
- if (!form.phone) return ElMessage.warning('请输入电话')
|
|
|
|
|
- if (!form.stationId) return ElMessage.warning('请选择所属站点')
|
|
|
|
|
- if (!form.address) return ElMessage.warning('请输入详细住址')
|
|
|
|
|
- if (!form.entryMethod) return ElMessage.warning('请选择入门方式')
|
|
|
|
|
- if (form.entryMethod === 'password' && !form.entryPassword) return ElMessage.warning('请输入开门密码')
|
|
|
|
|
- if (form.entryMethod === 'key' && !form.keyLocation) return ElMessage.warning('请输入钥匙存放位置')
|
|
|
|
|
- submitLoading.value = true
|
|
|
|
|
- form.tagIds = selectedTagIds.value
|
|
|
|
|
- if (regionCascaderValue.value && regionCascaderValue.value.length > 0) {
|
|
|
|
|
- form.regionCode = regionCascaderValue.value.join('/')
|
|
|
|
|
- } else {
|
|
|
|
|
- form.regionCode = ''
|
|
|
|
|
- }
|
|
|
|
|
- const api = isEdit.value ? updateCustomer(form) : addCustomer(form)
|
|
|
|
|
- api.then(() => {
|
|
|
|
|
- ElMessage.success('保存成功')
|
|
|
|
|
- dialogVisible.value = false
|
|
|
|
|
- getList()
|
|
|
|
|
- }).finally(() => {
|
|
|
|
|
- submitLoading.value = false
|
|
|
|
|
- })
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
const handleDelete = (row) => {
|
|
const handleDelete = (row) => {
|
|
|
ElMessageBox.confirm('确认删除该用户档案吗?', '提示', { type: 'warning' }).then(() => {
|
|
ElMessageBox.confirm('确认删除该用户档案吗?', '提示', { type: 'warning' }).then(() => {
|
|
|
delCustomer(row.id).then(() => {
|
|
delCustomer(row.id).then(() => {
|
|
@@ -809,121 +401,11 @@ const handleCommand = (command, row) => {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-const baseUrl = import.meta.env.VITE_APP_BASE_API
|
|
|
|
|
-const uploadUrl = baseUrl + '/resource/oss/upload'
|
|
|
|
|
-
|
|
|
|
|
-const handleUserUploadFile = async (file) => {
|
|
|
|
|
- const formData = new FormData()
|
|
|
|
|
- formData.append('file', file.raw)
|
|
|
|
|
- try {
|
|
|
|
|
- const headers = globalHeaders()
|
|
|
|
|
- const res = await fetch(uploadUrl, {
|
|
|
|
|
- method: 'POST',
|
|
|
|
|
- headers: {
|
|
|
|
|
- 'Authorization': headers.Authorization,
|
|
|
|
|
- 'clientid': headers.clientid
|
|
|
|
|
- },
|
|
|
|
|
- body: formData
|
|
|
|
|
- })
|
|
|
|
|
- const result = await res.json()
|
|
|
|
|
- if (result.code === 200) {
|
|
|
|
|
- form.avatar = result.data.ossId
|
|
|
|
|
- userAvatarDisplayUrl.value = result.data.url
|
|
|
|
|
- } else {
|
|
|
|
|
- ElMessage.error(result.msg || '头像上传失败')
|
|
|
|
|
- }
|
|
|
|
|
- } catch (e) {
|
|
|
|
|
- ElMessage.error('头像上传失败')
|
|
|
|
|
- }
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-const handlePetUploadFile = async (file) => {
|
|
|
|
|
- const formData = new FormData()
|
|
|
|
|
- formData.append('file', file.raw)
|
|
|
|
|
- try {
|
|
|
|
|
- const headers = globalHeaders()
|
|
|
|
|
- const res = await fetch(uploadUrl, {
|
|
|
|
|
- method: 'POST',
|
|
|
|
|
- headers: {
|
|
|
|
|
- 'Authorization': headers.Authorization,
|
|
|
|
|
- 'clientid': headers.clientid
|
|
|
|
|
- },
|
|
|
|
|
- body: formData
|
|
|
|
|
- })
|
|
|
|
|
- const result = await res.json()
|
|
|
|
|
- if (result.code === 200) {
|
|
|
|
|
- petForm.avatar = result.data.ossId
|
|
|
|
|
- petAvatarDisplayUrl.value = result.data.url
|
|
|
|
|
- } else {
|
|
|
|
|
- ElMessage.error(result.msg || '头像上传失败')
|
|
|
|
|
- }
|
|
|
|
|
- } catch (e) {
|
|
|
|
|
- ElMessage.error('头像上传失败')
|
|
|
|
|
- }
|
|
|
|
|
-}
|
|
|
|
|
-const handlePetUploadVaccineCert = async (file) => {
|
|
|
|
|
- const formData = new FormData()
|
|
|
|
|
- formData.append('file', file.raw)
|
|
|
|
|
- try {
|
|
|
|
|
- const headers = globalHeaders()
|
|
|
|
|
- const res = await fetch(uploadUrl, {
|
|
|
|
|
- method: 'POST',
|
|
|
|
|
- headers: {
|
|
|
|
|
- 'Authorization': headers.Authorization,
|
|
|
|
|
- 'clientid': headers.clientid
|
|
|
|
|
- },
|
|
|
|
|
- body: formData
|
|
|
|
|
- })
|
|
|
|
|
- const result = await res.json()
|
|
|
|
|
- if (result.code === 200) {
|
|
|
|
|
- petForm.vaccineCert = result.data.ossId
|
|
|
|
|
- petVaccineCertDisplayUrl.value = result.data.url
|
|
|
|
|
- } else {
|
|
|
|
|
- ElMessage.error(result.msg || '疫苗凭证上传失败')
|
|
|
|
|
- }
|
|
|
|
|
- } catch (e) {
|
|
|
|
|
- ElMessage.error('疫苗凭证上传失败')
|
|
|
|
|
- }
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-const openAddPet = () => {
|
|
|
|
|
- petDialogActiveTab.value = 'basic'
|
|
|
|
|
- Object.assign(petForm, {
|
|
|
|
|
- id: undefined, userId: currentUser.value.id, 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,
|
|
|
|
|
- vaccineStatus: '无', vaccineCert: undefined, medicalHistory: '', allergies: '', remark: '', tagIds: []
|
|
|
|
|
- })
|
|
|
|
|
- petAvatarDisplayUrl.value = ''
|
|
|
|
|
- petVaccineCertDisplayUrl.value = ''
|
|
|
|
|
- petDialogVisible.value = true
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
const handlePetDetail = (row) => {
|
|
const handlePetDetail = (row) => {
|
|
|
currentPet.value = row
|
|
currentPet.value = row
|
|
|
petDrawerVisible.value = true
|
|
petDrawerVisible.value = true
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-const handlePetEdit = (row) => {
|
|
|
|
|
- petDialogActiveTab.value = 'basic'
|
|
|
|
|
- Object.assign(petForm, {
|
|
|
|
|
- id: row.id, userId: row.userId, avatar: row.avatar, name: row.name, type: row.type,
|
|
|
|
|
- gender: row.gender, breed: row.breed, birthday: row.birthday, age: row.age,
|
|
|
|
|
- weight: row.weight, size: row.size, isSterilized: row.isSterilized,
|
|
|
|
|
- arrivalTime: row.arrivalTime, houseType: row.houseType, entryMethod: row.entryMethod,
|
|
|
|
|
- entryPassword: row.entryPassword, keyLocation: row.keyLocation,
|
|
|
|
|
- personality: row.personality, cutePersonality: row.cutePersonality,
|
|
|
|
|
- healthStatus: row.healthStatus, aggression: row.aggression,
|
|
|
|
|
- vaccineStatus: row.vaccineStatus, vaccineCert: row.vaccineCert,
|
|
|
|
|
- medicalHistory: row.medicalHistory, allergies: row.allergies, remark: row.remark,
|
|
|
|
|
- tagIds: row.tags ? row.tags.map(t => t.id) : []
|
|
|
|
|
- })
|
|
|
|
|
- petAvatarDisplayUrl.value = row.avatarUrl || ''
|
|
|
|
|
- petVaccineCertDisplayUrl.value = row.vaccineCertUrl || ''
|
|
|
|
|
- petDialogVisible.value = true
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
const handlePetRemark = (row) => {
|
|
const handlePetRemark = (row) => {
|
|
|
currentPet.value = row
|
|
currentPet.value = row
|
|
|
remarkForm.content = ''
|
|
remarkForm.content = ''
|
|
@@ -940,50 +422,28 @@ const handlePetDelete = (row) => {
|
|
|
})
|
|
})
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-const savePet = () => {
|
|
|
|
|
- if (!petForm.name) return ElMessage.warning('请输入宠物姓名');
|
|
|
|
|
- if (!petForm.userId) return ElMessage.warning('请选择所属主人');
|
|
|
|
|
- if (!petForm.breed) return ElMessage.warning('请选择品种');
|
|
|
|
|
- if (!petForm.size) return ElMessage.warning('请选择体型');
|
|
|
|
|
- if (petForm.weight === undefined || petForm.weight === null) return ElMessage.warning('请输入体重(kg)');
|
|
|
|
|
- if (petForm.age === undefined || petForm.age === null) return ElMessage.warning('请输入年龄(岁)');
|
|
|
|
|
- if (!petForm.houseType) return ElMessage.warning('请选择家庭房屋类型');
|
|
|
|
|
- if (!petForm.entryMethod) return ElMessage.warning('请选择入门方式');
|
|
|
|
|
- if (petForm.entryMethod === 'password' && !petForm.entryPassword) return ElMessage.warning('请输入门锁密码');
|
|
|
|
|
- if (petForm.entryMethod === 'key' && !petForm.keyLocation) return ElMessage.warning('请输入钥匙存放位置');
|
|
|
|
|
- if (!petForm.healthStatus) return ElMessage.warning('请选择健康状态');
|
|
|
|
|
- if (petForm.aggression === undefined || petForm.aggression === null) return ElMessage.warning('请选择是否有攻击倾向');
|
|
|
|
|
- if (!petForm.vaccineStatus) return ElMessage.warning('请选择疫苗情况');
|
|
|
|
|
- if (!petForm.medicalHistory) return ElMessage.warning('请输入既往病史');
|
|
|
|
|
- if (!petForm.allergies) return ElMessage.warning('请输入过敏史');
|
|
|
|
|
-
|
|
|
|
|
- submitLoading.value = true;
|
|
|
|
|
- const data = { ...petForm, aggression: Number(petForm.aggression) || 0 };
|
|
|
|
|
- const api = data.id ? updatePet(data) : addPet(data);
|
|
|
|
|
- api.then(() => {
|
|
|
|
|
- ElMessage.success('宠物档案保存成功');
|
|
|
|
|
- petDialogVisible.value = false;
|
|
|
|
|
- customerDetailRef.value?.refresh();
|
|
|
|
|
- getList();
|
|
|
|
|
- }).finally(() => {
|
|
|
|
|
- submitLoading.value = false;
|
|
|
|
|
- });
|
|
|
|
|
-};
|
|
|
|
|
-
|
|
|
|
|
onMounted(() => {
|
|
onMounted(() => {
|
|
|
getList()
|
|
getList()
|
|
|
- loadTags()
|
|
|
|
|
loadAreaStation()
|
|
loadAreaStation()
|
|
|
loadRegionData()
|
|
loadRegionData()
|
|
|
- getBrandList()
|
|
|
|
|
getServiceList()
|
|
getServiceList()
|
|
|
})
|
|
})
|
|
|
</script>
|
|
</script>
|
|
|
|
|
|
|
|
<style scoped>
|
|
<style scoped>
|
|
|
-.page-container { padding: 20px; }
|
|
|
|
|
-.card-header { display: flex; justify-content: space-between; align-items: center; }
|
|
|
|
|
-.title { font-weight: bold; }
|
|
|
|
|
|
|
+.page-container {
|
|
|
|
|
+ padding: 20px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.card-header {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ justify-content: space-between;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.title {
|
|
|
|
|
+ font-weight: bold;
|
|
|
|
|
+}
|
|
|
|
|
|
|
|
.profile-header {
|
|
.profile-header {
|
|
|
display: flex;
|
|
display: flex;
|
|
@@ -992,22 +452,27 @@ onMounted(() => {
|
|
|
padding-bottom: 20px;
|
|
padding-bottom: 20px;
|
|
|
border-bottom: 1px solid #f0f0f0;
|
|
border-bottom: 1px solid #f0f0f0;
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
.profile-basic {
|
|
.profile-basic {
|
|
|
margin-left: 20px;
|
|
margin-left: 20px;
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
.name-row {
|
|
.name-row {
|
|
|
display: flex;
|
|
display: flex;
|
|
|
align-items: center;
|
|
align-items: center;
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
.name {
|
|
.name {
|
|
|
font-size: 20px;
|
|
font-size: 20px;
|
|
|
font-weight: bold;
|
|
font-weight: bold;
|
|
|
color: #303133;
|
|
color: #303133;
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
.phone {
|
|
.phone {
|
|
|
margin-left: 10px;
|
|
margin-left: 10px;
|
|
|
color: #666;
|
|
color: #666;
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
.section-title {
|
|
.section-title {
|
|
|
font-size: 16px;
|
|
font-size: 16px;
|
|
|
font-weight: bold;
|
|
font-weight: bold;
|
|
@@ -1025,10 +490,12 @@ onMounted(() => {
|
|
|
border-bottom: 1px dashed #eee;
|
|
border-bottom: 1px dashed #eee;
|
|
|
color: #303133;
|
|
color: #303133;
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
.upload-avatar:hover {
|
|
.upload-avatar:hover {
|
|
|
cursor: pointer;
|
|
cursor: pointer;
|
|
|
opacity: 0.8;
|
|
opacity: 0.8;
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
.pagination-container {
|
|
.pagination-container {
|
|
|
margin-top: 20px;
|
|
margin-top: 20px;
|
|
|
display: flex;
|
|
display: flex;
|
|
@@ -1042,6 +509,7 @@ onMounted(() => {
|
|
|
overflow: hidden;
|
|
overflow: hidden;
|
|
|
display: inline-block;
|
|
display: inline-block;
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
.avatar-uploader-icon {
|
|
.avatar-uploader-icon {
|
|
|
font-size: 28px;
|
|
font-size: 28px;
|
|
|
color: #8c939d;
|
|
color: #8c939d;
|
|
@@ -1055,10 +523,12 @@ onMounted(() => {
|
|
|
align-items: center;
|
|
align-items: center;
|
|
|
transition: .2s;
|
|
transition: .2s;
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
.avatar-uploader-icon:hover {
|
|
.avatar-uploader-icon:hover {
|
|
|
border-color: #409EFF;
|
|
border-color: #409EFF;
|
|
|
color: #409EFF;
|
|
color: #409EFF;
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
.avatar {
|
|
.avatar {
|
|
|
width: 100px;
|
|
width: 100px;
|
|
|
height: 100px;
|
|
height: 100px;
|