Compare commits

2 Commits

Author SHA1 Message Date
30237639c7 feat(医疗模块): 更新新增/修改弹窗表单,添加关联用户功能
- 在新增/修改弹窗中,添加关联用户的输入框,支持手机号/邮箱的自动补全
- 更新表单验证规则,确保关联用户为必填项
- 调整表单项的样式,统一标签宽度,提升用户体验
- 增加用户列表加载功能,优化用户选择流程
2026-03-23 16:02:59 +08:00
301b3a2582 feat(统计业务): 新增全部用户统计页面及相关功能
- 新增用户统计页面,支持按日、月、年统计用户数据
- 引入用户统计基础组件,包含数据展示和导出功能
- 更新主统计页面以集成新的统计选项
2026-03-23 16:02:46 +08:00
6 changed files with 109 additions and 28 deletions

View File

@@ -61,7 +61,7 @@
<!-- 弹窗, 新增 / 修改 -->
<el-dialog :visible.sync="addOrUpdateVisible" :close-on-click-modal="false" :append-to-body="true" :title="titlesub"
width="50%" @close="cancleClose">
<el-form :inline="true" :model="addForm" ref="addFormRef" :rules="addFormRule">
<el-form :inline="true" :model="addForm" ref="addFormRef" :rules="addFormRule" label-width="100px">
<el-row type="flex" justify="center">
<el-form-item label="姓名" prop="name">
<el-input style="width:500px" v-model="addForm.name"></el-input>
@@ -76,17 +76,39 @@
</el-form-item>
</el-row>
<el-row type="flex" justify="center">
<el-form-item label="url" prop="url" label-width="48px">
<el-form-item label="关联用户" prop="tel">
<el-autocomplete
size="small"
style="width: 500px;"
v-model="addForm.tel"
:debounce="500"
:fetch-suggestions="loadAll"
placeholder="请输入手机号/邮箱"
@select="handleSelect"
>
<template #default="{ item }">
<div class="custom-item">
<span>{{ item.tel ? item.tel : item.email }}</span>
<span style="color: gray; margin-left: 10px;" v-if="item.name"
>({{ item.name }})</span
>
</div>
</template>
</el-autocomplete>
</el-form-item>
</el-row>
<el-row type="flex" justify="center">
<el-form-item label="url" prop="url">
<el-input style="width:500px" v-model="addForm.url"></el-input>
</el-form-item>
</el-row>
<el-row type="flex" justify="center">
<el-form-item label="内容" prop="content" label-width="48px">
<el-form-item label="内容" prop="content">
<el-input style="width:500px" v-model="addForm.content" :rows="5" type="textarea"></el-input>
</el-form-item>
</el-row>
<el-row type="flex" justify="center">
<el-form-item label="地址" prop="cityId" label-width="48px">
<el-form-item label="地址" prop="cityId">
<el-select v-model="addForm.provId" placeholder="请选择省份" style="width:249px" @change="provinceChange">
<el-option v-for="item in provinceEntity" :key="item.provId" :label="item.provName" :value="item.provId">
</el-option>
@@ -98,17 +120,19 @@
</el-form-item>
</el-row>
<el-row type="flex" justify="center">
<el-form-item label="图片" prop="img" label-width="48px">
<el-upload class="el-uploadfeng " ref="files"
:class="{ uoloadSty: dataForm.showBtnDealImg, disUoloadSty: dataForm.noneBtnImg }"
:action="baseUrl + '/oss/fileoss'" list-type="picture-card" :file-list="fileList"
:on-success="handlePicSuccess" accept=".jpeg,.jpg,.gif,.png" :on-remove="handleRemove">
<i class="el-icon-plus"></i>
</el-upload>
<el-form-item label="图片" prop="img">
<div style="width: 500px;">
<el-upload class="el-uploadfeng " ref="files"
:class="{ uoloadSty: dataForm.showBtnDealImg, disUoloadSty: dataForm.noneBtnImg }"
:action="baseUrl + '/oss/fileoss'" list-type="picture-card" :file-list="fileList"
:on-success="handlePicSuccess" accept=".jpeg,.jpg,.gif,.png" :on-remove="handleRemove">
<i class="el-icon-plus"></i>
</el-upload>
</div>
</el-form-item>
</el-row>
<el-row type="flex" justify="center">
<el-form-item label="排序" label-width="48px">
<el-form-item label="排序">
<el-input style="width:500px" v-model="addForm.sort"></el-input>
</el-form-item>
</el-row>
@@ -131,6 +155,7 @@
cityEntity: [],
provinceEntity: [], //省地址
typeList: [], //类型列表
userList: [], //用户列表
booknameList: [],
dataForm: {
dictType: '', //分类
@@ -145,6 +170,7 @@
content: '',
cityId: '',
provId: '',
tel: '',
sort: 0
},
editId: '',
@@ -153,6 +179,10 @@
required: true,
message: "请选择分类"
}],
tel: [{
required: true,
message: "请选择关联用户"
}],
name: [{
required: true,
message: "请输入姓名"
@@ -239,6 +269,32 @@
this.dataListLoading = false
})
},
loadAll(queryString, cb) {
if (queryString == "") {
return false;
}
this.$http({
url: this.$http.adornUrl("/book/user/getUserList"),
method: "post",
data: this.$http.adornData({
page: 1,
limit: 20,
key: queryString
})
}).then(({ data }) => {
if (data && data.code === 0) {
var arr = data.user.records;
cb(arr);
} else {
cb([]);
}
});
},
handleSelect(data) {
console.log("data at line 161:", data);
this.addForm.userId = data.id;
this.addForm.tel = data.tel;
},
provinceChange() {
this.addForm.cityId = ''
this.cityEntity = []
@@ -281,6 +337,7 @@
this.addForm.content = row.content
this.addForm.typeId = row.type.toString()
this.addForm.sort = row.sort
this.addForm.tel = row.tel
this.fileList = row.imgList
for (var i = 0; i < this.provinceEntity.length; i++) {
for (var j = 0; j < this.provinceEntity[i].cityList.length; j++) {
@@ -382,7 +439,9 @@
"content": this.addForm.content,
"type": this.addForm.typeId,
"cityId": this.addForm.cityId,
"sort": this.addForm.sort
"sort": this.addForm.sort,
"userId": this.addForm.userId,
"tel": this.addForm.tel
})
}).then(({
data

View File

@@ -0,0 +1,14 @@
<template>
<userStatisticsBase mode="null" />
</template>
<script>
import userStatisticsBase from './userStatisticsBase'
export default {
components: {
userStatisticsBase
}
}
</script>

View File

@@ -1,9 +1,10 @@
<template>
<div class="mod-config">
<el-tabs v-model="activeTab" type="card">
<el-tab-pane label="首页" name="homeUser" />
<el-tab-pane label="当日" name="dayUser" />
<el-tab-pane label="月度" name="monthUser" />
<el-tab-pane label="年度" name="yearUser" />
<el-tab-pane label="全部" name="allUser" />
<el-tab-pane label="留存率" name="retainUser" />
</el-tabs>
<keep-alive>
@@ -13,21 +14,23 @@
</template>
<script>
import homeUser from './homeUser'
import allUser from './allUser'
import dayUser from './dayUser'
import monthUser from './monthUser'
import yearUser from './yearUser'
import retainUser from './retainUser'
export default {
components: {
homeUser,
dayUser,
monthUser,
yearUser,
allUser,
retainUser
},
data() {
return {
activeTab: 'homeUser'
activeTab: 'dayUser'
}
}
}

View File

@@ -1,6 +1,6 @@
<template>
<div class="user-statistics-page" v-loading="loading">
<div v-if="mode !== 'day'" class="query-section">
<div v-if="mode !== 'null'" class="query-section">
<el-form :inline="true" @keyup.enter.native="getDataList">
<el-form-item :label="queryLabel">
<el-date-picker
@@ -50,7 +50,7 @@
<el-table
:data="tableData"
border
:height="mode !== 'day' ? 470 : 520"
:height="mode !== 'null' ? 470 : 520"
:header-cell-style="{ textAlign: 'center', padding: '6px 0' }"
:cell-style="{ textAlign: 'center', padding: '6px 0' }"
>
@@ -158,20 +158,23 @@ export default {
pickerType() {
if (this.mode === 'day') return 'date'
if (this.mode === 'month') return 'month'
return 'year'
if (this.mode === 'year') return 'year'
return ''
},
displayFormat() {
if (this.mode === 'day') return 'yyyy-MM-dd'
if (this.mode === 'month') return 'yyyy-MM'
return 'yyyy'
if (this.mode === 'year') return 'yyyy'
return ''
},
valueFormat() {
return this.displayFormat
},
timePrefix() {
if (this.mode === 'day') return '当日'
if (this.mode === 'month') return '当月'
return '当年'
// if (this.mode === 'day') return '当日'
// if (this.mode === 'month') return '当月'
// if (this.mode === 'year') return '当年'
return '当日'
},
pickerOptions() {
return {
@@ -213,7 +216,8 @@ export default {
const now = new Date()
if (this.mode === 'day') return formatDate(now)
if (this.mode === 'month') return formatMonth(now)
return formatYear(now)
if (this.mode === 'year') return formatYear(now)
return null
},
normalizeRows(userList = []) {
return userList.map((item) => ({
@@ -281,12 +285,13 @@ export default {
data: http.adornData({
date: this.currentDate
}),
responseType: 'blob'
responseType: 'blob',
timeout: 0
}).then((response) => {
const blob = new Blob([response.data])
const link = document.createElement('a')
link.href = URL.createObjectURL(blob)
link.download = `用户统计报表-${this.currentDate}.xlsx`
link.download = `用户统计报表-${this.currentDate || '全部'}.xlsx`
link.click()
URL.revokeObjectURL(link.href)
}).catch(() => {

View File

@@ -27,7 +27,7 @@
size="small"
style="width: 100%;"
v-model="dataForm.userKey"
debounce="500"
:debounce="500"
:fetch-suggestions="loadAll"
placeholder="请输入手机号/邮箱"
@select="handleSelect"