diff --git a/.env.development b/.env.development index e04c29d..5fedf0f 100644 --- a/.env.development +++ b/.env.development @@ -1,11 +1,14 @@ # 页面标题 -VUE_APP_TITLE = 客户 GEO TAG管理系统 +VUE_APP_TITLE = 客户 GeoTag管理系统 # 开发环境配置 ENV = 'development' -# GEO TAG/开发环境 +# GeoTag/开发环境 VUE_APP_BASE_API = '/dev-api' # 路由懒加载 VUE_CLI_BABEL_TRANSPILE_MODULES = true +VUE_APP_GOOGLE_MAPS_API_KEY = +VUE_APP_AMAP_WEB_KEY = +VUE_APP_AMAP_SECURITY_JS_CODE = diff --git a/.env.production b/.env.production index 26de082..3384cfa 100644 --- a/.env.production +++ b/.env.production @@ -1,5 +1,5 @@ # 页面标题 -VUE_APP_TITLE = 客户GEO TAG管理系统 +VUE_APP_TITLE = 客户GeoTag管理系统 # 生产环境配置 ENV = 'production' diff --git a/.env.staging b/.env.staging index e3101dc..8cc5180 100644 --- a/.env.staging +++ b/.env.staging @@ -1,8 +1,8 @@ NODE_ENV = production -VUE_APP_TITLE =客户 GEO TAG管理系统 +VUE_APP_TITLE =客户 GeoTag管理系统 # 测试环境配置 ENV = 'staging' -# GEO TAG/测试环境 +# GeoTag/测试环境 VUE_APP_BASE_API = '/stage-api' diff --git a/package.json b/package.json index 96e4462..d699381 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,8 @@ { "name": "ruoyi", "version": "3.8.9", - "description": "客户GEO TAG管理系统", - "author": "GEO TAG", + "description": "客户GeoTag管理系统", + "author": "GeoTag", "license": "MIT", "scripts": { "dev": "vue-cli-service serve", diff --git a/src/api/business/businessUser.js b/src/api/business/businessUser.js index 2b7e396..0133d4d 100644 --- a/src/api/business/businessUser.js +++ b/src/api/business/businessUser.js @@ -50,4 +50,12 @@ export function exportBusinessUser(query) { method: 'get', params: query }) -} \ No newline at end of file +} + +export function assignBusinessUserDevices(data) { + return request({ + url: '/business/businessUser/assign/devices', + method: 'post', + data: data + }) +} diff --git a/src/api/device/device.js b/src/api/device/device.js index bf2ce68..41826bc 100644 --- a/src/api/device/device.js +++ b/src/api/device/device.js @@ -15,6 +15,20 @@ export function getDevice(id) { }) } +export function getDeviceTrajectory(id) { + return request({ + url: '/device/device/' + id + '/trajectory', + method: 'get' + }) +} + +export function getDeviceTrajectoryMapConfig() { + return request({ + url: '/device/device/trajectory/map-config', + method: 'get' + }) +} + export function addDevice(data) { return request({ url: '/device/device', @@ -86,7 +100,6 @@ export function getBatchNo() { }) } -// 批量激活设备(将选中设备标记为激活:1) export function batchActivateDevice(ids) { return request({ url: '/device/device/activate/batch', @@ -97,6 +110,16 @@ export function batchActivateDevice(ids) { }) } +export function batchDisableDevice(ids) { + return request({ + url: '/device/device/disable/batch', + method: 'put', + data: { + ids: ids + } + }) +} + export function claimDeviceBatch(data) { return request({ url: '/device/device/claim/batch', @@ -111,4 +134,4 @@ export function listAllDevice(query) { method: 'get', params: query }) -} \ No newline at end of file +} diff --git a/src/api/system/role.js b/src/api/system/role.js index 801e56f..6c706ca 100644 --- a/src/api/system/role.js +++ b/src/api/system/role.js @@ -9,6 +9,14 @@ export function listRole(query) { }) } +// 查询当前用户所属企业的角色选项 +export function currentRoleOptions() { + return request({ + url: '/business/businessRole/current/options', + method: 'get' + }) +} + // 查询角色详细 export function getRole(roleId) { return request({ diff --git a/src/api/system/user.js b/src/api/system/user.js index b6cfbcc..0384e2b 100644 --- a/src/api/system/user.js +++ b/src/api/system/user.js @@ -4,7 +4,7 @@ import { praseStrEmpty } from "@/utils/ruoyi"; // 查询用户列表 export function listUser(query) { return request({ - url: '/business/businessUser/list', + url: '/business/businessUser/employee/page', method: 'get', params: query }) @@ -27,6 +27,15 @@ export function addUser(data) { }) } +// 新增员工 +export function addEmployeeUser(data) { + return request({ + url: '/business/businessUser/employee', + method: 'post', + data: data + }) +} + // 修改用户 export function updateUser(data) { return request({ @@ -36,6 +45,15 @@ export function updateUser(data) { }) } +// 修改员工 +export function updateEmployeeUser(data) { + return request({ + url: '/business/businessUser/employee', + method: 'put', + data: data + }) +} + // 删除用户 export function delUser(userId) { return request({ @@ -69,7 +87,7 @@ export function resetUserPwd(userId, password) { // 用户状态修改 export function changeUserStatus(userId, status) { const data = { - userId, + id: userId, status } return request({ @@ -125,3 +143,13 @@ export function importTemplate() { method: 'get' }) } + + + +export function listEmployeeUser(query) { + return request({ + url: '/business/businessUser/employee/list', + method: 'get', + params: query + }) +} diff --git a/src/components/business/BusinessSelect.vue b/src/components/business/BusinessSelect.vue index 1efd2ed..98c9165 100644 --- a/src/components/business/BusinessSelect.vue +++ b/src/components/business/BusinessSelect.vue @@ -1,56 +1,47 @@ - - + @@ -329,10 +383,36 @@ :visible.sync="businessSelectVisible" @select="handleBusinessSelect" /> + + + + + {{ detailForm.id || "-" }} {{ detailForm.sn || "-" }} {{ detailForm.mac || "-" }} - {{ detailForm.privateKey || "-" }} - {{ detailForm.batchNo || "-" }} {{ detailForm.hashid || "-" }} {{ detailForm.model || "-" }} - {{ detailForm.merchantName || "-" }} + + {{ detailForm.businessName || detailForm.merchantName || "-" }} + + + {{ formatAssignedUsers(detailForm.assignedUsers) }} + + + {{ detailForm.lastAddress || "-" }} + {{ detailForm.locateUpdateTime ? parseTime(detailForm.locateUpdateTime, "{y}-{m}-{d}") : "-" }} - {{ detailForm.lastLat || "-" }} - {{ detailForm.lastLng || "-" }} + + {{ formatCoordinates(detailForm.lastLat, detailForm.lastLng) }} + {{ detailForm.battery || "-" }} {{ detailForm.lastReportedTime ? parseTime(detailForm.lastReportedTime, "{y}-{m}-{d}") : "-" }} @@ -367,6 +454,10 @@ 关 闭 + @@ -474,20 +565,58 @@ import { addDevice, updateDevice, batchActivateDevice, + batchDisableDevice, exportDevice, getBatchNo, } from "@/api/device/device"; // 新增:导入相关API(需根据实际后端接口调整) import { importDeviceSync } from "@/api/device/device"; +import { assignBusinessUserDevices } from "@/api/business/businessUser"; // 导入企业选择组件 import BusinessSelect from "@/components/business/BusinessSelect"; // 替换为实际组件路径 import DeviceClaimDialog from "@/components/device"; +import DeviceTrajectoryDialog from "@/components/device/TrajectoryDialog"; +import UserSelector from "@/components/user"; + +function getDefaultImportForm() { + return { + orderCode: "", + batchNo: "", + remark: "", + bindBusinessId: "", + businessName: "", + }; +} + +function getDefaultQueryParams() { + return { + pageNum: 1, + pageSize: 10, + sn: null, + mac: null, + privateKey: null, + batchNo: null, + hashid: null, + model: null, + activationStatus: undefined, + bindBusinessId: null, + locateUpdateTime: null, + lastLat: null, + lastLng: null, + battery: null, + lastReportedTime: null, + lastLocationTime: null, + orderCode: null, + }; +} export default { name: "Device", components: { BusinessSelect, DeviceClaimDialog, + DeviceTrajectoryDialog, + UserSelector, }, data() { return { @@ -504,6 +633,10 @@ export default { showSearch: true, // 总条数 total: 0, + activationStatusOptions: [ + { label: "已启用", value: true }, + { label: "未启用", value: false }, + ], // 系统设备主表格数据 deviceList: [], // 弹出层标题 @@ -516,6 +649,16 @@ export default { detailLoading: false, // 详情数据 detailForm: null, + // 是否显示轨迹弹窗 + trajectoryOpen: false, + // 轨迹弹窗设备 + trajectoryDevice: null, + // 是否显示分配设备弹窗 + assignDeviceOpen: false, + // 分配提交中 + assignLoading: false, + // 当前选中的员工 + selectedAssignUsers: [], // 是否显示认领设备弹窗 claimDeviceOpen: false, // 是否显示导入弹窗 @@ -524,21 +667,12 @@ export default { importing: false, bindBusinessId: "", // 企业ID businessName: "", // 企业名称 + searchBusinessName: "", // 导入表单数据 - importForm: { - orderCode: "", - batchNo: "", // 新增:批次号 - remark: "", - bindBusinessId: "", // 必须初始化,否则校验不生效 - businessName: "", // 必须初始化,否则校验不生效 - }, + importForm: getDefaultImportForm(), // 导入表单校验规则 importRules: { - orderCode: [{ required: true, message: "订单号不能为空", trigger: "blur" }], file: [{ required: true, message: "请选择要上传的Excel文件", trigger: "change" }], - orderCode: [{ required: true, message: "订单号不能为空", trigger: "blur" }], - bindBusinessId: [{ required: true, message: "企业不能为空", trigger: "blur" }], - businessName: [{ required: true, message: "请选择企业名称", trigger: "blur" }], }, // 上传文件列表 fileList: [], @@ -549,23 +683,7 @@ export default { // 导入结果弹窗 resultOpen: false, // 查询参数 - queryParams: { - pageNum: 1, - pageSize: 10, - sn: null, - mac: null, - privateKey: null, - batchNo: null, - hashid: null, - model: null, - bindBusinessId: null, - locateUpdateTime: null, - lastLat: null, - lastLng: null, - battery: null, - lastReportedTime: null, - lastLocationTime: null, - }, + queryParams: getDefaultQueryParams(), // 表单参数 form: {}, // 表单校验 @@ -588,22 +706,37 @@ export default { }, // 企业选择弹窗开关 businessSelectVisible: false, + searchBusinessSelectVisible: false, }; }, created() { this.getList(); }, + deactivated() { + this.closeBusinessSelectDialogs(); + }, + beforeDestroy() { + this.closeBusinessSelectDialogs(); + }, methods: { + closeBusinessSelectDialogs() { + this.businessSelectVisible = false; + this.searchBusinessSelectVisible = false; + }, /** 查询系统设备主列表 */ getList() { this.loading = true; listDevice(this.queryParams) .then((response) => { - this.deviceList = response.data.list; - this.total = response.data.total; - this.loading = false; + const data = response.data || {}; + this.deviceList = Array.isArray(data.list) ? data.list : []; + this.total = Number(data.total) || 0; }) .catch(() => { + this.deviceList = []; + this.total = 0; + }) + .finally(() => { this.loading = false; }); }, @@ -643,7 +776,12 @@ export default { }, /** 重置按钮操作 */ resetQuery() { - this.resetForm("queryForm"); + if (this.$refs.queryForm) { + this.resetForm("queryForm"); + } + this.queryParams = getDefaultQueryParams(); + this.searchBusinessName = ""; + this.searchBusinessSelectVisible = false; this.handleQuery(); }, // 多选框选中数据 @@ -652,17 +790,121 @@ export default { this.single = selection.length !== 1; this.multiple = !selection.length; }, + getActivationStatusLabel(value) { + return value === true || value === 1 || value === "1" ? "是" : "否"; + }, + getActivationTagType(value) { + return value === true || value === 1 || value === "1" ? "success" : "info"; + }, + formatCoordinateValue(value) { + return value === null || value === undefined || value === "" ? "-" : value; + }, + formatCoordinates(lat, lng) { + return `${this.formatCoordinateValue(lat)} / ${this.formatCoordinateValue(lng)}`; + }, + formatAssignedUsers(users) { + if (!Array.isArray(users) || !users.length) { + return "-"; + } + return users + .map((user) => { + if (!user) { + return ""; + } + const name = user.nickName || ""; + const account = user.account || ""; + if (name && account) { + return `${name}(${account})`; + } + return name || account || ""; + }) + .filter(Boolean) + .join(",") || "-"; + }, /** 打开认领设备弹窗 */ handleClaimDevice() { this.claimDeviceOpen = true; }, - /** 批量激活设备 */ + /** 打开分配设备弹窗 */ + handleAssignDevice() { + if (!this.ids.length) { + this.$message.warning("请先勾选需要分配的设备"); + return; + } + this.assignDeviceOpen = true; + this.selectedAssignUsers = []; + this.$nextTick(() => { + if (this.$refs.userSelector && this.$refs.userSelector.clearSelectionState) { + this.$refs.userSelector.clearSelectionState(); + } + }); + }, + /** 选择员工账户 */ + handleAssignUserSelect(rows) { + this.selectedAssignUsers = Array.isArray(rows) ? rows : []; + }, + /** 关闭分配设备弹窗 */ + handleAssignDialogClose() { + this.assignDeviceOpen = false; + this.assignLoading = false; + this.selectedAssignUsers = []; + if (this.$refs.userSelector && this.$refs.userSelector.clearSelectionState) { + this.$refs.userSelector.clearSelectionState(); + } + }, + /** 提交分配设备 */ + handleAssignSubmit() { + if (!this.ids.length) { + this.$message.warning("请先勾选需要分配的设备"); + return; + } + if (!this.selectedAssignUsers.length) { + this.$message.warning("请选择员工账户"); + return; + } + const userIds = Array.from( + new Set( + this.selectedAssignUsers + .map((user) => user && user.id) + .filter((id) => id !== undefined && id !== null) + ) + ); + const deviceIds = Array.from(new Set(this.ids)); + if (!userIds.length) { + this.$message.warning("未获取到有效的员工账号"); + return; + } + this.$confirm( + `确认将选中的 ${deviceIds.length} 台设备分配给 ${userIds.length} 名员工吗?`, + "提示", + { + confirmButtonText: "确定", + cancelButtonText: "取消", + type: "warning", + } + ) + .then(async () => { + this.assignLoading = true; + await assignBusinessUserDevices({ + userIds, + deviceIds, + }); + this.$message.success("分配成功"); + this.handleAssignDialogClose(); + this.getList(); + }) + .catch(() => {}) + .finally(() => { + this.assignLoading = false; + }); + }, + /** 批量启用设备 */ handleBatchActivate() { if (!this.ids.length) { - this.$message.warning("请先勾选需要激活的设备"); + this.$message.warning("请先勾选需要启用的设备"); return; } - this.$confirm(`确认激活选中的 ${this.ids.length} 台设备吗?`, "提示", { + this.$confirm(`确认启用选中的 ${this.ids.length} 台设备吗?`, "提示", { confirmButtonText: "确定", cancelButtonText: "取消", type: "warning", @@ -671,7 +913,27 @@ export default { return batchActivateDevice(this.ids); }) .then(() => { - this.$message.success("批量激活成功"); + this.$message.success("批量启用成功"); + this.getList(); + }) + .catch(() => {}); + }, + /** 批量禁用设备 */ + handleBatchDisable() { + if (!this.ids.length) { + this.$message.warning("请先勾选需要禁用的设备"); + return; + } + this.$confirm(`确认禁用选中的 ${this.ids.length} 台设备吗?`, "提示", { + confirmButtonText: "确定", + cancelButtonText: "取消", + type: "warning", + }) + .then(() => { + return batchDisableDevice(this.ids); + }) + .then(() => { + this.$message.success("批量禁用成功"); this.getList(); }) .catch(() => {}); @@ -686,12 +948,28 @@ export default { this.detailForm = { ...row }; getDevice(row.id) .then((response) => { - this.detailForm = response.data || { ...row }; + this.detailForm = { + ...row, + ...(response.data || {}), + }; }) .finally(() => { this.detailLoading = false; }); }, + /** 查看设备轨迹 */ + handleTrajectory(row) { + if (!row || !row.id) { + this.$message.warning("未获取到设备信息"); + return; + } + this.trajectoryDevice = { + id: row.id, + sn: row.sn, + mac: row.mac, + }; + this.trajectoryOpen = true; + }, /** 认领成功回调 */ handleClaimSuccess() { this.claimDeviceOpen = false; @@ -769,15 +1047,10 @@ export default { // ========== 新增:Excel导入相关方法 ========== /** 打开导入弹窗 */ handleImport() { + this.closeBusinessSelectDialogs(); this.importOpen = true; // 重置导入表单和文件 - this.importForm = { - orderCode: "", - remark: "", - batchNo: "", - bindBusinessId: "", - businessName: "", - }; + this.importForm = getDefaultImportForm(); this.fileList = []; this.uploadFile = null; this.$nextTick(() => { @@ -846,10 +1119,6 @@ export default { // 3. 构建FormData:仅携带1个文件 const formData = new FormData(); - formData.append("orderCode", this.importForm.orderCode); - formData.append("remark", this.importForm.remark); - formData.append("bindBusinessId", this.importForm.bindBusinessId); - formData.append("batchNo", this.importForm.batchNo); formData.append("file", this.uploadFile); // 仅提交这1个文件 @@ -877,16 +1146,11 @@ export default { /** 取消导入 */ cancelImport() { this.importOpen = false; - this.importForm = { - orderCode: "", - remark: "", - batchNo: "", - bindBusinessId: "", - businessName: "", - }; // 加上batchNo重置 + this.importForm = getDefaultImportForm(); this.fileList = []; this.uploadFile = null; this.baseBatchNo = ""; + this.businessSelectVisible = false; }, getBatchNo() { return getBatchNo(); @@ -917,6 +1181,11 @@ export default { this.businessSelectVisible = false; // 关闭企业选择弹窗 }, + handleSearchBusinessSelect(row) { + this.queryParams.bindBusinessId = row.id; + this.searchBusinessName = row.name; + this.searchBusinessSelectVisible = false; + }, normalizeImportResult(data) { return { status: data?.status || "", @@ -975,4 +1244,10 @@ export default { line-height: 1.8; color: #606266; } + +.assign-selected-user { + float: left; + line-height: 32px; + color: #606266; +} diff --git a/src/views/index.vue b/src/views/index.vue index 8304de2..83f04bf 100644 --- a/src/views/index.vue +++ b/src/views/index.vue @@ -2,9 +2,9 @@
-

GEO TAG后台管理框架

+

GeoTag后台管理框架

- 一直想做一款后台管理系统,看了很多优秀的开源项目但是发现没有合适自己的。于是利用空闲休息时间开始自己写一套后台系统。如此有了GEO TAG管理系统,她可以用于所有的Web应用程序,如网站管理后台,网站会员中心,CMS,CRM,OA等等,当然,您也可以对她进行深度定制,以做出更强系统。所有前端后台代码封装过后十分精简易上手,出错概率低。同时支持移动客户端访问。系统会陆续更新一些实用功能。 + 一直想做一款后台管理系统,看了很多优秀的开源项目但是发现没有合适自己的。于是利用空闲休息时间开始自己写一套后台系统。如此有了GeoTag管理系统,她可以用于所有的Web应用程序,如网站管理后台,网站会员中心,CMS,CRM,OA等等,当然,您也可以对她进行深度定制,以做出更强系统。所有前端后台代码封装过后十分精简易上手,出错概率低。同时支持移动客户端访问。系统会陆续更新一些实用功能。

当前版本: v{{ version }} @@ -92,14 +92,14 @@

微信:/ *GEO TAG/ *GeoTag

支付宝:/ *GEO TAG/ *GeoTag

@@ -990,7 +990,7 @@
    -
  1. GEO TAG前后端分离系统正式发布
  2. +
  3. GeoTag前后端分离系统正式发布
diff --git a/src/views/login.vue b/src/views/login.vue index 73762e6..c710e7b 100644 --- a/src/views/login.vue +++ b/src/views/login.vue @@ -1,7 +1,7 @@ diff --git a/vue.config.js b/vue.config.js index 0c9331a..e27b5b5 100644 --- a/vue.config.js +++ b/vue.config.js @@ -7,7 +7,7 @@ function resolve(dir) { const CompressionPlugin = require('compression-webpack-plugin') -const name = process.env.VUE_APP_TITLE || '客户GEO TAG管理系统' // 网页标题 +const name = process.env.VUE_APP_TITLE || '客户GeoTag管理系统' // 网页标题 const baseUrl = 'http://127.0.0.1:10022' // 后端接口 // const baseUrl ="https://hfmt8jvy.currencyacquirer.online/stage-api/"