Browse Source

b端

master
hx 1 week ago
parent
commit
4b75e64901
  1. 7
      .env.development
  2. 2
      .env.production
  3. 4
      .env.staging
  4. 4
      package.json
  5. 10
      src/api/business/businessUser.js
  6. 27
      src/api/device/device.js
  7. 8
      src/api/system/role.js
  8. 32
      src/api/system/user.js
  9. 112
      src/components/business/BusinessSelect.vue
  10. 2
      src/settings.js
  11. 20
      src/utils/request.js
  12. 427
      src/views/device/device/index.vue
  13. 10
      src/views/index.vue
  14. 2
      src/views/login.vue
  15. 188
      src/views/system/user/index.vue
  16. 90
      src/views/system/user/profile/index.vue
  17. 19
      src/views/system/user/profile/userInfo.vue
  18. 2
      vue.config.js

7
.env.development

@ -1,11 +1,14 @@
# 页面标题 # 页面标题
VUE_APP_TITLE = 客户 GEO TAG管理系统 VUE_APP_TITLE = 客户 GeoTag管理系统
# 开发环境配置 # 开发环境配置
ENV = 'development' ENV = 'development'
# GEO TAG/开发环境 # GeoTag/开发环境
VUE_APP_BASE_API = '/dev-api' VUE_APP_BASE_API = '/dev-api'
# 路由懒加载 # 路由懒加载
VUE_CLI_BABEL_TRANSPILE_MODULES = true VUE_CLI_BABEL_TRANSPILE_MODULES = true
VUE_APP_GOOGLE_MAPS_API_KEY =
VUE_APP_AMAP_WEB_KEY =
VUE_APP_AMAP_SECURITY_JS_CODE =

2
.env.production

@ -1,5 +1,5 @@
# 页面标题 # 页面标题
VUE_APP_TITLE = 客户GEO TAG管理系统 VUE_APP_TITLE = 客户GeoTag管理系统
# 生产环境配置 # 生产环境配置
ENV = 'production' ENV = 'production'

4
.env.staging

@ -1,8 +1,8 @@
NODE_ENV = production NODE_ENV = production
VUE_APP_TITLE =客户 GEO TAG管理系统 VUE_APP_TITLE =客户 GeoTag管理系统
# 测试环境配置 # 测试环境配置
ENV = 'staging' ENV = 'staging'
# GEO TAG/测试环境 # GeoTag/测试环境
VUE_APP_BASE_API = '/stage-api' VUE_APP_BASE_API = '/stage-api'

4
package.json

@ -1,8 +1,8 @@
{ {
"name": "ruoyi", "name": "ruoyi",
"version": "3.8.9", "version": "3.8.9",
"description": "客户GEO TAG管理系统", "description": "客户GeoTag管理系统",
"author": "GEO TAG", "author": "GeoTag",
"license": "MIT", "license": "MIT",
"scripts": { "scripts": {
"dev": "vue-cli-service serve", "dev": "vue-cli-service serve",

10
src/api/business/businessUser.js

@ -50,4 +50,12 @@ export function exportBusinessUser(query) {
method: 'get', method: 'get',
params: query params: query
}) })
} }
export function assignBusinessUserDevices(data) {
return request({
url: '/business/businessUser/assign/devices',
method: 'post',
data: data
})
}

27
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) { export function addDevice(data) {
return request({ return request({
url: '/device/device', url: '/device/device',
@ -86,7 +100,6 @@ export function getBatchNo() {
}) })
} }
// 批量激活设备(将选中设备标记为激活:1)
export function batchActivateDevice(ids) { export function batchActivateDevice(ids) {
return request({ return request({
url: '/device/device/activate/batch', 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) { export function claimDeviceBatch(data) {
return request({ return request({
url: '/device/device/claim/batch', url: '/device/device/claim/batch',
@ -111,4 +134,4 @@ export function listAllDevice(query) {
method: 'get', method: 'get',
params: query params: query
}) })
} }

8
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) { export function getRole(roleId) {
return request({ return request({

32
src/api/system/user.js

@ -4,7 +4,7 @@ import { praseStrEmpty } from "@/utils/ruoyi";
// 查询用户列表 // 查询用户列表
export function listUser(query) { export function listUser(query) {
return request({ return request({
url: '/business/businessUser/list', url: '/business/businessUser/employee/page',
method: 'get', method: 'get',
params: query 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) { export function updateUser(data) {
return request({ 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) { export function delUser(userId) {
return request({ return request({
@ -69,7 +87,7 @@ export function resetUserPwd(userId, password) {
// 用户状态修改 // 用户状态修改
export function changeUserStatus(userId, status) { export function changeUserStatus(userId, status) {
const data = { const data = {
userId, id: userId,
status status
} }
return request({ return request({
@ -125,3 +143,13 @@ export function importTemplate() {
method: 'get' method: 'get'
}) })
} }
export function listEmployeeUser(query) {
return request({
url: '/business/businessUser/employee/list',
method: 'get',
params: query
})
}

112
src/components/business/BusinessSelect.vue

@ -1,56 +1,47 @@
<template> <template>
<el-dialog <el-dialog
title="选择企业" title="选择企业"
:visible.sync="visible" :visible.sync="dialogVisible"
width="800px" width="800px"
append-to-body append-to-body
@close="handleClose" @close="handleClose"
> >
<!-- 搜索表单移除多余的闭合标签 -->
<el-form <el-form
:model="queryParams"
ref="queryForm" ref="queryForm"
:model="queryParams"
:inline="true" :inline="true"
label-width="80px" label-width="80px"
class="mb8" class="mb8"
@submit.native.prevent="handleQuery"
> >
<el-form-item label="企业名称" prop="merchantName"> <el-form-item label="企业名称" prop="merchantName">
<el-input <el-input
v-model="queryParams.merchantName" v-model.trim="queryParams.merchantName"
placeholder="请输入企业名称" placeholder="请输入企业名称"
clearable clearable
size="small" size="small"
@keyup.enter.native="getList" @keyup.enter.native="handleQuery"
/> />
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="getList"> <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">
搜索 搜索
</el-button> </el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery"> <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
重置
</el-button>
</el-form-item> </el-form-item>
</el-form> </el-form>
<!-- 企业列表修复template标签归属 + 补充企业ID列 -->
<el-table <el-table
v-loading="loading" v-loading="loading"
:data="businessList" :data="businessList"
@row-dblclick="handleDblClick"
border border
stripe stripe
@row-dblclick="handleDblClick"
> >
<el-table-column label="企业ID" align="center" prop="id" width="120" /> <el-table-column label="企业ID" align="center" prop="id" width="120" />
<el-table-column <el-table-column label="企业名称" align="center" prop="merchantName" min-width="220" />
label="企业名称"
align="center"
prop="merchantName"
min-width="200"
/>
</el-table> </el-table>
<!-- 分页 -->
<pagination <pagination
v-show="total > 0" v-show="total > 0"
:total="total" :total="total"
@ -64,6 +55,14 @@
<script> <script>
import { listBusinessInfo } from "@/api/business/businessInfo"; import { listBusinessInfo } from "@/api/business/businessInfo";
function getDefaultQueryParams() {
return {
pageNum: 1,
pageSize: 10,
merchantName: null,
};
}
export default { export default {
name: "BusinessSelect", name: "BusinessSelect",
props: { props: {
@ -77,71 +76,70 @@ export default {
loading: false, loading: false,
total: 0, total: 0,
businessList: [], businessList: [],
queryParams: { queryParams: getDefaultQueryParams(),
pageNum: 1,
pageSize: 10,
merchantName: null,
},
}; };
}, },
computed: {
dialogVisible: {
get() {
return this.visible;
},
set(value) {
this.$emit("update:visible", value);
},
},
},
watch: { watch: {
// visible(value) {
visible(val) { if (value) {
if (val) { this.queryParams = getDefaultQueryParams();
this.resetQuery();
this.getList(); this.getList();
} }
}, },
}, },
methods: { methods: {
//
getList() { getList() {
this.loading = true; this.loading = true;
// 便
console.log("企业查询参数:", this.queryParams);
listBusinessInfo(this.queryParams) listBusinessInfo(this.queryParams)
.then((res) => { .then((response) => {
console.log("企业列表接口返回:", res); const data = response.data || {};
// this.businessList = Array.isArray(data.list) ? data.list : [];
this.businessList = res.data?.list || []; this.total = Number(data.total) || 0;
this.total = res.data?.total || 0;
this.loading = false;
}) })
.catch((err) => { .catch(() => {
console.error("加载企业列表失败:", err);
this.businessList = []; this.businessList = [];
this.total = 0; this.total = 0;
this.loading = false;
this.$message.error("加载企业列表失败,请稍后重试"); this.$message.error("加载企业列表失败,请稍后重试");
})
.finally(() => {
this.loading = false;
}); });
}, },
handleQuery() {
// this.queryParams.pageNum = 1;
this.getList();
},
resetQuery() { resetQuery() {
this.queryParams = { this.queryParams = getDefaultQueryParams();
pageNum: 1, if (this.$refs.queryForm) {
pageSize: 10, this.$refs.queryForm.resetFields();
merchantName: null, }
}; this.getList();
}, },
// +
handleDblClick(row) { handleDblClick(row) {
console.log("选中的企业数据:", row);
//
this.$emit("select", { this.$emit("select", {
id: row.id, id: row.id,
name: row.merchantName, name: row.merchantName,
merchantNo: row.merchantNo, merchantNo: row.merchantNo,
}); });
// this.dialogVisible = false;
this.$emit("update:visible", false); this.$message.success(`已选择企业:${row.merchantName}`);
this.$message.success("已选择企业:" + row.merchantName);
}, },
//
handleClose() { handleClose() {
this.resetQuery(); this.businessList = [];
this.total = 0;
this.queryParams = getDefaultQueryParams();
this.dialogVisible = false;
}, },
}, },
}; };

2
src/settings.js

@ -17,7 +17,7 @@ module.exports = {
/** /**
* 是否显示顶部导航 * 是否显示顶部导航
*/ */
topNav: true, topNav: false,
/** /**
* 是否显示 tagsView * 是否显示 tagsView

20
src/utils/request.js

@ -25,15 +25,23 @@ const service = axios.create({
service.interceptors.request.use( service.interceptors.request.use(
(config) => { (config) => {
// 接口密码加密 // 接口密码加密
if ( const encryptUrls = [
config.url == "/merchant/merchantInfo" || "/merchant/merchantInfo",
config.url == "/merchant/merchantInfo/updateMerchantPwdAdmin" "/merchant/merchantInfo/updateMerchantPwdAdmin",
) { "/business/businessUser",
const encryptMap = ["passwordHash", "newPasswordHash"]; "/business/businessUser/employee",
encryptMap.map((key) => { "/business/businessUser/resetPwd",
"/business/businessUser/profile/updatePwd",
];
if (encryptUrls.includes(config.url)) {
const encryptMap = ["passwordHash", "newPasswordHash", "oldPassword", "newPassword"];
encryptMap.forEach((key) => {
if (config.data && config.data[key]) { if (config.data && config.data[key]) {
config.data[key] = Encrypt(config.data[key]); config.data[key] = Encrypt(config.data[key]);
} }
if (config.params && config.params[key]) {
config.params[key] = Encrypt(config.params[key]);
}
}); });
} }
// 是否需要设置 token // 是否需要设置 token

427
src/views/device/device/index.vue

@ -6,13 +6,14 @@
:inline="true" :inline="true"
v-show="showSearch" v-show="showSearch"
label-width="68px" label-width="68px"
@submit.native.prevent="handleQuery"
> >
<!-- 新增订单号搜索项 --> <!-- 新增订单号搜索项 -->
<el-form-item label="所属批次" prop="batchNo"> <el-form-item label="订单号" prop="orderCode">
<el-input <el-input
v-model="queryParams.batchNo" v-model="queryParams.orderCode"
placeholder="请输入所属批次" placeholder="请输入订单号"
clearable clearable
size="small" size="small"
@keyup.enter.native="handleQuery" @keyup.enter.native="handleQuery"
@ -29,6 +30,22 @@
/> />
</el-form-item> </el-form-item>
<el-form-item label="是否启用" prop="activationStatus">
<el-select
v-model="queryParams.activationStatus"
placeholder="请选择启用状态"
clearable
size="small"
@keyup.enter.native="handleQuery"
>
<el-option label="否" :value="0" />
<el-option label="是" :value="1" />
</el-select>
</el-form-item>
<el-form-item> <el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery" <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery"
>搜索</el-button >搜索</el-button
@ -68,6 +85,7 @@
icon="el-icon-s-claim" icon="el-icon-s-claim"
size="mini" size="mini"
@click="handleClaimDevice" @click="handleClaimDevice"
v-hasPermi="['device:device:claim:batch']"
>认领设备</el-button> >认领设备</el-button>
</el-col> </el-col>
<el-col :span="1.5"> <el-col :span="1.5">
@ -77,8 +95,30 @@
icon="el-icon-circle-check" icon="el-icon-circle-check"
size="mini" size="mini"
:disabled="multiple" :disabled="multiple"
v-hasPermi="['device:device:activate:batch']"
@click="handleBatchActivate" @click="handleBatchActivate"
>批量激活</el-button> >批量启用</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="danger"
plain
icon="el-icon-circle-close"
size="mini"
:disabled="multiple"
@click="handleBatchDisable"
>批量禁用</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="info"
plain
icon="el-icon-user"
size="mini"
:disabled="multiple"
v-hasPermi="['device:device:activate:batch']"
@click="handleAssignDevice"
>分配设备</el-button>
</el-col> </el-col>
<!-- <el-col :span="1.5"> <!-- <el-col :span="1.5">
<el-button <el-button
@ -124,14 +164,18 @@
@selection-change="handleSelectionChange" @selection-change="handleSelectionChange"
> >
<el-table-column type="selection" width="55" align="center" /> <el-table-column type="selection" width="55" align="center" />
<el-table-column label="ID" align="center" prop="id" />
<el-table-column label="序列号" align="center" prop="sn" /> <el-table-column label="序列号" align="center" prop="sn" />
<el-table-column label="MAC 地址" align="center" prop="mac" /> <el-table-column label="MAC 地址" align="center" prop="mac" min-width="140" />
<el-table-column label="私钥" align="center" prop="privateKey" /> <el-table-column label="订单号" align="center" prop="orderCode" />
<el-table-column label="所属批次" align="center" prop="batchNo" /> <el-table-column label="最后地址名称" align="center" prop="lastAddress" min-width="180" />
<el-table-column label="设备的唯一哈希 ID" align="center" prop="hashid" />
<el-table-column label="型号" align="center" prop="model" /> <el-table-column label="是否启用" align="center" prop="activationStatus" width="100">
<el-table-column label="企业" align="center" prop="merchantName" /> <template slot-scope="scope">
<el-tag :type="getActivationTagType(scope.row.activationStatus)" size="mini">
{{ getActivationStatusLabel(scope.row.activationStatus) }}
</el-tag>
</template>
</el-table-column>
<!-- <el-table-column label="绑定企业id" align="center" prop="bindBusinessId" /> --> <!-- <el-table-column label="绑定企业id" align="center" prop="bindBusinessId" /> -->
<el-table-column <el-table-column
@ -144,8 +188,11 @@
<span>{{ parseTime(scope.row.locateUpdateTime, "{y}-{m}-{d}") }}</span> <span>{{ parseTime(scope.row.locateUpdateTime, "{y}-{m}-{d}") }}</span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="最后纬度" align="center" prop="lastLat" /> <el-table-column label="最后经纬度" align="center" min-width="220">
<el-table-column label="最后经度" align="center" prop="lastLng" /> <template slot-scope="scope">
<span>{{ formatCoordinates(scope.row.lastLat, scope.row.lastLng) }}</span>
</template>
</el-table-column>
<el-table-column label="电量" align="center" prop="battery" /> <el-table-column label="电量" align="center" prop="battery" />
<el-table-column <el-table-column
label="最后上报时间" label="最后上报时间"
@ -167,7 +214,7 @@
<span>{{ parseTime(scope.row.lastLocationTime, "{y}-{m}-{d}") }}</span> <span>{{ parseTime(scope.row.lastLocationTime, "{y}-{m}-{d}") }}</span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="操作" align="center" width="100" class-name="small-padding fixed-width"> <el-table-column label="操作" align="center" width="160" class-name="small-padding fixed-width">
<template slot-scope="scope"> <template slot-scope="scope">
<el-button <el-button
size="mini" size="mini"
@ -175,6 +222,12 @@
icon="el-icon-view" icon="el-icon-view"
@click="handleDetail(scope.row)" @click="handleDetail(scope.row)"
>详情</el-button> >详情</el-button>
<el-button
size="mini"
type="text"
icon="el-icon-location-outline"
@click="handleTrajectory(scope.row)"
>轨迹</el-button>
</template> </template>
</el-table-column> </el-table-column>
<!-- <el-table-column label="操作" align="center" class-name="small-padding fixed-width"> <!-- <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
@ -219,9 +272,10 @@
:model="importForm" :model="importForm"
:rules="importRules" :rules="importRules"
label-width="90px" label-width="90px"
@submit.native.prevent
> >
<!-- Excel文件上传仅1个文件修复核心 --> <!-- Excel文件上传仅1个文件修复核心 -->
@ -329,10 +383,36 @@
:visible.sync="businessSelectVisible" :visible.sync="businessSelectVisible"
@select="handleBusinessSelect" @select="handleBusinessSelect"
/> />
<BusinessSelect
:visible.sync="searchBusinessSelectVisible"
@select="handleSearchBusinessSelect"
/>
<DeviceClaimDialog <DeviceClaimDialog
:visible.sync="claimDeviceOpen" :visible.sync="claimDeviceOpen"
@success="handleClaimSuccess" @success="handleClaimSuccess"
/> />
<el-dialog
title="分配设备"
:visible.sync="assignDeviceOpen"
width="1200px"
append-to-body
@close="handleAssignDialogClose"
>
<UserSelector
ref="userSelector"
selection-mode
@select="handleAssignUserSelect"
/>
<div slot="footer" class="dialog-footer">
<span class="assign-selected-user">
{{ `已选择员工:${selectedAssignUsers.length}` }}
</span>
<el-button @click="handleAssignDialogClose"> </el-button>
<el-button type="primary" :loading="assignLoading" @click="handleAssignSubmit">
</el-button>
</div>
</el-dialog>
<el-dialog <el-dialog
title="设备详情" title="设备详情"
:visible.sync="detailOpen" :visible.sync="detailOpen"
@ -344,16 +424,23 @@
<el-descriptions-item label="ID">{{ detailForm.id || "-" }}</el-descriptions-item> <el-descriptions-item label="ID">{{ detailForm.id || "-" }}</el-descriptions-item>
<el-descriptions-item label="序列号">{{ detailForm.sn || "-" }}</el-descriptions-item> <el-descriptions-item label="序列号">{{ detailForm.sn || "-" }}</el-descriptions-item>
<el-descriptions-item label="MAC 地址">{{ detailForm.mac || "-" }}</el-descriptions-item> <el-descriptions-item label="MAC 地址">{{ detailForm.mac || "-" }}</el-descriptions-item>
<el-descriptions-item label="私钥">{{ detailForm.privateKey || "-" }}</el-descriptions-item>
<el-descriptions-item label="所属批次">{{ detailForm.batchNo || "-" }}</el-descriptions-item>
<el-descriptions-item label="设备的唯一哈希 ID">{{ detailForm.hashid || "-" }}</el-descriptions-item> <el-descriptions-item label="设备的唯一哈希 ID">{{ detailForm.hashid || "-" }}</el-descriptions-item>
<el-descriptions-item label="型号">{{ detailForm.model || "-" }}</el-descriptions-item> <el-descriptions-item label="型号">{{ detailForm.model || "-" }}</el-descriptions-item>
<el-descriptions-item label="企业">{{ detailForm.merchantName || "-" }}</el-descriptions-item> <el-descriptions-item label="企业">
{{ detailForm.businessName || detailForm.merchantName || "-" }}
</el-descriptions-item>
<el-descriptions-item label="分配员工">
{{ formatAssignedUsers(detailForm.assignedUsers) }}
</el-descriptions-item>
<el-descriptions-item label="最后地址名称">
{{ detailForm.lastAddress || "-" }}
</el-descriptions-item>
<el-descriptions-item label="最后位置更新时间"> <el-descriptions-item label="最后位置更新时间">
{{ detailForm.locateUpdateTime ? parseTime(detailForm.locateUpdateTime, "{y}-{m}-{d}") : "-" }} {{ detailForm.locateUpdateTime ? parseTime(detailForm.locateUpdateTime, "{y}-{m}-{d}") : "-" }}
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="最后纬度">{{ detailForm.lastLat || "-" }}</el-descriptions-item> <el-descriptions-item label="最后经纬度">
<el-descriptions-item label="最后经度">{{ detailForm.lastLng || "-" }}</el-descriptions-item> {{ formatCoordinates(detailForm.lastLat, detailForm.lastLng) }}
</el-descriptions-item>
<el-descriptions-item label="电量">{{ detailForm.battery || "-" }}</el-descriptions-item> <el-descriptions-item label="电量">{{ detailForm.battery || "-" }}</el-descriptions-item>
<el-descriptions-item label="最后上报时间"> <el-descriptions-item label="最后上报时间">
{{ detailForm.lastReportedTime ? parseTime(detailForm.lastReportedTime, "{y}-{m}-{d}") : "-" }} {{ detailForm.lastReportedTime ? parseTime(detailForm.lastReportedTime, "{y}-{m}-{d}") : "-" }}
@ -367,6 +454,10 @@
<el-button type="primary" @click="detailOpen = false"> </el-button> <el-button type="primary" @click="detailOpen = false"> </el-button>
</div> </div>
</el-dialog> </el-dialog>
<DeviceTrajectoryDialog
:visible.sync="trajectoryOpen"
:device="trajectoryDevice"
/>
<!-- 添加或修改系统设备主对话框 --> <!-- 添加或修改系统设备主对话框 -->
<el-dialog :title="title" :visible.sync="open" width="500px" append-to-body> <el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
<el-form ref="form" :model="form" :rules="rules" label-width="80px"> <el-form ref="form" :model="form" :rules="rules" label-width="80px">
@ -474,20 +565,58 @@ import {
addDevice, addDevice,
updateDevice, updateDevice,
batchActivateDevice, batchActivateDevice,
batchDisableDevice,
exportDevice, exportDevice,
getBatchNo, getBatchNo,
} from "@/api/device/device"; } from "@/api/device/device";
// API // API
import { importDeviceSync } from "@/api/device/device"; import { importDeviceSync } from "@/api/device/device";
import { assignBusinessUserDevices } from "@/api/business/businessUser";
// //
import BusinessSelect from "@/components/business/BusinessSelect"; // import BusinessSelect from "@/components/business/BusinessSelect"; //
import DeviceClaimDialog from "@/components/device"; 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 { export default {
name: "Device", name: "Device",
components: { components: {
BusinessSelect, BusinessSelect,
DeviceClaimDialog, DeviceClaimDialog,
DeviceTrajectoryDialog,
UserSelector,
}, },
data() { data() {
return { return {
@ -504,6 +633,10 @@ export default {
showSearch: true, showSearch: true,
// //
total: 0, total: 0,
activationStatusOptions: [
{ label: "已启用", value: true },
{ label: "未启用", value: false },
],
// //
deviceList: [], deviceList: [],
// //
@ -516,6 +649,16 @@ export default {
detailLoading: false, detailLoading: false,
// //
detailForm: null, detailForm: null,
//
trajectoryOpen: false,
//
trajectoryDevice: null,
//
assignDeviceOpen: false,
//
assignLoading: false,
//
selectedAssignUsers: [],
// //
claimDeviceOpen: false, claimDeviceOpen: false,
// //
@ -524,21 +667,12 @@ export default {
importing: false, importing: false,
bindBusinessId: "", // ID bindBusinessId: "", // ID
businessName: "", // businessName: "", //
searchBusinessName: "",
// //
importForm: { importForm: getDefaultImportForm(),
orderCode: "",
batchNo: "", //
remark: "",
bindBusinessId: "", //
businessName: "", //
},
// //
importRules: { importRules: {
orderCode: [{ required: true, message: "订单号不能为空", trigger: "blur" }],
file: [{ required: true, message: "请选择要上传的Excel文件", trigger: "change" }], 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: [], fileList: [],
@ -549,23 +683,7 @@ export default {
// //
resultOpen: false, resultOpen: false,
// //
queryParams: { queryParams: getDefaultQueryParams(),
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,
},
// //
form: {}, form: {},
// //
@ -588,22 +706,37 @@ export default {
}, },
// //
businessSelectVisible: false, businessSelectVisible: false,
searchBusinessSelectVisible: false,
}; };
}, },
created() { created() {
this.getList(); this.getList();
}, },
deactivated() {
this.closeBusinessSelectDialogs();
},
beforeDestroy() {
this.closeBusinessSelectDialogs();
},
methods: { methods: {
closeBusinessSelectDialogs() {
this.businessSelectVisible = false;
this.searchBusinessSelectVisible = false;
},
/** 查询系统设备主列表 */ /** 查询系统设备主列表 */
getList() { getList() {
this.loading = true; this.loading = true;
listDevice(this.queryParams) listDevice(this.queryParams)
.then((response) => { .then((response) => {
this.deviceList = response.data.list; const data = response.data || {};
this.total = response.data.total; this.deviceList = Array.isArray(data.list) ? data.list : [];
this.loading = false; this.total = Number(data.total) || 0;
}) })
.catch(() => { .catch(() => {
this.deviceList = [];
this.total = 0;
})
.finally(() => {
this.loading = false; this.loading = false;
}); });
}, },
@ -643,7 +776,12 @@ export default {
}, },
/** 重置按钮操作 */ /** 重置按钮操作 */
resetQuery() { resetQuery() {
this.resetForm("queryForm"); if (this.$refs.queryForm) {
this.resetForm("queryForm");
}
this.queryParams = getDefaultQueryParams();
this.searchBusinessName = "";
this.searchBusinessSelectVisible = false;
this.handleQuery(); this.handleQuery();
}, },
// //
@ -652,17 +790,121 @@ export default {
this.single = selection.length !== 1; this.single = selection.length !== 1;
this.multiple = !selection.length; 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() { handleClaimDevice() {
this.claimDeviceOpen = true; 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() { handleBatchActivate() {
if (!this.ids.length) { if (!this.ids.length) {
this.$message.warning("请先勾选需要激活的设备"); this.$message.warning("请先勾选需要启用的设备");
return; return;
} }
this.$confirm(`确认激活选中的 ${this.ids.length} 台设备吗?`, "提示", { this.$confirm(`确认启用选中的 ${this.ids.length} 台设备吗?`, "提示", {
confirmButtonText: "确定", confirmButtonText: "确定",
cancelButtonText: "取消", cancelButtonText: "取消",
type: "warning", type: "warning",
@ -671,7 +913,27 @@ export default {
return batchActivateDevice(this.ids); return batchActivateDevice(this.ids);
}) })
.then(() => { .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(); this.getList();
}) })
.catch(() => {}); .catch(() => {});
@ -686,12 +948,28 @@ export default {
this.detailForm = { ...row }; this.detailForm = { ...row };
getDevice(row.id) getDevice(row.id)
.then((response) => { .then((response) => {
this.detailForm = response.data || { ...row }; this.detailForm = {
...row,
...(response.data || {}),
};
}) })
.finally(() => { .finally(() => {
this.detailLoading = false; 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() { handleClaimSuccess() {
this.claimDeviceOpen = false; this.claimDeviceOpen = false;
@ -769,15 +1047,10 @@ export default {
// ========== Excel ========== // ========== Excel ==========
/** 打开导入弹窗 */ /** 打开导入弹窗 */
handleImport() { handleImport() {
this.closeBusinessSelectDialogs();
this.importOpen = true; this.importOpen = true;
// //
this.importForm = { this.importForm = getDefaultImportForm();
orderCode: "",
remark: "",
batchNo: "",
bindBusinessId: "",
businessName: "",
};
this.fileList = []; this.fileList = [];
this.uploadFile = null; this.uploadFile = null;
this.$nextTick(() => { this.$nextTick(() => {
@ -846,10 +1119,6 @@ export default {
// 3. FormData1 // 3. FormData1
const formData = new FormData(); 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 formData.append("file", this.uploadFile); // 1
@ -877,16 +1146,11 @@ export default {
/** 取消导入 */ /** 取消导入 */
cancelImport() { cancelImport() {
this.importOpen = false; this.importOpen = false;
this.importForm = { this.importForm = getDefaultImportForm();
orderCode: "",
remark: "",
batchNo: "",
bindBusinessId: "",
businessName: "",
}; // batchNo
this.fileList = []; this.fileList = [];
this.uploadFile = null; this.uploadFile = null;
this.baseBatchNo = ""; this.baseBatchNo = "";
this.businessSelectVisible = false;
}, },
getBatchNo() { getBatchNo() {
return getBatchNo(); return getBatchNo();
@ -917,6 +1181,11 @@ export default {
this.businessSelectVisible = false; // this.businessSelectVisible = false; //
}, },
handleSearchBusinessSelect(row) {
this.queryParams.bindBusinessId = row.id;
this.searchBusinessName = row.name;
this.searchBusinessSelectVisible = false;
},
normalizeImportResult(data) { normalizeImportResult(data) {
return { return {
status: data?.status || "", status: data?.status || "",
@ -975,4 +1244,10 @@ export default {
line-height: 1.8; line-height: 1.8;
color: #606266; color: #606266;
} }
.assign-selected-user {
float: left;
line-height: 32px;
color: #606266;
}
</style> </style>

10
src/views/index.vue

@ -2,9 +2,9 @@
<div class="app-container home"> <div class="app-container home">
<el-row :gutter="20"> <el-row :gutter="20">
<el-col :sm="24" :lg="12" style="padding-left: 20px"> <el-col :sm="24" :lg="12" style="padding-left: 20px">
<h2>GEO TAG后台管理框架</h2> <h2>GeoTag后台管理框架</h2>
<p> <p>
一直想做一款后台管理系统看了很多优秀的开源项目但是发现没有合适自己的于是利用空闲休息时间开始自己写一套后台系统如此有了GEO TAG管理系统她可以用于所有的Web应用程序如网站管理后台网站会员中心CMSCRMOA等等当然您也可以对她进行深度定制以做出更强系统所有前端后台代码封装过后十分精简易上手出错概率低同时支持移动客户端访问系统会陆续更新一些实用功能 一直想做一款后台管理系统看了很多优秀的开源项目但是发现没有合适自己的于是利用空闲休息时间开始自己写一套后台系统如此有了GeoTag管理系统她可以用于所有的Web应用程序如网站管理后台网站会员中心CMSCRMOA等等当然您也可以对她进行深度定制以做出更强系统所有前端后台代码封装过后十分精简易上手出错概率低同时支持移动客户端访问系统会陆续更新一些实用功能
</p> </p>
<p> <p>
<b>当前版本:</b> <span>v{{ version }}</span> <b>当前版本:</b> <span>v{{ version }}</span>
@ -92,14 +92,14 @@
<p> <p>
<i class="el-icon-chat-dot-round"></i> 微信<a <i class="el-icon-chat-dot-round"></i> 微信<a
href="javascript:;" href="javascript:;"
>/ *GEO TAG</a >/ *GeoTag</a
> >
</p> </p>
<p> <p>
<i class="el-icon-money"></i> 支付宝<a <i class="el-icon-money"></i> 支付宝<a
href="javascript:;" href="javascript:;"
class="支付宝信息" class="支付宝信息"
>/ *GEO TAG</a >/ *GeoTag</a
> >
</p> </p>
</div> </div>
@ -990,7 +990,7 @@
</el-collapse-item> </el-collapse-item>
<el-collapse-item title="v1.0.0 - 2019-10-08"> <el-collapse-item title="v1.0.0 - 2019-10-08">
<ol> <ol>
<li>GEO TAG前后端分离系统正式发布</li> <li>GeoTag前后端分离系统正式发布</li>
</ol> </ol>
</el-collapse-item> </el-collapse-item>
</el-collapse> </el-collapse>

2
src/views/login.vue

@ -1,7 +1,7 @@
<template> <template>
<div class="login"> <div class="login">
<el-form ref="loginForm" :model="loginForm" :rules="loginRules" class="login-form"> <el-form ref="loginForm" :model="loginForm" :rules="loginRules" class="login-form">
<h3 class="title">客户 GEO TAG管理后台</h3> <h3 class="title">客户 GeoTag管理后台</h3>
<el-form-item prop="username"> <el-form-item prop="username">
<el-input v-model="loginForm.username" type="text" auto-complete="off" placeholder="账号"> <el-input v-model="loginForm.username" type="text" auto-complete="off" placeholder="账号">
<svg-icon slot="prefix" icon-class="user" class="el-input__icon input-icon" /> <svg-icon slot="prefix" icon-class="user" class="el-input__icon input-icon" />

188
src/views/system/user/index.vue

@ -43,15 +43,17 @@
</el-form> </el-form>
<el-row :gutter="10" class="mb8"> <el-row :gutter="10" class="mb8">
<el-col :span="1.5">
</el-col>
<el-col :span="1.5"> <el-col :span="1.5">
<el-button <el-button
type="primary" type="info"
plain plain
icon="el-icon-plus" icon="el-icon-user"
size="mini" size="mini"
@click="handleAdd" @click="handleAddEmployee"
v-hasPermi="['system:user:add']" >新增员工</el-button>
>新增</el-button>
</el-col> </el-col>
<el-col :span="1.5"> <el-col :span="1.5">
<el-button <el-button
@ -64,17 +66,7 @@
v-hasPermi="['system:user:edit']" v-hasPermi="['system:user:edit']"
>修改</el-button> >修改</el-button>
</el-col> </el-col>
<el-col :span="1.5">
<el-button
type="danger"
plain
icon="el-icon-delete"
size="mini"
:disabled="multiple"
@click="handleDelete"
v-hasPermi="['system:user:remove']"
>删除</el-button>
</el-col>
<!-- <el-col :span="1.5"> <!-- <el-col :span="1.5">
<el-button <el-button
type="info" type="info"
@ -100,7 +92,7 @@
<el-table v-loading="loading" :data="userList" @selection-change="handleSelectionChange"> <el-table v-loading="loading" :data="userList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="50" align="center" /> <el-table-column type="selection" width="50" align="center" />
<el-table-column label="用户编号" align="center" key="id" prop="id" v-if="columns[0].visible" /> <el-table-column label="用户ID" align="center" key="id" prop="id" v-if="columns[0].visible" />
<el-table-column label="用户名称" align="center" key="account" prop="account" v-if="columns[1].visible" :show-overflow-tooltip="true" /> <el-table-column label="用户名称" align="center" key="account" prop="account" v-if="columns[1].visible" :show-overflow-tooltip="true" />
<el-table-column label="用户昵称" align="center" key="nickName" prop="nickName" v-if="columns[2].visible" :show-overflow-tooltip="true" /> <el-table-column label="用户昵称" align="center" key="nickName" prop="nickName" v-if="columns[2].visible" :show-overflow-tooltip="true" />
<!-- <el-table-column label="部门" align="center" key="deptName" prop="dept.deptName" v-if="columns[3].visible" :show-overflow-tooltip="true" />--> <!-- <el-table-column label="部门" align="center" key="deptName" prop="dept.deptName" v-if="columns[3].visible" :show-overflow-tooltip="true" />-->
@ -117,8 +109,8 @@
</el-table-column> </el-table-column>
<el-table-column label="谷歌令牌" align="center" key="googleAuthSecret" prop="googleAuthSecret" v-if="columns[6].visible"> <el-table-column label="谷歌令牌" align="center" key="googleAuthSecret" prop="googleAuthSecret" v-if="columns[6].visible">
<template slot-scope="scope"> <template slot-scope="scope">
<!-- <img :src="columns[6].url+'?content=otpauth://totp/GEO TAG_'+scope.row.account+'?secret='+scope.row.googleAuthSecret" style="width: 100px; height: 100px;"> --> <!-- <img :src="columns[6].url+'?content=otpauth://totp/GeoTag_'+scope.row.account+'?secret='+scope.row.googleAuthSecret" style="width: 100px; height: 100px;"> -->
<img :src="columns[6].url + '?content=' + encodeURIComponent(`otpauth://totp/GEO TAG_${scope.row.account}?secret=${scope.row.googleAuthSecret}`)" style="width: 100px; height: 100px;"> <img :src="columns[6].url + '?content=' + encodeURIComponent(`otpauth://totp/GeoTag_${scope.row.account}?secret=${scope.row.googleAuthSecret}`)" style="width: 100px; height: 100px;">
</template> </template>
</el-table-column> </el-table-column>
@ -141,14 +133,14 @@
@click="handleUpdate(scope.row)" @click="handleUpdate(scope.row)"
v-hasPermi="['system:user:edit']" v-hasPermi="['system:user:edit']"
>修改</el-button> >修改</el-button>
<el-button <!-- <el-button
v-if="scope.row.id !== 1" v-if="scope.row.id !== 1"
size="mini" size="mini"
type="text" type="text"
icon="el-icon-delete" icon="el-icon-delete"
@click="handleDelete(scope.row)" @click="handleDelete(scope.row)"
v-hasPermi="['system:user:remove']" v-hasPermi="['system:user:remove']"
>删除</el-button> >删除</el-button> -->
<el-button <el-button
size="mini" size="mini"
type="text" type="text"
@ -238,6 +230,45 @@
</div> </div>
</el-dialog> </el-dialog>
<el-dialog title="新增员工" :visible.sync="employeeOpen" width="500px" append-to-body>
<el-form ref="employeeForm" :model="employeeForm" :rules="employeeRules" label-width="90px">
<el-form-item label="昵称" prop="nickName">
<el-input v-model="employeeForm.nickName" placeholder="请输入昵称" />
</el-form-item>
<el-form-item label="登录账户" prop="account">
<el-input v-model="employeeForm.account" placeholder="请输入登录账户" />
</el-form-item>
<el-form-item label="密码" prop="passwordHash">
<el-input
v-model="employeeForm.passwordHash"
type="password"
show-password
placeholder="请输入密码"
/>
</el-form-item>
<el-form-item label="角色" prop="roleIds">
<el-select
v-model="employeeForm.roleIds"
multiple
filterable
placeholder="请选择角色"
style="width: 100%"
>
<el-option
v-for="item in roleOptions"
:key="item.roleId"
:label="(item.roleName || item.name) || ('角色' + item.roleId)"
:value="String(item.roleId)"
/>
</el-select>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitEmployeeForm"> </el-button>
<el-button @click="cancelEmployee"> </el-button>
</div>
</el-dialog>
<!-- 用户导入对话框 --> <!-- 用户导入对话框 -->
<el-dialog :title="upload.title" :visible.sync="upload.open" width="400px" append-to-body> <el-dialog :title="upload.title" :visible.sync="upload.open" width="400px" append-to-body>
<el-upload <el-upload
@ -272,8 +303,8 @@
</template> </template>
<script> <script>
import { listUser, getUser, delUser, addUser, updateUser, exportUser, resetUserPwd, changeUserStatus, importTemplate } from "@/api/system/user"; import { listUser, getUser, delUser, addUser, addEmployeeUser, updateEmployeeUser, exportUser, resetUserPwd, changeUserStatus, importTemplate } from "@/api/system/user";
import { listRole } from "@/api/system/role"; import { currentRoleOptions } from "@/api/system/role";
import { getToken } from "@/utils/auth"; import { getToken } from "@/utils/auth";
// import { treeselect } from "@/api/system/dept"; // import { treeselect } from "@/api/system/dept";
import Treeselect from "@riophae/vue-treeselect"; import Treeselect from "@riophae/vue-treeselect";
@ -304,6 +335,8 @@ export default {
deptOptions: undefined, deptOptions: undefined,
// //
open: false, open: false,
//
employeeOpen: false,
// //
deptName: undefined, deptName: undefined,
// //
@ -322,6 +355,12 @@ export default {
form: { form: {
roleIds: [] roleIds: []
}, },
employeeForm: {
nickName: undefined,
account: undefined,
passwordHash: undefined,
roleIds: []
},
defaultProps: { defaultProps: {
children: "children", children: "children",
label: "label" label: "label"
@ -369,6 +408,9 @@ export default {
nickName: [ nickName: [
{ required: true, message: "用户昵称不能为空", trigger: "blur" } { required: true, message: "用户昵称不能为空", trigger: "blur" }
], ],
roleIds: [
{ required: true, type: "array", min: 1, message: "\u8bf7\u9009\u62e9\u89d2\u8272", trigger: "change" }
],
passwordHash: [ passwordHash: [
{ required: true, message: "用户密码不能为空", trigger: "blur" } { required: true, message: "用户密码不能为空", trigger: "blur" }
], ],
@ -386,6 +428,20 @@ export default {
trigger: "blur" trigger: "blur"
} }
] ]
},
employeeRules: {
nickName: [
{ required: true, message: "昵称不能为空", trigger: "blur" }
],
account: [
{ required: true, message: "登录账户不能为空", trigger: "blur" }
],
roleIds: [
{ required: true, type: "array", min: 1, message: "\u8bf7\u9009\u62e9\u89d2\u8272", trigger: "change" }
],
passwordHash: [
{ required: true, message: "密码不能为空", trigger: "blur" }
]
} }
}; };
}, },
@ -410,14 +466,17 @@ export default {
// }); // });
}, },
methods: { methods: {
normalizeRoleOptions(list = []) {
return (Array.isArray(list) ? list : []).map(item => ({
roleId: item.roleId != null ? item.roleId : item.id,
roleName: item.roleName != null ? item.roleName : item.name
}));
},
/** 查询角色列表(拉取全部角色,统一为 roleId/roleName 便于下拉展示) */ /** 查询角色列表(拉取全部角色,统一为 roleId/roleName 便于下拉展示) */
getRoleList() { getRoleList() {
listRole({ pageNum: 1, pageSize: 1000 }).then(response => { return currentRoleOptions().then(response => {
const list = response.data.list || response.data || []; this.roleOptions = this.normalizeRoleOptions(response.data || []);
this.roleOptions = list.map(item => ({ return this.roleOptions;
roleId: item.roleId != null ? item.roleId : item.id,
roleName: item.roleName != null ? item.roleName : item.name
}));
}); });
}, },
/** 查询用户列表 */ /** 查询用户列表 */
@ -470,6 +529,10 @@ export default {
this.open = false; this.open = false;
this.reset(); this.reset();
}, },
cancelEmployee() {
this.employeeOpen = false;
this.resetEmployeeForm();
},
// //
reset() { reset() {
this.form = { this.form = {
@ -488,6 +551,15 @@ export default {
}; };
this.resetForm("form"); this.resetForm("form");
}, },
resetEmployeeForm() {
this.employeeForm = {
nickName: undefined,
account: undefined,
passwordHash: undefined,
roleIds: []
};
this.resetForm("employeeForm");
},
/** 搜索按钮操作 */ /** 搜索按钮操作 */
handleQuery() { handleQuery() {
this.queryParams.page = 1; this.queryParams.page = 1;
@ -513,31 +585,22 @@ export default {
this.title = "添加用户"; this.title = "添加用户";
this.form.passwordHash = this.passwordHash; this.form.passwordHash = this.passwordHash;
}, },
handleAddEmployee() {
this.resetEmployeeForm();
this.getRoleList();
this.employeeOpen = true;
},
/** 修改按钮操作 */ /** 修改按钮操作 */
handleUpdate(row) { handleUpdate(row) {
this.reset(); this.reset();
const id = row.id || this.ids; const id = row.id || this.ids;
Promise.all([getUser(id), listRole({ pageNum: 1, pageSize: 1000 })]).then(([userRes, roleRes]) => { Promise.all([getUser(id), this.getRoleList()]).then(([userRes, roleOptions]) => {
const data = userRes.data; // data const data = userRes.data; // data
// roleIdsroles data // roleIdsroles data
const resRoleIds = userRes.roleIds || []; const resRoleIds = userRes.roleIds || [];
const resRoles = userRes.roles || []; this.roleOptions = roleOptions;
const list = roleRes.data?.list || roleRes.data || [];
// listRole getUser roles roleName / // listRole getUser roles roleName /
const optionsMap = new Map();
list.forEach(item => {
optionsMap.set(String(item.roleId != null ? item.roleId : item.id), {
roleId: item.roleId != null ? item.roleId : item.id,
roleName: item.roleName != null ? item.roleName : item.name
});
});
resRoles.forEach(r => {
const rid = r.roleId != null ? r.roleId : r.id;
const name = r.roleName != null ? r.roleName : r.name;
if (rid != null) optionsMap.set(String(rid), { roleId: rid, roleName: name });
});
this.roleOptions = Array.from(optionsMap.values());
// roleIds // roleIds
const roleIds = resRoleIds.map(rid => String(rid)); const roleIds = resRoleIds.map(rid => String(rid));
@ -545,7 +608,7 @@ export default {
this.form = Object.assign({}, data, { this.form = Object.assign({}, data, {
postIds: userRes.postIds || [], postIds: userRes.postIds || [],
roleIds: roleIds, roleIds: roleIds,
passwordHash: "" passwordHash: undefined
}); });
this.$set(this.form, "roleIds", roleIds); this.$set(this.form, "roleIds", roleIds);
this.title = "修改用户"; this.title = "修改用户";
@ -569,14 +632,21 @@ export default {
submitForm: function() { submitForm: function() {
this.$refs["form"].validate(valid => { this.$refs["form"].validate(valid => {
if (valid) { if (valid) {
if (!Array.isArray(this.form.roleIds) || this.form.roleIds.length === 0) {
this.$modal.msgError("\u8bf7\u5148\u9009\u62e9\u89d2\u8272");
return;
}
// ID // ID
const submitData = { const submitData = {
...this.form, ...this.form,
roleIds: this.form.roleIds.map(id => Number(id)) roleIds: this.form.roleIds.map(id => Number(id))
}; };
if (!submitData.passwordHash) {
delete submitData.passwordHash;
}
if (this.form.id != undefined) { if (this.form.id != undefined) {
updateUser(submitData).then(response => { updateEmployeeUser(submitData).then(response => {
this.msgSuccess("修改成功"); this.msgSuccess("修改成功");
this.open = false; this.open = false;
this.getList(); this.getList();
@ -591,6 +661,30 @@ export default {
} }
}); });
}, },
submitEmployeeForm() {
this.$refs["employeeForm"].validate(valid => {
if (!valid) {
return;
}
if (!Array.isArray(this.employeeForm.roleIds) || this.employeeForm.roleIds.length === 0) {
this.$modal.msgError("\u8bf7\u5148\u9009\u62e9\u89d2\u8272");
return;
}
const submitData = {
nickName: this.employeeForm.nickName,
account: this.employeeForm.account,
passwordHash: this.employeeForm.passwordHash,
status: 0,
roleIds: (this.employeeForm.roleIds || []).map(id => Number(id))
};
addEmployeeUser(submitData).then(() => {
this.msgSuccess("新增员工成功");
this.employeeOpen = false;
this.resetEmployeeForm();
this.getList();
});
});
},
/** 删除按钮操作 */ /** 删除按钮操作 */
handleDelete(row) { handleDelete(row) {
const ids = row.id || this.ids; const ids = row.id || this.ids;

90
src/views/system/user/profile/index.vue

@ -1,98 +1,14 @@
<template> <template>
<div class="app-container"> <div class="app-container">
<el-row :gutter="20"> <profile-settings-card />
<el-col :span="6" :xs="24">
<el-card class="box-card">
<div slot="header" class="clearfix">
<span>个人信息</span>
</div>
<div>
<div class="text-center">
<userAvatar :user="user" />
</div>
<ul class="list-group list-group-striped">
<li class="list-group-item">
<svg-icon icon-class="user" />用户名称
<div class="pull-right">{{ user.userName }}</div>
</li>
<li class="list-group-item" v-if="commissionRate">
<svg-icon icon-class="money" />佣金比例
<div class="pull-right">{{ commissionRate }}</div>
</li>
<li class="list-group-item" v-if="user.phonenumber">
<svg-icon icon-class="phone" />手机号码
<div class="pull-right">{{ user.phonenumber }}</div>
</li>
<li class="list-group-item" v-if="user.email">
<svg-icon icon-class="email" />用户邮箱
<div class="pull-right">{{ user.email }}</div>
</li>
<li class="list-group-item" v-if="user.dept">
<svg-icon icon-class="tree" />所属部门
<div class="pull-right" v-if="user.dept">{{ user.dept.deptName }} / {{ postGroup }}</div>
</li>
<li class="list-group-item">
<svg-icon icon-class="peoples" />所属角色
<div class="pull-right">{{ roleGroup }}</div>
</li>
<li class="list-group-item">
<svg-icon icon-class="date" />创建日期
<div class="pull-right">{{ user.createTime }}</div>
</li>
</ul>
</div>
</el-card>
</el-col>
<el-col :span="18" :xs="24">
<el-card>
<div slot="header" class="clearfix">
<span>基本资料</span>
</div>
<el-tabs v-model="activeTab">
<el-tab-pane label="基本资料" name="userinfo">
<userInfo :user="user" />
</el-tab-pane>
<el-tab-pane label="修改密码" name="resetPwd">
<resetPwd :user="user" />
</el-tab-pane>
</el-tabs>
</el-card>
</el-col>
</el-row>
</div> </div>
</template> </template>
<script> <script>
import userAvatar from "./userAvatar"; import ProfileSettingsCard from "../components/ProfileSettingsCard";
import userInfo from "./userInfo";
import resetPwd from "./resetPwd";
import { getUserProfile } from "@/api/system/user";
export default { export default {
name: "Profile", name: "Profile",
components: { userAvatar, userInfo, resetPwd }, components: { ProfileSettingsCard },
data() {
return {
user: {},
roleGroup: {},
commissionRate:{},
postGroup: {},
activeTab: "userinfo"
};
},
created() {
this.getUser();
},
methods: {
getUser() {
getUserProfile().then(response => {
this.user = response.data;
this.roleGroup = response.roleGroup;
this.postGroup = response.postGroup;
this.commissionRate=response.commissionRate;
this.user.address=response.address;
});
}
}
}; };
</script> </script>

19
src/views/system/user/profile/userInfo.vue

@ -3,25 +3,10 @@
<el-form-item label="用户昵称" prop="nickName"> <el-form-item label="用户昵称" prop="nickName">
<el-input v-model="user.nickName" /> <el-input v-model="user.nickName" />
</el-form-item> </el-form-item>
<el-form-item label=" 钱包地址" prop="address":disabled="user.userType!='admin'">
<el-input v-model="user.address" />
</el-form-item>
<el-form-item label="手机号码" prop="phonenumber" v-if="user.userType=='admin'">
<el-input v-model="user.phonenumber" maxlength="11" />
</el-form-item>
<el-form-item label="邮箱" prop="email" v-if="user.userType=='admin'">
<el-input v-model="user.email" maxlength="50" />
</el-form-item>
<el-form-item label="性别" v-if="user.userType=='admin'">
<el-radio-group v-model="user.sex">
<el-radio label="0"></el-radio>
<el-radio label="1"></el-radio>
</el-radio-group>
</el-form-item>
<el-form-item v-if="user.userType=='admin'">
<el-button type="primary" size="mini" @click="submit">保存</el-button> <el-button type="primary" size="mini" @click="submit">保存</el-button>
<el-button type="danger" size="mini" @click="close">关闭</el-button> <el-button type="danger" size="mini" @click="close">关闭</el-button>
</el-form-item>
</el-form> </el-form>
</template> </template>

2
vue.config.js

@ -7,7 +7,7 @@ function resolve(dir) {
const CompressionPlugin = require('compression-webpack-plugin') 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 = 'http://127.0.0.1:10022' // 后端接口
// const baseUrl ="https://hfmt8jvy.currencyacquirer.online/stage-api/" // const baseUrl ="https://hfmt8jvy.currencyacquirer.online/stage-api/"

Loading…
Cancel
Save