Compare commits

...

23 Commits

Author SHA1 Message Date
67e06eb2e4 Merge branch 'main' of http://47.107.152.87:3000/dyf/dyf-vue-ui 2025-07-22 10:49:53 +08:00
f07c7c60af 设备列表二维码样式修改 2025-07-22 10:48:53 +08:00
28fcd65479 更新 2025-07-22 10:39:26 +08:00
2d0eda83e8 修改bug 2025-07-22 10:29:34 +08:00
6a49341c6a Merge branch 'main' of http://47.107.152.87:3000/dyf/dyf-vue-ui 2025-07-21 10:10:54 +08:00
bfa30c67bb 更新二维码 2025-07-21 10:09:42 +08:00
893fac77e6 Merge branch 'main' of http://47.107.152.87:3000/dyf/dyf-vue-ui 2025-07-19 17:54:50 +08:00
dd6cdf01e1 App用户绑定日期字段 2025-07-19 17:54:42 +08:00
1db495b08e 更新设备导入模版地址 2025-07-19 08:43:58 +08:00
e1738a6ea1 客户管理,账号状态修改 2025-07-18 14:54:44 +08:00
84aa490c20 修复设备列表bug 2025-07-18 09:37:57 +08:00
fb8bbbf5f5 修复编辑设备bug 2025-07-18 09:27:15 +08:00
a195b3e325 修复设备列表bug 2025-07-17 16:56:21 +08:00
c1adbbb9fe Merge branch 'main' of http://47.107.152.87:3000/dyf/dyf-vue-ui 2025-07-14 17:13:47 +08:00
ae445e058f 提交 2025-07-14 17:12:55 +08:00
5835c7c3fe 更新配置环境 2025-07-14 17:12:29 +08:00
a6b07348a6 查看用户绑定,解绑接口对接联调 2025-07-14 15:34:38 +08:00
a01f66dcc4 更新代码1 2025-07-10 17:31:00 +08:00
3df4bc3c9b 设备增加蓝牙设备的蓝牙名称,修复设备类型下拉数据bug 2025-07-10 13:33:41 +08:00
421df91afe 设备增加蓝牙字段 2025-07-10 11:17:17 +08:00
4cfef311ec 111 2025-07-10 11:03:19 +08:00
c66d309a3d Merge branch 'main' of http://47.107.152.87:3000/dyf/dyf-vue-ui 2025-07-10 10:25:50 +08:00
f2dada978d 修复账号管理状态 2025-07-10 10:24:42 +08:00
14 changed files with 341 additions and 233 deletions

View File

@ -5,7 +5,7 @@ VITE_APP_TITLE = 物联网管理系统
VITE_APP_ENV = 'development'
# 开发环境
VITE_APP_BASE_API = 'http://192.168.2.23:8001'
VITE_APP_BASE_API = 'http://192.168.2.23:8000'
# 应用访问路径 例如使用前缀 /admin/
VITE_APP_CONTEXT_PATH = '/'

View File

@ -1,11 +1,17 @@
# 页面标题
VITE_APP_TITLE = 物联网管理系统
# 生产环境配置
# 生产环境配置 晶全1
VITE_APP_ENV = 'https://fuyuanshen.com/backend'
# 应用访问路径 例如使用前缀 /admin/
VITE_APP_CONTEXT_PATH = '/sys/'
# 生产环境配置 富源晟2
# VITE_APP_ENV = 'https://fuyuanshen.com/backend-fys'
# 应用访问路径 晶全1
VITE_APP_CONTEXT_PATH = '/jingquan/'
# 应用访问路径 富源晟2
#VITE_APP_CONTEXT_PATH = '/sys/'
# 监控地址
VITE_APP_MONITOR_ADMIN = '/admin/applications'
@ -13,9 +19,12 @@ VITE_APP_MONITOR_ADMIN = '/admin/applications'
# SnailJob 控制台地址
VITE_APP_SNAILJOB_ADMIN = '/snail-job'
# 生产环境
# 生产环境 晶全3
VITE_APP_BASE_API = '/backend'
# 生产环境 富源晟3
#VITE_APP_BASE_API = '/backend-fys'
# 是否在打包时开启压缩,支持 gzip 和 brotli
VITE_BUILD_COMPRESS = gzip

View File

@ -29,55 +29,4 @@ npm run dev
npm run build:prod
# 前端访问地址 http://localhost:80
```
## 本框架与RuoYi的业务差异
| 业务 | 功能说明 | 本框架 | RuoYi |
| ------------ | ------------------------------------------------------------- | ------ | ----------------------------- |
| 租户管理 | 系统内租户的管理 如:租户套餐、过期时间、用户数量、企业信息等 | 支持 | 无 |
| 租户套餐管理 | 系统内租户所能使用的套餐管理 如:套餐内所包含的菜单等 | 支持 | 无 |
| 用户管理 | 用户的管理配置 如:新增用户、分配用户所属部门、角色、岗位等 | 支持 | 支持 |
| 部门管理 | 配置系统组织机构(公司、部门、小组) 树结构展现支持数据权限 | 支持 | 支持 |
| 岗位管理 | 配置系统用户所属担任职务 | 支持 | 支持 |
| 菜单管理 | 配置系统菜单、操作权限、按钮权限标识等 | 支持 | 支持 |
| 角色管理 | 角色菜单权限分配、设置角色按机构进行数据范围权限划分 | 支持 | 支持 |
| 字典管理 | 对系统中经常使用的一些较为固定的数据进行维护 | 支持 | 支持 |
| 参数管理 | 对系统动态配置常用参数 | 支持 | 支持 |
| 通知公告 | 系统通知公告信息发布维护 | 支持 | 支持 |
| 操作日志 | 系统正常操作日志记录和查询 系统异常信息日志记录和查询 | 支持 | 支持 |
| 登录日志 | 系统登录日志记录查询包含登录异常 | 支持 | 支持 |
| 文件管理 | 系统文件展示、上传、下载、删除等管理 | 支持 | 无 |
| 文件配置管理 | 系统文件上传、下载所需要的配置信息动态添加、修改、删除等管理 | 支持 | 无 |
| 在线用户管理 | 已登录系统的在线用户信息监控与强制踢出操作 | 支持 | 支持 |
| 定时任务 | 运行报表、任务管理(添加、修改、删除)、日志管理、执行器管理等 | 支持 | 仅支持任务与日志管理 |
| 代码生成 | 多数据源前后端代码的生成java、html、xml、sql支持CRUD下载 | 支持 | 仅支持单数据源 |
| 系统接口 | 根据业务代码自动生成相关的api接口文档 | 支持 | 支持 |
| 服务监控 | 监视集群系统CPU、内存、磁盘、堆栈、在线日志、Spring相关配置等 | 支持 | 仅支持单机CPU、内存、磁盘监控 |
| 缓存监控 | 对系统的缓存信息查询,命令统计等。 | 支持 | 支持 |
| 在线构建器 | 拖动表单元素生成相应的HTML代码。 | 支持 | 支持 |
| 使用案例 | 系统的一些功能案例 | 支持 | 不支持 |
## 演示图例
| | |
| ---------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
| ![输入图片说明](https://foruda.gitee.com/images/1680077524361362822/270bb429_1766278.png '屏幕截图') | ![输入图片说明](https://foruda.gitee.com/images/1680077619939771291/989bf9b6_1766278.png '屏幕截图') |
| ![输入图片说明](https://foruda.gitee.com/images/1680077681751513929/1c27c5bd_1766278.png '屏幕截图') | ![输入图片说明](https://foruda.gitee.com/images/1680077721559267315/74d63e23_1766278.png '屏幕截图') |
| ![输入图片说明](https://foruda.gitee.com/images/1680077765638904515/1b75d4a6_1766278.png '屏幕截图') | ![输入图片说明](https://foruda.gitee.com/images/1680078026375951297/eded7a4b_1766278.png '屏幕截图') |
| ![输入图片说明](https://foruda.gitee.com/images/1680078237104531207/0eb1b6a7_1766278.png '屏幕截图') | ![输入图片说明](https://foruda.gitee.com/images/1680078254306078709/5931e22f_1766278.png '屏幕截图') |
| ![输入图片说明](https://foruda.gitee.com/images/1680078287971528493/0b9af60a_1766278.png '屏幕截图') | ![输入图片说明](https://foruda.gitee.com/images/1680078308138770249/8d3b6696_1766278.png '屏幕截图') |
| ![输入图片说明](https://foruda.gitee.com/images/1680078352553634393/db5ef880_1766278.png '屏幕截图') | ![输入图片说明](https://foruda.gitee.com/images/1680078378238393374/601e4357_1766278.png '屏幕截图') |
| ![输入图片说明](https://foruda.gitee.com/images/1680078414983206024/2aae27c1_1766278.png '屏幕截图') | ![输入图片说明](https://foruda.gitee.com/images/1680078446738419874/ecce7d59_1766278.png '屏幕截图') |
| ![输入图片说明](https://foruda.gitee.com/images/1680078475971341775/149e8634_1766278.png '屏幕截图') | ![输入图片说明](https://foruda.gitee.com/images/1680078491666717143/3fadece7_1766278.png '屏幕截图') |
| ![输入图片说明](https://foruda.gitee.com/images/1680078558863188826/fb8ced2a_1766278.png '屏幕截图') | ![输入图片说明](https://foruda.gitee.com/images/1680078574561685461/ae68a0b2_1766278.png '屏幕截图') |
| ![输入图片说明](https://foruda.gitee.com/images/1680078594932772013/9d8bfec6_1766278.png '屏幕截图') | ![输入图片说明](https://foruda.gitee.com/images/1680078626493093532/fcfe4ff6_1766278.png '屏幕截图') |
| ![输入图片说明](https://foruda.gitee.com/images/1680078643608812515/0295bd4f_1766278.png '屏幕截图') | ![输入图片说明](https://foruda.gitee.com/images/1680078685196286463/d7612c81_1766278.png '屏幕截图') |
| ![输入图片说明](https://foruda.gitee.com/images/1680078703877318597/56fce0bc_1766278.png '屏幕截图') | ![输入图片说明](https://foruda.gitee.com/images/1680078716586545643/b6dbd68f_1766278.png '屏幕截图') |
| ![输入图片说明](https://foruda.gitee.com/images/1680078734103217688/eb1e6aa6_1766278.png '屏幕截图') | ![输入图片说明](https://foruda.gitee.com/images/1680078759131415480/73c525d8_1766278.png '屏幕截图') |
| ![输入图片说明](https://foruda.gitee.com/images/1680078779416197879/75e3ed02_1766278.png '屏幕截图') | ![输入图片说明](https://foruda.gitee.com/images/1680078802329118061/77e10915_1766278.png '屏幕截图') |
| ![输入图片说明](https://foruda.gitee.com/images/1680078893627848351/34a1c342_1766278.png '屏幕截图') | ![输入图片说明](https://foruda.gitee.com/images/1680078928175016986/f126ec4a_1766278.png '屏幕截图') |
| ![输入图片说明](https://foruda.gitee.com/images/1680078941718318363/b68a0f72_1766278.png '屏幕截图') | ![输入图片说明](https://foruda.gitee.com/images/1680078963175518631/3bb769a1_1766278.png '屏幕截图') |
| ![输入图片说明](https://foruda.gitee.com/images/1680078982294090567/b31c343d_1766278.png '屏幕截图') | ![输入图片说明](https://foruda.gitee.com/images/1680079000642440444/77ca82a9_1766278.png '屏幕截图') |
| ![输入图片说明](https://foruda.gitee.com/images/1680079020995074177/03b7d52e_1766278.png '屏幕截图') | ![输入图片说明](https://foruda.gitee.com/images/1680079039367822173/76811806_1766278.png '屏幕截图') |
| ![输入图片说明](https://foruda.gitee.com/images/1680079274333484664/4dfdc7c0_1766278.png '屏幕截图') | ![输入图片说明](https://foruda.gitee.com/images/1680079290467458224/d6715fcf_1766278.png '屏幕截图') |

View File

@ -38,6 +38,7 @@
"mitt": "^3.0.1",
"nprogress": "0.2.0",
"pinia": "3.0.2",
"qrcode-vue3": "^1.7.1",
"screenfull": "6.0.2",
"vue": "3.5.13",
"vue-cropper": "1.1.1",

View File

@ -72,12 +72,11 @@ export const userAllCustomerAll = () => {
}
// 解绑
export const deviceUnbind = (data:any) => {
export const deviceUnbind = (params:any) => {
return request({
url: '/api/device/unbind',
method: 'post',
data
method: 'GET',
params
})
}
// 撤回

View File

@ -4,5 +4,44 @@ export interface deviceQuery extends PageQuery {
deviceImei: string;
deviceType: string;
deviceStatus: string;
bluetoothName?: string; // 蓝牙名称查询字段
}
export interface deviceForm {
id?: string | number;
deviceName: string;
deviceMac?: string;
deviceImei?: string;
deviceType: string;
devicePic?: string;
image?: string | File;
remark?: string;
password?: string;
customerId?: string | number;
bluetoothName?: string; // 蓝牙名称字段
}
export interface deviceVO {
id: string | number;
deviceName: string;
deviceMac?: string;
deviceImei?: string;
deviceType: string;
devicePic?: string;
deviceStatus: number;
bindingStatus: number;
remark?: string;
createTime?: string;
createByName?: string;
customerName?: string;
customerId?: string | number;
typeName?: string;
bluetoothName?: string; // 蓝牙名称字段
}
export interface deviceTypeOption {
id: string | number;
typeName: string;
value: string | number;
communicationMode?: string;
}

View File

@ -4,7 +4,7 @@ import { AxiosPromise } from 'axios';
* 查询设备列表
* @param query
*/
export const userList = (params): AxiosPromise => {
export const userList = (params: any): AxiosPromise => {
return request({
url: '/app/user/list',
method: 'get',
@ -12,7 +12,7 @@ export const userList = (params): AxiosPromise => {
});
};
// 绑定设备列表
export const deviceList = (params): AxiosPromise => {
export const deviceList = (params: any): AxiosPromise => {
return request({
url: '/api/app/device',
method: 'get',
@ -20,12 +20,24 @@ export const deviceList = (params): AxiosPromise => {
});
};
// 账号状态
export const userStatus = (data): AxiosPromise => {
export const userStatus = (data: any): AxiosPromise => {
return request({
url: '/api/app/device',
method: 'put',
data
});
};
// 解绑设备
export const deviceUnBind = (data: any): AxiosPromise => {
return request({
url: '/api/app/device/unBind',
method: 'delete',
data,
headers: {
'Content-Type': 'multipart/form-data', //设置正确的 Content-Type
},
});
};
export default { userList,deviceList,userStatus }
export default { userList, deviceList, userStatus, deviceUnBind }

View File

@ -20,7 +20,7 @@
<div class="avatar-wrapper">
<!-- <img :src="userStore.avatar" class="user-avatar" /> -->
<img src="@/assets/images/avatar.png" class="user-avatar" />
<div style="margin-left: 10px;">{{ useUserStore().roles[0] }}</div>
<div style="margin-left: 10px;">{{ useUserStore().nickname }}</div>
<el-icon><caret-bottom /></el-icon>
</div>
<template #dropdown>
@ -61,7 +61,7 @@ const newNotice = ref(<number>0);
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const userId = ref(userStore.userId);
console.log(useUserStore().roles[0], 'userStoreuserStoreuserStore');
// console.log(useUserStore(), 'userStoreuserStoreuserStore');
const companyName = ref(undefined);
@ -82,7 +82,7 @@ const dynamicTenantEvent = async (tenantId: string) => {
if (companyName.value != null && companyName.value !== '') {
await dynamicTenant(tenantId);
dynamic.value = true;
await proxy?.$router.push('/');
await router.push('/');
await proxy?.$tab.closeAllPage();
await proxy?.$tab.refreshPage();
}
@ -91,7 +91,7 @@ const dynamicTenantEvent = async (tenantId: string) => {
const dynamicClearEvent = async () => {
await dynamicClear();
dynamic.value = false;
await proxy?.$router.push('/');
await router.push('/');
await proxy?.$tab.closeAllPage();
await proxy?.$tab.refreshPage();
};

View File

@ -1,29 +1,6 @@
import { createWebHistory, createRouter, RouteRecordRaw } from 'vue-router';
/* Layout */
import Layout from '@/layout/index.vue';
/**
* Note: 路由配置项
*
* hidden: true // 当设置 true 的时候该路由不会再侧边栏出现 如401login等页面或者如一些编辑页面/edit/1
* alwaysShow: true // 当你一个路由下面的 children 声明的路由大于1个时自动会变成嵌套的模式--如组件页面
* // 只有一个时,会将那个子路由当做根路由显示在侧边栏--如引导页面
* // 若你想不管路由下面的 children 声明的个数都显示你的根路由
* // 你可以设置 alwaysShow: true这样它就会忽略之前定义的规则一直显示根路由
* redirect: noRedirect // 当设置 noRedirect 的时候该路由在面包屑导航中不可被点击
* name:'router-name' // 设定路由的名字,一定要填写不然使用<keep-alive>时会出现各种问题
* query: '{"id": 1, "name": "ry"}' // 访问路由的默认传递参数
* roles: ['admin', 'common'] // 访问路由的角色权限
* permissions: ['a:a:a', 'b:b:b'] // 访问路由的菜单权限
* meta : {
noCache: true // 如果设置为true则不会被 <keep-alive> 缓存(默认 false)
title: 'title' // 设置该路由在侧边栏和面包屑中展示的名字
icon: 'svg-name' // 设置该路由的图标对应路径src/assets/icons/svg
breadcrumb: false // 如果设置为false则不会在breadcrumb面包屑中显示
activeMenu: '/system/user' // 当路由设置了该属性,则会高亮相对应的侧边栏。
}
*/
// 公共路由
export const constantRoutes: RouteRecordRaw[] = [
{

View File

@ -34,9 +34,9 @@
</div>
</template>
</el-table-column>
<el-table-column label="创建时间" align="center" prop="loginDate" width="180">
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
<template #default="scope">
<span>{{ proxy.parseTime(scope.row.loginDate) }}</span>
<span>{{ proxy.parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" fixed="right" align="center">
@ -137,10 +137,10 @@ const handleStatusChange = (row: any) => {
getList();
}
}).catch(() => {
row.status = !row.status
getList();
})
}).catch(() => {
row.status = !row.status
getList();
})
};
onMounted(() => {

View File

@ -12,23 +12,23 @@
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</div>
<!-- 表格 -->
<el-table v-loading="bindingLoading" :data="boundDevices" style="width: 100%; margin-top: 20px;" height="400">
<el-table v-loading="bindingLoading" :data="boundDevices" style="margin-top: 20px;">
<el-table-column prop="deviceName" label="设备名称" />
<el-table-column prop="devicePic" label="设备图片">
<template #default="scope">
<el-image style="width: 40px; height: 40px" :src="scope.row.devicePic"
:preview-src-list="[scope.row.devicePic]" />
<el-popover placement="right" trigger="click">
<template #reference>
<img :src="scope.row.devicePic" style="width: 40px; height: 40px; cursor: pointer;"
class="hover:opacity-80 transition-opacity" />
</template>
<img :src="scope.row.devicePic" />
</el-popover>
</template>
</el-table-column>
<el-table-column prop="deviceMac" label="设备MAC" />
<el-table-column prop="deviceImei" label="设备IMEI" />
<el-table-column prop="deviceTypeName" label="设备类型" />
<el-table-column prop="deviceStatus" label="状态">
<template #default="scope">
<span>{{ scope.row.deviceStatus === 1 ? '正常' : '失效' }}</span>
</template>
</el-table-column>
<el-table-column prop="createTime" label="绑定日期" />
<el-table-column prop="typeName" label="设备类型" />
<el-table-column prop="bindingTime" label="绑定日期" />
<el-table-column label="操作" width="100" align="center">
<template #default="scope">
<el-button type="danger" plain @click="handleUnbind(scope.row)">解绑</el-button>
@ -74,27 +74,40 @@ const handleQuery = () => {
/** 重置按钮操作 */
const resetQuery = () => {
queryParams.pageNum = 1;
queryParams.deviceImei = '',
queryParams.deviceMac = '',
queryParams.deviceName = '',
getList()
};
// 列表
const getList = async () => {
bindingLoading.value = true;
const res = await api.deviceList(queryParams);
let data = {
bindingUserId: info.value.userId,
pageNum: queryParams.pageNum,
pageSize: queryParams.pageSize,
deviceName: queryParams.deviceName,
deviceMac: queryParams.deviceMac,
deviceImei: queryParams.deviceImei
}
const res = await api.deviceList(data);
boundDevices.value = res.rows;
total.value = res.total;
bindingLoading.value = false;
};
// 解绑
const handleUnbind = (row) => {
proxy?.$modal.confirm.confirm('此操作将解绑设备 "' + row.deviceName + '", 是否继续?', '提示', {
proxy?.$modal.confirm('此操作将解绑设备 "' + row.deviceName + '", 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
const data = {
deviceMac: row.deviceMac,
customerId: info.value.id
// deviceMac: row.deviceMac,
id: row.id
}
api.deviceList(data).then(() => {
api.deviceUnBind(data).then(() => {
proxy?.$modal.msgSuccess({ type: 'success', message: '解绑成功!' })
getList()
})

View File

@ -9,10 +9,10 @@
<el-input v-model="queryParams.blurry" placeholder="请输入客户名称" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="客户状态" prop="status">
<el-select v-model="queryParams.enabled" clearable placeholder="客户状态" class="filter-item">
<el-select v-model="queryParams.status" clearable placeholder="客户状态" class="filter-item">
<el-option label="全部" :value="''" />
<el-option label="启用" :value="true" />
<el-option label="禁用" :value="false" />
<el-option label="启用" :value="0" />
<el-option label="禁用" :value="1" />
</el-select>
</el-form-item>
<el-form-item label="创建时间" style="width: 308px">
@ -53,10 +53,10 @@
<el-table-column type="selection" width="50" align="center" />
<el-table-column label="客户名称" align="center" prop="nickName" />
<el-table-column label="客户账号" align="center" prop="userName" :show-overflow-tooltip="true" />
<el-table-column label="客户状态" align="center" prop="enabled">
<el-table-column label="客户状态" align="center" prop="status">
<template #default="scope">
<div @click="handleStatusChange(scope.row)">
<el-switch v-model="scope.row.enabled" />
<el-switch v-model="scope.row.status" active-value="0" inactive-value="1" />
</div>
</template>
</el-table-column>
@ -104,7 +104,7 @@
<el-row>
<el-col :span="24">
<el-form-item v-if="form.customerId === ''" label="账号密码" prop="password">
<el-input v-model="form.password" placeholder="请输入账号密码" />
<el-input v-model="form.password" placeholder="请输入账号密码" />
</el-form-item>
</el-col>
</el-row>
@ -121,22 +121,48 @@
<script setup name="User" lang="ts">
import api from '@/api/customerManagement';
import { UserForm, UserQuery, UserVO } from '@/api/customerManagement/types';
import { to } from 'await-to-js';
import { ComponentInternalInstance, getCurrentInstance, onMounted, reactive, ref, toRefs } from 'vue';
import { ElDialog, ElForm, ElFormItem, ElInput, ElRow, ElCol, ElButton, ElCard, ElSelect, ElOption, ElDatePicker, ElTable, ElTableColumn, ElTooltip, ElSwitch } from 'element-plus';
// 定义接口
interface UserVO {
customerId: string | number;
nickName: string;
userName: string;
status: string;
createTime: string;
userId: number;
}
interface UserForm extends UserVO {
password?: string;
enabled?: boolean;
}
interface UserQuery {
pageNum: number;
pageSize: number;
blurry: string;
status: string;
enabled: string;
}
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const customerList = ref<UserVO[]>();
const loading = ref(true);
const showSearch = ref(true);
const ids = ref<Array<number | string>>([]);
const ids = ref<UserVO[]>([]);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const dateRange = ref<[DateModelType, DateModelType]>(['', '']);
const initPassword = ref<string>('');
const queryFormRef = ref<ElFormInstance>();
const userFormRef = ref<ElFormInstance>();
const formDialogRef = ref<ElDialogInstance>();
const queryFormRef = ref<InstanceType<typeof ElForm>>();
const userFormRef = ref<InstanceType<typeof ElForm>>();
const formDialogRef = ref<InstanceType<typeof ElDialog>>();
const loadingIng = ref(false)
const dialog = reactive<DialogOption>({
visible: false,
title: ''
@ -144,19 +170,27 @@ const dialog = reactive<DialogOption>({
const initFormData: UserForm = {
userName: '',
nickName: undefined,
nickName: '',
password: '',
enabled: true,
customerId: ''
customerId: '',
status: '',
createTime: '',
userId: 0,
};
const initData: PageData<UserForm, UserQuery> = {
const initData: {
form: UserForm,
queryParams: UserQuery,
rules: any
} = {
form: { ...initFormData },
queryParams: {
pageNum: 1,
pageSize: 10,
blurry: '',
enabled: '',
status: ''
},
rules: {
nickName: [
@ -171,14 +205,22 @@ const initData: PageData<UserForm, UserQuery> = {
trigger: 'blur'
}
],
password: [
{ required: true, message: '请输入账号密码', trigger: 'blur' },
{
min: 5,
max: 20,
message: '用户密码长度必须介于 5 和 20 之间',
trigger: 'blur'
},
{ pattern: /^[^<>"'|\\]+$/, message: '不能包含非法字符:< > " \' \\ |', trigger: 'blur' }
],
}
};
const data = reactive<PageData<UserForm, UserQuery>>(initData);
const data = reactive(initData);
const { queryParams, form, rules } = toRefs<PageData<UserForm, UserQuery>>(data);
const { queryParams, form, rules } = toRefs(data);
/** 查询用户列表 */
const getList = async () => {
@ -198,28 +240,24 @@ const handleQuery = () => {
/** 重置按钮操作 */
const resetQuery = () => {
dateRange.value = ['', ''];
queryFormRef.value?.resetFields();
queryParams.value.pageNum = 1;
queryParams.value.status=''
handleQuery();
};
/** 删除按钮操作 */
const handleDelete = async (row?: UserVO) => {
// 批量删除逻辑
let arrey = ids.value.map((item) => item.customerId);
if (!row) {
const [err] = await to(proxy?.$modal.confirm(`是否确认删除选中的 ${ids.value.length} 条数据?`) as any);
if (!err) {
await api.deleteCustomer(arrey);
await getList();
proxy?.$modal.msgSuccess('删除成功');
}
return;
}
// 单行删除逻辑
const [err] = await to(proxy?.$modal.confirm('是否确认删除"' + row.nickName + '"的数据项?') as any);
const customerIds = row ? [row.customerId] : ids.value.map(item => item.customerId);
const nickNames = row ? row.nickName : ids.value.map(item => item.nickName).join(',');
const [err] = await to(proxy?.$modal.confirm('是否确认删除"' + nickNames + '"的数据项?', '系统提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}) as any);
if (!err) {
await api.deleteCustomer([row.customerId]);
await api.deleteCustomer(customerIds);
await getList();
proxy?.$modal.msgSuccess('删除成功');
}
@ -227,25 +265,33 @@ const handleDelete = async (row?: UserVO) => {
/** 用户状态修改 */
const handleStatusChange = async (row: any) => {
console.log(row, '33333333');
const newStatus = row.enabled; // 获取新的状态值(取反)
const text = newStatus ? '启用' : '停用';
const originalStatus = row.status;
const actionText = row.status == 0 ? '启用' : '停用';
try {
await proxy?.$modal.confirm(`确认要${text}"${row.nickName}"客户吗?`);
await proxy?.$modal.confirm(`确认要${actionText}"${row.nickName}"客户吗?`, '系统提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
});
await api.updateCustomer({
customerId: row.customerId,
enabled: newStatus
enabled: originalStatus == 0 ? true : false
});
proxy?.$modal.msgSuccess(text + '成功');
proxy?.$modal.msgSuccess(actionText + '成功');
await getList();
} catch (err) {
row.enabled = !newStatus; // 回滚状态
if (err == 'cancel') {
console.log(err, 'errr');
proxy?.$modal.msgWarning('操作已取消');
await getList();
}
}
};
/** 选择条数 */
const handleSelectionChange = (selection: UserVO[]) => {
ids.value = selection.map((item) => item);
ids.value = selection;
single.value = selection.length != 1;
multiple.value = !selection.length;
};
@ -277,11 +323,10 @@ const handleUpdate = async (row?: UserForm) => {
dialog.title = '修改客户';
try {
if (row) {
// 从行内按钮调用,直接使用行数据
Object.assign(form.value, row);
} else {
const customerId = ids.value[0];
Object.assign(form.value, customerId);
const selectedId = ids.value[0];
Object.assign(form.value, selectedId);
}
} catch (error) {
dialog.visible = false;
@ -290,15 +335,17 @@ const handleUpdate = async (row?: UserForm) => {
/** 提交按钮 */
const submitForm = () => {
userFormRef.value?.validate(async (valid: boolean) => {
if (valid) {
loadingIng.value = true
form.value.customerId ? await api.updateCustomer(form.value) : await api.addCustomer(form.value);
proxy?.$modal.msgSuccess('操作成功');
dialog.visible = false;
await getList();
loadingIng.value = false
loadingIng.value = true;
try {
form.value.customerId ? await api.updateCustomer(form.value) : await api.addCustomer(form.value);
proxy?.$modal.msgSuccess('操作成功');
dialog.visible = false;
await getList();
} finally {
loadingIng.value = false;
}
}
});
};
@ -317,11 +364,10 @@ const closeDialog = () => {
const resetForm = () => {
userFormRef.value?.resetFields();
userFormRef.value?.clearValidate();
form.value.customerId = undefined;
form.value.customerId = '';
};
onMounted(() => {
getList(); // 初始化列表数据
});
</script>

View File

@ -136,42 +136,58 @@
<script setup name="User" lang="ts">
import api from '@/api/equipmentManagement/deviceType/index';
import { UserForm, deviceTypeQuery, deviceTypeVO } from '@/api/equipmentManagement/deviceType/types';
import { UserQuery, UserVO } from '@/api/system/user/types';
import { deviceTypeQuery } from '@/api/equipmentManagement/deviceType/types';
import { to } from 'await-to-js';
import { ComponentInternalInstance, getCurrentInstance, onMounted, reactive, ref, toRefs } from 'vue';
import { ElDialog, ElForm, ElFormItem, ElInput, ElRow, ElCol, ElButton, ElCard, ElSelect, ElOption, ElDatePicker, ElTable, ElTableColumn, ElTooltip, ElSwitch } from 'element-plus';
interface deviceTypeVO {
id: string | number;
typeName: string;
isSupportBle: boolean;
locateMode: string;
communicationMode: string;
createTime: string;
createByName: string;
}
interface DeviceTypeForm extends deviceTypeVO {
// 可能有其他字段
}
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const deviceTypeList = ref<deviceTypeVO[]>();
import { to } from 'await-to-js';
const loading = ref(true);
const showSearch = ref(true);
const ids = ref<Array<number | string>>([]);
const ids = ref<deviceTypeVO[]>([]);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const initPassword = ref<string>('');
const queryFormRef = ref<ElFormInstance>();
const userFormRef = ref<ElFormInstance>();
const formDialogRef = ref<ElDialogInstance>();
const queryFormRef = ref<InstanceType<typeof ElForm>>();
const userFormRef = ref<InstanceType<typeof ElForm>>();
const formDialogRef = ref<InstanceType<typeof ElDialog>>();
const loadingIng = ref(false)
const dialog = reactive<DialogOption>({
visible: false,
title: ''
});
const initFormData: UserForm = {
const initFormData: DeviceTypeForm = {
typeName: '',
isSupportBle: '',
isSupportBle: false,
locateMode: '',
communicationMode: '',
id: ''
id: '',
createTime: '',
createByName: '',
};
const initData: PageData<UserForm, deviceTypeQuery> = {
const initData = {
form: { ...initFormData },
queryParams: {
pageNum: 1,
pageSize: 10,
typeName: '',
},
rules: {
typeName: [
@ -185,8 +201,15 @@ const initData: PageData<UserForm, deviceTypeQuery> = {
],
}
};
const data = reactive<PageData<UserForm, UserQuery>>(initData);
const { queryParams, form, rules } = toRefs<PageData<UserForm, UserQuery>>(data);
const data = reactive<{
form: DeviceTypeForm,
queryParams: deviceTypeQuery,
rules: any
}>(initData);
const { queryParams, form, rules } = toRefs(data);
/** 查询用户列表 */
const getList = async () => {
loading.value = true;
@ -196,7 +219,6 @@ const getList = async () => {
total.value = res.total;
};
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.value.pageNum = 1;
@ -204,42 +226,34 @@ const handleQuery = () => {
};
/** 重置按钮操作 */
const resetQuery = () => {
queryParams.value.typeName = ''
queryFormRef.value?.resetFields();
queryParams.value.pageNum = 1;
getList();
};
/** 删除按钮操作 */
const handleDelete = async (row?: UserVO) => {
// 批量删除逻辑
let arrey = ids.value.map((item) => item.id);
if (!row) {
const [err] = await to(proxy?.$modal.confirm(`是否确认删除选中的 ${ids.value.length} 条数据?`) as any);
if (!err) {
await api.deleteDeviceType(arrey);
await getList();
proxy?.$modal.msgSuccess('删除成功');
}
return;
}
// 单行删除逻辑
const [err] = await to(proxy?.$modal.confirm('是否确认删除"' + row.typeName + '"的数据项?') as any);
const handleDelete = async (row?: deviceTypeVO) => {
const deviceTypeIds = row ? [row.id] : ids.value.map(item => item.id);
const typeNames = row ? row.typeName : ids.value.map(item => item.typeName).join(',');
const [err] = await to(proxy?.$modal.confirm('是否确认删除"' + typeNames + '"的数据项?', '系统提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}) as any);
if (!err) {
await api.deleteDeviceType([row.id]);
await api.deleteDeviceType(deviceTypeIds);
await getList();
proxy?.$modal.msgSuccess('删除成功');
}
};
/** 选择条数 */
const handleSelectionChange = (selection: UserVO[]) => {
ids.value = selection.map((item) => item);
const handleSelectionChange = (selection: deviceTypeVO[]) => {
ids.value = selection;
single.value = selection.length != 1;
multiple.value = !selection.length;
};
/** 重置操作表单 */
const reset = () => {
form.value = { ...initFormData };
@ -256,21 +270,19 @@ const handleAdd = async () => {
reset();
dialog.visible = true;
dialog.title = '新增设备类型';
form.value.password = initPassword.value.toString();
};
/** 修改按钮操作 */
const handleUpdate = async (row?: UserForm) => {
const handleUpdate = async (row?: DeviceTypeForm) => {
reset();
dialog.visible = true;
dialog.title = '修改设备类型';
try {
if (row) {
// 从行内按钮调用,直接使用行数据
Object.assign(form.value, row);
} else {
const customerId = ids.value[0];
Object.assign(form.value, customerId);
const selectedId = ids.value[0];
Object.assign(form.value, selectedId);
}
} catch (error) {
dialog.visible = false;
@ -282,11 +294,14 @@ const submitForm = () => {
userFormRef.value?.validate(async (valid: boolean) => {
if (valid) {
loadingIng.value = true;
form.value.id ? await api.updateDeviceType(form.value) : await api.addDeviceType(form.value);
proxy?.$modal.msgSuccess('操作成功');
dialog.visible = false;
await getList();
loadingIng.value = false;
try {
form.value.id ? await api.updateDeviceType(form.value) : await api.addDeviceType(form.value);
proxy?.$modal.msgSuccess('操作成功');
dialog.visible = false;
await getList();
} finally {
loadingIng.value = false;
}
}
});
};
@ -297,7 +312,6 @@ const submitForm = () => {
const closeDialog = () => {
dialog.visible = false;
resetForm();
loadingIng.value = false;
};
/**
@ -306,11 +320,11 @@ const closeDialog = () => {
const resetForm = () => {
userFormRef.value?.resetFields();
userFormRef.value?.clearValidate();
form.value.customerId = undefined;
form.value.id = '';
};
onMounted(() => {
getList(); // 初始化列表数据
});
</script>

View File

@ -90,6 +90,7 @@
</template>
</el-table-column>
<el-table-column prop="deviceMac" label="设备MAC" />
<el-table-column prop="bluetoothName" label="蓝牙名称" />
<el-table-column prop="deviceImei" label="设备IMEI" />
<el-table-column prop="typeName" label="设备类型" />
<el-table-column prop="bindingStatus" label="绑定状态">
@ -115,7 +116,7 @@
<el-tooltip v-if="scope.row.id !== 1 && scope.row.deviceStatus == 1" content="修改" placement="top">
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)"></el-button>
</el-tooltip>
<el-tooltip v-if="scope.row.id !== 1" content="删除" placement="top">
<el-tooltip v-if="!scope.row.customerName && scope.row.deviceStatus == 1" content="删除" placement="top">
<el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)"></el-button>
</el-tooltip>
<el-tooltip v-if="scope.row.deviceStatus == 1 && !scope.row.customerName" content="分配" placement="top">
@ -128,6 +129,9 @@
placement="top">
<el-button link type="primary" icon="Refresh" @click="handleUnbind(scope.row)"></el-button>
</el-tooltip>
<el-tooltip v-if="scope.row.deviceImei" content="查看二维码" placement="top">
<el-button link type="primary" icon="Postcard" @click="showQrCode(scope.row)"></el-button>
</el-tooltip>
</template>
</el-table-column>
</el-table>
@ -165,6 +169,13 @@
</el-form-item>
</el-col>
</el-row>
<el-row v-if="showMacField">
<el-col :span="24">
<el-form-item label="蓝牙名称" prop="bluetoothName">
<el-input v-model="form.bluetoothName" placeholder="请输入蓝牙名称" />
</el-form-item>
</el-col>
</el-row>
<el-row v-if="showImeiField">
<el-col :span="24">
<el-form-item label="设备IMEI" prop="deviceImei">
@ -263,12 +274,25 @@
</div>
</template>
</el-dialog>
<!-- IMEI 二维码弹窗 -->
<el-dialog v-model="qrCodeDialogVisible" title="设备IMEI二维码" width="20%" append-to-body>
<div style="text-align: center;">
<QRCodeVue3 :value="qrCodeValue" :size="100" />
<p style="margin-top: 10px;">{{ qrCodeValue }}</p>
</div>
<template #footer>
<div class="dialog-footer">
<el-button @click="qrCodeDialogVisible = false">关闭</el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup name="User" lang="ts">
import QRCodeVue3 from 'qrcode-vue3';
import api from '@/api/equipmentManagement/device/index';
import { deviceForm, deviceQuery, deviceVO } from '@/api/equipmentManagement/device/types';
import { deviceForm, deviceQuery, deviceVO, deviceTypeOption } from '@/api/equipmentManagement/device/types';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const deviceDist = ref<deviceVO[]>();
import { to } from 'await-to-js';
@ -277,7 +301,7 @@ import { getBearerToken } from '@/utils/auth'
const loading = ref(true);
const showSearch = ref(true);
const dateRange = ref<[DateModelType, DateModelType]>(['', '']);
const ids = ref<Array<number | string>>([]);
const ids = ref<deviceVO[]>([]);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
@ -297,6 +321,8 @@ const loadingIng = ref(false)
const assignCustomerId = ref(); //分配客户id
const batchAssignCustomerId = ref() //批量分配客户id
const customerList = ref()
const qrCodeDialogVisible = ref(false);
const qrCodeValue = ref('');
const dialog = reactive<DialogOption>({
visible: false,
title: ''
@ -309,7 +335,8 @@ const initFormData: deviceForm = {
remark: '',
id: '',
deviceType: "",
image: ''
image: '',
bluetoothName: '' // 蓝牙名称字段
};
const initData: PageData<deviceForm, deviceQuery> = {
@ -333,8 +360,8 @@ const initData: PageData<deviceForm, deviceQuery> = {
],
}
};
const data = reactive<PageData<deviceVO, deviceQuery>>(initData);
const { queryParams, form, rules } = toRefs<PageData<deviceVO, deviceQuery>>(data);
const data = reactive<PageData<deviceForm, deviceQuery>>(initData);
const { queryParams, form, rules } = toRefs<PageData<deviceForm, deviceQuery>>(data);
/** 查询设备列表 */
const getList = async () => {
loading.value = true;
@ -362,7 +389,7 @@ const handleDelete = async (row?: deviceVO) => {
// 批量删除逻辑
let arrey = ids.value.map((item) => item.id);
if (!row) {
const [err] = await to(proxy?.$modal.confirm(`是否确认删除选中的 ${ids.value.length} 条数据?`) as any);
const [err] = await to(proxy?.$modal.confirm(`是否确认删除选中的 ${ids.value.length} 条数据?`, '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }));
if (!err) {
await api.deleteDevice(arrey);
await getList();
@ -371,7 +398,7 @@ const handleDelete = async (row?: deviceVO) => {
return;
}
// 单行删除逻辑
const [err] = await to(proxy?.$modal.confirm('是否确认删除"' + row.deviceName + '"的数据项?') as any);
const [err] = await to(proxy?.$modal.confirm('是否确认删除"' + row.deviceName + '"的数据项?', '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }));
if (!err) {
await api.deleteDevice([row.id]);
await getList();
@ -396,12 +423,15 @@ const handleUnbind = (row) => {
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
api.deviceUnbind(row).then(res => {
if (res.code === 0) {
proxy?.$modal.msgSuccess('解绑成功')
let data = {
id: row.id
}
api.deviceUnbind(data).then(res => {
if (res.code == 200) {
proxy?.$modal.msgSuccess(res.msg)
getList(); // 初始化列表数据
} else {
proxy?.$modal.msgError(res.msg || '解绑失败')
proxy?.$modal.msgError(res.msg)
}
})
}).catch(() => {
@ -416,7 +446,7 @@ const handleWithdraw = (row: any) => {
type: 'warning'
}).then(() => {
api.withdrawDevice([row.id]).then(res => {
if (res.code === 0) {
if (res.code === 200) {
proxy?.$modal.msgSuccess('撤回成功')
getList(); // 初始化列表数据
} else {
@ -458,6 +488,8 @@ const handleAdd = async () => {
// 新增时默认不显示
showMacField.value = false;
showImeiField.value = false;
// 每次打开弹框时获取最新的设备类型数据
getDeviceType();
};
/** 修改按钮操作 */
@ -465,8 +497,12 @@ const handleUpdate = async (row?: deviceForm) => {
reset();
dialog.visible = true;
dialog.title = '修改设备';
// 每次打开弹框时获取最新的设备类型数据
getDeviceType();
try {
if (row) {
// 使用 nextTick 确保对话框完全渲染后再设置表单值
await nextTick();
Object.assign(form.value, row);
form.value.image = row.devicePic
// 编辑时根据已有值显示字段
@ -518,7 +554,7 @@ const handleDeviceTypeChange = async (deviceTypeId: string | number) => {
return;
}
const res = await api.getCommunicationMode({ id: deviceTypeId });
if (res.code === 0 && res.data) {
if (res.code == 200 && res.data) {
communicationModeInfo.value = res.data;
// 根据通讯方式确定显示哪个字段
if (res.data.communicationMode == '1') { // 蓝牙设备 - 显示MAC
@ -538,7 +574,9 @@ const handleDeviceTypeChange = async (deviceTypeId: string | number) => {
};
// 覆盖默认的上传行为,可以自定义上传的实现
const httpRequestImg = (parm) => { };
const httpRequestImg = (parm): Promise<any> => {
return Promise.resolve();
};
const beforeUpload = (file) => {
const isLt2M = file.size / 1024 / 1024 < 2;
const isJPG = file.type === "image/jpeg" || file.type === "image/png";
@ -593,6 +631,10 @@ const submitForm = async () => {
if (form.value.deviceImei) {
formData.append('deviceImei', form.value.deviceImei);
}
// 添加蓝牙名称字段(仅蓝牙设备)
if (form.value.bluetoothName) {
formData.append('bluetoothName', form.value.bluetoothName);
}
// 根据操作类型设置URL和方法
const isAdd = !form.value.id; // 注意这里逻辑反了应该是没有id时是新增
const res = await request({
@ -603,7 +645,7 @@ const submitForm = async () => {
'Content-Type': 'multipart/form-data'
}
});
if (res.code == 0) {
if (res.code == 200) {
proxy?.$modal.msgSuccess('操作成功');
dialog.visible = false;
loadingIng.value = false;
@ -633,6 +675,13 @@ const closeDialog = () => {
resetForm();
};
const showQrCode = (row: any) => {
if (row.deviceImei) {
qrCodeValue.value = row.deviceImei;
qrCodeDialogVisible.value = true;
}
};
/**
* 重置表单
*/
@ -644,7 +693,7 @@ const resetForm = () => {
// 设备类型
const getDeviceType = () => {
api.deviceTypeAll().then(res => {
if (res.code == 0) {
if (res.code == 200) {
deviceTypeOptions.value = res.data
}
}).catch(err => {
@ -654,7 +703,7 @@ const getDeviceType = () => {
// 客户下拉框
const getAllCustomerAll = () => {
api.userAllCustomerAll().then(res => {
if (res.code == 0) {
if (res.code == 200) {
customerList.value = res.data
}
})
@ -680,7 +729,7 @@ const handleAssignConfirm = () => {
deviceIds: [assignRow.value.id]
}
api.deviceAssignCustomer(data).then((res) => {
if (res.code == 0) {
if (res.code == 200) {
loadingIng.value = false;
const customer = customerList.value.find(c => c.id === assignCustomerId.value)
const customerName = customer ? customer.nickName : `ID: ${assignCustomerId.value}`
@ -714,7 +763,7 @@ const handleBatchImport = () => {
const downloadTemplate = () => {
// 这里可用 window.open 或 a 标签下载模板
const link = document.createElement('a');
link.href = 'http://fuyuanshen.com:81/images/excel/equipmenttemplate.xlsx';
link.href = 'https://fuyuanshen.com/fys/Equipmentimporttemplate/EquipmentImportTemplate.xlsx';
link.download = '设备数据导入模板.xlsx'; // 可选:指定下载文件名
link.style.display = 'none'; // 隐藏标签
document.body.appendChild(link);
@ -732,7 +781,7 @@ const beforeImportUpload = (file: any) => {
//添加tokenf方法head_upload 直接返回 getBearerToken()
const head_upload = () => getBearerToken();
const handleImportSuccess = (response: any) => {
if (response.code === 0) {
if (response.code == 200) {
importResult.value.isShow = true
if (response.data) {
importResult.value.succeed = response.data.successCount || 0
@ -766,7 +815,7 @@ const handleBatchAssignConfirm = () => {
deviceIds: selectedIds // 选中的设备ID数组
}
api.deviceAssignCustomer(data).then((res) => {
if (res.code == 0) {
if (res.code == 200) {
batchAssignDialogVisible.value = false
getList();
return proxy?.$modal.msgSuccess(`分配成功`)