新增功能
This commit is contained in:
@@ -0,0 +1,46 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
// 粉丝列表
|
||||
export function fanLists(params: any) {
|
||||
return request.get({ url: '/fan/lists', params })
|
||||
}
|
||||
|
||||
// 添加粉丝
|
||||
export function fanAdd(params: any) {
|
||||
return request.post({ url: '/fan/add', params })
|
||||
}
|
||||
|
||||
// 编辑粉丝
|
||||
export function fanEdit(params: any) {
|
||||
return request.post({ url: '/fan/edit', params })
|
||||
}
|
||||
|
||||
// 删除粉丝
|
||||
export function fanDelete(params: any) {
|
||||
return request.post({ url: '/fan/delete', params })
|
||||
}
|
||||
|
||||
// 粉丝详情
|
||||
export function fanDetail(params: any) {
|
||||
return request.get({ url: '/fan/detail', params })
|
||||
}
|
||||
|
||||
// 回访记录列表
|
||||
export function fanVisitRecordLists(params: any) {
|
||||
return request.get({ url: '/fan/visitRecordLists', params })
|
||||
}
|
||||
|
||||
// 添加回访记录
|
||||
export function fanVisitRecordAdd(params: any) {
|
||||
return request.post({ url: '/fan/addVisitRecord', params })
|
||||
}
|
||||
|
||||
// 编辑回访记录
|
||||
export function fanVisitRecordEdit(params: any) {
|
||||
return request.post({ url: '/fan/editVisitRecord', params })
|
||||
}
|
||||
|
||||
// 删除回访记录
|
||||
export function fanVisitRecordDelete(params: any) {
|
||||
return request.post({ url: '/fan/deleteVisitRecord', params })
|
||||
}
|
||||
@@ -105,3 +105,35 @@ export function getCallRecords(params: any) {
|
||||
export function generateMiniProgramQrcode(params: any) {
|
||||
return request.post({ url: '/tcm.diagnosis/generateMiniProgramQrcode', params })
|
||||
}
|
||||
|
||||
// 生成订单小程序码
|
||||
export function generateOrderQrcode(params: any) {
|
||||
return request.post({ url: '/tcm.diagnosis/generateOrderQrcode', params })
|
||||
}
|
||||
|
||||
// ========== 企业微信聊天记录 ==========
|
||||
|
||||
// 获取企业微信聊天记录
|
||||
export function getWechatChatRecords(params: any) {
|
||||
return request.get({ url: '/tcm.diagnosis/getWechatChatRecords', params })
|
||||
}
|
||||
|
||||
// 添加企业微信聊天记录
|
||||
export function addWechatChatRecord(params: any) {
|
||||
return request.post({ url: '/tcm.diagnosis/addWechatChatRecord', params })
|
||||
}
|
||||
|
||||
// 删除企业微信聊天记录
|
||||
export function deleteWechatChatRecord(params: any) {
|
||||
return request.post({ url: '/tcm.diagnosis/deleteWechatChatRecord', params })
|
||||
}
|
||||
|
||||
// 获取企业微信外部联系人信息
|
||||
export function getWechatExternalContact(params: any) {
|
||||
return request.get({ url: '/tcm.diagnosis/getWechatExternalContact', params })
|
||||
}
|
||||
|
||||
// 获取会话内容存档开启的成员列表
|
||||
export function getMsgAuditPermitUsers() {
|
||||
return request.get({ url: '/tcm.diagnosis/getMsgAuditPermitUsers' })
|
||||
}
|
||||
|
||||
@@ -52,6 +52,14 @@ export const constantRoutes: Array<RouteRecordRaw> = [
|
||||
{
|
||||
path: '/decoration/pc_details',
|
||||
component: () => import('@/views/decoration/pc_details.vue')
|
||||
},
|
||||
{
|
||||
path: '/fans/h5',
|
||||
component: () => import('@/views/fans/h5.vue')
|
||||
},
|
||||
{
|
||||
path: '/tcm/diagnosis/h5',
|
||||
component: () => import('@/views/tcm/diagnosis/index_h5.vue')
|
||||
}
|
||||
// {
|
||||
// path: '/dev_tools',
|
||||
|
||||
@@ -0,0 +1,770 @@
|
||||
<template>
|
||||
<div class="fans-h5">
|
||||
<!-- 顶部搜索 -->
|
||||
<div class="h5-header">
|
||||
<div class="h5-search">
|
||||
<el-input
|
||||
v-model="keyword"
|
||||
placeholder="搜索姓名 / 手机号"
|
||||
clearable
|
||||
prefix-icon="Search"
|
||||
@keyup.enter="handleSearch"
|
||||
@clear="handleSearch"
|
||||
/>
|
||||
</div>
|
||||
<div class="h5-filter-row">
|
||||
<el-select v-model="formData.gender" placeholder="性别" clearable size="small" @change="handleSearch">
|
||||
<el-option label="男" :value="1" />
|
||||
<el-option label="女" :value="2" />
|
||||
</el-select>
|
||||
<el-select v-model="formData.status" placeholder="状态" clearable size="small" @change="handleSearch">
|
||||
<el-option label="启用" :value="1" />
|
||||
<el-option label="禁用" :value="0" />
|
||||
</el-select>
|
||||
<el-button type="primary" size="small" @click="handleSearch">查询</el-button>
|
||||
<el-button size="small" @click="handleReset">重置</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 粉丝卡片列表 -->
|
||||
<div class="h5-list" v-loading="listLoading">
|
||||
<div v-if="fanList.length === 0 && !listLoading" class="h5-empty">
|
||||
<el-empty description="暂无粉丝数据" />
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-for="item in fanList"
|
||||
:key="item.id"
|
||||
class="h5-card"
|
||||
>
|
||||
<div class="h5-card-header">
|
||||
<div class="h5-card-name">
|
||||
<span class="name">{{ item.name }}</span>
|
||||
<el-tag size="small" :type="item.gender === 1 ? '' : item.gender === 2 ? 'danger' : 'info'" effect="plain">
|
||||
{{ item.gender_desc }}
|
||||
</el-tag>
|
||||
<span v-if="item.age" class="age">{{ item.age }}岁</span>
|
||||
</div>
|
||||
<el-tag size="small" :type="item.status == 1 ? 'success' : 'danger'">
|
||||
{{ item.status == 1 ? '启用' : '禁用' }}
|
||||
</el-tag>
|
||||
</div>
|
||||
|
||||
<div class="h5-card-body">
|
||||
<div class="info-line">
|
||||
<span class="label">手机</span>
|
||||
<span class="value">{{ item.phone || '-' }}</span>
|
||||
</div>
|
||||
<div class="info-line">
|
||||
<span class="label">身份证</span>
|
||||
<span class="value">{{ item.id_card || '-' }}</span>
|
||||
</div>
|
||||
<div v-if="item.remark" class="info-line">
|
||||
<span class="label">备注</span>
|
||||
<span class="value remark">{{ item.remark }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="h5-card-footer">
|
||||
<div class="footer-left">
|
||||
<div class="visit-info">
|
||||
回访 <span class="visit-count">{{ item.visit_count || 0 }}</span> 次
|
||||
</div>
|
||||
<div v-if="item.creator_name" class="creator-info">
|
||||
登记人:{{ item.creator_name }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="actions">
|
||||
<el-button type="primary" text size="small" @click="handleEdit(item)">编辑</el-button>
|
||||
<el-button type="success" text size="small" @click="handleVisitRecords(item)">回访</el-button>
|
||||
<el-button type="danger" text size="small" @click="handleDelete(item)">删除</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 加载更多 -->
|
||||
<div v-if="hasMore" class="h5-load-more" @click="loadMore">
|
||||
<span v-if="!moreLoading">加载更多</span>
|
||||
<span v-else>加载中...</span>
|
||||
</div>
|
||||
<div v-if="!hasMore && fanList.length > 0" class="h5-no-more">没有更多了</div>
|
||||
</div>
|
||||
|
||||
<!-- 悬浮新增按钮 -->
|
||||
<div class="h5-fab" @click="handleAdd">
|
||||
<el-icon :size="24"><Plus /></el-icon>
|
||||
</div>
|
||||
|
||||
<!-- 新增/编辑粉丝 -->
|
||||
<el-drawer
|
||||
v-model="fanDrawerVisible"
|
||||
:title="fanDialogType === 'add' ? '新增粉丝' : '编辑粉丝'"
|
||||
direction="btt"
|
||||
size="85%"
|
||||
:close-on-click-modal="false"
|
||||
@closed="resetFanForm"
|
||||
>
|
||||
<el-form
|
||||
ref="fanFormRef"
|
||||
:model="fanForm"
|
||||
:rules="fanRules"
|
||||
label-position="top"
|
||||
class="h5-form"
|
||||
>
|
||||
<el-form-item label="姓名" prop="name">
|
||||
<el-input v-model="fanForm.name" placeholder="请输入姓名" maxlength="50" />
|
||||
</el-form-item>
|
||||
<el-form-item label="手机号" prop="phone">
|
||||
<el-input v-model="fanForm.phone" placeholder="请输入手机号" maxlength="11" />
|
||||
</el-form-item>
|
||||
<el-form-item label="身份证号" prop="id_card">
|
||||
<el-input v-model="fanForm.id_card" placeholder="请输入身份证号码" maxlength="18" />
|
||||
</el-form-item>
|
||||
<div class="h5-form-row">
|
||||
<el-form-item label="年龄" prop="age" class="flex-1">
|
||||
<el-input-number v-model="fanForm.age" :min="0" :max="150" controls-position="right" class="!w-full" />
|
||||
</el-form-item>
|
||||
<el-form-item label="性别" prop="gender" class="flex-1">
|
||||
<el-radio-group v-model="fanForm.gender">
|
||||
<el-radio :label="1">男</el-radio>
|
||||
<el-radio :label="2">女</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</div>
|
||||
<el-form-item label="状态">
|
||||
<el-switch v-model="fanForm.status" :active-value="1" :inactive-value="0" active-text="启用" inactive-text="禁用" />
|
||||
</el-form-item>
|
||||
<el-form-item label="备注">
|
||||
<el-input v-model="fanForm.remark" type="textarea" :rows="3" placeholder="请输入备注" maxlength="500" show-word-limit />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button class="flex-1" @click="fanDrawerVisible = false">取消</el-button>
|
||||
<el-button class="flex-1" type="primary" :loading="fanSubmitLoading" @click="handleFanSubmit">确定</el-button>
|
||||
</template>
|
||||
</el-drawer>
|
||||
|
||||
<!-- 回访记录 -->
|
||||
<el-drawer
|
||||
v-model="visitDrawerVisible"
|
||||
:title="`回访记录 - ${currentFan?.name || ''}`"
|
||||
direction="rtl"
|
||||
size="100%"
|
||||
:close-on-click-modal="false"
|
||||
>
|
||||
<div class="h5-visit-header">
|
||||
<el-button type="primary" size="small" @click="handleAddVisitRecord">+ 新增回访</el-button>
|
||||
</div>
|
||||
|
||||
<div class="h5-visit-list" v-loading="visitLoading">
|
||||
<div v-if="visitRecords.length === 0 && !visitLoading" class="h5-empty">
|
||||
<el-empty description="暂无回访记录" :image-size="80" />
|
||||
</div>
|
||||
|
||||
<div v-for="item in visitRecords" :key="item.id" class="h5-visit-card">
|
||||
<div class="visit-card-header">
|
||||
<div class="visit-card-tags">
|
||||
<el-tag size="small" :type="visitTypeTagMap[item.visit_type]">{{ item.visit_type_desc }}</el-tag>
|
||||
<span class="visit-time">{{ item.visit_time_text }}</span>
|
||||
</div>
|
||||
<div class="visit-card-actions">
|
||||
<el-button type="primary" text size="small" @click="handleEditVisitRecord(item)">编辑</el-button>
|
||||
<el-button type="danger" text size="small" @click="handleDeleteVisitRecord(item.id)">删除</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="visit-card-content">{{ item.content }}</div>
|
||||
<div v-if="item.result" class="visit-card-result">
|
||||
<span class="label">结果:</span>{{ item.result }}
|
||||
</div>
|
||||
<div class="visit-card-meta">
|
||||
<span v-if="item.next_visit_time_text" class="next-visit">下次回访:{{ item.next_visit_time_text }}</span>
|
||||
<span v-if="item.operator_name" class="operator">{{ item.operator_name }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="visitTotal > visitRecords.length" class="h5-load-more" @click="loadMoreVisitRecords">
|
||||
加载更多
|
||||
</div>
|
||||
</div>
|
||||
</el-drawer>
|
||||
|
||||
<!-- 新增/编辑回访记录 -->
|
||||
<el-drawer
|
||||
v-model="visitFormDrawerVisible"
|
||||
:title="visitDialogType === 'add' ? '新增回访记录' : '编辑回访记录'"
|
||||
direction="btt"
|
||||
size="80%"
|
||||
:close-on-click-modal="false"
|
||||
@closed="resetVisitForm"
|
||||
>
|
||||
<el-form
|
||||
ref="visitFormRef"
|
||||
:model="visitForm"
|
||||
:rules="visitRules"
|
||||
label-position="top"
|
||||
class="h5-form"
|
||||
>
|
||||
<el-form-item label="回访类型" prop="visit_type">
|
||||
<el-radio-group v-model="visitForm.visit_type" class="h5-radio-group">
|
||||
<el-radio-button :label="1">电话</el-radio-button>
|
||||
<el-radio-button :label="2">微信</el-radio-button>
|
||||
<el-radio-button :label="3">短信</el-radio-button>
|
||||
<el-radio-button :label="4">上门</el-radio-button>
|
||||
<el-radio-button :label="5">其他</el-radio-button>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="回访时间" prop="visit_time">
|
||||
<el-date-picker
|
||||
v-model="visitForm.visit_time"
|
||||
type="datetime"
|
||||
placeholder="请选择回访时间"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
class="!w-full"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="回访内容" prop="content">
|
||||
<el-input v-model="visitForm.content" type="textarea" :rows="4" placeholder="请输入回访内容" />
|
||||
</el-form-item>
|
||||
<el-form-item label="回访结果">
|
||||
<el-input v-model="visitForm.result" type="textarea" :rows="3" placeholder="请输入回访结果" />
|
||||
</el-form-item>
|
||||
<el-form-item label="下次回访时间">
|
||||
<el-date-picker
|
||||
v-model="visitForm.next_visit_time"
|
||||
type="datetime"
|
||||
placeholder="请选择下次回访时间"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
class="!w-full"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button class="flex-1" @click="visitFormDrawerVisible = false">取消</el-button>
|
||||
<el-button class="flex-1" type="primary" :loading="visitSubmitLoading" @click="handleVisitSubmit">确定</el-button>
|
||||
</template>
|
||||
</el-drawer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="fansH5">
|
||||
import {
|
||||
fanLists,
|
||||
fanAdd,
|
||||
fanEdit,
|
||||
fanDelete,
|
||||
fanVisitRecordLists,
|
||||
fanVisitRecordAdd,
|
||||
fanVisitRecordEdit,
|
||||
fanVisitRecordDelete
|
||||
} from '@/api/fans'
|
||||
import feedback from '@/utils/feedback'
|
||||
import dayjs from 'dayjs'
|
||||
import { Plus } from '@element-plus/icons-vue'
|
||||
import type { FormInstance, FormRules } from 'element-plus'
|
||||
|
||||
// ==================== 粉丝列表 ====================
|
||||
const keyword = ref('')
|
||||
const formData = ref({
|
||||
name: '',
|
||||
phone: '',
|
||||
gender: '' as string | number,
|
||||
status: '' as string | number
|
||||
})
|
||||
const fanList = ref<any[]>([])
|
||||
const listLoading = ref(false)
|
||||
const moreLoading = ref(false)
|
||||
const pageNo = ref(1)
|
||||
const pageSize = 15
|
||||
const total = ref(0)
|
||||
const hasMore = computed(() => fanList.value.length < total.value)
|
||||
|
||||
const loadList = async (isLoadMore = false) => {
|
||||
if (isLoadMore) {
|
||||
moreLoading.value = true
|
||||
} else {
|
||||
listLoading.value = true
|
||||
}
|
||||
try {
|
||||
const params: any = {
|
||||
page_no: pageNo.value,
|
||||
page_size: pageSize,
|
||||
...formData.value
|
||||
}
|
||||
if (keyword.value) {
|
||||
if (/^\d+$/.test(keyword.value)) {
|
||||
params.phone = keyword.value
|
||||
} else {
|
||||
params.name = keyword.value
|
||||
}
|
||||
}
|
||||
const res = await fanLists(params)
|
||||
if (isLoadMore) {
|
||||
fanList.value = [...fanList.value, ...(res.lists || [])]
|
||||
} else {
|
||||
fanList.value = res.lists || []
|
||||
}
|
||||
total.value = res.count || 0
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
} finally {
|
||||
listLoading.value = false
|
||||
moreLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleSearch = () => {
|
||||
pageNo.value = 1
|
||||
loadList()
|
||||
}
|
||||
|
||||
const handleReset = () => {
|
||||
keyword.value = ''
|
||||
formData.value = { name: '', phone: '', gender: '', status: '' }
|
||||
handleSearch()
|
||||
}
|
||||
|
||||
const loadMore = () => {
|
||||
if (moreLoading.value) return
|
||||
pageNo.value++
|
||||
loadList(true)
|
||||
}
|
||||
|
||||
// ==================== 新增/编辑粉丝 ====================
|
||||
const fanDrawerVisible = ref(false)
|
||||
const fanDialogType = ref<'add' | 'edit'>('add')
|
||||
const fanFormRef = ref<FormInstance>()
|
||||
const fanSubmitLoading = ref(false)
|
||||
|
||||
const fanForm = ref({
|
||||
id: 0, name: '', phone: '', id_card: '', age: 0, gender: 1, status: 1, remark: ''
|
||||
})
|
||||
|
||||
const fanRules: FormRules = {
|
||||
name: [{ required: true, message: '请输入姓名', trigger: 'blur' }],
|
||||
phone: [
|
||||
{ required: true, message: '请输入手机号', trigger: 'blur' },
|
||||
{ pattern: /^1[3-9]\d{9}$/, message: '手机号格式不正确', trigger: 'blur' }
|
||||
],
|
||||
id_card: [
|
||||
{ pattern: /(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/, message: '身份证号格式不正确', trigger: 'blur' }
|
||||
],
|
||||
gender: [{ required: true, message: '请选择性别', trigger: 'change' }]
|
||||
}
|
||||
|
||||
const handleAdd = () => {
|
||||
fanDialogType.value = 'add'
|
||||
fanDrawerVisible.value = true
|
||||
}
|
||||
|
||||
const handleEdit = (row: any) => {
|
||||
fanDialogType.value = 'edit'
|
||||
fanForm.value = {
|
||||
id: row.id, name: row.name, phone: row.phone, id_card: row.id_card,
|
||||
age: row.age, gender: row.gender, status: row.status, remark: row.remark
|
||||
}
|
||||
fanDrawerVisible.value = true
|
||||
}
|
||||
|
||||
const resetFanForm = () => {
|
||||
fanForm.value = { id: 0, name: '', phone: '', id_card: '', age: 0, gender: 1, status: 1, remark: '' }
|
||||
fanFormRef.value?.resetFields()
|
||||
}
|
||||
|
||||
const handleFanSubmit = async () => {
|
||||
await fanFormRef.value?.validate()
|
||||
fanSubmitLoading.value = true
|
||||
try {
|
||||
if (fanDialogType.value === 'add') {
|
||||
await fanAdd(fanForm.value)
|
||||
} else {
|
||||
await fanEdit(fanForm.value)
|
||||
}
|
||||
fanDrawerVisible.value = false
|
||||
handleSearch()
|
||||
} catch (error: any) {
|
||||
} finally {
|
||||
fanSubmitLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleDelete = async (item: any) => {
|
||||
await feedback.confirm(`确定删除粉丝「${item.name}」吗?`)
|
||||
try {
|
||||
await fanDelete({ id: item.id })
|
||||
handleSearch()
|
||||
} catch (error: any) {
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 回访记录 ====================
|
||||
const visitDrawerVisible = ref(false)
|
||||
const currentFan = ref<any>(null)
|
||||
const visitRecords = ref<any[]>([])
|
||||
const visitLoading = ref(false)
|
||||
const visitTotal = ref(0)
|
||||
const visitPageNo = ref(1)
|
||||
|
||||
const visitTypeTagMap: Record<number, string> = {
|
||||
1: '', 2: 'success', 3: 'info', 4: 'warning', 5: 'danger'
|
||||
}
|
||||
|
||||
const handleVisitRecords = (row: any) => {
|
||||
currentFan.value = row
|
||||
visitDrawerVisible.value = true
|
||||
visitPageNo.value = 1
|
||||
visitRecords.value = []
|
||||
loadVisitRecords()
|
||||
}
|
||||
|
||||
const loadVisitRecords = async () => {
|
||||
visitLoading.value = true
|
||||
try {
|
||||
const res = await fanVisitRecordLists({
|
||||
fan_id: currentFan.value.id,
|
||||
page_no: visitPageNo.value,
|
||||
page_size: 15
|
||||
})
|
||||
if (visitPageNo.value === 1) {
|
||||
visitRecords.value = res.lists || []
|
||||
} else {
|
||||
visitRecords.value = [...visitRecords.value, ...(res.lists || [])]
|
||||
}
|
||||
visitTotal.value = res.count || 0
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
} finally {
|
||||
visitLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const loadMoreVisitRecords = () => {
|
||||
visitPageNo.value++
|
||||
loadVisitRecords()
|
||||
}
|
||||
|
||||
// ==================== 新增/编辑回访记录 ====================
|
||||
const visitFormDrawerVisible = ref(false)
|
||||
const visitDialogType = ref<'add' | 'edit'>('add')
|
||||
const visitFormRef = ref<FormInstance>()
|
||||
const visitSubmitLoading = ref(false)
|
||||
|
||||
const visitForm = ref({
|
||||
id: 0, fan_id: 0, visit_type: 1, visit_time: '', content: '', result: '', next_visit_time: ''
|
||||
})
|
||||
|
||||
const visitRules: FormRules = {
|
||||
visit_type: [{ required: true, message: '请选择回访类型', trigger: 'change' }],
|
||||
visit_time: [{ required: true, message: '请选择回访时间', trigger: 'change' }],
|
||||
content: [{ required: true, message: '请输入回访内容', trigger: 'blur' }]
|
||||
}
|
||||
|
||||
const handleAddVisitRecord = () => {
|
||||
visitDialogType.value = 'add'
|
||||
visitForm.value = {
|
||||
id: 0, fan_id: currentFan.value.id, visit_type: 1,
|
||||
visit_time: dayjs().format('YYYY-MM-DD HH:mm:ss'),
|
||||
content: '', result: '', next_visit_time: ''
|
||||
}
|
||||
visitFormDrawerVisible.value = true
|
||||
}
|
||||
|
||||
const handleEditVisitRecord = (row: any) => {
|
||||
visitDialogType.value = 'edit'
|
||||
visitForm.value = {
|
||||
id: row.id, fan_id: row.fan_id, visit_type: row.visit_type,
|
||||
visit_time: row.visit_time_text || '', content: row.content || '',
|
||||
result: row.result || '', next_visit_time: row.next_visit_time_text || ''
|
||||
}
|
||||
visitFormDrawerVisible.value = true
|
||||
}
|
||||
|
||||
const resetVisitForm = () => {
|
||||
visitForm.value = { id: 0, fan_id: 0, visit_type: 1, visit_time: '', content: '', result: '', next_visit_time: '' }
|
||||
visitFormRef.value?.resetFields()
|
||||
}
|
||||
|
||||
const handleVisitSubmit = async () => {
|
||||
await visitFormRef.value?.validate()
|
||||
visitSubmitLoading.value = true
|
||||
try {
|
||||
if (visitDialogType.value === 'add') {
|
||||
await fanVisitRecordAdd(visitForm.value)
|
||||
} else {
|
||||
await fanVisitRecordEdit(visitForm.value)
|
||||
}
|
||||
visitFormDrawerVisible.value = false
|
||||
visitPageNo.value = 1
|
||||
loadVisitRecords()
|
||||
} catch (error: any) {
|
||||
} finally {
|
||||
visitSubmitLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleDeleteVisitRecord = async (id: number) => {
|
||||
await feedback.confirm('确定删除该回访记录吗?')
|
||||
try {
|
||||
await fanVisitRecordDelete({ id })
|
||||
visitPageNo.value = 1
|
||||
loadVisitRecords()
|
||||
} catch (error: any) {
|
||||
}
|
||||
}
|
||||
|
||||
loadList()
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.fans-h5 {
|
||||
max-width: 500px;
|
||||
margin: 0 auto;
|
||||
min-height: 100vh;
|
||||
background: #f5f6fa;
|
||||
padding-bottom: 80px;
|
||||
}
|
||||
|
||||
.h5-header {
|
||||
background: #fff;
|
||||
padding: 12px 16px;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 10;
|
||||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.h5-search {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.h5-filter-row {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
|
||||
.el-select {
|
||||
width: 80px;
|
||||
}
|
||||
}
|
||||
|
||||
.h5-list {
|
||||
padding: 12px 16px;
|
||||
}
|
||||
|
||||
.h5-card {
|
||||
background: #fff;
|
||||
border-radius: 12px;
|
||||
margin-bottom: 12px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 1px 6px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.h5-card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 14px 16px 0;
|
||||
}
|
||||
|
||||
.h5-card-name {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
|
||||
.name {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.age {
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
}
|
||||
}
|
||||
|
||||
.h5-card-body {
|
||||
padding: 10px 16px;
|
||||
}
|
||||
|
||||
.info-line {
|
||||
display: flex;
|
||||
font-size: 13px;
|
||||
line-height: 2;
|
||||
|
||||
.label {
|
||||
color: #909399;
|
||||
width: 50px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.value {
|
||||
color: #606266;
|
||||
flex: 1;
|
||||
|
||||
&.remark {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.h5-card-footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 10px 16px;
|
||||
border-top: 1px solid #f2f3f5;
|
||||
|
||||
.footer-left {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.visit-info {
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.visit-count {
|
||||
color: #67c23a;
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.creator-info {
|
||||
font-size: 11px;
|
||||
color: #b0b3b8;
|
||||
}
|
||||
}
|
||||
|
||||
.h5-fab {
|
||||
position: fixed;
|
||||
right: 20px;
|
||||
bottom: 40px;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border-radius: 50%;
|
||||
background: linear-gradient(135deg, #409eff 0%, #337ecc 100%);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #fff;
|
||||
box-shadow: 0 4px 16px rgba(64, 158, 255, 0.4);
|
||||
cursor: pointer;
|
||||
z-index: 99;
|
||||
transition: transform 0.2s;
|
||||
|
||||
&:active {
|
||||
transform: scale(0.9);
|
||||
}
|
||||
}
|
||||
|
||||
.h5-load-more {
|
||||
text-align: center;
|
||||
padding: 16px;
|
||||
font-size: 13px;
|
||||
color: #409eff;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.h5-no-more {
|
||||
text-align: center;
|
||||
padding: 16px;
|
||||
font-size: 12px;
|
||||
color: #c0c4cc;
|
||||
}
|
||||
|
||||
.h5-empty {
|
||||
padding: 40px 0;
|
||||
}
|
||||
|
||||
.h5-form {
|
||||
:deep(.el-form-item__label) {
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
.h5-form-row {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.h5-radio-group {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
// 回访记录
|
||||
.h5-visit-header {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.h5-visit-list {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.h5-visit-card {
|
||||
background: #f9fafb;
|
||||
border-radius: 10px;
|
||||
padding: 14px;
|
||||
margin-bottom: 10px;
|
||||
border-left: 3px solid #409eff;
|
||||
}
|
||||
|
||||
.visit-card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.visit-card-tags {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
|
||||
.visit-time {
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
}
|
||||
}
|
||||
|
||||
.visit-card-content {
|
||||
font-size: 14px;
|
||||
color: #303133;
|
||||
line-height: 1.6;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.visit-card-result {
|
||||
font-size: 13px;
|
||||
color: #606266;
|
||||
margin-bottom: 6px;
|
||||
|
||||
.label {
|
||||
color: #909399;
|
||||
}
|
||||
}
|
||||
|
||||
.visit-card-meta {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
|
||||
.next-visit {
|
||||
color: #e6a23c;
|
||||
}
|
||||
}
|
||||
|
||||
// 抽屉底部按钮
|
||||
:deep(.el-drawer__footer) {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
padding: 12px 16px;
|
||||
border-top: 1px solid #f2f3f5;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,513 @@
|
||||
<template>
|
||||
<div class="fans-management">
|
||||
<!-- 搜索区域 -->
|
||||
<el-card class="!border-none" shadow="never">
|
||||
<el-form class="ls-form" :model="formData" inline>
|
||||
<el-form-item class="w-[280px]" label="姓名">
|
||||
<el-input
|
||||
placeholder="请输入姓名"
|
||||
v-model="formData.name"
|
||||
clearable
|
||||
@keyup.enter="resetPage"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item class="w-[280px]" label="手机号">
|
||||
<el-input
|
||||
placeholder="请输入手机号"
|
||||
v-model="formData.phone"
|
||||
clearable
|
||||
@keyup.enter="resetPage"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item class="w-[180px]" label="性别">
|
||||
<el-select v-model="formData.gender" placeholder="请选择" clearable>
|
||||
<el-option label="男" :value="1" />
|
||||
<el-option label="女" :value="2" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item class="w-[180px]" label="状态">
|
||||
<el-select v-model="formData.status" placeholder="请选择" clearable>
|
||||
<el-option label="启用" :value="1" />
|
||||
<el-option label="禁用" :value="0" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="resetPage">查询</el-button>
|
||||
<el-button @click="resetParams">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
|
||||
<!-- 列表区域 -->
|
||||
<el-card class="!border-none mt-4" shadow="never">
|
||||
<div class="mb-4">
|
||||
<el-button type="primary" @click="handleAdd">新增粉丝</el-button>
|
||||
</div>
|
||||
<el-table :data="pager.lists" size="large" v-loading="pager.loading">
|
||||
<el-table-column label="ID" prop="id" width="70" />
|
||||
<el-table-column label="姓名" prop="name" min-width="100" />
|
||||
<el-table-column label="手机号" prop="phone" min-width="130" />
|
||||
<el-table-column label="身份证号" prop="id_card" min-width="180" />
|
||||
<el-table-column label="性别" prop="gender_desc" width="80" />
|
||||
<el-table-column label="年龄" prop="age" width="70" />
|
||||
<el-table-column label="备注" prop="remark" min-width="150" show-overflow-tooltip />
|
||||
<el-table-column label="登记人" prop="creator_name" min-width="100">
|
||||
<template #default="{ row }">
|
||||
<span>{{ row.creator_name || '-' }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="回访次数" width="90" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag v-if="row.visit_count > 0" type="success">{{ row.visit_count }}</el-tag>
|
||||
<span v-else class="text-gray-400">0</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" width="80">
|
||||
<template #default="{ row }">
|
||||
<el-tag v-if="row.status == 1" type="success">启用</el-tag>
|
||||
<el-tag v-else type="danger">禁用</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="创建时间" min-width="170">
|
||||
<template #default="{ row }">
|
||||
<span>{{ formatDateTime(row.create_time) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="220" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button type="primary" link @click="handleEdit(row)">编辑</el-button>
|
||||
<el-button type="success" link @click="handleVisitRecords(row)">回访记录</el-button>
|
||||
<el-button type="danger" link @click="handleDelete(row.id)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="flex mt-4 justify-end">
|
||||
<pagination v-model="pager" @change="getLists" />
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 新增/编辑粉丝弹窗 -->
|
||||
<el-dialog
|
||||
v-model="fanDialogVisible"
|
||||
:title="fanDialogType === 'add' ? '新增粉丝' : '编辑粉丝'"
|
||||
width="600px"
|
||||
:close-on-click-modal="false"
|
||||
@closed="resetFanForm"
|
||||
>
|
||||
<el-form
|
||||
ref="fanFormRef"
|
||||
:model="fanForm"
|
||||
:rules="fanRules"
|
||||
label-width="100px"
|
||||
>
|
||||
<el-form-item label="姓名" prop="name">
|
||||
<el-input v-model="fanForm.name" placeholder="请输入姓名" maxlength="50" />
|
||||
</el-form-item>
|
||||
<el-form-item label="手机号" prop="phone">
|
||||
<el-input v-model="fanForm.phone" placeholder="请输入手机号" maxlength="11" />
|
||||
</el-form-item>
|
||||
<el-form-item label="身份证号" prop="id_card">
|
||||
<el-input v-model="fanForm.id_card" placeholder="请输入身份证号码" maxlength="18" />
|
||||
</el-form-item>
|
||||
<el-form-item label="年龄" prop="age">
|
||||
<el-input-number v-model="fanForm.age" :min="0" :max="150" />
|
||||
</el-form-item>
|
||||
<el-form-item label="性别" prop="gender">
|
||||
<el-radio-group v-model="fanForm.gender">
|
||||
<el-radio :label="1">男</el-radio>
|
||||
<el-radio :label="2">女</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-radio-group v-model="fanForm.status">
|
||||
<el-radio :label="1">启用</el-radio>
|
||||
<el-radio :label="0">禁用</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input
|
||||
v-model="fanForm.remark"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
placeholder="请输入备注"
|
||||
maxlength="500"
|
||||
show-word-limit
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="fanDialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" :loading="fanSubmitLoading" @click="handleFanSubmit">确定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 回访记录抽屉 -->
|
||||
<el-drawer
|
||||
v-model="visitDrawerVisible"
|
||||
:title="`回访记录 - ${currentFan?.name || ''}`"
|
||||
size="700px"
|
||||
:close-on-click-modal="false"
|
||||
>
|
||||
<div class="mb-4">
|
||||
<el-button type="primary" @click="handleAddVisitRecord">新增回访记录</el-button>
|
||||
</div>
|
||||
|
||||
<el-table :data="visitRecords" size="large" v-loading="visitLoading">
|
||||
<el-table-column label="回访类型" width="100">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="visitTypeTagMap[row.visit_type]">
|
||||
{{ row.visit_type_desc }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="回访时间" prop="visit_time_text" min-width="150" />
|
||||
<el-table-column label="回访内容" prop="content" min-width="200" show-overflow-tooltip />
|
||||
<el-table-column label="回访结果" prop="result" min-width="150" show-overflow-tooltip />
|
||||
<el-table-column label="下次回访" prop="next_visit_time_text" min-width="150" />
|
||||
<el-table-column label="操作人" prop="operator_name" width="100" />
|
||||
<el-table-column label="操作" width="120" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button type="primary" link @click="handleEditVisitRecord(row)">编辑</el-button>
|
||||
<el-button type="danger" link @click="handleDeleteVisitRecord(row.id)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<div v-if="visitTotal > visitRecords.length" class="text-center mt-4">
|
||||
<el-button link type="primary" @click="loadMoreVisitRecords">加载更多</el-button>
|
||||
</div>
|
||||
</el-drawer>
|
||||
|
||||
<!-- 新增/编辑回访记录弹窗 -->
|
||||
<el-dialog
|
||||
v-model="visitDialogVisible"
|
||||
:title="visitDialogType === 'add' ? '新增回访记录' : '编辑回访记录'"
|
||||
width="600px"
|
||||
:close-on-click-modal="false"
|
||||
@closed="resetVisitForm"
|
||||
>
|
||||
<el-form
|
||||
ref="visitFormRef"
|
||||
:model="visitForm"
|
||||
:rules="visitRules"
|
||||
label-width="120px"
|
||||
>
|
||||
<el-form-item label="回访类型" prop="visit_type">
|
||||
<el-select v-model="visitForm.visit_type" placeholder="请选择回访类型">
|
||||
<el-option label="电话" :value="1" />
|
||||
<el-option label="微信" :value="2" />
|
||||
<el-option label="短信" :value="3" />
|
||||
<el-option label="上门" :value="4" />
|
||||
<el-option label="其他" :value="5" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="回访时间" prop="visit_time">
|
||||
<el-date-picker
|
||||
v-model="visitForm.visit_time"
|
||||
type="datetime"
|
||||
placeholder="请选择回访时间"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="回访内容" prop="content">
|
||||
<el-input
|
||||
v-model="visitForm.content"
|
||||
type="textarea"
|
||||
:rows="4"
|
||||
placeholder="请输入回访内容"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="回访结果" prop="result">
|
||||
<el-input
|
||||
v-model="visitForm.result"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
placeholder="请输入回访结果"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="下次回访时间" prop="next_visit_time">
|
||||
<el-date-picker
|
||||
v-model="visitForm.next_visit_time"
|
||||
type="datetime"
|
||||
placeholder="请选择下次回访时间"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="visitDialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" :loading="visitSubmitLoading" @click="handleVisitSubmit">确定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="fansManagement">
|
||||
import {
|
||||
fanLists,
|
||||
fanAdd,
|
||||
fanEdit,
|
||||
fanDelete,
|
||||
fanVisitRecordLists,
|
||||
fanVisitRecordAdd,
|
||||
fanVisitRecordEdit,
|
||||
fanVisitRecordDelete
|
||||
} from '@/api/fans'
|
||||
import { usePaging } from '@/hooks/usePaging'
|
||||
import feedback from '@/utils/feedback'
|
||||
import dayjs from 'dayjs'
|
||||
import type { FormInstance, FormRules } from 'element-plus'
|
||||
|
||||
const formatDateTime = (timestamp: number | string) => {
|
||||
if (!timestamp) return '-'
|
||||
const ts = String(timestamp).length === 10 ? Number(timestamp) * 1000 : Number(timestamp)
|
||||
return dayjs(ts).format('YYYY-MM-DD HH:mm:ss')
|
||||
}
|
||||
|
||||
// ==================== 粉丝列表 ====================
|
||||
const formData = ref({
|
||||
name: '',
|
||||
phone: '',
|
||||
gender: '' as string | number,
|
||||
status: '' as string | number
|
||||
})
|
||||
|
||||
const { pager, getLists, resetParams, resetPage } = usePaging({
|
||||
fetchFun: fanLists,
|
||||
params: formData.value
|
||||
})
|
||||
|
||||
// ==================== 新增/编辑粉丝 ====================
|
||||
const fanDialogVisible = ref(false)
|
||||
const fanDialogType = ref<'add' | 'edit'>('add')
|
||||
const fanFormRef = ref<FormInstance>()
|
||||
const fanSubmitLoading = ref(false)
|
||||
|
||||
const fanForm = ref({
|
||||
id: 0,
|
||||
name: '',
|
||||
phone: '',
|
||||
id_card: '',
|
||||
age: 0,
|
||||
gender: 1,
|
||||
status: 1,
|
||||
remark: ''
|
||||
})
|
||||
|
||||
const fanRules: FormRules = {
|
||||
name: [{ required: true, message: '请输入姓名', trigger: 'blur' }],
|
||||
phone: [
|
||||
{ required: true, message: '请输入手机号', trigger: 'blur' },
|
||||
{ pattern: /^1[3-9]\d{9}$/, message: '手机号格式不正确', trigger: 'blur' }
|
||||
],
|
||||
id_card: [
|
||||
{ pattern: /(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/, message: '身份证号格式不正确', trigger: 'blur' }
|
||||
],
|
||||
gender: [{ required: true, message: '请选择性别', trigger: 'change' }]
|
||||
}
|
||||
|
||||
const handleAdd = () => {
|
||||
fanDialogType.value = 'add'
|
||||
fanDialogVisible.value = true
|
||||
}
|
||||
|
||||
const handleEdit = (row: any) => {
|
||||
fanDialogType.value = 'edit'
|
||||
fanForm.value = {
|
||||
id: row.id,
|
||||
name: row.name,
|
||||
phone: row.phone,
|
||||
id_card: row.id_card,
|
||||
age: row.age,
|
||||
gender: row.gender,
|
||||
status: row.status,
|
||||
remark: row.remark
|
||||
}
|
||||
fanDialogVisible.value = true
|
||||
}
|
||||
|
||||
const resetFanForm = () => {
|
||||
fanForm.value = {
|
||||
id: 0,
|
||||
name: '',
|
||||
phone: '',
|
||||
id_card: '',
|
||||
age: 0,
|
||||
gender: 1,
|
||||
status: 1,
|
||||
remark: ''
|
||||
}
|
||||
fanFormRef.value?.resetFields()
|
||||
}
|
||||
|
||||
const handleFanSubmit = async () => {
|
||||
await fanFormRef.value?.validate()
|
||||
fanSubmitLoading.value = true
|
||||
try {
|
||||
if (fanDialogType.value === 'add') {
|
||||
await fanAdd(fanForm.value)
|
||||
} else {
|
||||
await fanEdit(fanForm.value)
|
||||
}
|
||||
fanDialogVisible.value = false
|
||||
getLists()
|
||||
} catch (error: any) {
|
||||
} finally {
|
||||
fanSubmitLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleDelete = async (id: number) => {
|
||||
await feedback.confirm('确定要删除该粉丝吗?')
|
||||
try {
|
||||
await fanDelete({ id })
|
||||
getLists()
|
||||
} catch (error: any) {
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 回访记录 ====================
|
||||
const visitDrawerVisible = ref(false)
|
||||
const currentFan = ref<any>(null)
|
||||
const visitRecords = ref<any[]>([])
|
||||
const visitLoading = ref(false)
|
||||
const visitTotal = ref(0)
|
||||
const visitPageNo = ref(1)
|
||||
|
||||
const visitTypeTagMap: Record<number, string> = {
|
||||
1: '',
|
||||
2: 'success',
|
||||
3: 'info',
|
||||
4: 'warning',
|
||||
5: 'danger'
|
||||
}
|
||||
|
||||
const handleVisitRecords = (row: any) => {
|
||||
currentFan.value = row
|
||||
visitDrawerVisible.value = true
|
||||
visitPageNo.value = 1
|
||||
visitRecords.value = []
|
||||
loadVisitRecords()
|
||||
}
|
||||
|
||||
const loadVisitRecords = async () => {
|
||||
visitLoading.value = true
|
||||
try {
|
||||
const res = await fanVisitRecordLists({
|
||||
fan_id: currentFan.value.id,
|
||||
page_no: visitPageNo.value,
|
||||
page_size: 15
|
||||
})
|
||||
if (visitPageNo.value === 1) {
|
||||
visitRecords.value = res.lists || []
|
||||
} else {
|
||||
visitRecords.value = [...visitRecords.value, ...(res.lists || [])]
|
||||
}
|
||||
visitTotal.value = res.count || 0
|
||||
} catch (error) {
|
||||
console.error('获取回访记录失败:', error)
|
||||
} finally {
|
||||
visitLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const loadMoreVisitRecords = () => {
|
||||
visitPageNo.value++
|
||||
loadVisitRecords()
|
||||
}
|
||||
|
||||
// ==================== 新增/编辑回访记录 ====================
|
||||
const visitDialogVisible = ref(false)
|
||||
const visitDialogType = ref<'add' | 'edit'>('add')
|
||||
const visitFormRef = ref<FormInstance>()
|
||||
const visitSubmitLoading = ref(false)
|
||||
|
||||
const visitForm = ref({
|
||||
id: 0,
|
||||
fan_id: 0,
|
||||
visit_type: 1,
|
||||
visit_time: '',
|
||||
content: '',
|
||||
result: '',
|
||||
next_visit_time: ''
|
||||
})
|
||||
|
||||
const visitRules: FormRules = {
|
||||
visit_type: [{ required: true, message: '请选择回访类型', trigger: 'change' }],
|
||||
visit_time: [{ required: true, message: '请选择回访时间', trigger: 'change' }],
|
||||
content: [{ required: true, message: '请输入回访内容', trigger: 'blur' }]
|
||||
}
|
||||
|
||||
const handleAddVisitRecord = () => {
|
||||
visitDialogType.value = 'add'
|
||||
visitForm.value = {
|
||||
id: 0,
|
||||
fan_id: currentFan.value.id,
|
||||
visit_type: 1,
|
||||
visit_time: dayjs().format('YYYY-MM-DD HH:mm:ss'),
|
||||
content: '',
|
||||
result: '',
|
||||
next_visit_time: ''
|
||||
}
|
||||
visitDialogVisible.value = true
|
||||
}
|
||||
|
||||
const handleEditVisitRecord = (row: any) => {
|
||||
visitDialogType.value = 'edit'
|
||||
visitForm.value = {
|
||||
id: row.id,
|
||||
fan_id: row.fan_id,
|
||||
visit_type: row.visit_type,
|
||||
visit_time: row.visit_time_text || '',
|
||||
content: row.content || '',
|
||||
result: row.result || '',
|
||||
next_visit_time: row.next_visit_time_text || ''
|
||||
}
|
||||
visitDialogVisible.value = true
|
||||
}
|
||||
|
||||
const resetVisitForm = () => {
|
||||
visitForm.value = {
|
||||
id: 0,
|
||||
fan_id: 0,
|
||||
visit_type: 1,
|
||||
visit_time: '',
|
||||
content: '',
|
||||
result: '',
|
||||
next_visit_time: ''
|
||||
}
|
||||
visitFormRef.value?.resetFields()
|
||||
}
|
||||
|
||||
const handleVisitSubmit = async () => {
|
||||
await visitFormRef.value?.validate()
|
||||
visitSubmitLoading.value = true
|
||||
try {
|
||||
if (visitDialogType.value === 'add') {
|
||||
await fanVisitRecordAdd(visitForm.value)
|
||||
} else {
|
||||
await fanVisitRecordEdit(visitForm.value)
|
||||
}
|
||||
visitDialogVisible.value = false
|
||||
visitPageNo.value = 1
|
||||
loadVisitRecords()
|
||||
} catch (error: any) {
|
||||
} finally {
|
||||
visitSubmitLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleDeleteVisitRecord = async (id: number) => {
|
||||
await feedback.confirm('确定要删除该回访记录吗?')
|
||||
try {
|
||||
await fanVisitRecordDelete({ id })
|
||||
visitPageNo.value = 1
|
||||
loadVisitRecords()
|
||||
} catch (error: any) {
|
||||
}
|
||||
}
|
||||
|
||||
getLists()
|
||||
</script>
|
||||
@@ -135,6 +135,13 @@
|
||||
>
|
||||
详情
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
link
|
||||
@click="handleOrderQrcode(row)"
|
||||
>
|
||||
小程序码
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="row.status === 1"
|
||||
v-perms="['order.order/pay']"
|
||||
@@ -337,12 +344,46 @@
|
||||
</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
<!-- 小程序码弹窗 -->
|
||||
<el-dialog
|
||||
v-model="qrcodeDialogVisible"
|
||||
title="支付小程序码"
|
||||
width="400px"
|
||||
:close-on-click-modal="false"
|
||||
>
|
||||
<div class="flex flex-col items-center justify-center py-4">
|
||||
<div v-if="qrcodeLoading" class="text-center">
|
||||
<el-icon class="is-loading" :size="40">
|
||||
<Loading />
|
||||
</el-icon>
|
||||
<div class="mt-4 text-gray-500">生成中...</div>
|
||||
</div>
|
||||
<div v-else-if="qrcodeUrl" class="text-center">
|
||||
<img :src="qrcodeUrl" alt="小程序二维码" class="w-64 h-64 border border-gray-200 rounded" />
|
||||
<div class="mt-4 text-sm text-gray-600">
|
||||
<div>订单号:{{ qrcodeOrderNo }}</div>
|
||||
<div class="mt-2">请使用微信扫描二维码完成支付</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="text-center text-gray-500">
|
||||
生成失败,请重试
|
||||
</div>
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button @click="qrcodeDialogVisible = false">关闭</el-button>
|
||||
<el-button v-if="!qrcodeLoading" type="primary" @click="handleOrderQrcodeRefresh">
|
||||
重新生成
|
||||
</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="orderList">
|
||||
import { usePaging } from '@/hooks/usePaging'
|
||||
import { orderLists, orderDetail, orderPay, orderCancel, orderDelete, orderRefund, orderExport, orderCreate, searchPatients as searchPatientsAPI, alipayPay, wechatPay } from '@/api/order'
|
||||
import { generateOrderQrcode } from '@/api/tcm'
|
||||
import { Loading } from '@element-plus/icons-vue'
|
||||
import feedback from '@/utils/feedback'
|
||||
|
||||
const queryParams = reactive({
|
||||
@@ -638,6 +679,41 @@ const handleDelete = async (row: any) => {
|
||||
}
|
||||
}
|
||||
|
||||
// 小程序码相关
|
||||
const qrcodeDialogVisible = ref(false)
|
||||
const qrcodeLoading = ref(false)
|
||||
const qrcodeUrl = ref('')
|
||||
const qrcodeOrderNo = ref('')
|
||||
|
||||
const handleOrderQrcode = async (row: any) => {
|
||||
if (!row.order_no) {
|
||||
feedback.msgWarning('订单号不存在')
|
||||
return
|
||||
}
|
||||
qrcodeOrderNo.value = row.order_no
|
||||
qrcodeDialogVisible.value = true
|
||||
qrcodeLoading.value = true
|
||||
qrcodeUrl.value = ''
|
||||
try {
|
||||
const res = await generateOrderQrcode({ order_no: row.order_no })
|
||||
if (res?.qrcode_url) {
|
||||
qrcodeUrl.value = res.qrcode_url
|
||||
} else {
|
||||
feedback.msgError('二维码生成失败')
|
||||
}
|
||||
} catch (e: any) {
|
||||
feedback.msgError(e?.msg || '生成二维码失败')
|
||||
} finally {
|
||||
qrcodeLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleOrderQrcodeRefresh = () => {
|
||||
if (qrcodeOrderNo.value) {
|
||||
handleOrderQrcode({ order_no: qrcodeOrderNo.value })
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getLists()
|
||||
})
|
||||
|
||||
@@ -170,7 +170,7 @@
|
||||
link
|
||||
@click="handleComplete(row)"
|
||||
>
|
||||
完成面诊
|
||||
接诊完成
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="row.status === 1"
|
||||
@@ -188,7 +188,7 @@
|
||||
link
|
||||
@click="handleMiniProgramQRCode(row)"
|
||||
>
|
||||
确认诊单二维码
|
||||
视频二维码
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
@@ -21,16 +21,16 @@
|
||||
<!-- 基本信息 -->
|
||||
<el-divider content-position="left">基本信息</el-divider>
|
||||
|
||||
<el-form-item label="患者ID">
|
||||
<el-form-item label="诊单ID">
|
||||
<el-input v-model="formData.patient_id" disabled placeholder="系统自动生成" />
|
||||
</el-form-item>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="患者姓名" prop="patient_name">
|
||||
<el-form-item label="姓名" prop="patient_name">
|
||||
<el-input
|
||||
v-model="formData.patient_name"
|
||||
placeholder="请输入患者姓名"
|
||||
placeholder="请输入姓名"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
@@ -94,6 +94,7 @@
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
|
||||
<!-- 生命体征 -->
|
||||
<el-divider content-position="left">生命体征</el-divider>
|
||||
|
||||
@@ -589,6 +590,7 @@
|
||||
</div>
|
||||
</template>
|
||||
</el-drawer>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -596,7 +598,9 @@
|
||||
import { tcmDiagnosisAdd, tcmDiagnosisEdit, tcmDiagnosisDetail, checkPhone, checkIdCard } from '@/api/tcm'
|
||||
import { getDictData } from '@/api/app'
|
||||
import feedback from '@/utils/feedback'
|
||||
import useUserStore from '@/stores/modules/user'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { QuestionFilled } from '@element-plus/icons-vue'
|
||||
import BloodRecordList from './components/BloodRecordList.vue'
|
||||
|
||||
const emit = defineEmits(['success'])
|
||||
@@ -659,7 +663,8 @@ const formData = ref({
|
||||
prescription: '',
|
||||
doctor_advice: '',
|
||||
remark: '',
|
||||
status: 1
|
||||
status: 1,
|
||||
external_userid: ''
|
||||
})
|
||||
|
||||
// 自定义验证规则
|
||||
@@ -832,6 +837,9 @@ const open = async (type: string, id?: number) => {
|
||||
if (type === 'edit' && id) {
|
||||
const data = await tcmDiagnosisDetail({ id })
|
||||
formData.value = data
|
||||
} else if (type === 'add') {
|
||||
const userStore = useUserStore()
|
||||
formData.value.assistant_id = userStore.userInfo?.id || ''
|
||||
}
|
||||
}
|
||||
|
||||
@@ -917,7 +925,8 @@ const handleClose = () => {
|
||||
prescription: '',
|
||||
doctor_advice: '',
|
||||
remark: '',
|
||||
status: 1
|
||||
status: 1,
|
||||
external_userid: ''
|
||||
}
|
||||
visible.value = false
|
||||
}
|
||||
@@ -1082,4 +1091,5 @@ defineExpose({
|
||||
.el-select-dropdown {
|
||||
z-index: 3000 !important;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@@ -76,8 +76,9 @@
|
||||
@selection-change="handleSelectionChange"
|
||||
>
|
||||
<el-table-column type="selection" width="55" />
|
||||
<el-table-column label="账户编号" prop="id" width="80" />
|
||||
<el-table-column label="账户编号" prop="id" width="90" />
|
||||
<el-table-column label="账户姓名" prop="patient_name" min-width="100" />
|
||||
|
||||
<el-table-column label="性别" prop="gender_desc" width="80" />
|
||||
<el-table-column label="年龄" prop="age" width="80" />
|
||||
<el-table-column label="诊断日期" prop="diagnosis_date_text" min-width="120" />
|
||||
@@ -86,6 +87,12 @@
|
||||
<span>{{ getDictLabel(diagnosisTypeOptions, row.diagnosis_type) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="确认诊单" min-width="100">
|
||||
<template #default="{ row }">
|
||||
<el-tag v-if="row.DiagnosisViewRecord && row.DiagnosisViewRecord.length > 0" type="success" size="small">已确认</el-tag>
|
||||
<el-tag v-else type="info" size="small">未确认</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="证型" prop="syndrome_type" min-width="100">
|
||||
<template #default="{ row }">
|
||||
<span>{{ getDictLabel(syndromeTypeOptions, row.syndrome_type) }}</span>
|
||||
@@ -96,22 +103,22 @@
|
||||
<span>{{ getAssistantName(row.assistant_id || row.assistant) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" width="80">
|
||||
<!-- <el-table-column label="状态" width="80">
|
||||
<template #default="{ row }">
|
||||
<el-tag v-if="row.status == 1" type="success">启用</el-tag>
|
||||
<el-tag v-else type="danger">禁用</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table-column> -->
|
||||
<el-table-column label="创建时间" prop="create_time" min-width="160">
|
||||
<template #default="{ row }">
|
||||
<span>{{ formatDateTime(row.create_time) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="240" fixed="right">
|
||||
<el-table-column label="操作" width="300" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button type="primary" link @click="handleDetail(row.id)">
|
||||
<!-- <el-button type="primary" link @click="handleDetail(row.id)">
|
||||
查看
|
||||
</el-button>
|
||||
</el-button> -->
|
||||
<el-button
|
||||
v-perms="['tcm.diagnosis/assign']"
|
||||
type="success"
|
||||
@@ -155,6 +162,12 @@
|
||||
<el-button type="primary" link @click="handleEdit(row.id)" v-perms="['tcm.diagnosis/edit']">
|
||||
诊单
|
||||
</el-button>
|
||||
<!-- <el-button type="primary" link @click="handleWechatRecords(row)">
|
||||
企微记录
|
||||
</el-button> -->
|
||||
<el-button type="primary" link @click="handleCreateOrder(row)">
|
||||
创建订单
|
||||
</el-button>
|
||||
<el-button type="danger" link @click="handleDelete(row.id)" v-perms="['tcm.diagnosis/delete']">
|
||||
删除
|
||||
</el-button>
|
||||
@@ -248,11 +261,170 @@
|
||||
</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 创建订单弹窗 -->
|
||||
<el-dialog
|
||||
v-model="createOrderVisible"
|
||||
title="创建订单"
|
||||
width="600px"
|
||||
@close="resetOrderForm"
|
||||
>
|
||||
<el-form
|
||||
ref="orderFormRef"
|
||||
:model="orderForm"
|
||||
:rules="orderRules"
|
||||
label-width="100px"
|
||||
>
|
||||
<el-form-item label="患者">
|
||||
<span class="font-medium">{{ orderForm.patient_name }} ({{ orderForm.phone }})</span>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="订单类型" prop="order_type">
|
||||
<el-select v-model="orderForm.order_type" placeholder="请选择" class="w-full">
|
||||
<el-option label="挂号费" :value="1" />
|
||||
<el-option label="问诊费" :value="2" />
|
||||
<el-option label="药品费用" :value="3" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="订单金额" prop="amount">
|
||||
<el-input-number
|
||||
v-model="orderForm.amount"
|
||||
:min="0"
|
||||
:step="0.01"
|
||||
:precision="2"
|
||||
placeholder="请输入金额"
|
||||
class="w-full"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input
|
||||
v-model="orderForm.remark"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
placeholder="请输入备注(可选)"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="createOrderVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="submitOrder" :loading="orderLoading">创建订单</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 企业微信聊天记录抽屉 -->
|
||||
<el-drawer
|
||||
v-model="wechatDrawerVisible"
|
||||
:title="`企业微信记录 - ${wechatCurrentPatient?.patient_name || ''}`"
|
||||
size="600px"
|
||||
:destroy-on-close="true"
|
||||
>
|
||||
<!-- 患者与企微信息 -->
|
||||
<div v-if="wechatContactInfo" class="mb-4">
|
||||
<el-descriptions :column="2" border size="small">
|
||||
<el-descriptions-item label="患者姓名">{{ wechatCurrentPatient?.patient_name }}</el-descriptions-item>
|
||||
<el-descriptions-item label="手机号">{{ wechatContactInfo.phone || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="企微联系人ID">
|
||||
<span v-if="wechatContactInfo.external_userid">{{ wechatContactInfo.external_userid }}</span>
|
||||
<el-tag v-else type="info" size="small">未关联</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="会话存档">
|
||||
<el-tag v-if="wechatContactInfo.msgaudit_enabled" type="success" size="small">已开通</el-tag>
|
||||
<el-tag v-else type="info" size="small">未配置</el-tag>
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<!-- 存档成员列表 -->
|
||||
<div v-if="wechatContactInfo.permit_users?.length" class="mt-2">
|
||||
<span class="text-sm text-gray-500 mr-2">存档成员:</span>
|
||||
<el-tag
|
||||
v-for="uid in wechatContactInfo.permit_users"
|
||||
:key="uid"
|
||||
size="small"
|
||||
class="mr-1"
|
||||
>
|
||||
{{ uid }}
|
||||
</el-tag>
|
||||
</div>
|
||||
<!-- 同意存档情况 -->
|
||||
<div v-if="wechatContactInfo.msgaudit_agree?.length" class="mt-2">
|
||||
<span class="text-sm text-gray-500 mr-2">存档同意:</span>
|
||||
<el-tag
|
||||
v-for="(info, idx) in wechatContactInfo.msgaudit_agree"
|
||||
:key="idx"
|
||||
:type="info.agree_status === 'Agree' ? 'success' : 'warning'"
|
||||
size="small"
|
||||
class="mr-1"
|
||||
>
|
||||
{{ info.userid }}: {{ info.agree_status === 'Agree' ? '已同意' : info.agree_status }}
|
||||
</el-tag>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-divider />
|
||||
|
||||
<!-- 添加记录 -->
|
||||
<div class="mb-4">
|
||||
<div class="flex gap-2">
|
||||
<el-input
|
||||
v-model="wechatNewNote"
|
||||
type="textarea"
|
||||
:rows="2"
|
||||
placeholder="添加沟通记录..."
|
||||
class="flex-1"
|
||||
/>
|
||||
<el-button type="primary" @click="handleAddRecord" :loading="wechatAddLoading" class="self-end">
|
||||
添加
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 记录列表 -->
|
||||
<div v-loading="wechatRecordsLoading">
|
||||
<div v-if="wechatRecords.length === 0" class="text-center text-gray-400 py-10">
|
||||
暂无聊天记录
|
||||
</div>
|
||||
<div v-else class="record-list">
|
||||
<div
|
||||
v-for="record in wechatRecords"
|
||||
:key="record.id"
|
||||
class="record-item"
|
||||
>
|
||||
<div class="record-header">
|
||||
<div class="flex items-center gap-2">
|
||||
<el-tag :type="record.direction === 0 ? 'primary' : 'success'" size="small">
|
||||
{{ record.direction === 0 ? '员工' : '客户' }}
|
||||
</el-tag>
|
||||
<span class="font-medium text-sm">
|
||||
{{ record.direction === 0 ? record.staff_name : record.external_name || wechatCurrentPatient?.patient_name }}
|
||||
</span>
|
||||
<el-tag v-if="record.msg_type === 'note'" type="warning" size="small">备注</el-tag>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-xs text-gray-400">{{ formatTimestamp(record.chat_time) }}</span>
|
||||
<el-button type="danger" link size="small" @click="handleDeleteRecord(record.id)">
|
||||
删除
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="record-content">{{ record.content }}</div>
|
||||
<div v-if="record.media_url" class="mt-1">
|
||||
<el-image :src="record.media_url" style="max-width: 200px" fit="contain" :preview-src-list="[record.media_url]" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="wechatRecordTotal > wechatRecords.length" class="text-center mt-4">
|
||||
<el-button link type="primary" @click="loadMoreRecords">加载更多</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</el-drawer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="tcmDiagnosis">
|
||||
import { tcmDiagnosisLists, tcmDiagnosisDelete, tcmDiagnosisAssign, getAssistants, generateMiniProgramQrcode } from '@/api/tcm'
|
||||
import { tcmDiagnosisLists, tcmDiagnosisDelete, tcmDiagnosisAssign, getAssistants, generateMiniProgramQrcode, generateOrderQrcode, getWechatChatRecords, addWechatChatRecord, deleteWechatChatRecord, getWechatExternalContact } from '@/api/tcm'
|
||||
import { orderCreate } from '@/api/order'
|
||||
import { usePaging } from '@/hooks/usePaging'
|
||||
import { getDictData } from '@/api/app'
|
||||
import { getWeappConfig } from '@/api/channel/weapp'
|
||||
@@ -340,6 +512,83 @@ const detailRef = ref()
|
||||
const appointmentRef = ref()
|
||||
const userStore = useUserStore()
|
||||
|
||||
// 创建订单相关
|
||||
const createOrderVisible = ref(false)
|
||||
const orderFormRef = ref()
|
||||
const orderLoading = ref(false)
|
||||
const orderForm = reactive({
|
||||
patient_id: '' as string | number,
|
||||
patient_name: '',
|
||||
phone: '',
|
||||
order_type: '' as string | number,
|
||||
amount: 0,
|
||||
remark: ''
|
||||
})
|
||||
const orderRules = {
|
||||
order_type: [{ required: true, message: '请选择订单类型', trigger: 'change' }],
|
||||
amount: [{ required: true, message: '请输入订单金额', trigger: 'blur' }]
|
||||
}
|
||||
|
||||
const handleCreateOrder = (row: any) => {
|
||||
orderForm.patient_id = row.patient_id || row.id
|
||||
orderForm.patient_name = row.patient_name
|
||||
orderForm.phone = row.phone || ''
|
||||
orderForm.order_type = ''
|
||||
orderForm.amount = 0
|
||||
orderForm.remark = ''
|
||||
orderFormRef.value?.clearValidate()
|
||||
createOrderVisible.value = true
|
||||
}
|
||||
|
||||
const resetOrderForm = () => {
|
||||
orderForm.patient_id = ''
|
||||
orderForm.patient_name = ''
|
||||
orderForm.phone = ''
|
||||
orderForm.order_type = ''
|
||||
orderForm.amount = 0
|
||||
orderForm.remark = ''
|
||||
orderFormRef.value?.clearValidate()
|
||||
}
|
||||
|
||||
const submitOrder = async () => {
|
||||
try {
|
||||
await orderFormRef.value?.validate()
|
||||
orderLoading.value = true
|
||||
const res = await orderCreate({
|
||||
patient_id: orderForm.patient_id,
|
||||
order_type: orderForm.order_type,
|
||||
amount: orderForm.amount,
|
||||
remark: orderForm.remark
|
||||
})
|
||||
createOrderVisible.value = false
|
||||
getLists()
|
||||
|
||||
const orderNo = res?.order_no
|
||||
if (orderNo) {
|
||||
qrcodeDialogVisible.value = true
|
||||
qrcodeLoading.value = true
|
||||
qrcodeUrl.value = ''
|
||||
currentQRCodePatient.value = { patient_name: orderForm.patient_name }
|
||||
try {
|
||||
const qrRes = await generateOrderQrcode({ order_no: orderNo })
|
||||
if (qrRes?.qrcode_url) {
|
||||
qrcodeUrl.value = qrRes.qrcode_url
|
||||
} else {
|
||||
feedback.msgError('二维码生成失败')
|
||||
}
|
||||
} catch (e: any) {
|
||||
feedback.msgError(e?.msg || '生成二维码失败')
|
||||
} finally {
|
||||
qrcodeLoading.value = false
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('创建订单失败:', error)
|
||||
} finally {
|
||||
orderLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 小程序二维码相关
|
||||
const qrcodeDialogVisible = ref(false)
|
||||
const qrcodeLoading = ref(false)
|
||||
@@ -502,10 +751,134 @@ const handleRegenerateQRCode = () => {
|
||||
}
|
||||
}
|
||||
|
||||
// ========== 企业微信聊天记录 ==========
|
||||
const wechatDrawerVisible = ref(false)
|
||||
const wechatCurrentPatient = ref<any>(null)
|
||||
const wechatRecords = ref<any[]>([])
|
||||
const wechatRecordTotal = ref(0)
|
||||
const wechatRecordsLoading = ref(false)
|
||||
const wechatContactInfo = ref<any>(null)
|
||||
const wechatNewNote = ref('')
|
||||
const wechatAddLoading = ref(false)
|
||||
const wechatPage = ref(1)
|
||||
|
||||
const formatTimestamp = (ts: number) => {
|
||||
if (!ts) return '-'
|
||||
const t = String(ts).length === 10 ? ts * 1000 : ts
|
||||
return dayjs(t).format('YYYY-MM-DD HH:mm')
|
||||
}
|
||||
|
||||
const handleWechatRecords = async (row: any) => {
|
||||
wechatCurrentPatient.value = row
|
||||
wechatDrawerVisible.value = true
|
||||
wechatRecords.value = []
|
||||
wechatRecordTotal.value = 0
|
||||
wechatContactInfo.value = null
|
||||
wechatNewNote.value = ''
|
||||
wechatPage.value = 1
|
||||
|
||||
// 并行加载聊天记录和外部联系人信息
|
||||
wechatRecordsLoading.value = true
|
||||
try {
|
||||
const [recordsRes, contactRes] = await Promise.all([
|
||||
getWechatChatRecords({ diagnosis_id: row.id, patient_id: row.patient_id, page_no: 1, page_size: 20 }),
|
||||
getWechatExternalContact({ patient_id: row.patient_id || row.id }).catch(() => null)
|
||||
])
|
||||
|
||||
wechatRecords.value = recordsRes?.lists || []
|
||||
wechatRecordTotal.value = recordsRes?.count || 0
|
||||
wechatContactInfo.value = contactRes
|
||||
} catch (e) {
|
||||
console.error('加载企业微信记录失败:', e)
|
||||
} finally {
|
||||
wechatRecordsLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const loadMoreRecords = async () => {
|
||||
if (!wechatCurrentPatient.value) return
|
||||
wechatPage.value++
|
||||
try {
|
||||
const res = await getWechatChatRecords({
|
||||
diagnosis_id: wechatCurrentPatient.value.id,
|
||||
patient_id: wechatCurrentPatient.value.patient_id,
|
||||
page_no: wechatPage.value,
|
||||
page_size: 20
|
||||
})
|
||||
wechatRecords.value.push(...(res?.lists || []))
|
||||
} catch (e) {
|
||||
console.error('加载更多记录失败:', e)
|
||||
}
|
||||
}
|
||||
|
||||
const handleAddRecord = async () => {
|
||||
if (!wechatNewNote.value.trim()) {
|
||||
feedback.msgWarning('请输入内容')
|
||||
return
|
||||
}
|
||||
wechatAddLoading.value = true
|
||||
try {
|
||||
await addWechatChatRecord({
|
||||
diagnosis_id: wechatCurrentPatient.value.id,
|
||||
patient_id: wechatCurrentPatient.value.patient_id,
|
||||
content: wechatNewNote.value.trim(),
|
||||
msg_type: 'note',
|
||||
staff_name: userStore.userInfo?.name || '',
|
||||
external_name: wechatCurrentPatient.value.patient_name || '',
|
||||
})
|
||||
wechatNewNote.value = ''
|
||||
// 重新加载记录
|
||||
handleWechatRecords(wechatCurrentPatient.value)
|
||||
} catch (e: any) {
|
||||
feedback.msgError(e?.msg || '添加失败')
|
||||
} finally {
|
||||
wechatAddLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleDeleteRecord = async (id: number) => {
|
||||
await feedback.confirm('确定删除该记录吗?')
|
||||
try {
|
||||
await deleteWechatChatRecord({ id })
|
||||
wechatRecords.value = wechatRecords.value.filter(r => r.id !== id)
|
||||
wechatRecordTotal.value--
|
||||
} catch (e: any) {
|
||||
feedback.msgError(e?.msg || '删除失败')
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await getDictOptions()
|
||||
getLists()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
<style lang="scss" scoped>
|
||||
.record-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.record-item {
|
||||
background: #f9fafb;
|
||||
border-radius: 8px;
|
||||
padding: 12px;
|
||||
border: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.record-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.record-content {
|
||||
font-size: 14px;
|
||||
line-height: 1.6;
|
||||
color: #333;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-all;
|
||||
}
|
||||
</style>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user