32 changed files with 4926 additions and 764 deletions
@ -0,0 +1,400 @@ |
|||||
|
<template> |
||||
|
<div class="personnel-approval-demo"> |
||||
|
<el-card class="demo-card" shadow="hover"> |
||||
|
<template #header> |
||||
|
<div class="card-header"> |
||||
|
<h3>人员添加审批功能演示</h3> |
||||
|
<p class="desc">该功能展示如何将人员添加接入审批流程</p> |
||||
|
</div> |
||||
|
</template> |
||||
|
|
||||
|
<!-- 审批配置选择 --> |
||||
|
<div class="section"> |
||||
|
<h4>第一步:选择审批配置</h4> |
||||
|
<el-form :model="form" label-width="120px" size="default"> |
||||
|
<el-form-item label="是否使用审批:"> |
||||
|
<el-switch |
||||
|
v-model="form.useApproval" |
||||
|
active-text="启用审批流程" |
||||
|
inactive-text="直接添加" |
||||
|
@change="onApprovalToggle"> |
||||
|
</el-switch> |
||||
|
</el-form-item> |
||||
|
|
||||
|
<el-form-item |
||||
|
v-if="form.useApproval" |
||||
|
label="审批配置:" |
||||
|
required> |
||||
|
<el-select |
||||
|
v-model="form.approvalConfigId" |
||||
|
placeholder="请选择审批配置" |
||||
|
:loading="configLoading" |
||||
|
clearable> |
||||
|
<el-option |
||||
|
v-for="config in approvalConfigs" |
||||
|
:key="config.id" |
||||
|
:label="config.config_name" |
||||
|
:value="config.id"> |
||||
|
<span>{{ config.config_name }}</span> |
||||
|
<span style="float: right; color: #8492a6;">{{ config.description }}</span> |
||||
|
</el-option> |
||||
|
</el-select> |
||||
|
</el-form-item> |
||||
|
</el-form> |
||||
|
</div> |
||||
|
|
||||
|
<!-- 人员信息填写 --> |
||||
|
<div class="section"> |
||||
|
<h4>第二步:填写人员信息</h4> |
||||
|
<el-form :model="personnelForm" :rules="rules" ref="personnelFormRef" label-width="120px"> |
||||
|
<el-row :gutter="20"> |
||||
|
<el-col :span="12"> |
||||
|
<el-form-item label="姓名:" prop="name"> |
||||
|
<el-input v-model="personnelForm.name" placeholder="请输入姓名"></el-input> |
||||
|
</el-form-item> |
||||
|
</el-col> |
||||
|
<el-col :span="12"> |
||||
|
<el-form-item label="性别:" prop="gender"> |
||||
|
<el-radio-group v-model="personnelForm.gender"> |
||||
|
<el-radio :label="0">男</el-radio> |
||||
|
<el-radio :label="1">女</el-radio> |
||||
|
</el-radio-group> |
||||
|
</el-form-item> |
||||
|
</el-col> |
||||
|
</el-row> |
||||
|
|
||||
|
<el-row :gutter="20"> |
||||
|
<el-col :span="12"> |
||||
|
<el-form-item label="手机号:" prop="phone"> |
||||
|
<el-input v-model="personnelForm.phone" placeholder="请输入手机号"></el-input> |
||||
|
</el-form-item> |
||||
|
</el-col> |
||||
|
<el-col :span="12"> |
||||
|
<el-form-item label="状态:" prop="status"> |
||||
|
<el-select v-model="personnelForm.status" placeholder="请选择状态"> |
||||
|
<el-option label="正常" :value="1"></el-option> |
||||
|
<el-option label="禁用" :value="0"></el-option> |
||||
|
</el-select> |
||||
|
</el-form-item> |
||||
|
</el-col> |
||||
|
</el-row> |
||||
|
|
||||
|
<el-form-item label="家庭住址:"> |
||||
|
<el-input v-model="personnelForm.address" placeholder="请输入家庭住址"></el-input> |
||||
|
</el-form-item> |
||||
|
|
||||
|
<el-form-item label="学历:"> |
||||
|
<el-select v-model="personnelForm.education" placeholder="请选择学历"> |
||||
|
<el-option label="高中及以下" value="高中及以下"></el-option> |
||||
|
<el-option label="大专" value="大专"></el-option> |
||||
|
<el-option label="本科" value="本科"></el-option> |
||||
|
<el-option label="硕士" value="硕士"></el-option> |
||||
|
<el-option label="博士" value="博士"></el-option> |
||||
|
</el-select> |
||||
|
</el-form-item> |
||||
|
|
||||
|
<el-form-item label="个人简介:"> |
||||
|
<el-input |
||||
|
v-model="personnelForm.profile" |
||||
|
type="textarea" |
||||
|
:rows="3" |
||||
|
placeholder="请输入个人简介"> |
||||
|
</el-input> |
||||
|
</el-form-item> |
||||
|
|
||||
|
<el-form-item label="是否系统用户:"> |
||||
|
<el-switch |
||||
|
v-model="personnelForm.isSysUser" |
||||
|
active-text="是" |
||||
|
inactive-text="否"> |
||||
|
</el-switch> |
||||
|
</el-form-item> |
||||
|
</el-form> |
||||
|
</div> |
||||
|
|
||||
|
<!-- 提交按钮 --> |
||||
|
<div class="section"> |
||||
|
<h4>第三步:提交申请</h4> |
||||
|
<el-button |
||||
|
type="primary" |
||||
|
size="large" |
||||
|
:loading="submitLoading" |
||||
|
@click="handleSubmit"> |
||||
|
{{ form.useApproval ? '提交审批申请' : '直接添加人员' }} |
||||
|
</el-button> |
||||
|
<el-button |
||||
|
size="large" |
||||
|
@click="handleReset"> |
||||
|
重置表单 |
||||
|
</el-button> |
||||
|
</div> |
||||
|
|
||||
|
<!-- 结果展示 --> |
||||
|
<div v-if="result" class="section result-section"> |
||||
|
<h4>处理结果</h4> |
||||
|
<el-alert |
||||
|
:title="result.title" |
||||
|
:description="result.description" |
||||
|
:type="result.type" |
||||
|
show-icon |
||||
|
:closable="false"> |
||||
|
</el-alert> |
||||
|
</div> |
||||
|
</el-card> |
||||
|
|
||||
|
<!-- 审批流程状态展示 --> |
||||
|
<el-card v-if="showApprovalStatus" class="approval-status-card" shadow="hover"> |
||||
|
<template #header> |
||||
|
<h3>审批流程状态</h3> |
||||
|
</template> |
||||
|
<div class="approval-flow"> |
||||
|
<el-steps :active="currentStep" finish-status="success" align-center> |
||||
|
<el-step |
||||
|
v-for="(step, index) in approvalSteps" |
||||
|
:key="index" |
||||
|
:title="step.title" |
||||
|
:description="step.description"> |
||||
|
</el-step> |
||||
|
</el-steps> |
||||
|
</div> |
||||
|
</el-card> |
||||
|
</div> |
||||
|
</template> |
||||
|
|
||||
|
<script setup> |
||||
|
import { ref, reactive, onMounted } from 'vue' |
||||
|
import { ElMessage, ElMessageBox } from 'element-plus' |
||||
|
import { apiRequest } from '@/utils/request' |
||||
|
|
||||
|
// 响应式数据 |
||||
|
const form = reactive({ |
||||
|
useApproval: false, |
||||
|
approvalConfigId: null |
||||
|
}) |
||||
|
|
||||
|
const personnelForm = reactive({ |
||||
|
name: '', |
||||
|
gender: 0, |
||||
|
phone: '', |
||||
|
address: '', |
||||
|
education: '', |
||||
|
profile: '', |
||||
|
status: 1, |
||||
|
isSysUser: false, |
||||
|
info: {} |
||||
|
}) |
||||
|
|
||||
|
const approvalConfigs = ref([]) |
||||
|
const configLoading = ref(false) |
||||
|
const submitLoading = ref(false) |
||||
|
const result = ref(null) |
||||
|
const showApprovalStatus = ref(false) |
||||
|
const currentStep = ref(0) |
||||
|
const approvalSteps = ref([]) |
||||
|
|
||||
|
// 表单验证规则 |
||||
|
const rules = { |
||||
|
name: [ |
||||
|
{ required: true, message: '请输入姓名', trigger: 'blur' } |
||||
|
], |
||||
|
phone: [ |
||||
|
{ required: true, message: '请输入手机号', trigger: 'blur' }, |
||||
|
{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号', trigger: 'blur' } |
||||
|
], |
||||
|
gender: [ |
||||
|
{ required: true, message: '请选择性别', trigger: 'change' } |
||||
|
], |
||||
|
status: [ |
||||
|
{ required: true, message: '请选择状态', trigger: 'change' } |
||||
|
] |
||||
|
} |
||||
|
|
||||
|
const personnelFormRef = ref() |
||||
|
|
||||
|
// 获取审批配置列表 |
||||
|
const loadApprovalConfigs = async () => { |
||||
|
configLoading.value = true |
||||
|
try { |
||||
|
const response = await apiRequest({ |
||||
|
url: '/adminapi/personnel/approval-configs', |
||||
|
method: 'GET' |
||||
|
}) |
||||
|
if (response.data.code === 1) { |
||||
|
approvalConfigs.value = response.data.data.list || [] |
||||
|
} |
||||
|
} catch (error) { |
||||
|
ElMessage.error('获取审批配置失败') |
||||
|
} finally { |
||||
|
configLoading.value = false |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 审批开关切换 |
||||
|
const onApprovalToggle = (value) => { |
||||
|
if (value && approvalConfigs.value.length === 0) { |
||||
|
loadApprovalConfigs() |
||||
|
} |
||||
|
if (!value) { |
||||
|
form.approvalConfigId = null |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 提交表单 |
||||
|
const handleSubmit = async () => { |
||||
|
// 验证表单 |
||||
|
const valid = await personnelFormRef.value.validate() |
||||
|
if (!valid) { |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
// 如果使用审批流程,检查是否选择了配置 |
||||
|
if (form.useApproval && !form.approvalConfigId) { |
||||
|
ElMessage.warning('请选择审批配置') |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
submitLoading.value = true |
||||
|
try { |
||||
|
const submitData = { |
||||
|
...personnelForm, |
||||
|
use_approval: form.useApproval ? 1 : 0, |
||||
|
approval_config_id: form.approvalConfigId || 0 |
||||
|
} |
||||
|
|
||||
|
const response = await apiRequest({ |
||||
|
url: '/adminapi/personnel/personnel', |
||||
|
method: 'POST', |
||||
|
data: submitData |
||||
|
}) |
||||
|
|
||||
|
if (response.data.code === 1) { |
||||
|
if (form.useApproval) { |
||||
|
result.value = { |
||||
|
title: '审批申请提交成功', |
||||
|
description: `已成功提交人员添加审批申请,流程ID: ${response.data.data.process_id},请等待审批人处理。`, |
||||
|
type: 'success' |
||||
|
} |
||||
|
showApprovalDemo() |
||||
|
} else { |
||||
|
result.value = { |
||||
|
title: '人员添加成功', |
||||
|
description: `人员 ${personnelForm.name} 添加成功,ID: ${response.data.data.id}`, |
||||
|
type: 'success' |
||||
|
} |
||||
|
} |
||||
|
} else { |
||||
|
result.value = { |
||||
|
title: '提交失败', |
||||
|
description: response.data.msg || '操作失败,请重试', |
||||
|
type: 'error' |
||||
|
} |
||||
|
} |
||||
|
} catch (error) { |
||||
|
result.value = { |
||||
|
title: '提交失败', |
||||
|
description: '网络错误,请稍后重试', |
||||
|
type: 'error' |
||||
|
} |
||||
|
} finally { |
||||
|
submitLoading.value = false |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 展示审批流程演示 |
||||
|
const showApprovalDemo = () => { |
||||
|
showApprovalStatus.value = true |
||||
|
approvalSteps.value = [ |
||||
|
{ title: '提交申请', description: '用户提交人员添加申请' }, |
||||
|
{ title: '部门审批', description: '部门负责人审批' }, |
||||
|
{ title: 'HR审批', description: 'HR部门审批' }, |
||||
|
{ title: '完成', description: '审批完成,创建人员记录' } |
||||
|
] |
||||
|
currentStep.value = 1 // 模拟当前在第二步 |
||||
|
} |
||||
|
|
||||
|
// 重置表单 |
||||
|
const handleReset = () => { |
||||
|
personnelFormRef.value.resetFields() |
||||
|
form.useApproval = false |
||||
|
form.approvalConfigId = null |
||||
|
result.value = null |
||||
|
showApprovalStatus.value = false |
||||
|
} |
||||
|
|
||||
|
// 组件挂载时初始化 |
||||
|
onMounted(() => { |
||||
|
// 可以在这里初始化一些演示数据 |
||||
|
personnelForm.name = '张三' |
||||
|
personnelForm.phone = '13800138000' |
||||
|
personnelForm.address = '北京市朝阳区' |
||||
|
personnelForm.education = '本科' |
||||
|
personnelForm.profile = '具有丰富的教学经验,专业技能扎实。' |
||||
|
}) |
||||
|
</script> |
||||
|
|
||||
|
<style scoped> |
||||
|
.personnel-approval-demo { |
||||
|
padding: 20px; |
||||
|
} |
||||
|
|
||||
|
.demo-card { |
||||
|
margin-bottom: 20px; |
||||
|
} |
||||
|
|
||||
|
.card-header { |
||||
|
text-align: center; |
||||
|
} |
||||
|
|
||||
|
.card-header h3 { |
||||
|
margin: 0 0 10px 0; |
||||
|
color: #303133; |
||||
|
} |
||||
|
|
||||
|
.card-header .desc { |
||||
|
margin: 0; |
||||
|
color: #909399; |
||||
|
font-size: 14px; |
||||
|
} |
||||
|
|
||||
|
.section { |
||||
|
margin-bottom: 30px; |
||||
|
padding-bottom: 20px; |
||||
|
border-bottom: 1px solid #ebeef5; |
||||
|
} |
||||
|
|
||||
|
.section:last-child { |
||||
|
border-bottom: none; |
||||
|
margin-bottom: 0; |
||||
|
} |
||||
|
|
||||
|
.section h4 { |
||||
|
margin: 0 0 20px 0; |
||||
|
color: #606266; |
||||
|
font-size: 16px; |
||||
|
font-weight: 600; |
||||
|
} |
||||
|
|
||||
|
.result-section { |
||||
|
background: #f8f9fa; |
||||
|
padding: 20px; |
||||
|
border-radius: 8px; |
||||
|
} |
||||
|
|
||||
|
.approval-status-card { |
||||
|
margin-top: 20px; |
||||
|
} |
||||
|
|
||||
|
.approval-flow { |
||||
|
padding: 20px 0; |
||||
|
} |
||||
|
|
||||
|
:deep(.el-step__description) { |
||||
|
color: #909399; |
||||
|
font-size: 12px; |
||||
|
} |
||||
|
|
||||
|
:deep(.el-alert__description) { |
||||
|
font-size: 14px; |
||||
|
line-height: 1.6; |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,155 @@ |
|||||
|
<?php |
||||
|
// +---------------------------------------------------------------------- |
||||
|
// | Niucloud-admin 企业快速开发的多应用管理平台 |
||||
|
// +---------------------------------------------------------------------- |
||||
|
// | 官方网址:https://www.niucloud.com |
||||
|
// +---------------------------------------------------------------------- |
||||
|
// | niucloud团队 版权所有 开源版本可自由商用 |
||||
|
// +---------------------------------------------------------------------- |
||||
|
// | Author: Niucloud Team |
||||
|
// +---------------------------------------------------------------------- |
||||
|
|
||||
|
namespace app\api\controller\common; |
||||
|
|
||||
|
use app\service\api\common\DictService; |
||||
|
use core\base\BaseController; |
||||
|
|
||||
|
/** |
||||
|
* 字典批量获取控制器 |
||||
|
* Class Dict |
||||
|
* @package app\api\controller\common |
||||
|
*/ |
||||
|
class Dict extends BaseController |
||||
|
{ |
||||
|
/** |
||||
|
* 批量获取字典数据 |
||||
|
* @return \think\Response |
||||
|
*/ |
||||
|
public function getBatchDict() |
||||
|
{ |
||||
|
$keys = input('keys', []); |
||||
|
|
||||
|
// 支持字符串格式(逗号分隔)和数组格式 |
||||
|
if (is_string($keys)) { |
||||
|
$keys = array_filter(explode(',', $keys)); |
||||
|
} |
||||
|
|
||||
|
if (!is_array($keys) || empty($keys)) { |
||||
|
return fail('参数错误:keys必须是非空数组或逗号分隔的字符串'); |
||||
|
} |
||||
|
|
||||
|
// 限制一次最多获取20个字典,防止性能问题 |
||||
|
if (count($keys) > 20) { |
||||
|
return fail('一次最多获取20个字典'); |
||||
|
} |
||||
|
|
||||
|
try { |
||||
|
$dictService = new DictService(); |
||||
|
$result = $dictService->getBatchDict($keys); |
||||
|
|
||||
|
return success($result); |
||||
|
} catch (\Exception $e) { |
||||
|
return fail('获取字典数据失败:' . $e->getMessage()); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 根据业务场景获取字典数据 |
||||
|
* @return \think\Response |
||||
|
*/ |
||||
|
public function getDictByScene() |
||||
|
{ |
||||
|
$scene = input('scene', ''); |
||||
|
|
||||
|
if (empty($scene)) { |
||||
|
return fail('参数错误:scene不能为空'); |
||||
|
} |
||||
|
|
||||
|
try { |
||||
|
$dictService = new DictService(); |
||||
|
|
||||
|
// 获取场景对应的字典keys |
||||
|
$keys = $dictService->getDictKeysByScene($scene); |
||||
|
|
||||
|
if (empty($keys)) { |
||||
|
return fail('不支持的业务场景:' . $scene); |
||||
|
} |
||||
|
|
||||
|
// 批量获取字典数据 |
||||
|
$result = $dictService->getBatchDict($keys); |
||||
|
|
||||
|
return success([ |
||||
|
'scene' => $scene, |
||||
|
'data' => $result |
||||
|
]); |
||||
|
} catch (\Exception $e) { |
||||
|
return fail('获取字典数据失败:' . $e->getMessage()); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 获取单个字典数据 |
||||
|
* @return \think\Response |
||||
|
*/ |
||||
|
public function getDict() |
||||
|
{ |
||||
|
$key = input('key', ''); |
||||
|
$useCache = input('use_cache', 1); |
||||
|
|
||||
|
if (empty($key)) { |
||||
|
return fail('参数错误:key不能为空'); |
||||
|
} |
||||
|
|
||||
|
try { |
||||
|
$dictService = new DictService(); |
||||
|
$result = $dictService->getDict($key, (bool)$useCache); |
||||
|
|
||||
|
return success($result); |
||||
|
} catch (\Exception $e) { |
||||
|
return fail('获取字典数据失败:' . $e->getMessage()); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 清除字典缓存 |
||||
|
* @return \think\Response |
||||
|
*/ |
||||
|
public function clearDictCache() |
||||
|
{ |
||||
|
$keys = input('keys', null); |
||||
|
|
||||
|
// 支持字符串格式(逗号分隔)和数组格式 |
||||
|
if (is_string($keys)) { |
||||
|
$keys = array_filter(explode(',', $keys)); |
||||
|
} |
||||
|
|
||||
|
try { |
||||
|
$dictService = new DictService(); |
||||
|
$result = $dictService->clearDictCache($keys); |
||||
|
|
||||
|
if ($result) { |
||||
|
return success('缓存清除成功'); |
||||
|
} else { |
||||
|
return fail('缓存清除失败'); |
||||
|
} |
||||
|
} catch (\Exception $e) { |
||||
|
return fail('缓存清除失败:' . $e->getMessage()); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 获取常用字典映射关系 |
||||
|
* @return \think\Response |
||||
|
*/ |
||||
|
public function getDictMapping() |
||||
|
{ |
||||
|
try { |
||||
|
$dictService = new DictService(); |
||||
|
$mapping = $dictService->getCommonDictMapping(); |
||||
|
|
||||
|
return success($mapping); |
||||
|
} catch (\Exception $e) { |
||||
|
return fail('获取字典映射失败:' . $e->getMessage()); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,82 @@ |
|||||
|
<?php |
||||
|
// +---------------------------------------------------------------------- |
||||
|
// | Niucloud-admin 企业快速开发的多应用管理平台 |
||||
|
// +---------------------------------------------------------------------- |
||||
|
// | 官方网址:https://www.niucloud.com |
||||
|
// +---------------------------------------------------------------------- |
||||
|
// | niucloud团队 版权所有 开源版本可自由商用 |
||||
|
// +---------------------------------------------------------------------- |
||||
|
// | Author: Niucloud Team |
||||
|
// +---------------------------------------------------------------------- |
||||
|
|
||||
|
namespace app\model\sys; |
||||
|
|
||||
|
use core\base\BaseModel; |
||||
|
|
||||
|
/** |
||||
|
* 系统数据字典模型 |
||||
|
* Class SysDict |
||||
|
* @package app\model\sys |
||||
|
*/ |
||||
|
class SysDict extends BaseModel |
||||
|
{ |
||||
|
/** |
||||
|
* 数据表主键 |
||||
|
* @var string |
||||
|
*/ |
||||
|
protected $pk = 'id'; |
||||
|
|
||||
|
/** |
||||
|
* 模型名称 |
||||
|
* @var string |
||||
|
*/ |
||||
|
protected $name = 'sys_dict'; |
||||
|
|
||||
|
protected $type = [ |
||||
|
'value' => 'json' |
||||
|
]; |
||||
|
|
||||
|
// 设置json类型字段 |
||||
|
protected $json = ['value']; |
||||
|
// 设置JSON数据返回数组 |
||||
|
protected $jsonAssoc = true; |
||||
|
|
||||
|
/** |
||||
|
* 搜索器:数据字典字典名称 |
||||
|
* @param $query |
||||
|
* @param $value |
||||
|
* @param $data |
||||
|
*/ |
||||
|
public function searchNameAttr($query, $value, $data) |
||||
|
{ |
||||
|
if ($value != '') { |
||||
|
$query->where("name", $value); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 搜索器:数据字典字典关键词 |
||||
|
* @param $query |
||||
|
* @param $value |
||||
|
* @param $data |
||||
|
*/ |
||||
|
public function searchKeyAttr($query, $value, $data) |
||||
|
{ |
||||
|
if ($value != '') { |
||||
|
$query->where("key", $value); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 搜索器:数据字典状态 |
||||
|
* @param $query |
||||
|
* @param $value |
||||
|
* @param $data |
||||
|
*/ |
||||
|
public function searchStatusAttr($query, $value, $data) |
||||
|
{ |
||||
|
if ($value !== '') { |
||||
|
$query->where("status", $value); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,160 @@ |
|||||
|
<?php |
||||
|
// +---------------------------------------------------------------------- |
||||
|
// | Niucloud-admin 企业快速开发的多应用管理平台 |
||||
|
// +---------------------------------------------------------------------- |
||||
|
// | 官方网址:https://www.niucloud.com |
||||
|
// +---------------------------------------------------------------------- |
||||
|
// | niucloud团队 版权所有 开源版本可自由商用 |
||||
|
// +---------------------------------------------------------------------- |
||||
|
// | Author: Niucloud Team |
||||
|
// +---------------------------------------------------------------------- |
||||
|
|
||||
|
namespace app\service\api\apiService; |
||||
|
|
||||
|
use app\model\service_logs\ServiceLogs; |
||||
|
use core\base\BaseApiService; |
||||
|
|
||||
|
/** |
||||
|
* 服务API服务层 |
||||
|
* Class ServiceService |
||||
|
* @package app\service\api\apiService |
||||
|
*/ |
||||
|
class ServiceService extends BaseApiService |
||||
|
{ |
||||
|
public function __construct() |
||||
|
{ |
||||
|
parent::__construct(); |
||||
|
$this->model = new ServiceLogs(); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 获取员工服务记录列表 |
||||
|
* @param array $where |
||||
|
* @return array |
||||
|
*/ |
||||
|
public function getMyServiceLogs(array $where = []) |
||||
|
{ |
||||
|
// 获取当前登录的员工ID |
||||
|
$staffId = $this->uid; |
||||
|
|
||||
|
$field = 'id,service_id,staff_id,status,service_remark,feedback,score,created_at,updated_at'; |
||||
|
$order = 'created_at desc'; |
||||
|
|
||||
|
$search_model = $this->model |
||||
|
->withSearch(["staff_id"], ['staff_id' => $staffId]) |
||||
|
->with(['service', 'staff']) |
||||
|
->field($field) |
||||
|
->order($order); |
||||
|
|
||||
|
$list = $this->pageQuery($search_model); |
||||
|
|
||||
|
// 如果没有数据,为演示目的返回一些测试数据 |
||||
|
if (empty($list['data']) && isset($where['demo'])) { |
||||
|
$list = [ |
||||
|
'data' => [ |
||||
|
[ |
||||
|
'id' => 1, |
||||
|
'service_id' => 1, |
||||
|
'staff_id' => $staffId, |
||||
|
'status' => 0, |
||||
|
'service_remark' => '', |
||||
|
'feedback' => '', |
||||
|
'score' => null, |
||||
|
'created_at' => time(), |
||||
|
'updated_at' => time(), |
||||
|
'service_name' => '体能训练指导服务', |
||||
|
'preview_image_url' => '', |
||||
|
'description' => '专业的体能训练指导,帮助学员提升身体素质', |
||||
|
'service_type' => '训练指导' |
||||
|
], |
||||
|
[ |
||||
|
'id' => 2, |
||||
|
'service_id' => 2, |
||||
|
'staff_id' => $staffId, |
||||
|
'status' => 1, |
||||
|
'service_remark' => '学员表现**优秀**,完成了所有训练项目\n• 力量训练:90%完成度\n• 耐力训练:85%完成度\n• 柔韧性训练:95%完成度', |
||||
|
'feedback' => '孩子很喜欢这次训练,教练很专业', |
||||
|
'score' => 95, |
||||
|
'created_at' => time() - 86400, |
||||
|
'updated_at' => time() - 3600, |
||||
|
'service_name' => '技能训练服务', |
||||
|
'preview_image_url' => '', |
||||
|
'description' => '专项技能训练,提升运动技巧', |
||||
|
'service_type' => '技能培训' |
||||
|
] |
||||
|
], |
||||
|
'current_page' => 1, |
||||
|
'last_page' => 1, |
||||
|
'per_page' => 10, |
||||
|
'total' => 2 |
||||
|
]; |
||||
|
} |
||||
|
|
||||
|
return $list; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 获取服务记录详情 |
||||
|
* @param int $id |
||||
|
* @return array |
||||
|
*/ |
||||
|
public function getServiceLogDetail(int $id) |
||||
|
{ |
||||
|
// 获取当前登录的员工ID |
||||
|
$staffId = $this->uid; |
||||
|
|
||||
|
$field = 'id,service_id,staff_id,status,service_remark,feedback,score,created_at,updated_at'; |
||||
|
|
||||
|
$info = $this->model |
||||
|
->with(['service', 'staff']) |
||||
|
->field($field) |
||||
|
->where([ |
||||
|
['id', "=", $id], |
||||
|
['staff_id', "=", $staffId] |
||||
|
]) |
||||
|
->findOrEmpty() |
||||
|
->toArray(); |
||||
|
|
||||
|
return $info; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 更新服务结果 |
||||
|
* @param int $id |
||||
|
* @param string $serviceRemark |
||||
|
* @return array |
||||
|
*/ |
||||
|
public function updateServiceRemark(int $id, string $serviceRemark) |
||||
|
{ |
||||
|
// 获取当前登录的员工ID |
||||
|
$staffId = $this->uid; |
||||
|
|
||||
|
// 查找对应的服务记录 |
||||
|
$serviceLog = $this->model |
||||
|
->where([ |
||||
|
['id', '=', $id], |
||||
|
['staff_id', '=', $staffId] |
||||
|
]) |
||||
|
->find(); |
||||
|
|
||||
|
if (!$serviceLog) { |
||||
|
return ['code' => 0, 'msg' => '服务记录不存在']; |
||||
|
} |
||||
|
|
||||
|
// 检查状态,只有非完成状态才能修改 |
||||
|
if ($serviceLog->status == 1) { |
||||
|
return ['code' => 0, 'msg' => '服务已完成,无法修改']; |
||||
|
} |
||||
|
|
||||
|
try { |
||||
|
// 更新服务结果 |
||||
|
$serviceLog->service_remark = $serviceRemark; |
||||
|
$serviceLog->updated_at = time(); |
||||
|
$serviceLog->save(); |
||||
|
|
||||
|
return ['code' => 1, 'msg' => '更新成功']; |
||||
|
} catch (\Exception $e) { |
||||
|
return ['code' => 0, 'msg' => '更新失败:' . $e->getMessage()]; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,297 @@ |
|||||
|
<?php |
||||
|
// +---------------------------------------------------------------------- |
||||
|
// | Niucloud-admin 企业快速开发的多应用管理平台 |
||||
|
// +---------------------------------------------------------------------- |
||||
|
// | 官方网址:https://www.niucloud.com |
||||
|
// +---------------------------------------------------------------------- |
||||
|
// | niucloud团队 版权所有 开源版本可自由商用 |
||||
|
// +---------------------------------------------------------------------- |
||||
|
// | Author: Niucloud Team |
||||
|
// +---------------------------------------------------------------------- |
||||
|
|
||||
|
namespace app\service\api\common; |
||||
|
|
||||
|
use app\model\dict\Dict; |
||||
|
use core\base\BaseApiService; |
||||
|
|
||||
|
/** |
||||
|
* 字典批量获取服务 |
||||
|
* Class DictService |
||||
|
* @package app\service\api\common |
||||
|
*/ |
||||
|
class DictService extends BaseApiService |
||||
|
{ |
||||
|
public function __construct() |
||||
|
{ |
||||
|
parent::__construct(); |
||||
|
$this->model = new Dict(); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 批量获取字典数据 |
||||
|
* @param array $keys 字典key数组 |
||||
|
* @return array |
||||
|
*/ |
||||
|
public function getBatchDict(array $keys = []): array |
||||
|
{ |
||||
|
if (empty($keys)) { |
||||
|
return []; |
||||
|
} |
||||
|
|
||||
|
// 验证keys参数 |
||||
|
$validKeys = array_filter($keys, function($key) { |
||||
|
return is_string($key) && !empty(trim($key)); |
||||
|
}); |
||||
|
|
||||
|
if (empty($validKeys)) { |
||||
|
return []; |
||||
|
} |
||||
|
|
||||
|
try { |
||||
|
// 批量查询字典数据 |
||||
|
$dictData = $this->model |
||||
|
->whereIn('key', $validKeys) |
||||
|
->field('key, dictionary') |
||||
|
->select() |
||||
|
->toArray(); |
||||
|
|
||||
|
$result = []; |
||||
|
|
||||
|
foreach ($dictData as $dict) { |
||||
|
$key = $dict['key']; |
||||
|
$dictionary = $dict['dictionary']; |
||||
|
|
||||
|
// 解析字典值 - 模型已自动处理JSON转换 |
||||
|
if (!empty($dictionary) && is_array($dictionary)) { |
||||
|
$result[$key] = $dictionary; |
||||
|
} else if (!empty($dictionary) && is_string($dictionary)) { |
||||
|
// 如果是字符串,尝试解析 |
||||
|
$decodedValue = json_decode($dictionary, true); |
||||
|
if (json_last_error() === JSON_ERROR_NONE && is_array($decodedValue)) { |
||||
|
$result[$key] = $decodedValue; |
||||
|
} else { |
||||
|
$result[$key] = $this->parseStringValue($dictionary); |
||||
|
} |
||||
|
} else { |
||||
|
$result[$key] = []; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 为没有找到的key返回空数组 |
||||
|
foreach ($validKeys as $key) { |
||||
|
if (!isset($result[$key])) { |
||||
|
$result[$key] = []; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return $result; |
||||
|
|
||||
|
} catch (\Exception $e) { |
||||
|
// 记录错误日志 |
||||
|
trace('批量获取字典数据失败: ' . $e->getMessage(), 'error'); |
||||
|
|
||||
|
// 返回空结果 |
||||
|
$result = []; |
||||
|
foreach ($validKeys as $key) { |
||||
|
$result[$key] = []; |
||||
|
} |
||||
|
return $result; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 获取单个字典数据(带缓存) |
||||
|
* @param string $key 字典key |
||||
|
* @param bool $useCache 是否使用缓存 |
||||
|
* @return array |
||||
|
*/ |
||||
|
public function getDict(string $key, bool $useCache = true): array |
||||
|
{ |
||||
|
if (empty($key)) { |
||||
|
return []; |
||||
|
} |
||||
|
|
||||
|
$cacheKey = "dict_cache_{$key}"; |
||||
|
|
||||
|
// 如果使用缓存,先尝试从缓存获取 |
||||
|
if ($useCache) { |
||||
|
$cached = cache($cacheKey); |
||||
|
if ($cached !== false) { |
||||
|
return $cached; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
try { |
||||
|
$dict = $this->model |
||||
|
->where('key', $key) |
||||
|
->field('dictionary') |
||||
|
->find(); |
||||
|
|
||||
|
$result = []; |
||||
|
if ($dict && !empty($dict['dictionary'])) { |
||||
|
if (is_array($dict['dictionary'])) { |
||||
|
$result = $dict['dictionary']; |
||||
|
} else { |
||||
|
$decodedValue = json_decode($dict['dictionary'], true); |
||||
|
if (json_last_error() === JSON_ERROR_NONE && is_array($decodedValue)) { |
||||
|
$result = $decodedValue; |
||||
|
} else { |
||||
|
$result = $this->parseStringValue($dict['dictionary']); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 缓存结果(缓存30分钟) |
||||
|
if ($useCache) { |
||||
|
cache($cacheKey, $result, 1800); |
||||
|
} |
||||
|
|
||||
|
return $result; |
||||
|
|
||||
|
} catch (\Exception $e) { |
||||
|
trace('获取字典数据失败: ' . $e->getMessage(), 'error'); |
||||
|
return []; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 清除字典缓存 |
||||
|
* @param string|array $keys 要清除的字典key,为空则清除所有字典缓存 |
||||
|
* @return bool |
||||
|
*/ |
||||
|
public function clearDictCache($keys = null): bool |
||||
|
{ |
||||
|
try { |
||||
|
if (is_null($keys)) { |
||||
|
// 清除所有字典缓存(需要实现cache标签功能或使用其他方式) |
||||
|
// 这里简化处理,实际项目中可以使用cache标签 |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
if (is_string($keys)) { |
||||
|
$keys = [$keys]; |
||||
|
} |
||||
|
|
||||
|
if (is_array($keys)) { |
||||
|
foreach ($keys as $key) { |
||||
|
cache("dict_cache_{$key}", null); |
||||
|
} |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
return false; |
||||
|
} catch (\Exception $e) { |
||||
|
trace('清除字典缓存失败: ' . $e->getMessage(), 'error'); |
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 解析字符串格式的字典值 |
||||
|
* @param string $value |
||||
|
* @return array |
||||
|
*/ |
||||
|
private function parseStringValue(string $value): array |
||||
|
{ |
||||
|
// 尝试按行分割,每行格式如:key|value 或 key:value |
||||
|
$lines = array_filter(explode("\n", $value)); |
||||
|
$result = []; |
||||
|
|
||||
|
foreach ($lines as $line) { |
||||
|
$line = trim($line); |
||||
|
if (empty($line)) { |
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
// 尝试多种分隔符 |
||||
|
$separators = ['|', ':', '=', ',']; |
||||
|
$parsed = false; |
||||
|
|
||||
|
foreach ($separators as $sep) { |
||||
|
if (strpos($line, $sep) !== false) { |
||||
|
$parts = explode($sep, $line, 2); |
||||
|
if (count($parts) === 2) { |
||||
|
$result[] = [ |
||||
|
'name' => trim($parts[1]), |
||||
|
'value' => trim($parts[0]), |
||||
|
'sort' => count($result), |
||||
|
'memo' => '' |
||||
|
]; |
||||
|
$parsed = true; |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 如果没有找到分隔符,将整行作为值,索引作为key |
||||
|
if (!$parsed) { |
||||
|
$result[] = [ |
||||
|
'name' => $line, |
||||
|
'value' => (string)count($result), |
||||
|
'sort' => count($result), |
||||
|
'memo' => '' |
||||
|
]; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return $result; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 获取常用字典映射 |
||||
|
* @return array |
||||
|
*/ |
||||
|
public function getCommonDictMapping(): array |
||||
|
{ |
||||
|
return [ |
||||
|
// 客户来源相关 |
||||
|
'source_channel' => 'SourceChannel', |
||||
|
'source' => 'source', |
||||
|
'purchasing_power' => 'customer_purchasing_power', |
||||
|
'cognitive_idea' => 'cognitive_concept', |
||||
|
'decision_maker' => 'decision_maker', |
||||
|
'initial_intent' => 'preliminarycustomerintention', |
||||
|
'status' => 'kh_status', |
||||
|
'distance' => 'distance', |
||||
|
|
||||
|
// 人员管理相关 |
||||
|
'gender' => 'gender', |
||||
|
'education' => 'education', |
||||
|
'position' => 'position', |
||||
|
'department' => 'department', |
||||
|
|
||||
|
// 课程相关 |
||||
|
'course_type' => 'course_type', |
||||
|
'course_level' => 'course_level', |
||||
|
'course_status' => 'course_status', |
||||
|
|
||||
|
// 其他常用字典 |
||||
|
'yes_no' => 'yes_no', |
||||
|
'enable_disable' => 'enable_disable' |
||||
|
]; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 根据业务场景获取字典keys |
||||
|
* @param string $scene 业务场景 |
||||
|
* @return array |
||||
|
*/ |
||||
|
public function getDictKeysByScene(string $scene): array |
||||
|
{ |
||||
|
$sceneMapping = [ |
||||
|
'customer_add' => [ |
||||
|
'SourceChannel', 'source', 'customer_purchasing_power', |
||||
|
'cognitive_concept', 'decision_maker', 'preliminarycustomerintention', |
||||
|
'kh_status', 'distance' |
||||
|
], |
||||
|
'personnel_add' => [ |
||||
|
'gender', 'education', 'position', 'department', 'enable_disable' |
||||
|
], |
||||
|
'course_add' => [ |
||||
|
'course_type', 'course_level', 'course_status', 'enable_disable' |
||||
|
] |
||||
|
]; |
||||
|
|
||||
|
return $sceneMapping[$scene] ?? []; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,85 @@ |
|||||
|
import { |
||||
|
Api_url |
||||
|
} from './config' |
||||
|
|
||||
|
// 静默请求工具 - 用于字典获取等不需要显示加载提示的场景
|
||||
|
const axiosQuiet = { |
||||
|
// 静默请求方法
|
||||
|
request(options) { |
||||
|
return new Promise((resolve, reject) => { |
||||
|
// 创建请求配置
|
||||
|
const config = { |
||||
|
url: Api_url + options.url, |
||||
|
data: options.data, |
||||
|
method: options.method || 'GET', |
||||
|
header: { |
||||
|
'token': uni.getStorageSync("token") |
||||
|
}, |
||||
|
timeout: 10000 // 设置10秒超时
|
||||
|
}; |
||||
|
|
||||
|
console.log('静默请求配置:', config); |
||||
|
|
||||
|
uni.request({ |
||||
|
...config, |
||||
|
success: (res) => { |
||||
|
try { |
||||
|
const { statusCode, data } = res; |
||||
|
console.log('静默请求响应:', res); |
||||
|
|
||||
|
// 处理HTTP状态码
|
||||
|
if (statusCode >= 200 && statusCode < 300) { |
||||
|
// 处理业务状态码
|
||||
|
if (data && data.code) { |
||||
|
if (data.code === 1) { // 成功状态码为1
|
||||
|
resolve(data); |
||||
|
} else if (data.code === 401) { |
||||
|
// 401错误静默处理,不显示提示
|
||||
|
console.warn('静默请求401错误:', data.msg); |
||||
|
reject(data); |
||||
|
} else { |
||||
|
// 其他业务错误也静默处理
|
||||
|
console.warn('静默请求业务错误:', data.msg); |
||||
|
reject(data); |
||||
|
} |
||||
|
} else { |
||||
|
resolve(data); |
||||
|
} |
||||
|
} else { |
||||
|
// HTTP错误
|
||||
|
console.warn('静默请求HTTP错误:', statusCode); |
||||
|
reject(res); |
||||
|
} |
||||
|
} catch (error) { |
||||
|
console.error('静默请求处理失败:', error); |
||||
|
reject(error); |
||||
|
} |
||||
|
}, |
||||
|
fail: (error) => { |
||||
|
console.warn('静默请求失败:', error); |
||||
|
reject(error); |
||||
|
} |
||||
|
}); |
||||
|
}); |
||||
|
}, |
||||
|
|
||||
|
// GET请求
|
||||
|
get(url, data = {}) { |
||||
|
return this.request({ |
||||
|
url, |
||||
|
data, |
||||
|
method: 'GET' |
||||
|
}); |
||||
|
}, |
||||
|
|
||||
|
// POST请求
|
||||
|
post(url, data = {}) { |
||||
|
return this.request({ |
||||
|
url, |
||||
|
data, |
||||
|
method: 'POST' |
||||
|
}); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
export default axiosQuiet; |
||||
@ -0,0 +1,332 @@ |
|||||
|
import axiosQuiet from './axiosQuiet.js' |
||||
|
|
||||
|
/** |
||||
|
* 字典工具类 - 支持批量获取和缓存 |
||||
|
*/ |
||||
|
class DictUtil { |
||||
|
constructor() { |
||||
|
this.cacheKey = 'dict_cache' |
||||
|
this.cacheExpire = 30 * 60 * 1000 // 30分钟过期
|
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 批量获取字典数据 |
||||
|
* @param {Array} keys 字典key数组 |
||||
|
* @param {Boolean} useCache 是否使用缓存 |
||||
|
* @returns {Promise<Object>} 字典数据对象 |
||||
|
*/ |
||||
|
async getBatchDict(keys = [], useCache = true) { |
||||
|
if (!Array.isArray(keys) || keys.length === 0) { |
||||
|
console.warn('字典keys参数必须是非空数组') |
||||
|
return {} |
||||
|
} |
||||
|
|
||||
|
try { |
||||
|
// 如果使用缓存,先检查缓存
|
||||
|
let cachedData = {} |
||||
|
let uncachedKeys = [] |
||||
|
|
||||
|
if (useCache) { |
||||
|
const cache = this.getCache() |
||||
|
uncachedKeys = keys.filter(key => { |
||||
|
if (cache[key] && this.isCacheValid(cache[key])) { |
||||
|
cachedData[key] = cache[key].data |
||||
|
return false |
||||
|
} |
||||
|
return true |
||||
|
}) |
||||
|
} else { |
||||
|
uncachedKeys = [...keys] |
||||
|
} |
||||
|
|
||||
|
// 如果所有数据都在缓存中,直接返回
|
||||
|
if (uncachedKeys.length === 0) { |
||||
|
return cachedData |
||||
|
} |
||||
|
|
||||
|
// 请求未缓存的数据
|
||||
|
try { |
||||
|
const response = await axiosQuiet.get('/dict/batch', { |
||||
|
keys: uncachedKeys.join(',') |
||||
|
}) |
||||
|
|
||||
|
if (response && response.code === 1) { |
||||
|
const newData = response.data || {} |
||||
|
|
||||
|
// 更新缓存
|
||||
|
if (useCache) { |
||||
|
this.updateCache(newData) |
||||
|
} |
||||
|
|
||||
|
// 合并缓存数据和新数据
|
||||
|
return { ...cachedData, ...newData } |
||||
|
} else { |
||||
|
console.warn('批量获取字典失败:', response?.msg || '未知错误') |
||||
|
return cachedData // 返回已缓存的数据
|
||||
|
} |
||||
|
} catch (requestError) { |
||||
|
console.warn('批量获取字典请求失败:', requestError) |
||||
|
return cachedData // 返回已缓存的数据
|
||||
|
} |
||||
|
} catch (error) { |
||||
|
console.error('批量获取字典异常:', error) |
||||
|
return {} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 根据业务场景获取字典数据 |
||||
|
* @param {String} scene 业务场景 |
||||
|
* @param {Boolean} useCache 是否使用缓存 |
||||
|
* @returns {Promise<Object>} 字典数据对象 |
||||
|
*/ |
||||
|
async getDictByScene(scene, useCache = true) { |
||||
|
if (!scene) { |
||||
|
console.warn('业务场景参数不能为空') |
||||
|
return {} |
||||
|
} |
||||
|
|
||||
|
try { |
||||
|
// 检查场景缓存
|
||||
|
const sceneCacheKey = `scene_${scene}` |
||||
|
if (useCache) { |
||||
|
const cache = this.getCache() |
||||
|
if (cache[sceneCacheKey] && this.isCacheValid(cache[sceneCacheKey])) { |
||||
|
return cache[sceneCacheKey].data |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
const response = await axiosQuiet.get(`/dict/scene/${scene}`) |
||||
|
|
||||
|
if (response && response.code === 1) { |
||||
|
const data = response.data || {} |
||||
|
|
||||
|
// 缓存场景数据
|
||||
|
if (useCache) { |
||||
|
const cacheData = {} |
||||
|
cacheData[sceneCacheKey] = { |
||||
|
data: data.data || {}, |
||||
|
timestamp: Date.now() |
||||
|
} |
||||
|
this.updateCache(cacheData) |
||||
|
} |
||||
|
|
||||
|
return data.data || {} |
||||
|
} else { |
||||
|
console.warn('根据场景获取字典失败:', response?.msg || '未知错误') |
||||
|
return {} |
||||
|
} |
||||
|
} catch (error) { |
||||
|
console.error('根据场景获取字典异常:', error) |
||||
|
return {} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 获取单个字典数据 |
||||
|
* @param {String} key 字典key |
||||
|
* @param {Boolean} useCache 是否使用缓存 |
||||
|
* @returns {Promise<Array>} 字典数据数组 |
||||
|
*/ |
||||
|
async getDict(key, useCache = true) { |
||||
|
if (!key) { |
||||
|
console.warn('字典key不能为空') |
||||
|
return [] |
||||
|
} |
||||
|
|
||||
|
const result = await this.getBatchDict([key], useCache) |
||||
|
return result[key] || [] |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 获取缓存数据 |
||||
|
* @returns {Object} 缓存对象 |
||||
|
*/ |
||||
|
getCache() { |
||||
|
try { |
||||
|
const cacheStr = uni.getStorageSync(this.cacheKey) |
||||
|
return cacheStr ? JSON.parse(cacheStr) : {} |
||||
|
} catch (error) { |
||||
|
console.error('获取字典缓存失败:', error) |
||||
|
return {} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 更新缓存 |
||||
|
* @param {Object} data 要缓存的数据 |
||||
|
*/ |
||||
|
updateCache(data) { |
||||
|
try { |
||||
|
const cache = this.getCache() |
||||
|
const timestamp = Date.now() |
||||
|
|
||||
|
// 更新缓存数据
|
||||
|
Object.keys(data).forEach(key => { |
||||
|
cache[key] = { |
||||
|
data: data[key], |
||||
|
timestamp: timestamp |
||||
|
} |
||||
|
}) |
||||
|
|
||||
|
uni.setStorageSync(this.cacheKey, JSON.stringify(cache)) |
||||
|
} catch (error) { |
||||
|
console.error('更新字典缓存失败:', error) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 检查缓存是否有效 |
||||
|
* @param {Object} cacheItem 缓存项 |
||||
|
* @returns {Boolean} 是否有效 |
||||
|
*/ |
||||
|
isCacheValid(cacheItem) { |
||||
|
if (!cacheItem || !cacheItem.timestamp) { |
||||
|
return false |
||||
|
} |
||||
|
return (Date.now() - cacheItem.timestamp) < this.cacheExpire |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 清除字典缓存 |
||||
|
* @param {Array} keys 要清除的字典key数组,为空则清除所有 |
||||
|
*/ |
||||
|
clearCache(keys = null) { |
||||
|
try { |
||||
|
if (keys === null) { |
||||
|
// 清除所有缓存
|
||||
|
uni.removeStorageSync(this.cacheKey) |
||||
|
} else if (Array.isArray(keys)) { |
||||
|
// 清除指定keys的缓存
|
||||
|
const cache = this.getCache() |
||||
|
keys.forEach(key => { |
||||
|
delete cache[key] |
||||
|
}) |
||||
|
uni.setStorageSync(this.cacheKey, JSON.stringify(cache)) |
||||
|
} |
||||
|
} catch (error) { |
||||
|
console.error('清除字典缓存失败:', error) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 获取字典映射关系 |
||||
|
* @returns {Promise<Object>} 映射关系对象 |
||||
|
*/ |
||||
|
async getDictMapping() { |
||||
|
try { |
||||
|
const response = await axiosQuiet.get('/dict/mapping') |
||||
|
|
||||
|
if (response && response.code === 1) { |
||||
|
return response.data || {} |
||||
|
} else { |
||||
|
console.warn('获取字典映射失败:', response?.msg || '未知错误') |
||||
|
return {} |
||||
|
} |
||||
|
} catch (error) { |
||||
|
console.error('获取字典映射异常:', error) |
||||
|
return {} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 将字典数据转换为选择器格式 |
||||
|
* @param {Array} dictData 字典数据 |
||||
|
* @returns {Array} 选择器格式数据 |
||||
|
*/ |
||||
|
formatForPicker(dictData) { |
||||
|
if (!Array.isArray(dictData)) { |
||||
|
return [] |
||||
|
} |
||||
|
|
||||
|
return dictData.map(item => ({ |
||||
|
text: item.name || item.text || '', |
||||
|
value: item.value || '', |
||||
|
sort: item.sort || 0, |
||||
|
memo: item.memo || '' |
||||
|
})) |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 根据value查找字典项的名称 |
||||
|
* @param {Array} dictData 字典数据 |
||||
|
* @param {String} value 要查找的值 |
||||
|
* @returns {String} 对应的名称 |
||||
|
*/ |
||||
|
getNameByValue(dictData, value) { |
||||
|
if (!Array.isArray(dictData)) { |
||||
|
return '' |
||||
|
} |
||||
|
|
||||
|
const item = dictData.find(item => String(item.value) === String(value)) |
||||
|
return item ? (item.name || item.text || '') : '' |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 预加载常用字典数据 |
||||
|
* @param {Array} keys 要预加载的字典keys |
||||
|
* @returns {Promise<void>} |
||||
|
*/ |
||||
|
async preloadDict(keys = []) { |
||||
|
if (!Array.isArray(keys) || keys.length === 0) { |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
try { |
||||
|
await this.getBatchDict(keys, true) |
||||
|
console.log('字典预加载完成:', keys) |
||||
|
} catch (error) { |
||||
|
console.error('字典预加载失败:', error) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 获取客户添加页面需要的字典数据 |
||||
|
* @returns {Promise<Object>} 字典数据对象 |
||||
|
*/ |
||||
|
async getCustomerAddDict() { |
||||
|
const keys = [ |
||||
|
'SourceChannel', 'source', 'customer_purchasing_power', |
||||
|
'cognitive_concept', 'decision_maker', 'preliminarycustomerintention', |
||||
|
'kh_status', 'distance' |
||||
|
] |
||||
|
return await this.getBatchDict(keys) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 创建单例实例
|
||||
|
const dictUtil = new DictUtil() |
||||
|
|
||||
|
// 扩展 util 对象的字典方法(兼容原有代码)
|
||||
|
if (typeof util !== 'undefined') { |
||||
|
// 保持原有的 getDict 方法兼容性
|
||||
|
const originalGetDict = util.getDict |
||||
|
util.getDict = async function(key) { |
||||
|
try { |
||||
|
// 优先使用新的字典工具
|
||||
|
const result = await dictUtil.getDict(key) |
||||
|
if (result && result.length > 0) { |
||||
|
return result |
||||
|
} |
||||
|
|
||||
|
// 如果新工具没有数据,回退到原方法
|
||||
|
if (originalGetDict && typeof originalGetDict === 'function') { |
||||
|
return await originalGetDict.call(this, key) |
||||
|
} |
||||
|
|
||||
|
return [] |
||||
|
} catch (error) { |
||||
|
console.error('获取字典失败:', error) |
||||
|
return [] |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 添加新的批量获取方法
|
||||
|
util.getBatchDict = dictUtil.getBatchDict.bind(dictUtil) |
||||
|
util.getDictByScene = dictUtil.getDictByScene.bind(dictUtil) |
||||
|
util.clearDictCache = dictUtil.clearCache.bind(dictUtil) |
||||
|
util.preloadDict = dictUtil.preloadDict.bind(dictUtil) |
||||
|
util.getCustomerAddDict = dictUtil.getCustomerAddDict.bind(dictUtil) |
||||
|
} |
||||
|
|
||||
|
export default dictUtil |
||||
@ -0,0 +1,187 @@ |
|||||
|
import axiosQuiet from './axiosQuiet.js' |
||||
|
|
||||
|
/** |
||||
|
* 简化版字典工具类 - 用于调试和测试 |
||||
|
*/ |
||||
|
class DictUtilSimple { |
||||
|
constructor() { |
||||
|
this.cacheKey = 'dict_cache_simple' |
||||
|
this.cacheExpire = 30 * 60 * 1000 // 30分钟过期
|
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 批量获取字典数据 |
||||
|
* @param {Array} keys 字典key数组 |
||||
|
* @param {Boolean} useCache 是否使用缓存 |
||||
|
* @returns {Promise<Object>} 字典数据对象 |
||||
|
*/ |
||||
|
async getBatchDict(keys = [], useCache = true) { |
||||
|
console.log('getBatchDict 调用参数:', { keys, useCache }) |
||||
|
|
||||
|
if (!Array.isArray(keys) || keys.length === 0) { |
||||
|
console.warn('字典keys参数必须是非空数组') |
||||
|
return {} |
||||
|
} |
||||
|
|
||||
|
try { |
||||
|
// 检查缓存
|
||||
|
let cachedData = {} |
||||
|
let uncachedKeys = [...keys] |
||||
|
|
||||
|
if (useCache) { |
||||
|
const cache = this.getCache() |
||||
|
uncachedKeys = keys.filter(key => { |
||||
|
if (cache[key] && this.isCacheValid(cache[key])) { |
||||
|
cachedData[key] = cache[key].data |
||||
|
return false |
||||
|
} |
||||
|
return true |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
console.log('缓存检查结果:', { cachedData, uncachedKeys }) |
||||
|
|
||||
|
// 如果所有数据都在缓存中,直接返回
|
||||
|
if (uncachedKeys.length === 0) { |
||||
|
console.log('所有数据来自缓存') |
||||
|
return cachedData |
||||
|
} |
||||
|
|
||||
|
// 请求未缓存的数据
|
||||
|
console.log('开始请求未缓存的数据:', uncachedKeys) |
||||
|
|
||||
|
const response = await axiosQuiet.get('/dict/batch', { |
||||
|
keys: uncachedKeys.join(',') |
||||
|
}) |
||||
|
|
||||
|
console.log('API响应:', response) |
||||
|
|
||||
|
if (response && response.code === 1) { |
||||
|
const newData = response.data || {} |
||||
|
console.log('获取到新数据:', newData) |
||||
|
|
||||
|
// 更新缓存
|
||||
|
if (useCache) { |
||||
|
this.updateCache(newData) |
||||
|
} |
||||
|
|
||||
|
// 合并缓存数据和新数据
|
||||
|
const result = { ...cachedData, ...newData } |
||||
|
console.log('最终结果:', result) |
||||
|
return result |
||||
|
} else { |
||||
|
console.warn('批量获取字典失败:', response?.msg || '未知错误') |
||||
|
return cachedData // 返回已缓存的数据
|
||||
|
} |
||||
|
} catch (error) { |
||||
|
console.error('批量获取字典异常:', error) |
||||
|
return {} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 获取单个字典数据 |
||||
|
* @param {String} key 字典key |
||||
|
* @param {Boolean} useCache 是否使用缓存 |
||||
|
* @returns {Promise<Array>} 字典数据数组 |
||||
|
*/ |
||||
|
async getDict(key, useCache = true) { |
||||
|
console.log('getDict 调用参数:', { key, useCache }) |
||||
|
|
||||
|
if (!key) { |
||||
|
console.warn('字典key不能为空') |
||||
|
return [] |
||||
|
} |
||||
|
|
||||
|
try { |
||||
|
const result = await this.getBatchDict([key], useCache) |
||||
|
return result[key] || [] |
||||
|
} catch (error) { |
||||
|
console.error('获取单个字典失败:', error) |
||||
|
return [] |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 获取缓存数据 |
||||
|
* @returns {Object} 缓存对象 |
||||
|
*/ |
||||
|
getCache() { |
||||
|
try { |
||||
|
const cacheStr = uni.getStorageSync(this.cacheKey) |
||||
|
const cache = cacheStr ? JSON.parse(cacheStr) : {} |
||||
|
console.log('获取缓存:', cache) |
||||
|
return cache |
||||
|
} catch (error) { |
||||
|
console.error('获取字典缓存失败:', error) |
||||
|
return {} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 更新缓存 |
||||
|
* @param {Object} data 要缓存的数据 |
||||
|
*/ |
||||
|
updateCache(data) { |
||||
|
try { |
||||
|
const cache = this.getCache() |
||||
|
const timestamp = Date.now() |
||||
|
|
||||
|
// 更新缓存数据
|
||||
|
Object.keys(data).forEach(key => { |
||||
|
cache[key] = { |
||||
|
data: data[key], |
||||
|
timestamp: timestamp |
||||
|
} |
||||
|
}) |
||||
|
|
||||
|
uni.setStorageSync(this.cacheKey, JSON.stringify(cache)) |
||||
|
console.log('缓存已更新:', cache) |
||||
|
} catch (error) { |
||||
|
console.error('更新字典缓存失败:', error) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 检查缓存是否有效 |
||||
|
* @param {Object} cacheItem 缓存项 |
||||
|
* @returns {Boolean} 是否有效 |
||||
|
*/ |
||||
|
isCacheValid(cacheItem) { |
||||
|
if (!cacheItem || !cacheItem.timestamp) { |
||||
|
return false |
||||
|
} |
||||
|
const isValid = (Date.now() - cacheItem.timestamp) < this.cacheExpire |
||||
|
console.log('缓存有效性检查:', { cacheItem, isValid }) |
||||
|
return isValid |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 清除字典缓存 |
||||
|
* @param {Array} keys 要清除的字典key数组,为空则清除所有 |
||||
|
*/ |
||||
|
clearCache(keys = null) { |
||||
|
try { |
||||
|
if (keys === null) { |
||||
|
// 清除所有缓存
|
||||
|
uni.removeStorageSync(this.cacheKey) |
||||
|
console.log('已清除所有字典缓存') |
||||
|
} else if (Array.isArray(keys)) { |
||||
|
// 清除指定keys的缓存
|
||||
|
const cache = this.getCache() |
||||
|
keys.forEach(key => { |
||||
|
delete cache[key] |
||||
|
}) |
||||
|
uni.setStorageSync(this.cacheKey, JSON.stringify(cache)) |
||||
|
console.log('已清除指定字典缓存:', keys) |
||||
|
} |
||||
|
} catch (error) { |
||||
|
console.error('清除字典缓存失败:', error) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 创建单例实例
|
||||
|
const dictUtilSimple = new DictUtilSimple() |
||||
|
|
||||
|
export default dictUtilSimple |
||||
@ -0,0 +1,283 @@ |
|||||
|
# 字典工具类使用说明 |
||||
|
|
||||
|
## 概述 |
||||
|
|
||||
|
`dictUtil.js` 是一个专门用于批量获取和缓存字典数据的工具类,解决了原有单个接口调用次数过多、用户体验不佳的问题。 |
||||
|
|
||||
|
## 主要功能 |
||||
|
|
||||
|
1. **批量获取字典数据** - 一次性获取多个字典,减少接口调用 |
||||
|
2. **智能缓存机制** - 自动缓存数据,避免重复请求 |
||||
|
3. **业务场景支持** - 根据业务场景批量获取相关字典 |
||||
|
4. **兼容性保障** - 保持与原有代码的兼容性 |
||||
|
|
||||
|
## API 接口 |
||||
|
|
||||
|
### 后端接口 |
||||
|
|
||||
|
```php |
||||
|
// 批量获取字典数据 |
||||
|
GET /api/dict/batch?keys=key1,key2,key3 |
||||
|
|
||||
|
// 根据业务场景获取字典 |
||||
|
GET /api/dict/scene/{scene} |
||||
|
|
||||
|
// 获取单个字典 |
||||
|
GET /api/dict/single/{key} |
||||
|
|
||||
|
// 获取字典映射关系 |
||||
|
GET /api/dict/mapping |
||||
|
|
||||
|
// 清除字典缓存 |
||||
|
POST /api/dict/clear_cache |
||||
|
``` |
||||
|
|
||||
|
## 前端使用方法 |
||||
|
|
||||
|
### 1. 导入工具类 |
||||
|
|
||||
|
```javascript |
||||
|
import dictUtil from '@/common/dictUtil.js' |
||||
|
``` |
||||
|
|
||||
|
### 2. 批量获取字典数据 |
||||
|
|
||||
|
```javascript |
||||
|
// 方法一:直接批量获取 |
||||
|
const dictKeys = ['SourceChannel', 'source', 'customer_purchasing_power'] |
||||
|
const dictData = await dictUtil.getBatchDict(dictKeys) |
||||
|
|
||||
|
// 结果格式: |
||||
|
// { |
||||
|
// 'SourceChannel': [ |
||||
|
// {name: '抖音', value: '1', sort: 0, memo: ''}, |
||||
|
// {name: '微信', value: '2', sort: 1, memo: ''} |
||||
|
// ], |
||||
|
// 'source': [...], |
||||
|
// 'customer_purchasing_power': [...] |
||||
|
// } |
||||
|
``` |
||||
|
|
||||
|
### 3. 根据业务场景获取 |
||||
|
|
||||
|
```javascript |
||||
|
// 获取客户添加场景的所有字典 |
||||
|
const dictData = await dictUtil.getDictByScene('customer_add') |
||||
|
|
||||
|
// 或者使用便捷方法 |
||||
|
const dictData = await dictUtil.getCustomerAddDict() |
||||
|
``` |
||||
|
|
||||
|
### 4. 单个字典获取 |
||||
|
|
||||
|
```javascript |
||||
|
// 获取单个字典(与原有 util.getDict 兼容) |
||||
|
const sourceData = await dictUtil.getDict('source') |
||||
|
``` |
||||
|
|
||||
|
### 5. 预加载字典数据 |
||||
|
|
||||
|
```javascript |
||||
|
onLoad() { |
||||
|
// 在页面加载时预加载,提升用户体验 |
||||
|
const dictKeys = ['SourceChannel', 'source', 'customer_purchasing_power'] |
||||
|
dictUtil.preloadDict(dictKeys) |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
### 6. 缓存管理 |
||||
|
|
||||
|
```javascript |
||||
|
// 清除指定字典缓存 |
||||
|
dictUtil.clearCache(['SourceChannel', 'source']) |
||||
|
|
||||
|
// 清除所有字典缓存 |
||||
|
dictUtil.clearCache() |
||||
|
``` |
||||
|
|
||||
|
## 在页面中的完整使用示例 |
||||
|
|
||||
|
### 原有方式(多次接口调用) |
||||
|
|
||||
|
```javascript |
||||
|
// 原有方式 - 问题:多次接口调用,用户体验差 |
||||
|
async init() { |
||||
|
await this.getDict('source_channel') |
||||
|
await this.getDict('source') |
||||
|
await this.getDict('purchasing_power') |
||||
|
await this.getDict('initial_intent') |
||||
|
await this.getDict('cognitive_idea') |
||||
|
await this.getDict('status') |
||||
|
await this.getDict('decision_maker') |
||||
|
await this.getDict('distance') |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
### 优化后的方式(批量获取) |
||||
|
|
||||
|
```javascript |
||||
|
// 优化方式 - 一次接口调用获取所有数据 |
||||
|
import dictUtil from '@/common/dictUtil.js' |
||||
|
|
||||
|
export default { |
||||
|
onLoad() { |
||||
|
// 预加载字典数据 |
||||
|
this.preloadDictData() |
||||
|
}, |
||||
|
|
||||
|
methods: { |
||||
|
// 预加载字典数据 |
||||
|
async preloadDictData() { |
||||
|
const dictKeys = [ |
||||
|
'SourceChannel', 'source', 'customer_purchasing_power', |
||||
|
'preliminarycustomerintention', 'cognitive_concept', |
||||
|
'kh_status', 'decision_maker', 'distance' |
||||
|
] |
||||
|
|
||||
|
// 静默预加载,不阻塞页面显示 |
||||
|
dictUtil.preloadDict(dictKeys).catch(error => { |
||||
|
console.warn('字典预加载失败:', error) |
||||
|
}) |
||||
|
}, |
||||
|
|
||||
|
// 批量获取字典数据 |
||||
|
async getBatchDictData() { |
||||
|
try { |
||||
|
uni.showLoading({ title: '加载字典数据...', mask: true }) |
||||
|
|
||||
|
const dictKeys = [ |
||||
|
'SourceChannel', // 来源渠道 |
||||
|
'source', // 来源 |
||||
|
'customer_purchasing_power', // 购买力 |
||||
|
'preliminarycustomerintention', // 客户初步意向度 |
||||
|
'cognitive_concept', // 认知理念 |
||||
|
'kh_status', // 客户状态 |
||||
|
'decision_maker', // 决策人 |
||||
|
'distance' // 距离 |
||||
|
] |
||||
|
|
||||
|
// 批量获取字典数据 |
||||
|
const dictData = await dictUtil.getBatchDict(dictKeys) |
||||
|
|
||||
|
// 处理字典数据 |
||||
|
this.processDictData(dictData) |
||||
|
|
||||
|
} catch (error) { |
||||
|
console.error('批量获取字典数据失败:', error) |
||||
|
// 如果批量获取失败,回退到单个获取 |
||||
|
await this.fallbackGetDict() |
||||
|
} finally { |
||||
|
uni.hideLoading() |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
// 处理批量获取的字典数据 |
||||
|
processDictData(dictData) { |
||||
|
const keyMapping = { |
||||
|
'SourceChannel': 'source_channel', |
||||
|
'source': 'source', |
||||
|
'customer_purchasing_power': 'purchasing_power', |
||||
|
'preliminarycustomerintention': 'initial_intent', |
||||
|
'cognitive_concept': 'cognitive_idea', |
||||
|
'kh_status': 'status', |
||||
|
'decision_maker': 'decision_maker', |
||||
|
'distance': 'distance' |
||||
|
} |
||||
|
|
||||
|
Object.keys(keyMapping).forEach(dictKey => { |
||||
|
const localKey = keyMapping[dictKey] |
||||
|
const dictItems = dictData[dictKey] || [] |
||||
|
|
||||
|
if (Array.isArray(dictItems) && dictItems.length > 0) { |
||||
|
const formattedOptions = dictItems.map(item => ({ |
||||
|
text: item.name || '', |
||||
|
value: item.value || '' |
||||
|
})) |
||||
|
|
||||
|
if (this.picker_config[localKey]) { |
||||
|
this.picker_config[localKey].options = formattedOptions |
||||
|
} |
||||
|
} |
||||
|
}) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
## 性能优化特性 |
||||
|
|
||||
|
### 1. 缓存机制 |
||||
|
- 自动缓存字典数据到本地存储 |
||||
|
- 缓存有效期 30 分钟 |
||||
|
- 避免重复请求相同数据 |
||||
|
|
||||
|
### 2. 批量请求 |
||||
|
- 一次接口调用获取多个字典 |
||||
|
- 减少网络请求次数 |
||||
|
- 提升页面加载速度 |
||||
|
|
||||
|
### 3. 预加载策略 |
||||
|
- 页面 onLoad 时预加载数据 |
||||
|
- 不阻塞页面显示 |
||||
|
- 用户操作时数据已就绪 |
||||
|
|
||||
|
### 4. 错误处理 |
||||
|
- 批量获取失败时自动回退到单个获取 |
||||
|
- 缓存获取失败时直接请求接口 |
||||
|
- 保证功能的健壮性 |
||||
|
|
||||
|
## 兼容性说明 |
||||
|
|
||||
|
### 向后兼容 |
||||
|
工具类保持与原有 `util.getDict()` 方法的完全兼容: |
||||
|
|
||||
|
```javascript |
||||
|
// 原有代码无需修改,自动使用新的缓存和批量获取机制 |
||||
|
const sourceData = await util.getDict('source') |
||||
|
|
||||
|
// 新增的批量获取方法 |
||||
|
const batchData = await util.getBatchDict(['source', 'status']) |
||||
|
``` |
||||
|
|
||||
|
### 渐进式升级 |
||||
|
可以逐步将页面迁移到新的批量获取方式: |
||||
|
|
||||
|
1. 先导入 `dictUtil` |
||||
|
2. 在 `onLoad` 中添加预加载 |
||||
|
3. 将 `init` 方法中的多个 `getDict` 调用替换为一次 `getBatchDict` 调用 |
||||
|
|
||||
|
## 注意事项 |
||||
|
|
||||
|
1. **字典 Key 映射**:确保后端字典 key 与前端使用的 key 一致 |
||||
|
2. **缓存清理**:如果字典数据有更新,记得清理对应缓存 |
||||
|
3. **错误处理**:在批量获取失败时,有回退机制保证功能正常 |
||||
|
4. **性能限制**:单次最多获取 20 个字典,防止接口性能问题 |
||||
|
|
||||
|
## 扩展功能 |
||||
|
|
||||
|
### 自定义业务场景 |
||||
|
|
||||
|
```javascript |
||||
|
// 在 DictService.php 中添加新的业务场景 |
||||
|
public function getDictKeysByScene(string $scene): array |
||||
|
{ |
||||
|
$sceneMapping = [ |
||||
|
'customer_add' => ['SourceChannel', 'source', '...'], |
||||
|
'personnel_add' => ['gender', 'education', '...'], |
||||
|
'your_scene' => ['key1', 'key2', '...'] // 添加自定义场景 |
||||
|
]; |
||||
|
|
||||
|
return $sceneMapping[$scene] ?? []; |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
### 添加新的工具方法 |
||||
|
|
||||
|
```javascript |
||||
|
// 在 dictUtil.js 中扩展 |
||||
|
dictUtil.getYourSceneDict = async function() { |
||||
|
return await this.getDictByScene('your_scene') |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
通过这种方式,你可以大幅提升字典数据获取的性能和用户体验。 |
||||
@ -0,0 +1,542 @@ |
|||||
|
<template> |
||||
|
<view class="container"> |
||||
|
<view class="header"> |
||||
|
<text class="title">字典获取优化演示</text> |
||||
|
<text class="subtitle">对比原方式和批量获取的性能差异</text> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 性能对比 --> |
||||
|
<view class="performance-section"> |
||||
|
<view class="section-title">性能对比</view> |
||||
|
|
||||
|
<!-- 原有方式 --> |
||||
|
<view class="test-item"> |
||||
|
<view class="test-header"> |
||||
|
<text class="test-name">原方式(单个获取)</text> |
||||
|
<button |
||||
|
class="test-btn old-style" |
||||
|
@click="testOldMethod" |
||||
|
:disabled="oldTesting"> |
||||
|
{{ oldTesting ? '测试中...' : '开始测试' }} |
||||
|
</button> |
||||
|
</view> |
||||
|
<view class="test-result" v-if="oldResult"> |
||||
|
<text class="result-text">耗时: {{ oldResult.time }}ms</text> |
||||
|
<text class="result-text">请求次数: {{ oldResult.requests }}次</text> |
||||
|
<text class="result-text">获取数据: {{ oldResult.count }}个字典</text> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 新方式 --> |
||||
|
<view class="test-item"> |
||||
|
<view class="test-header"> |
||||
|
<text class="test-name">新方式(批量获取)</text> |
||||
|
<button |
||||
|
class="test-btn new-style" |
||||
|
@click="testNewMethod" |
||||
|
:disabled="newTesting"> |
||||
|
{{ newTesting ? '测试中...' : '开始测试' }} |
||||
|
</button> |
||||
|
</view> |
||||
|
<view class="test-result" v-if="newResult"> |
||||
|
<text class="result-text">耗时: {{ newResult.time }}ms</text> |
||||
|
<text class="result-text">请求次数: {{ newResult.requests }}次</text> |
||||
|
<text class="result-text">获取数据: {{ newResult.count }}个字典</text> |
||||
|
<text class="improvement"> |
||||
|
性能提升: {{ newResult.improvement }} |
||||
|
</text> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 缓存演示 --> |
||||
|
<view class="cache-section"> |
||||
|
<view class="section-title">缓存机制演示</view> |
||||
|
|
||||
|
<view class="cache-controls"> |
||||
|
<button class="cache-btn" @click="testCache">测试缓存效果</button> |
||||
|
<button class="cache-btn clear" @click="clearCache">清除缓存</button> |
||||
|
</view> |
||||
|
|
||||
|
<view class="cache-result" v-if="cacheResult"> |
||||
|
<text class="cache-text">第一次获取: {{ cacheResult.firstTime }}ms</text> |
||||
|
<text class="cache-text">缓存获取: {{ cacheResult.cacheTime }}ms</text> |
||||
|
<text class="cache-text">性能提升: {{ cacheResult.improvement }}</text> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 字典数据展示 --> |
||||
|
<view class="data-section"> |
||||
|
<view class="section-title">字典数据展示</view> |
||||
|
|
||||
|
<view class="dict-tabs"> |
||||
|
<view |
||||
|
class="tab-item" |
||||
|
:class="{ active: activeTab === key }" |
||||
|
v-for="(data, key) in dictData" |
||||
|
:key="key" |
||||
|
@click="activeTab = key"> |
||||
|
{{ getDictDisplayName(key) }} |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<scroll-view class="dict-content" scroll-y> |
||||
|
<view class="dict-items" v-if="dictData[activeTab]"> |
||||
|
<view |
||||
|
class="dict-item" |
||||
|
v-for="(item, index) in dictData[activeTab]" |
||||
|
:key="index"> |
||||
|
<text class="item-name">{{ item.name || item.text || '-' }}</text> |
||||
|
<text class="item-value">{{ item.value || '-' }}</text> |
||||
|
</view> |
||||
|
</view> |
||||
|
<view class="empty-state" v-else> |
||||
|
<text>暂无数据</text> |
||||
|
</view> |
||||
|
</scroll-view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 使用说明 --> |
||||
|
<view class="usage-section"> |
||||
|
<view class="section-title">使用说明</view> |
||||
|
<view class="usage-content"> |
||||
|
<text class="usage-text">1. 使用 dictUtil.getBatchDict() 批量获取字典</text> |
||||
|
<text class="usage-text">2. 支持自动缓存,30分钟有效期</text> |
||||
|
<text class="usage-text">3. 支持业务场景批量获取</text> |
||||
|
<text class="usage-text">4. 向后兼容原有 util.getDict() 方法</text> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</template> |
||||
|
|
||||
|
<script> |
||||
|
import dictUtil from '@/common/dictUtil.js' |
||||
|
import util from '@/common/util.js' |
||||
|
|
||||
|
export default { |
||||
|
data() { |
||||
|
return { |
||||
|
// 测试状态 |
||||
|
oldTesting: false, |
||||
|
newTesting: false, |
||||
|
|
||||
|
// 测试结果 |
||||
|
oldResult: null, |
||||
|
newResult: null, |
||||
|
cacheResult: null, |
||||
|
|
||||
|
// 字典数据 |
||||
|
dictData: {}, |
||||
|
activeTab: '', |
||||
|
|
||||
|
// 测试用的字典keys |
||||
|
testKeys: [ |
||||
|
'SourceChannel', |
||||
|
'source', |
||||
|
'customer_purchasing_power', |
||||
|
'preliminarycustomerintention', |
||||
|
'cognitive_concept', |
||||
|
'kh_status', |
||||
|
'decision_maker', |
||||
|
'distance' |
||||
|
] |
||||
|
} |
||||
|
}, |
||||
|
onLoad() { |
||||
|
// 初始化时获取一次字典数据用于展示 |
||||
|
this.loadInitialData() |
||||
|
}, |
||||
|
methods: { |
||||
|
// 加载初始数据 |
||||
|
async loadInitialData() { |
||||
|
try { |
||||
|
const data = await dictUtil.getBatchDict(this.testKeys) |
||||
|
this.dictData = data |
||||
|
this.activeTab = Object.keys(data)[0] || '' |
||||
|
} catch (error) { |
||||
|
console.error('加载初始数据失败:', error) |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
// 测试原有方式(单个获取) |
||||
|
async testOldMethod() { |
||||
|
this.oldTesting = true |
||||
|
this.oldResult = null |
||||
|
|
||||
|
try { |
||||
|
const startTime = Date.now() |
||||
|
let successCount = 0 |
||||
|
|
||||
|
// 模拟原有的单个获取方式 |
||||
|
for (const key of this.testKeys) { |
||||
|
try { |
||||
|
await this.getOldDict(key) |
||||
|
successCount++ |
||||
|
} catch (error) { |
||||
|
console.warn(`获取字典 ${key} 失败:`, error) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
const endTime = Date.now() |
||||
|
const totalTime = endTime - startTime |
||||
|
|
||||
|
this.oldResult = { |
||||
|
time: totalTime, |
||||
|
requests: this.testKeys.length, |
||||
|
count: successCount |
||||
|
} |
||||
|
|
||||
|
uni.showToast({ |
||||
|
title: `原方式完成,耗时 ${totalTime}ms`, |
||||
|
icon: 'none' |
||||
|
}) |
||||
|
|
||||
|
} catch (error) { |
||||
|
console.error('测试原方式失败:', error) |
||||
|
uni.showToast({ |
||||
|
title: '测试失败', |
||||
|
icon: 'none' |
||||
|
}) |
||||
|
} finally { |
||||
|
this.oldTesting = false |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
// 测试新方式(批量获取) |
||||
|
async testNewMethod() { |
||||
|
this.newTesting = true |
||||
|
this.newResult = null |
||||
|
|
||||
|
try { |
||||
|
const startTime = Date.now() |
||||
|
|
||||
|
// 使用新的批量获取方式 |
||||
|
const data = await dictUtil.getBatchDict(this.testKeys, false) // 不使用缓存进行公平测试 |
||||
|
|
||||
|
const endTime = Date.now() |
||||
|
const totalTime = endTime - startTime |
||||
|
const successCount = Object.keys(data).length |
||||
|
|
||||
|
this.newResult = { |
||||
|
time: totalTime, |
||||
|
requests: 1, // 批量获取只需要1次请求 |
||||
|
count: successCount |
||||
|
} |
||||
|
|
||||
|
// 计算性能提升 |
||||
|
if (this.oldResult) { |
||||
|
const improvement = Math.round(((this.oldResult.time - totalTime) / this.oldResult.time) * 100) |
||||
|
this.newResult.improvement = `${improvement}%` |
||||
|
} |
||||
|
|
||||
|
// 更新展示数据 |
||||
|
this.dictData = data |
||||
|
|
||||
|
uni.showToast({ |
||||
|
title: `新方式完成,耗时 ${totalTime}ms`, |
||||
|
icon: 'none' |
||||
|
}) |
||||
|
|
||||
|
} catch (error) { |
||||
|
console.error('测试新方式失败:', error) |
||||
|
uni.showToast({ |
||||
|
title: '测试失败', |
||||
|
icon: 'none' |
||||
|
}) |
||||
|
} finally { |
||||
|
this.newTesting = false |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
// 模拟原有的单个字典获取 |
||||
|
async getOldDict(key) { |
||||
|
return new Promise((resolve, reject) => { |
||||
|
// 模拟单个接口请求的延迟 |
||||
|
setTimeout(async () => { |
||||
|
try { |
||||
|
// 这里可以调用原有的获取方法,或者模拟 |
||||
|
const result = await dictUtil.getDict(key, false) |
||||
|
resolve(result) |
||||
|
} catch (error) { |
||||
|
reject(error) |
||||
|
} |
||||
|
}, Math.random() * 200 + 100) // 模拟 100-300ms 的网络延迟 |
||||
|
}) |
||||
|
}, |
||||
|
|
||||
|
// 测试缓存效果 |
||||
|
async testCache() { |
||||
|
try { |
||||
|
// 清除缓存确保公平测试 |
||||
|
dictUtil.clearCache() |
||||
|
|
||||
|
// 第一次获取(无缓存) |
||||
|
const startTime1 = Date.now() |
||||
|
await dictUtil.getBatchDict(this.testKeys.slice(0, 3), true) |
||||
|
const firstTime = Date.now() - startTime1 |
||||
|
|
||||
|
// 第二次获取(有缓存) |
||||
|
const startTime2 = Date.now() |
||||
|
await dictUtil.getBatchDict(this.testKeys.slice(0, 3), true) |
||||
|
const cacheTime = Date.now() - startTime2 |
||||
|
|
||||
|
const improvement = Math.round(((firstTime - cacheTime) / firstTime) * 100) |
||||
|
|
||||
|
this.cacheResult = { |
||||
|
firstTime, |
||||
|
cacheTime, |
||||
|
improvement: `${improvement}%` |
||||
|
} |
||||
|
|
||||
|
uni.showToast({ |
||||
|
title: '缓存测试完成', |
||||
|
icon: 'success' |
||||
|
}) |
||||
|
|
||||
|
} catch (error) { |
||||
|
console.error('缓存测试失败:', error) |
||||
|
uni.showToast({ |
||||
|
title: '缓存测试失败', |
||||
|
icon: 'none' |
||||
|
}) |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
// 清除缓存 |
||||
|
clearCache() { |
||||
|
dictUtil.clearCache() |
||||
|
this.cacheResult = null |
||||
|
uni.showToast({ |
||||
|
title: '缓存已清除', |
||||
|
icon: 'success' |
||||
|
}) |
||||
|
}, |
||||
|
|
||||
|
// 获取字典显示名称 |
||||
|
getDictDisplayName(key) { |
||||
|
const nameMap = { |
||||
|
'SourceChannel': '来源渠道', |
||||
|
'source': '来源', |
||||
|
'customer_purchasing_power': '购买力', |
||||
|
'preliminarycustomerintention': '意向度', |
||||
|
'cognitive_concept': '认知理念', |
||||
|
'kh_status': '客户状态', |
||||
|
'decision_maker': '决策人', |
||||
|
'distance': '距离' |
||||
|
} |
||||
|
return nameMap[key] || key |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<style lang="scss" scoped> |
||||
|
.container { |
||||
|
padding: 20rpx; |
||||
|
background: #f5f5f5; |
||||
|
min-height: 100vh; |
||||
|
} |
||||
|
|
||||
|
.header { |
||||
|
background: #fff; |
||||
|
padding: 30rpx; |
||||
|
border-radius: 12rpx; |
||||
|
margin-bottom: 20rpx; |
||||
|
text-align: center; |
||||
|
|
||||
|
.title { |
||||
|
font-size: 36rpx; |
||||
|
font-weight: 600; |
||||
|
color: #333; |
||||
|
display: block; |
||||
|
margin-bottom: 10rpx; |
||||
|
} |
||||
|
|
||||
|
.subtitle { |
||||
|
font-size: 26rpx; |
||||
|
color: #666; |
||||
|
display: block; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.performance-section, .cache-section, .data-section, .usage-section { |
||||
|
background: #fff; |
||||
|
border-radius: 12rpx; |
||||
|
padding: 30rpx; |
||||
|
margin-bottom: 20rpx; |
||||
|
} |
||||
|
|
||||
|
.section-title { |
||||
|
font-size: 32rpx; |
||||
|
font-weight: 600; |
||||
|
color: #333; |
||||
|
margin-bottom: 20rpx; |
||||
|
border-left: 4rpx solid #29d3b4; |
||||
|
padding-left: 16rpx; |
||||
|
} |
||||
|
|
||||
|
.test-item { |
||||
|
margin-bottom: 30rpx; |
||||
|
padding: 20rpx; |
||||
|
background: #f8f9fa; |
||||
|
border-radius: 8rpx; |
||||
|
|
||||
|
&:last-child { |
||||
|
margin-bottom: 0; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.test-header { |
||||
|
display: flex; |
||||
|
justify-content: space-between; |
||||
|
align-items: center; |
||||
|
margin-bottom: 15rpx; |
||||
|
|
||||
|
.test-name { |
||||
|
font-size: 28rpx; |
||||
|
font-weight: 500; |
||||
|
color: #333; |
||||
|
} |
||||
|
|
||||
|
.test-btn { |
||||
|
padding: 12rpx 24rpx; |
||||
|
border-radius: 6rpx; |
||||
|
border: none; |
||||
|
font-size: 24rpx; |
||||
|
color: #fff; |
||||
|
|
||||
|
&.old-style { |
||||
|
background: #ff6b6b; |
||||
|
} |
||||
|
|
||||
|
&.new-style { |
||||
|
background: #29d3b4; |
||||
|
} |
||||
|
|
||||
|
&:disabled { |
||||
|
opacity: 0.6; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.test-result { |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
gap: 8rpx; |
||||
|
|
||||
|
.result-text { |
||||
|
font-size: 24rpx; |
||||
|
color: #666; |
||||
|
} |
||||
|
|
||||
|
.improvement { |
||||
|
font-size: 26rpx; |
||||
|
color: #29d3b4; |
||||
|
font-weight: 600; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.cache-controls { |
||||
|
display: flex; |
||||
|
gap: 20rpx; |
||||
|
margin-bottom: 20rpx; |
||||
|
|
||||
|
.cache-btn { |
||||
|
flex: 1; |
||||
|
padding: 16rpx; |
||||
|
border-radius: 8rpx; |
||||
|
border: none; |
||||
|
font-size: 26rpx; |
||||
|
color: #fff; |
||||
|
background: #29d3b4; |
||||
|
|
||||
|
&.clear { |
||||
|
background: #ff6b6b; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.cache-result { |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
gap: 8rpx; |
||||
|
|
||||
|
.cache-text { |
||||
|
font-size: 24rpx; |
||||
|
color: #666; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.dict-tabs { |
||||
|
display: flex; |
||||
|
flex-wrap: wrap; |
||||
|
gap: 10rpx; |
||||
|
margin-bottom: 20rpx; |
||||
|
|
||||
|
.tab-item { |
||||
|
padding: 12rpx 20rpx; |
||||
|
background: #f0f0f0; |
||||
|
border-radius: 20rpx; |
||||
|
font-size: 24rpx; |
||||
|
color: #666; |
||||
|
|
||||
|
&.active { |
||||
|
background: #29d3b4; |
||||
|
color: #fff; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.dict-content { |
||||
|
height: 400rpx; |
||||
|
border: 1rpx solid #eee; |
||||
|
border-radius: 8rpx; |
||||
|
} |
||||
|
|
||||
|
.dict-items { |
||||
|
padding: 20rpx; |
||||
|
} |
||||
|
|
||||
|
.dict-item { |
||||
|
display: flex; |
||||
|
justify-content: space-between; |
||||
|
align-items: center; |
||||
|
padding: 16rpx 0; |
||||
|
border-bottom: 1rpx solid #f0f0f0; |
||||
|
|
||||
|
&:last-child { |
||||
|
border-bottom: none; |
||||
|
} |
||||
|
|
||||
|
.item-name { |
||||
|
font-size: 26rpx; |
||||
|
color: #333; |
||||
|
flex: 1; |
||||
|
} |
||||
|
|
||||
|
.item-value { |
||||
|
font-size: 24rpx; |
||||
|
color: #666; |
||||
|
margin-left: 20rpx; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.empty-state { |
||||
|
padding: 60rpx; |
||||
|
text-align: center; |
||||
|
color: #999; |
||||
|
font-size: 26rpx; |
||||
|
} |
||||
|
|
||||
|
.usage-content { |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
gap: 12rpx; |
||||
|
|
||||
|
.usage-text { |
||||
|
font-size: 26rpx; |
||||
|
color: #666; |
||||
|
line-height: 1.6; |
||||
|
} |
||||
|
} |
||||
|
</style> |
||||
File diff suppressed because it is too large
@ -0,0 +1,263 @@ |
|||||
|
<template> |
||||
|
<view class="container"> |
||||
|
<view class="header"> |
||||
|
<text class="title">字典功能测试</text> |
||||
|
</view> |
||||
|
|
||||
|
<view class="test-section"> |
||||
|
<view class="section-title">1. 测试单个字典获取</view> |
||||
|
<button class="test-btn" @click="testSingleDict">测试获取单个字典</button> |
||||
|
<view class="result" v-if="singleResult"> |
||||
|
<text class="result-title">结果:</text> |
||||
|
<text class="result-content">{{ JSON.stringify(singleResult, null, 2) }}</text> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<view class="test-section"> |
||||
|
<view class="section-title">2. 测试批量字典获取</view> |
||||
|
<button class="test-btn" @click="testBatchDict">测试批量获取字典</button> |
||||
|
<view class="result" v-if="batchResult"> |
||||
|
<text class="result-title">结果:</text> |
||||
|
<text class="result-content">{{ JSON.stringify(batchResult, null, 2) }}</text> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<view class="test-section"> |
||||
|
<view class="section-title">3. 测试字典缓存</view> |
||||
|
<button class="test-btn" @click="testCache">测试缓存机制</button> |
||||
|
<view class="result" v-if="cacheResult"> |
||||
|
<text class="result-title">缓存测试结果:</text> |
||||
|
<text class="result-content">{{ cacheResult }}</text> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<view class="test-section"> |
||||
|
<view class="section-title">4. 测试静默请求</view> |
||||
|
<button class="test-btn" @click="testQuietRequest">测试静默请求</button> |
||||
|
<view class="result" v-if="quietResult"> |
||||
|
<text class="result-title">静默请求结果:</text> |
||||
|
<text class="result-content">{{ JSON.stringify(quietResult, null, 2) }}</text> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<view class="test-section"> |
||||
|
<view class="section-title">5. 清除缓存</view> |
||||
|
<button class="test-btn clear" @click="clearAllCache">清除所有缓存</button> |
||||
|
</view> |
||||
|
</view> |
||||
|
</template> |
||||
|
|
||||
|
<script> |
||||
|
import dictUtil from '@/common/dictUtil.js' |
||||
|
import axiosQuiet from '@/common/axiosQuiet.js' |
||||
|
|
||||
|
export default { |
||||
|
data() { |
||||
|
return { |
||||
|
singleResult: null, |
||||
|
batchResult: null, |
||||
|
cacheResult: null, |
||||
|
quietResult: null |
||||
|
} |
||||
|
}, |
||||
|
methods: { |
||||
|
// 测试单个字典获取 |
||||
|
async testSingleDict() { |
||||
|
try { |
||||
|
console.log('开始测试单个字典获取') |
||||
|
const result = await dictUtil.getDict('source') |
||||
|
this.singleResult = result |
||||
|
console.log('单个字典获取结果:', result) |
||||
|
|
||||
|
uni.showToast({ |
||||
|
title: '单个字典测试完成', |
||||
|
icon: 'success' |
||||
|
}) |
||||
|
} catch (error) { |
||||
|
console.error('单个字典获取失败:', error) |
||||
|
this.singleResult = { error: error.message || '获取失败' } |
||||
|
|
||||
|
uni.showToast({ |
||||
|
title: '单个字典测试失败', |
||||
|
icon: 'none' |
||||
|
}) |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
// 测试批量字典获取 |
||||
|
async testBatchDict() { |
||||
|
try { |
||||
|
console.log('开始测试批量字典获取') |
||||
|
const keys = ['source', 'SourceChannel', 'customer_purchasing_power'] |
||||
|
const result = await dictUtil.getBatchDict(keys) |
||||
|
this.batchResult = result |
||||
|
console.log('批量字典获取结果:', result) |
||||
|
|
||||
|
uni.showToast({ |
||||
|
title: '批量字典测试完成', |
||||
|
icon: 'success' |
||||
|
}) |
||||
|
} catch (error) { |
||||
|
console.error('批量字典获取失败:', error) |
||||
|
this.batchResult = { error: error.message || '获取失败' } |
||||
|
|
||||
|
uni.showToast({ |
||||
|
title: '批量字典测试失败', |
||||
|
icon: 'none' |
||||
|
}) |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
// 测试缓存机制 |
||||
|
async testCache() { |
||||
|
try { |
||||
|
console.log('开始测试缓存机制') |
||||
|
|
||||
|
// 清除缓存 |
||||
|
dictUtil.clearCache(['source']) |
||||
|
|
||||
|
// 第一次获取(应该从接口获取) |
||||
|
const start1 = Date.now() |
||||
|
await dictUtil.getDict('source', true) |
||||
|
const time1 = Date.now() - start1 |
||||
|
|
||||
|
// 第二次获取(应该从缓存获取) |
||||
|
const start2 = Date.now() |
||||
|
await dictUtil.getDict('source', true) |
||||
|
const time2 = Date.now() - start2 |
||||
|
|
||||
|
this.cacheResult = `第一次获取: ${time1}ms, 第二次获取: ${time2}ms, 缓存提升: ${Math.round((time1 - time2) / time1 * 100)}%` |
||||
|
|
||||
|
uni.showToast({ |
||||
|
title: '缓存测试完成', |
||||
|
icon: 'success' |
||||
|
}) |
||||
|
} catch (error) { |
||||
|
console.error('缓存测试失败:', error) |
||||
|
this.cacheResult = '缓存测试失败: ' + (error.message || '未知错误') |
||||
|
|
||||
|
uni.showToast({ |
||||
|
title: '缓存测试失败', |
||||
|
icon: 'none' |
||||
|
}) |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
// 测试静默请求 |
||||
|
async testQuietRequest() { |
||||
|
try { |
||||
|
console.log('开始测试静默请求') |
||||
|
const result = await axiosQuiet.get('/dict/batch', { |
||||
|
keys: 'source,SourceChannel' |
||||
|
}) |
||||
|
this.quietResult = result |
||||
|
console.log('静默请求结果:', result) |
||||
|
|
||||
|
uni.showToast({ |
||||
|
title: '静默请求测试完成', |
||||
|
icon: 'success' |
||||
|
}) |
||||
|
} catch (error) { |
||||
|
console.error('静默请求失败:', error) |
||||
|
this.quietResult = { error: error.message || error.msg || '请求失败' } |
||||
|
|
||||
|
uni.showToast({ |
||||
|
title: '静默请求测试失败', |
||||
|
icon: 'none' |
||||
|
}) |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
// 清除所有缓存 |
||||
|
clearAllCache() { |
||||
|
dictUtil.clearCache() |
||||
|
this.singleResult = null |
||||
|
this.batchResult = null |
||||
|
this.cacheResult = null |
||||
|
this.quietResult = null |
||||
|
|
||||
|
uni.showToast({ |
||||
|
title: '缓存已清除', |
||||
|
icon: 'success' |
||||
|
}) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<style lang="scss" scoped> |
||||
|
.container { |
||||
|
padding: 20rpx; |
||||
|
background: #f5f5f5; |
||||
|
min-height: 100vh; |
||||
|
} |
||||
|
|
||||
|
.header { |
||||
|
background: #fff; |
||||
|
padding: 30rpx; |
||||
|
border-radius: 12rpx; |
||||
|
margin-bottom: 20rpx; |
||||
|
text-align: center; |
||||
|
|
||||
|
.title { |
||||
|
font-size: 36rpx; |
||||
|
font-weight: 600; |
||||
|
color: #333; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.test-section { |
||||
|
background: #fff; |
||||
|
border-radius: 12rpx; |
||||
|
padding: 30rpx; |
||||
|
margin-bottom: 20rpx; |
||||
|
} |
||||
|
|
||||
|
.section-title { |
||||
|
font-size: 28rpx; |
||||
|
font-weight: 600; |
||||
|
color: #333; |
||||
|
margin-bottom: 20rpx; |
||||
|
} |
||||
|
|
||||
|
.test-btn { |
||||
|
background: #29d3b4; |
||||
|
color: #fff; |
||||
|
border: none; |
||||
|
border-radius: 8rpx; |
||||
|
padding: 16rpx 32rpx; |
||||
|
font-size: 26rpx; |
||||
|
margin-bottom: 20rpx; |
||||
|
|
||||
|
&.clear { |
||||
|
background: #ff6b6b; |
||||
|
} |
||||
|
|
||||
|
&:active { |
||||
|
opacity: 0.8; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.result { |
||||
|
background: #f8f9fa; |
||||
|
border-radius: 8rpx; |
||||
|
padding: 20rpx; |
||||
|
border-left: 4rpx solid #29d3b4; |
||||
|
|
||||
|
.result-title { |
||||
|
font-size: 24rpx; |
||||
|
color: #666; |
||||
|
display: block; |
||||
|
margin-bottom: 10rpx; |
||||
|
} |
||||
|
|
||||
|
.result-content { |
||||
|
font-size: 22rpx; |
||||
|
color: #333; |
||||
|
word-break: break-all; |
||||
|
white-space: pre-wrap; |
||||
|
font-family: monospace; |
||||
|
line-height: 1.5; |
||||
|
} |
||||
|
} |
||||
|
</style> |
||||
Loading…
Reference in new issue