From dc37f4822e4333a1297733f2d671ab6f8f9f9d2a Mon Sep 17 00:00:00 2001 From: lion <120344285@qq.com> Date: Tue, 7 Apr 2026 17:16:05 +0800 Subject: [PATCH] up --- .env.development | 6 +- .env.production | 4 +- .env.staging | 4 +- package.json | 3 +- src/api/system/user.js | 30 +- src/api/visit/vip.js | 42 ++ src/components/morecharts/PieChart.vue | 32 +- src/views/dashboard/components/PanelGroup.vue | 220 +++--- src/views/dashboard/index.vue | 93 ++- src/views/system/user.vue | 170 ++++- src/views/visit/check.vue | 196 ++--- src/views/visit/component/addCommon.vue | 690 +++++++++++------- src/views/visit/component/addVip.vue | 179 +++++ src/views/visit/longterm.vue | 289 ++++++-- src/views/visit/record.vue | 344 ++++++--- src/views/visit/vip.vue | 222 ++++++ vue.config.js | 5 +- 17 files changed, 1869 insertions(+), 660 deletions(-) create mode 100644 src/api/visit/vip.js create mode 100644 src/views/visit/component/addVip.vue create mode 100644 src/views/visit/vip.vue diff --git a/.env.development b/.env.development index 5a5271d..564053e 100644 --- a/.env.development +++ b/.env.development @@ -1,6 +1,6 @@ # just a flag ENV = 'development' -# base api -VUE_APP_BASE_API = https://bd-fangke.ali251.langye.net -VUE_APP_UPLOAD_API = https://bd-fangke.ali251.langye.net/api/admin/upload-file +# base api(与测试/正式环境同一后端域名) +VUE_APP_BASE_API = http://yxbd-fangke.ali251.langye.net +VUE_APP_UPLOAD_API = http://yxbd-fangke.ali251.langye.net/api/admin/upload-file diff --git a/.env.production b/.env.production index ab0119b..66f3506 100644 --- a/.env.production +++ b/.env.production @@ -2,5 +2,5 @@ ENV = 'production' # base api -VUE_APP_BASE_API = https://bd-fangke.ali251.langye.net -VUE_APP_UPLOAD_API = https://bd-fangke.ali251.langye.net/api/admin/upload-file \ No newline at end of file +VUE_APP_BASE_API = +VUE_APP_UPLOAD_API = /api/admin/upload-file diff --git a/.env.staging b/.env.staging index a8793a0..69a97b4 100644 --- a/.env.staging +++ b/.env.staging @@ -4,5 +4,5 @@ NODE_ENV = production ENV = 'staging' # base api -VUE_APP_BASE_API = '/stage-api' - +VUE_APP_BASE_API = https://yxbd-fangke.ali251.langye.net +VUE_APP_UPLOAD_API = https://yxbd-fangke.ali251.langye.net/api/admin/upload-file diff --git a/package.json b/package.json index c596a6e..14f1721 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,8 @@ "vue": "2.6.10", "vue-count-to": "^1.0.13", "vue-router": "3.0.6", - "vuex": "3.1.0" + "vuex": "3.1.0", + "xlsx": "^0.18.5" }, "devDependencies": { "@vue/cli-plugin-babel": "4.4.4", diff --git a/src/api/system/user.js b/src/api/system/user.js index 1cb4a77..c551c77 100644 --- a/src/api/system/user.js +++ b/src/api/system/user.js @@ -11,10 +11,10 @@ export function save(data) { export function listuser(params) { return request({ url: '/api/admin/admin', - method: 'get', + method: 'get', params }) -} +} export function del(data) { return request({ @@ -22,13 +22,33 @@ export function del(data) { method: 'post', data }) -} - +} + export function setRoles(data) { return request({ url: '/api/admin/admin/set-roles', method: 'post', data }) -} +} + +export function importPreview(data) { + return request({ + url: '/api/admin/admin/import-preview', + method: 'post', + data, + timeout: 120000, + isLoading: true + }) +} + +export function importSubmit(data) { + return request({ + url: '/api/admin/admin/import-submit', + method: 'post', + data, + timeout: 120000, + isLoading: true + }) +} diff --git a/src/api/visit/vip.js b/src/api/visit/vip.js new file mode 100644 index 0000000..e7ae9e8 --- /dev/null +++ b/src/api/visit/vip.js @@ -0,0 +1,42 @@ +import request from "@/utils/request"; + +export function getList(params) { + return request({ + url: "/api/admin/vip-customer/index", + method: "get", + params + }); +} + +export function show(params) { + return request({ + method: "get", + url: "/api/admin/vip-customer/show", + params + }); +} + +export function save(data) { + return request({ + method: "post", + url: "/api/admin/vip-customer/save", + data + }); +} + +export function destroy(params) { + return request({ + method: "get", + url: "/api/admin/vip-customer/destroy", + params + }); +} + +export function importVip(data) { + return request({ + method: "post", + url: "/api/admin/vip-customer/import", + data + }); +} + diff --git a/src/components/morecharts/PieChart.vue b/src/components/morecharts/PieChart.vue index 3979b90..82af3f1 100644 --- a/src/components/morecharts/PieChart.vue +++ b/src/components/morecharts/PieChart.vue @@ -60,8 +60,8 @@ this.setOptions(this.chartData); }, setOptions(chartdata) { - console.log(chartdata.xArr) this.chart.setOption({ + color: ['#409EFF', '#8B6EF5', '#2BB673', '#FF8A00'], dataZoom: [ //给x轴设置滚动条 // { @@ -127,30 +127,30 @@ xAxis: [{ type: 'category', data: chartdata.xArr, - axisLine: { - show: false //不显示坐标轴轴线 - }, - axisTick: { - show: false //不显示坐标轴刻度 + axisLine: { + show: false //不显示坐标轴轴线 + }, + axisTick: { + show: false //不显示坐标轴刻度 }, }], yAxis: [{ type: 'value', - minInterval: 1, - axisLine: { - show: false //不显示坐标轴轴线 - }, - axisTick: { - show: false //不显示坐标轴刻度 + minInterval: 1, + axisLine: { + show: false //不显示坐标轴轴线 + }, + axisTick: { + show: false //不显示坐标轴刻度 }, - }], + }], radiusArr:[], series: [{ name: '数据', type: 'pie', stack: 'vistors', - barWidth: '60%', - radius: chartdata.radiusArr, + barWidth: '60%', + radius: chartdata.radiusArr, center: ['50%', '45%'], data: chartdata.yArr, animationDuration @@ -160,4 +160,4 @@ } } } - + diff --git a/src/views/dashboard/components/PanelGroup.vue b/src/views/dashboard/components/PanelGroup.vue index 2c638c6..67a9268 100644 --- a/src/views/dashboard/components/PanelGroup.vue +++ b/src/views/dashboard/components/PanelGroup.vue @@ -10,15 +10,15 @@
总人数/总入厂人数
-
- - - - - - +
+ + + + + +
核销比:{{toCaculateper(totaldata.common_visit.enter_visit,totaldata.common_visit.total)}}
@@ -29,13 +29,13 @@
- - - - - + + + + +
核销比:{{toCaculateper(totaldata.common_visit.today_enter_visit,totaldata.common_visit.today_total)}}
@@ -50,38 +50,78 @@
-
总人数/总核销人数
-
-
-
- - - - - - -
-
核销比:{{toCaculateper(totaldata.work_visit.enter_visit,totaldata.work_visit.total)}}
-
-
-
-
今日人数/今日核销人数
-
-
-
- - - - - - -
-
核销比:{{toCaculateper(totaldata.work_visit.today_enter_visit,totaldata.work_visit.today_total)}}
-
+
总人数/总核销人数
+
+
+
+ + + + + + +
+
核销比:{{toCaculateper(totaldata.work_visit.enter_visit,totaldata.work_visit.total)}}
+
+
+
+
今日人数/今日核销人数
+
+
+
+ + + + + + +
+
核销比:{{toCaculateper(totaldata.work_visit.today_enter_visit,totaldata.work_visit.today_total)}}
+
+
+
+
+
+ VIP客户 + +
+
+
总人数/总核销人数
+
+
+
+ + + + + + +
+
核销比:{{toCaculateper(totaldata.vip_visit.enter_visit,totaldata.vip_visit.total)}}
+
+
+
+
今日人数/今日核销人数
+
+
+
+ + + + + + +
+
核销比:{{toCaculateper(totaldata.vip_visit.today_enter_visit,totaldata.vip_visit.today_total)}}
+
@@ -91,42 +131,42 @@
-
总预约数/总核销数
-
-
-
- - - - - - -
-
核销比:{{toCaculateper(totaldata.car_visit.enter_visit,totaldata.car_visit.total)}}
-
-
-
-
今日预约数/今日核销数
-
-
-
- - - - - - -
-
核销比:{{toCaculateper(totaldata.work_visit.today_enter_visit,totaldata.car_visit.today_total)}}
-
+
总预约数/总核销数
+
+
+
+ + + + + + +
+
核销比:{{toCaculateper(totaldata.car_visit.enter_visit,totaldata.car_visit.total)}}
+
+
+
+
今日预约数/今日核销数
+
+
+
+ + + + + + +
+
核销比:{{toCaculateper(totaldata.car_visit.today_enter_visit,totaldata.car_visit.today_total)}}
+
- - + +
@@ -146,20 +186,26 @@ "common_visit": { "enter_visit": 0, "today_enter_visit": 0, - "today_total": 0, + "today_total": 0, "total":0 }, "work_visit": { "enter_visit": 0, "today_enter_visit": 0, - "today_total": 0, + "today_total": 0, "total":0 }, "car_visit": { "enter_visit": 0, "today_enter_visit": 0, - "today_total": 0, + "today_total": 0, "total":0 + }, + "vip_visit": { + "enter_visit": 0, + "today_enter_visit": 0, + "today_total": 0, + "total": 0 } } } @@ -211,7 +257,7 @@ .box { position: relative; - width: 33%; + width: 24%; margin-left: 0.5%; margin-right: 0.5%; // margin-bottom: 2.375rem; @@ -476,4 +522,4 @@ } } } - + diff --git a/src/views/dashboard/index.vue b/src/views/dashboard/index.vue index 7cac527..83f2293 100644 --- a/src/views/dashboard/index.vue +++ b/src/views/dashboard/index.vue @@ -37,41 +37,53 @@ chartData: {}, pieData:{ - // xArr:['普通访客','施工访客','物流车辆'], yArr:[ { - value:20, + value:0, name:"普通访客" },{ - value:40, + value:0, name:"施工访客" },{ - value:40, + value:0, name:"物流车辆" + },{ + value:0, + name:"VIP客户" }], radiusArr:'50%' }, LineData:{ xArr: ['9:00-10:00','10:00-11:00','11:00-12:00','12:00-13:00'], - legendArr: ["普通访客", "施工访客", "物流车辆"], + legendArr: ["普通访客", "施工访客", "物流车辆", "VIP客户"], series:[ { name: '普通访客', type: 'line', stack: 'Total', - data: [20,10,40,70] + data: [0,0,0,0], + itemStyle: { color: '#409EFF' } }, { name: '施工访客', type: 'line', stack: 'Total', - data: [15,75,35,45] + data: [0,0,0,0], + itemStyle: { color: '#8B6EF5' } }, { name: '物流车辆', type: 'line', stack: 'Total', - data: [75,35,60,10] + data: [0,0,0,0], + itemStyle: { color: '#2BB673' } + }, + { + name: 'VIP客户', + type: 'line', + stack: 'Total', + data: [0,0,0,0], + itemStyle: { color: '#FF8A00' } } ] } @@ -87,26 +99,53 @@ methods: { async loadData() { await getChartsHome().then((res) => { - console.log(res); this.list = res.list; - // this.chartData = res; - // let _business_data = []; - // let _collect_data = []; - // res.business_data.map(item => { - // _business_data.push(item.server_money_total) - // _collect_data.push(item.collect_money) - // }) - // this.business_data = _business_data; - // this.collect_data = _collect_data; - // let _customerArr = []; - // let _orderArr = []; - - // res.order_data.map(item => { - // _customerArr.push(item.active_customer) - // _orderArr.push(item.order_total) - // }) - // this.customerArr = _customerArr; - // this.orderArr = _orderArr; + + this.pieData = { + ...this.pieData, + yArr: [ + { value: res.list?.common_visit?.total || 0, name: "普通访客" }, + { value: res.list?.work_visit?.total || 0, name: "施工访客" }, + { value: res.list?.car_visit?.total || 0, name: "物流车辆" }, + { value: res.list?.vip_visit?.total || 0, name: "VIP客户" } + ] + }; + + const dateList = res.all_date_list || []; + this.LineData = { + ...this.LineData, + xArr: dateList.map(item => item.date?.slice(5) || item.date), + series: [ + { + name: '普通访客', + type: 'line', + stack: 'Total', + data: dateList.map(item => item.common_visit || 0), + itemStyle: { color: '#409EFF' } + }, + { + name: '施工访客', + type: 'line', + stack: 'Total', + data: dateList.map(item => item.work_visit || 0), + itemStyle: { color: '#8B6EF5' } + }, + { + name: '物流车辆', + type: 'line', + stack: 'Total', + data: dateList.map(item => item.car_visit || 0), + itemStyle: { color: '#2BB673' } + }, + { + name: 'VIP客户', + type: 'line', + stack: 'Total', + data: dateList.map(item => item.vip_visit || 0), + itemStyle: { color: '#FF8A00' } + } + ] + }; }).catch() }, init() { diff --git a/src/views/system/user.vue b/src/views/system/user.vue index 092f9a5..38b18a1 100644 --- a/src/views/system/user.vue +++ b/src/views/system/user.vue @@ -10,6 +10,7 @@ +
@@ -99,6 +100,60 @@ 确 定
+ + +
+ 模板列:姓名、用户名、手机号、所属部门、职位、生日、邮箱(必填:姓名、用户名、手机号、所属部门);可选列 密码。 + 密码规则:新建用户密码留空则初始为「Admin+当前年份」;填写则使用填写密码。已存在用户密码留空则不修改密码;填写则重置为填写密码。 +
+
+ 下载导入模板 + + 选择文件并预览 + + 支持 `.xls/.xlsx/.csv`,请先预览再导入 +
+
+ 模板字段缺失:{{ previewSummary.missingHeaders.join("、") }} +
+
+ 共 {{ previewRows.length }} 条;已存在用户 {{ previewSummary.existsCount }} 条;缺失部门 {{ previewSummary.missingDepartmentCount }} 条 +
+ + + + + + + + + + + + + + + + +
请先选择导入文件进行预览
+ + 取消 + 确认导入 + +
@@ -109,9 +164,12 @@ save, setRoles, listuser, - del + del, + importPreview, + importSubmit } from "../../api/system/user.js"; import {listCommondepartment} from '../../api/common.js' + import * as XLSX from "xlsx"; import { list } from "../../api/system/role.js"; @@ -182,7 +240,15 @@ searchFields: { keyword: "" }, - tableData: [] + tableData: [], + importDialogVisible: false, + importFile: null, + previewRows: [], + previewSummary: { + existsCount: 0, + missingDepartmentCount: 0, + missingHeaders: [] + } } }, methods: { @@ -310,6 +376,106 @@ this.$refs[formName].resetFields(); that.dialogFormVisible = false; }, + openImportDialog() { + this.importDialogVisible = true; + this.importFile = null; + this.previewRows = []; + this.previewSummary = { + existsCount: 0, + missingDepartmentCount: 0, + missingHeaders: [] + }; + }, + downloadTemplate() { + // 使用 aoa_to_sheet 固定第 1 行为表头,避免 json_to_sheet 在部分环境下列顺序/展示异常 + const header = ["姓名", "用户名", "手机号", "所属部门", "职位", "生日", "邮箱", "密码"]; + const example = ["张三", "zhangsan", "13800000000", "生产部", "总监", "1990-01-01", "zhangsan@example.com", ""]; + const ws = XLSX.utils.aoa_to_sheet([header, example]); + ws["!cols"] = [ + { wch: 10 }, + { wch: 12 }, + { wch: 14 }, + { wch: 14 }, + { wch: 10 }, + { wch: 12 }, + { wch: 28 }, + { wch: 10 } + ]; + const wb = XLSX.utils.book_new(); + XLSX.utils.book_append_sheet(wb, ws, "用户导入"); + XLSX.writeFile(wb, "用户导入模板.xlsx"); + }, + // 使用 http-request 自定义上传,避免 before-upload + async 在部分环境下不触发完成的问题 + handleImportHttpRequest(option) { + const file = option.file; + this.runImportPreview(file); + }, + async runImportPreview(file) { + const isAllow = /\.(xls|xlsx|csv)$/i.test(file.name); + if (!isAllow) { + this.$Message.warning("仅支持xls/xlsx/csv格式"); + return; + } + this.importFile = file; + const formData = new FormData(); + formData.append("file", file); + try { + const res = await importPreview(formData); + this.previewRows = res.rows || []; + this.previewSummary.existsCount = res.exists_count || 0; + this.previewSummary.missingDepartmentCount = (res.missing_department_rows || []).length; + this.previewSummary.missingHeaders = []; + if (this.previewSummary.existsCount > 0) { + this.$Message.warning(`检测到${this.previewSummary.existsCount}个已存在用户名,导入时可选择是否更新`); + } + if (this.previewSummary.missingDepartmentCount > 0) { + this.$Message.error("存在未创建的部门,请先创建部门后再导入"); + } + } catch (error) { + this.previewRows = []; + const msg = + (error && error.response && error.response.data && (error.response.data.errmsg || error.response.data.message)) || + (error && error.message) || + ""; + const headerMatch = msg.match(/(.+字段不存在)/g); + this.previewSummary.missingHeaders = headerMatch || []; + this.$Message.warning(msg || "预览失败"); + } + }, + submitImport() { + if (!this.importFile) { + this.$Message.warning("请先选择导入文件"); + return; + } + if (this.previewSummary.missingDepartmentCount > 0) { + this.$Message.error("请先创建部门,再执行导入"); + return; + } + const doImport = async (forceUpdate) => { + const formData = new FormData(); + formData.append("file", this.importFile); + formData.append("force_update", forceUpdate ? 1 : 0); + const result = await importSubmit(formData); + this.$Message.success(`导入完成:新增${result.created || 0},更新${result.updated || 0}`); + this.importDialogVisible = false; + this.load(); + }; + if (this.previewSummary.existsCount > 0) { + this.$Modal.confirm({ + title: `用户已存在,是否直接更新?`, + content: "检测到部分用户名已存在,确认后将直接更新这些用户信息。", + onOk: () => doImport(true).catch((error) => { + const message = error?.response?.data?.message || "导入失败"; + this.$Message.error(message); + }), + }); + return; + } + doImport(false).catch((error) => { + const message = error?.response?.data?.message || "导入失败"; + this.$Message.error(message); + }); + }, setrole(obj) { var that = this; var rolelist = obj.rolelist; diff --git a/src/views/visit/check.vue b/src/views/visit/check.vue index 318d426..5a381df 100644 --- a/src/views/visit/check.vue +++ b/src/views/visit/check.vue @@ -13,33 +13,38 @@ +
访客类型
+ + + +
只看自己被访记录
- - -
只看自己审核记录
- - -
只看自己创建
- + + +
只看自己审核记录
+ + +
只看自己创建
+ 查询 @@ -51,36 +56,36 @@ - @@ -89,11 +94,11 @@ import { getList, destroy - } from '@/api/visit/record.js' - import showVisit from '@/views/visit/component/showVisit' + } from '@/api/visit/record.js' + import showVisit from '@/views/visit/component/showVisit' import store from "@/store/modules/user.js" export default { - components: { + components: { showVisit }, data() { @@ -104,13 +109,20 @@ page_size: 10, keyword: "", audit_status: 0, + type: '', my_accept_admin: 0, - my_audit: 1, - my_self:0, + my_audit: 1, + my_self:0, is_auth:0 - }, + }, is_admin:false, selectRange: [], + typeList: [ + { id: 1, value: '普通访客' }, + { id: 2, value: '施工访客' }, + { id: 3, value: '物流车辆' }, + { id: 4, value: 'VIP访客' } + ], statusList: [{ id: -1, value: '待学习' @@ -165,19 +177,19 @@ sortable: false, prop: 'audit_status_text', width: 120 - },{ - label: '被访人', - sortable: false, - prop: 'accept_admin.name', - width: 120, + },{ + label: '被访人', + sortable: false, + prop: 'accept_admin.name', + width: 120, }, { label: '是否随访', sortable: false, prop: 'follw_people', - width: 80, - formatter:(cell, data, value)=>{ - return value.length>0?'是':'否' + width: 80, + formatter:(cell, data, value)=>{ + return value.length>0?'是':'否' } }, { @@ -190,9 +202,9 @@ label: '证件类型', sortable: false, prop: 'credent', - width: 120, - formatter:(cell, data, value)=>{ - return value==1?'身份证':'护照' + width: 120, + formatter:(cell, data, value)=>{ + return value==1?'身份证':'护照' }, }, { @@ -236,23 +248,23 @@ { label: '创建人', sortable: false, - prop: 'admin.name', - width: 120, - formatter(cell, data, value){ - return value?value:'' + prop: 'admin.name', + width: 120, + formatter(cell, data, value){ + return value?value:'' } } ] } }, computed: {}, - mounted() { - const state = store.state - console.log("state",state) - if(state.myRoles && state.myRoles.includes('系统管理员')){ - this.select.my_audit = 0 - this.is_admin = true - } + mounted() { + const state = store.state + console.log("state",state) + if(state.myRoles && state.myRoles.includes('系统管理员')){ + this.select.my_audit = 0 + this.is_admin = true + } console.log(this.is_admin,state.myRoles.includes('系统管理员')) this.getList() }, @@ -263,7 +275,7 @@ this.data = res.data this.total = res.total }, - deleteStudy(row) { + deleteStudy(row) { console.log(row) destroy({ id: row.id @@ -271,13 +283,13 @@ this.$successMessage('destroy', '拜访记录') this.getList() }) - }, - checkRecords(row){ - this.$refs['showVisit'].id = row.id - this.$refs['showVisit'].formDataType='checkrecord' - this.$refs['showVisit'].isShow = true - // this.$refs['checkRecord'].id = row.id - // this.$refs['checkRecord'].isShow = true + }, + checkRecords(row){ + this.$refs['showVisit'].id = row.id + this.$refs['showVisit'].formDataType='checkrecord' + this.$refs['showVisit'].isShow = true + // this.$refs['checkRecord'].id = row.id + // this.$refs['checkRecord'].isShow = true } }, } @@ -317,4 +329,4 @@ line-height: 21px; } } - + diff --git a/src/views/visit/component/addCommon.vue b/src/views/visit/component/addCommon.vue index 71fc8dd..2df4b11 100644 --- a/src/views/visit/component/addCommon.vue +++ b/src/views/visit/component/addCommon.vue @@ -73,9 +73,9 @@
车辆类型:
-
- - {{item.value}} +
+ + {{item.value}}
@@ -99,6 +99,21 @@
+ - - - - - - + + + + + + - --> -