feat: add app management pages

This commit is contained in:
yuhaiming
2026-07-01 10:18:30 +08:00
parent 6bc3c618fe
commit f5dc2e2c3a
32 changed files with 3981 additions and 182 deletions

View File

@@ -1,6 +1,6 @@
# 页面标题
VITE_APP_TITLE = RuoYi-Vue-Plus多租户管理系统
VITE_APP_LOGO_TITLE = RuoYi-Vue-Plus
VITE_APP_TITLE = 防御机管理系统
VITE_APP_LOGO_TITLE = 防御机管理系统
# 开发环境配置
VITE_APP_ENV = 'development'
@@ -12,19 +12,21 @@ VITE_APP_BASE_API = '/dev-api'
VITE_APP_CONTEXT_PATH = '/'
# 监控地址
VITE_APP_MONITOR_ADMIN = 'http://localhost:9090/admin/applications'
VITE_APP_MONITOR_ADMIN = 'http://localhost:9001/admin/applications'
# SnailJob 控制台地址
VITE_APP_SNAILJOB_ADMIN = 'http://localhost:8800/snail-job'
VITE_APP_SNAILJOB_ADMIN = 'http://localhost:9002/snail-job'
VITE_APP_PORT = 80
# 接口加密功能开关(如需关闭 后端也必须对应关闭)
VITE_APP_ENCRYPT = true
VITE_APP_ENCRYPT = false
# 接口加密传输 RSA 公钥与后端解密私钥对应 如更换需前后端一同更换
VITE_APP_RSA_PUBLIC_KEY = 'MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKoR8mX0rGKLqzcWmOzbfj64K8ZIgOdHnzkXSOVOZbFu/TJhZ7rFAN+eaGkl3C4buccQd/EjEsj9ir7ijT7h96MCAwEAAQ=='
#VITE_APP_RSA_PUBLIC_KEY = 'MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKoR8mX0rGKLqzcWmOzbfj64K8ZIgOdHnzkXSOVOZbFu/TJhZ7rFAN+eaGkl3C4buccQd/EjEsj9ir7ijT7h96MCAwEAAQ=='
VITE_APP_RSA_PUBLIC_KEY = 'MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAJJA9FRt24dkoEqTRBdm9yx6MQ5+f+rDIrLdCAXFUtpGcsT+kea/iGzk50OfSzSYxQYF8g/iksanB9XN0Ab6anECAwEAAQ=='
# 接口响应解密 RSA 私钥与后端加密公钥对应 如更换需前后端一同更换
VITE_APP_RSA_PRIVATE_KEY = 'MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAmc3CuPiGL/LcIIm7zryCEIbl1SPzBkr75E2VMtxegyZ1lYRD+7TZGAPkvIsBcaMs6Nsy0L78n2qh+lIZMpLH8wIDAQABAkEAk82Mhz0tlv6IVCyIcw/s3f0E+WLmtPFyR9/WtV3Y5aaejUkU60JpX4m5xNR2VaqOLTZAYjW8Wy0aXr3zYIhhQQIhAMfqR9oFdYw1J9SsNc+CrhugAvKTi0+BF6VoL6psWhvbAiEAxPPNTmrkmrXwdm/pQQu3UOQmc2vCZ5tiKpW10CgJi8kCIFGkL6utxw93Ncj4exE/gPLvKcT+1Emnoox+O9kRXss5AiAMtYLJDaLEzPrAWcZeeSgSIzbL+ecokmFKSDDcRske6QIgSMkHedwND1olF8vlKsJUGK3BcdtM8w4Xq7BpSBwsloE='
#VITE_APP_RSA_PRIVATE_KEY = 'MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAmc3CuPiGL/LcIIm7zryCEIbl1SPzBkr75E2VMtxegyZ1lYRD+7TZGAPkvIsBcaMs6Nsy0L78n2qh+lIZMpLH8wIDAQABAkEAk82Mhz0tlv6IVCyIcw/s3f0E+WLmtPFyR9/WtV3Y5aaejUkU60JpX4m5xNR2VaqOLTZAYjW8Wy0aXr3zYIhhQQIhAMfqR9oFdYw1J9SsNc+CrhugAvKTi0+BF6VoL6psWhvbAiEAxPPNTmrkmrXwdm/pQQu3UOQmc2vCZ5tiKpW10CgJi8kCIFGkL6utxw93Ncj4exE/gPLvKcT+1Emnoox+O9kRXss5AiAMtYLJDaLEzPrAWcZeeSgSIzbL+ecokmFKSDDcRske6QIgSMkHedwND1olF8vlKsJUGK3BcdtM8w4Xq7BpSBwsloE='
VITE_APP_RSA_PRIVATE_KEY = 'MIIBOgIBAAJBAIHPXmO+vf9u33iXv97aYeuPsJ7Ut+GdBjpT7JOIEE1rIEIfaKK268uPwAHW+/fNOKQiTBuunI4PAiL7CAQoVeECAwEAAQJACS6S3aP9/9W8MY/XqZAcQbOjPvOK94bKNvv9YvN1O785/2U5z7hUHvF8O8qIttlMNqhJ60JO9x58EUE51/dZ1QIhAISwFmIah9HoOLR5M+sBsUb5dIAm1mg0hj7T/5LyMTO7AiEA+nKebopGVz7LVkX53m29975r3T+DYu9n24eIqNwTDRMCIHn6xxj4+tNlgHuo0KnCrI5q1UbabVDuuFsZ2WHldIXxAiEAx6BA5H9XHgexcC0Kw97ZgvXHaG1NvrhxIizgIyh4Dv0CIB+VslCqTQTr9idyfVuURAiDdzg/9tm07GhWLDn6I6+E'
# 客户端id
VITE_APP_CLIENT_ID = 'e5cd7e4891bf95d1d19206ce24a7b32e'

View File

@@ -1,6 +1,6 @@
# 页面标题
VITE_APP_TITLE = RuoYi-Vue-Plus多租户管理系统
VITE_APP_LOGO_TITLE = RuoYi-Vue-Plus
VITE_APP_TITLE = 防御机管理系统
VITE_APP_LOGO_TITLE = 防御机管理系统
# 生产环境配置
VITE_APP_ENV = 'production'
@@ -23,7 +23,8 @@ VITE_BUILD_COMPRESS = gzip
VITE_APP_PORT = 80
# 接口加密功能开关(如需关闭 后端也必须对应关闭)
VITE_APP_ENCRYPT = true
# defensive_machine 后端 api-decrypt.enabled = false
VITE_APP_ENCRYPT = false
# 接口加密传输 RSA 公钥与后端解密私钥对应 如更换需前后端一同更换
VITE_APP_RSA_PUBLIC_KEY = 'MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKoR8mX0rGKLqzcWmOzbfj64K8ZIgOdHnzkXSOVOZbFu/TJhZ7rFAN+eaGkl3C4buccQd/EjEsj9ir7ijT7h96MCAwEAAQ=='
# 接口响应解密 RSA 私钥与后端加密公钥对应 如更换需前后端一同更换

BIN
dm-ui-dist.zip Normal file

Binary file not shown.

View File

@@ -44,7 +44,9 @@
"vue-json-pretty": "2.6.0",
"vue-router": "5.0.3",
"vue-types": "6.0.0",
"vxe-table": "4.18.1"
"vxe-table": "4.18.1",
"xgplayer": "^3.0.24",
"xgplayer-flv": "^3.0.24"
},
"devDependencies": {
"@iconify/json": "^2.2.448",

View File

@@ -0,0 +1,90 @@
# Put this server block in the BaoTa site's Nginx config.
# Replace server_name with your domain, or keep _ for IP-based access.
server {
listen 8000;
server_name _;
root /www/wwwroot/dm-ui/dist;
index index.html;
client_max_body_size 20m;
gzip on;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss image/svg+xml;
location / {
try_files $uri $uri/ /index.html;
}
location ^~ /assets/ {
try_files $uri =404;
expires 30d;
add_header Cache-Control "public, immutable";
}
location ^~ /prod-api/resource/sse {
rewrite ^/prod-api/(.*)$ /$1 break;
proxy_pass http://127.0.0.1:8080;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_buffering off;
proxy_cache off;
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
}
location ^~ /prod-api/resource/websocket {
rewrite ^/prod-api/(.*)$ /$1 break;
proxy_pass http://127.0.0.1:8080;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
}
location ^~ /prod-api/ {
rewrite ^/prod-api/(.*)$ /$1 break;
proxy_pass http://127.0.0.1:8080;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 300s;
proxy_send_timeout 300s;
}
location = /admin {
return 301 /admin/;
}
location ^~ /admin/ {
proxy_pass http://127.0.0.1:9001/admin/;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location = /snail-job {
return 301 /snail-job/;
}
location ^~ /snail-job/ {
proxy_pass http://127.0.0.1:9002/snail-job/;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}

View File

@@ -0,0 +1,68 @@
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
import { DeviceVO, DeviceForm, DeviceQuery } from '@/api/app/device/types';
/**
* 查询设备信息
列表
* @param query
* @returns {*}
*/
export const listDevice = (query?: DeviceQuery): AxiosPromise<DeviceVO[]> => {
return request({
url: '/app/device/list',
method: 'get',
params: query
});
};
/**
* 查询设备信息
详细
* @param id
*/
export const getDevice = (id: string | number): AxiosPromise<DeviceVO> => {
return request({
url: '/app/device/' + id,
method: 'get'
});
};
/**
* 新增设备信息
* @param data
*/
export const addDevice = (data: DeviceForm) => {
return request({
url: '/app/device',
method: 'post',
data: data
});
};
/**
* 修改设备信息
* @param data
*/
export const updateDevice = (data: DeviceForm) => {
return request({
url: '/app/device',
method: 'put',
data: data
});
};
/**
* 删除设备信息
* @param id
*/
export const delDevice = (id: string | number | Array<string | number>) => {
return request({
url: '/app/device/' + id,
method: 'delete'
});
};

306
src/api/app/device/types.ts Normal file
View File

@@ -0,0 +1,306 @@
export interface DeviceVO {
/**
*
*/
id: string | number;
/**
* 设备编号
*/
deviceNo: string;
/**
* 设备名称
*/
deviceName: string;
/**
* 状态 1-在线 0-离线 2-故障 3-到期
*/
status: string;
/**
* WiFi名称
*/
wifiName: string;
/**
* WiFi密码
*/
wifiPassword: string;
/**
* 强光开启
*/
light: string;
/**
* 告警播放
*/
alertAudio: string;
/**
* 告警内容
*/
audioContet: string;
/**
* 打击距离
*/
strikeRange: string;
/**
* 打击间隔
*/
strikeInterval: string;
/**
* 容器数量
*/
container: string;
/**
* 打击位置
*/
strikePosition: string;
/**
* 自动布放开始时间
*/
startTime: string;
/**
* 自动布放结束时间
*/
endTime: string;
/**
* 工作模式 0-自动 1-半自动
*/
workMode: string;
/**
* 设备型号
*/
deviceEm: string;
/**
* 设备序列号
*/
deviceSn: string;
/**
* 固件版本
*/
fwVer: string;
/**
* 识别模式 0-蓝牙 1-蓝牙+人脸
*/
patternPerception?: string;
}
export interface DeviceForm extends BaseEntity {
/**
*
*/
id?: string | number;
/**
* 设备编号
*/
deviceNo?: string;
/**
* 设备名称
*/
deviceName?: string;
/**
* 状态 1-在线 0-离线 2-故障 3-到期
*/
status?: string;
/**
* WiFi名称
*/
wifiName?: string;
/**
* WiFi密码
*/
wifiPassword?: string;
/**
* 强光开启
*/
light?: string;
/**
* 告警播放
*/
alertAudio?: string;
/**
* 告警内容
*/
audioContet?: string;
/**
* 打击距离
*/
strikeRange?: string;
/**
* 打击间隔
*/
strikeInterval?: string;
/**
* 容器数量
*/
container?: string;
/**
* 打击位置
*/
strikePosition?: string;
/**
* 自动布放开始时间
*/
startTime?: string;
/**
* 自动布放结束时间
*/
endTime?: string;
/**
* 工作模式 0-自动 1-半自动
*/
workMode?: string;
/**
* 设备型号
*/
deviceEm?: string;
/**
* 设备序列号
*/
deviceSn?: string;
/**
* 固件版本
*/
fwVer?: string;
/**
* 识别模式 0-蓝牙 1-蓝牙+人脸
*/
patternPerception?: string;
}
export interface DeviceQuery extends PageQuery {
/**
* 设备编号
*/
deviceNo?: string;
/**
* 设备名称
*/
deviceName?: string;
/**
* 状态 1-在线 0-离线 2-故障 3-到期
*/
status?: string;
/**
* WiFi名称
*/
wifiName?: string;
/**
* WiFi密码
*/
wifiPassword?: string;
/**
* 强光开启
*/
light?: string;
/**
* 告警播放
*/
alertAudio?: string;
/**
* 告警内容
*/
audioContet?: string;
/**
* 打击距离
*/
strikeRange?: string;
/**
* 打击间隔
*/
strikeInterval?: string;
/**
* 容器数量
*/
container?: string;
/**
* 打击位置
*/
strikePosition?: string;
/**
* 自动布放开始时间
*/
startTime?: string;
/**
* 自动布放结束时间
*/
endTime?: string;
/**
* 工作模式 0-自动 1-半自动
*/
workMode?: string;
/**
* 设备型号
*/
deviceEm?: string;
/**
* 设备序列号
*/
deviceSn?: string;
/**
* 固件版本
*/
fwVer?: string;
/**
* 识别模式 0-蓝牙 1-蓝牙+人脸
*/
patternPerception?: string;
/**
* 日期范围参数
*/
params?: any;
}

View File

@@ -0,0 +1,63 @@
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
import { DeviceUserVO, DeviceUserForm, DeviceUserQuery } from '@/api/app/deviceUser/types';
/**
* 查询设备成员关联列表
* @param query
* @returns {*}
*/
export const listDeviceUser = (query?: DeviceUserQuery): AxiosPromise<DeviceUserVO[]> => {
return request({
url: '/app/deviceUser/list',
method: 'get',
params: query
});
};
/**
* 查询设备成员关联详细
* @param deviceId
*/
export const getDeviceUser = (deviceId: string | number): AxiosPromise<DeviceUserVO> => {
return request({
url: '/app/deviceUser/' + deviceId,
method: 'get'
});
};
/**
* 新增设备成员关联
* @param data
*/
export const addDeviceUser = (data: DeviceUserForm) => {
return request({
url: '/app/deviceUser',
method: 'post',
data: data
});
};
/**
* 修改设备成员关联
* @param data
*/
export const updateDeviceUser = (data: DeviceUserForm) => {
return request({
url: '/app/deviceUser',
method: 'put',
data: data
});
};
/**
* 删除设备成员关联
* @param deviceId
*/
export const delDeviceUser = (deviceId: string | number | Array<string | number>) => {
return request({
url: '/app/deviceUser/' + deviceId,
method: 'delete'
});
};

View File

@@ -0,0 +1,78 @@
export interface DeviceUserVO {
/**
*
*/
deviceId: string | number;
/**
* 设备编号
*/
deviceNo: string;
/**
* 设备名称
*/
deviceName: string;
/**
* 用户ID
*/
userId: string | number;
/**
* 用户昵称
*/
nickName: string;
}
export interface DeviceUserForm extends BaseEntity {
/**
*
*/
deviceId?: string | number;
/**
* 设备编号
*/
deviceNo?: string;
/**
* 设备名称
*/
deviceName?: string;
/**
* 用户ID
*/
userId?: string | number;
/**
* 用户昵称
*/
nickName?: string;
}
export interface DeviceUserQuery extends PageQuery {
/**
* 设备编号
*/
deviceNo?: string;
/**
* 设备名称
*/
deviceName?: string;
/**
* 用户昵称
*/
nickName?: string;
/**
* 日期范围参数
*/
params?: any;
}

View File

@@ -0,0 +1,80 @@
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
import { FamilyVO, FamilyForm, FamilyQuery } from '@/api/app/family/types';
/**
* 查询家庭成员列表
* @param query
* @returns {*}
*/
export const listFamily = (query?: FamilyQuery): AxiosPromise<FamilyVO[]> => {
return request({
url: '/app/family/list',
method: 'get',
params: query
});
};
/**
* 查询家庭成员详细
* @param id
*/
export const getFamily = (id: string | number): AxiosPromise<FamilyVO> => {
return request({
url: '/app/family/' + id,
method: 'get'
});
};
/**
* 新增家庭成员
* @param data
*/
export const addFamily = (data: FamilyForm) => {
return request({
url: '/app/family',
method: 'post',
data: data
});
};
/**
* 修改家庭成员
* @param data
*/
export const updateFamily = (data: FamilyForm) => {
return request({
url: '/app/family',
method: 'put',
data: data
});
};
/**
* 删除家庭成员
* @param id
*/
export const delFamily = (id: string | number | Array<string | number>) => {
return request({
url: '/app/family/' + id,
method: 'delete'
});
};
/**
* 家庭成员状态修改
* @param id 家庭成员ID
* @param status 状态
*/
export const changeFamilyStatus = (id: string | number, status: string) => {
const data = {
id,
status
};
return request({
url: '/app/family/changeStatus',
method: 'put',
data: data
});
};

View File

@@ -0,0 +1,83 @@
export interface FamilyVO {
/**
*
*/
id: string | number;
/**
* 家长id
*/
parentId: string | number;
/**
* 家长昵称
*/
parentName: string;
/**
* 成员id
*/
userId: string | number;
/**
* 账号状态0正常 1停用
*/
status: string;
}
export interface FamilyForm extends BaseEntity {
/**
*
*/
id?: string | number;
/**
* 家长id
*/
parentId?: string | number;
/**
* 家长昵称
*/
parentName?: string;
/**
* 成员id
*/
userId?: string | number;
/**
* 账号状态0正常 1停用
*/
status?: string;
}
export interface FamilyQuery extends PageQuery {
/**
* 家长id
*/
parentId?: string | number;
/**
* 家长昵称
*/
parentName?: string;
/**
* 成员id
*/
userId?: string | number;
/**
* 账号状态0正常 1停用
*/
status?: string;
/**
* 日期范围参数
*/
params?: any;
}

View File

@@ -0,0 +1,63 @@
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
import { FamilyDeviceVO, FamilyDeviceForm, FamilyDeviceQuery } from '@/api/app/familyDevice/types';
/**
* 查询家庭设备关联列表
* @param query
* @returns {*}
*/
export const listFamilyDevice = (query?: FamilyDeviceQuery): AxiosPromise<FamilyDeviceVO[]> => {
return request({
url: '/app/familyDevice/list',
method: 'get',
params: query
});
};
/**
* 查询家庭设备关联详细
* @param userId
*/
export const getFamilyDevice = (userId: string | number): AxiosPromise<FamilyDeviceVO> => {
return request({
url: '/app/familyDevice/' + userId,
method: 'get'
});
};
/**
* 新增家庭设备关联
* @param data
*/
export const addFamilyDevice = (data: FamilyDeviceForm) => {
return request({
url: '/app/familyDevice',
method: 'post',
data: data
});
};
/**
* 修改家庭设备关联
* @param data
*/
export const updateFamilyDevice = (data: FamilyDeviceForm) => {
return request({
url: '/app/familyDevice',
method: 'put',
data: data
});
};
/**
* 删除家庭设备关联
* @param userId
*/
export const delFamilyDevice = (userId: string | number | Array<string | number>) => {
return request({
url: '/app/familyDevice/' + userId,
method: 'delete'
});
};

View File

@@ -0,0 +1,78 @@
export interface FamilyDeviceVO {
/**
* 用户ID
*/
userId: string | number;
/**
* 用户昵称
*/
nickName: string;
/**
*
*/
deviceId: string | number;
/**
* 设备编号
*/
deviceNo: string;
/**
* 设备名称
*/
deviceName: string;
}
export interface FamilyDeviceForm extends BaseEntity {
/**
* 用户ID
*/
userId?: string | number;
/**
* 用户昵称
*/
nickName?: string;
/**
*
*/
deviceId?: string | number;
/**
* 设备编号
*/
deviceNo?: string;
/**
* 设备名称
*/
deviceName?: string;
}
export interface FamilyDeviceQuery extends PageQuery {
/**
* 用户昵称
*/
nickName?: string;
/**
* 设备编号
*/
deviceNo?: string;
/**
* 设备名称
*/
deviceName?: string;
/**
* 日期范围参数
*/
params?: any;
}

View File

@@ -0,0 +1,63 @@
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
import { MessageVO, MessageForm, MessageQuery } from '@/api/app/message/types';
/**
* 查询告警消息列列表
* @param query
* @returns {*}
*/
export const listMessage = (query?: MessageQuery): AxiosPromise<MessageVO[]> => {
return request({
url: '/app/message/list',
method: 'get',
params: query
});
};
/**
* 查询告警消息列详细
* @param id
*/
export const getMessage = (id: string | number): AxiosPromise<MessageVO> => {
return request({
url: '/app/message/' + id,
method: 'get'
});
};
/**
* 新增告警消息列
* @param data
*/
export const addMessage = (data: MessageForm) => {
return request({
url: '/app/message',
method: 'post',
data: data
});
};
/**
* 修改告警消息列
* @param data
*/
export const updateMessage = (data: MessageForm) => {
return request({
url: '/app/message',
method: 'put',
data: data
});
};
/**
* 删除告警消息列
* @param id
*/
export const delMessage = (id: string | number | Array<string | number>) => {
return request({
url: '/app/message/' + id,
method: 'delete'
});
};

View File

@@ -0,0 +1,87 @@
export interface MessageVO {
/**
*
*/
id: string | number;
/**
* 类型
*/
category: number;
/**
* 告警类型
*/
categoryName: string;
/**
* 图片地址
*/
image: string;
/**
* 图片地址Url
*/
imageUrl: string;
/**
* 视频地址
*/
video: string | number;
}
export interface MessageForm extends BaseEntity {
/**
*
*/
id?: string | number;
/**
* 类型
*/
category?: number;
/**
* 告警类型
*/
categoryName?: string;
/**
* 图片地址
*/
image?: string;
/**
* 视频地址
*/
video?: string | number;
}
export interface MessageQuery extends PageQuery {
/**
* 类型
*/
category?: number;
/**
* 告警类型
*/
categoryName?: string;
/**
* 图片地址
*/
image?: string;
/**
* 视频地址
*/
video?: string | number;
/**
* 日期范围参数
*/
params?: any;
}

101
src/api/app/user/index.ts Normal file
View File

@@ -0,0 +1,101 @@
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
import { UserVO, UserForm, UserQuery } from '@/api/app/user/types';
/**
* 查询app用户信息列表
* @param query
* @returns {*}
*/
export const listUser = (query?: UserQuery): AxiosPromise<UserVO[]> => {
return request({
url: '/app/user/list',
method: 'get',
params: query
});
};
/**
* 查询app用户信息详细
* @param userId
*/
export const getUser = (userId: string | number): AxiosPromise<UserVO> => {
return request({
url: '/app/user/' + userId,
method: 'get'
});
};
/**
* 新增app用户信息
* @param data
*/
export const addUser = (data: UserForm) => {
return request({
url: '/app/user',
method: 'post',
data: data
});
};
/**
* 修改app用户信息
* @param data
*/
export const updateUser = (data: UserForm) => {
return request({
url: '/app/user',
method: 'put',
data: data
});
};
/**
* 删除app用户信息
* @param userId
*/
export const delUser = (userId: string | number | Array<string | number>) => {
return request({
url: '/app/user/' + userId,
method: 'delete'
});
};
/**
* 用户状态修改
* @param userId 用户ID
* @param status 用户状态
*/
export const changeUserStatus = (userId: string | number, status: string) => {
const data = {
userId,
status
};
return request({
url: '/app/user/changeStatus',
method: 'put',
data: data
});
};
/**
* 重置用户密码
* @param userId 用户ID
* @param password 新密码
*/
export const resetUserPwd = (userId: string | number, password: string) => {
const data = {
userId,
password
};
return request({
url: '/app/user/resetPwd',
method: 'put',
headers: {
isEncrypt: true,
repeatSubmit: false
},
data: data
});
};

273
src/api/app/user/types.ts Normal file
View File

@@ -0,0 +1,273 @@
export interface UserVO {
/**
* 用户ID
*/
userId: string | number;
/**
* 用户账号
*/
userName: string;
/**
* 用户昵称
*/
nickName: string;
/**
* 身份证号
*/
idCard: string | number;
/**
* 用户类型sys_user系统用户
*/
userType: string;
/**
* 用户邮箱
*/
email: string;
/**
* 手机号码
*/
phonenumber: string;
/**
* 用户性别0男 1女 2未知
*/
sex: string;
/**
* 头像地址
*/
avatar: number;
/**
* 密码
*/
password: string;
/**
* 账号状态0正常 1停用
*/
status: string;
/**
* 人脸
*/
faceAddr: string;
/**
* 手机mac地址
*/
mac: string;
/**
* 邀请码
*/
inviteUsed: string;
/**
* 邀请码创建时间
*/
inviteTime: string;
/**
* 最后登录IP
*/
loginIp: string;
/**
* 最后登录时间
*/
loginDate: string;
/**
* 备注
*/
remark: string;
}
export interface UserForm extends BaseEntity {
/**
* 用户ID
*/
userId?: string | number;
/**
* 用户账号
*/
userName?: string;
/**
* 用户昵称
*/
nickName?: string;
/**
* 身份证号
*/
idCard?: string | number;
/**
* 用户类型sys_user系统用户
*/
userType?: string;
/**
* 用户邮箱
*/
email?: string;
/**
* 手机号码
*/
phonenumber?: string;
/**
* 用户性别0男 1女 2未知
*/
sex?: string;
/**
* 头像地址
*/
avatar?: number;
/**
* 密码
*/
password?: string;
/**
* 账号状态0正常 1停用
*/
status?: string;
/**
* 人脸
*/
faceAddr?: string;
/**
* 手机mac地址
*/
mac?: string;
/**
* 邀请码
*/
inviteUsed?: string;
/**
* 邀请码创建时间
*/
inviteTime?: string;
/**
* 最后登录IP
*/
loginIp?: string;
/**
* 最后登录时间
*/
loginDate?: string;
/**
* 备注
*/
remark?: string;
}
export interface UserQuery extends PageQuery {
/**
* 用户账号
*/
userName?: string;
/**
* 用户昵称
*/
nickName?: string;
/**
* 身份证号
*/
idCard?: string | number;
/**
* 用户类型sys_user系统用户
*/
userType?: string;
/**
* 用户邮箱
*/
email?: string;
/**
* 手机号码
*/
phonenumber?: string;
/**
* 用户性别0男 1女 2未知
*/
sex?: string;
/**
* 头像地址
*/
avatar?: number;
/**
* 密码
*/
password?: string;
/**
* 账号状态0正常 1停用
*/
status?: string;
/**
* 人脸
*/
faceAddr?: string;
/**
* 手机mac地址
*/
mac?: string;
/**
* 邀请码
*/
inviteUsed?: string;
/**
* 邀请码创建时间
*/
inviteTime?: string;
/**
* 最后登录IP
*/
loginIp?: string;
/**
* 最后登录时间
*/
loginDate?: string;
/**
* 日期范围参数
*/
params?: any;
}

View File

@@ -0,0 +1,63 @@
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
import { UserdeviceTokenVO, UserdeviceTokenForm, UserdeviceTokenQuery } from '@/api/app/userdeviceToken/types';
/**
* 查询推送用户手机token列表
* @param query
* @returns {*}
*/
export const listUserdeviceToken = (query?: UserdeviceTokenQuery): AxiosPromise<UserdeviceTokenVO[]> => {
return request({
url: '/app/userdeviceToken/list',
method: 'get',
params: query
});
};
/**
* 查询推送用户手机token详细
* @param id
*/
export const getUserdeviceToken = (id: string | number): AxiosPromise<UserdeviceTokenVO> => {
return request({
url: '/app/userdeviceToken/' + id,
method: 'get'
});
};
/**
* 新增推送用户手机token
* @param data
*/
export const addUserdeviceToken = (data: UserdeviceTokenForm) => {
return request({
url: '/app/userdeviceToken',
method: 'post',
data: data
});
};
/**
* 修改推送用户手机token
* @param data
*/
export const updateUserdeviceToken = (data: UserdeviceTokenForm) => {
return request({
url: '/app/userdeviceToken',
method: 'put',
data: data
});
};
/**
* 删除推送用户手机token
* @param id
*/
export const delUserdeviceToken = (id: string | number | Array<string | number>) => {
return request({
url: '/app/userdeviceToken/' + id,
method: 'delete'
});
};

View File

@@ -0,0 +1,83 @@
export interface UserdeviceTokenVO {
/**
*
*/
id: string | number;
/**
* 账号用户id
*/
userId: string | number;
/**
* 设备token
*/
token: string;
/**
* 平台 iOS/Android
*/
platform: string;
/**
*
*/
createdTime: string;
}
export interface UserdeviceTokenForm extends BaseEntity {
/**
*
*/
id?: string | number;
/**
* 账号用户id
*/
userId?: string | number;
/**
* 设备token
*/
token?: string;
/**
* 平台 iOS/Android
*/
platform?: string;
/**
*
*/
createdTime?: string;
}
export interface UserdeviceTokenQuery extends PageQuery {
/**
* 账号用户id
*/
userId?: string | number;
/**
* 设备token
*/
token?: string;
/**
* 平台 iOS/Android
*/
platform?: string;
/**
*
*/
createdTime?: string;
/**
* 日期范围参数
*/
params?: any;
}

View File

@@ -144,7 +144,7 @@ const computedIds = (data) => {
const getList = () => {
loading.value = true;
api.listRole(proxy?.addDateRange(queryParams.value, dateRange.value)).then((res) => {
roleList.value = res.rows;
roleList.value = res.data;
total.value = res.total;
loading.value = false;
});

View File

@@ -0,0 +1,45 @@
# 2026-06-04
## user/index.vue - 人脸列多图片展示
- 将人脸列从单张 `image-preview` 改为多张 `el-image` 缩略图布局
- 新增 `faceUrls()` 工具函数,将逗号分隔的 `faceAddr` 拆分为数组
- 每张缩略图 40x40flex 横排gap 4px
- 通过 `preview-src-list` + `initial-index` 实现点击任意一张可轮播预览全部
- 添加 hover 放大动效和图片加载失败的占位 icon
## user/index.vue - 用户账号列展示优化
- "用户账号"列改为优先展示手机号、其次邮箱、最后 userName 的降级逻辑
- 模板表达式:`phonenumber || email || userName || '-'`
## user/index.vue - 仿 sysuser 添加重置密码
- API: `d:\code\plus-ui\src\api\app\user\index.ts` 新增 `resetUserPwd(userId, password)``PUT /app/user/resetPwd`
- View: 操作列新增"重置密码"按钮Key 图标),权限 `app:user:resetPwd`
- `handleResetPwd`: ElMessageBox.prompt 弹窗输入新密码,校验 5-20 位、禁非法字符 `<>"'|\`
- 成功提示包含新密码明文
## user/index.vue - 账号状态改为 el-switch 开关
- API: `d:\code\plus-ui\src\api\app\user\index.ts` 新增 `changeUserStatus(userId, status)``PUT /app/user/changeStatus`
- 状态列从纯文本改为 `el-switch``active-value="0"`(启用)、`inactive-value="1"`(停用)
- `handleStatusChange`: 切换前弹确认框,操作失败时回滚开关状态
## message/index.vue - 视频地址改为在线播放
- 视频地址列从纯文本改为"播放"按钮(`VideoPlay` 图标 + 点击触发 `handlePlayVideo`
- 新增视频播放弹窗:`<video>` 标签 + `controls` + `autoplay`,宽度 100%,最大高度 60vh
- `videoDialog`: `{ visible: boolean; url: string }`,关闭弹窗时清空 url
- 无视频时显示 `-`
## message/index.vue - 视频地址改为在线播放ZLMediaKit HTTP-FLV
- 安装 `xgplayer` + `xgplayer-flv` 依赖npm install --save
- 视频地址列从纯文本改为"播放"按钮(`VideoPlay` 图标)
- 点击按钮弹出视频播放弹窗720px 宽),内嵌 xgplayer 实例
- `initPlayer(url)`: 创建 xgplayer配置 `flv.mediaDataSource.isLive=true``type=flv`
- `destroyPlayer()`: 弹窗关闭时销毁播放器,防止内存泄漏
- `nextTick` 延迟初始化确保 DOM 渲染完毕
- 播放器容器 CSS: 60vh 高度、黑色背景、圆角、overflow hidden
## family/index.vue - 状态改为 el-switch 开关
- API: `d:\code\plus-ui\src\api\app\family\index.ts` 新增 `changeFamilyStatus(id, status)``PUT /app/family/changeStatus`
- 移除重复的纯文本"账号状态"列,保留 el-switch 列
- `handleStatusChange`: 修复从错误引用(`phonenumber/email/userName``changeUserStatus`)改为 FamilyVO 字段(`parentName/id`)和 `changeFamilyStatus`
- 操作失败时自动回滚开关状态

View File

@@ -0,0 +1,32 @@
# 2026-06-05
## device/index.vue - 设备信息页面4个字段展示改造
### 改动概述
按用户要求修改设备信息页面的4个字段展示方式
| 字段 | 改造前 | 改造后 |
|---|---|---|
| `status` | 纯数字 (0/1/2/3) | el-tag 彩色标签0=离线(info)、1=在线(success)、2=故障(danger)、3=到期(warning) |
| `light` | 纯数字输入框 → el-switch → **el-tag 标签** | el-tag 彩色标签0=不开启(info)、1=开启(success),移除开关交互 |
| `alertAudio` | 纯数字输入框 → el-switch → **el-tag 标签** | el-tag 彩色标签0=不播放(info)、1=播放(success),移除开关交互 |
| `recognizeMode` | 不存在 | 新增字段el-tag 展示0=蓝牙(info)、1=蓝牙+人脸(primary) |
| `workMode` | 纯数字文本 | el-tag 彩色标签0=自动(success)、1=半自动(primary) |
### 涉及文件
1. **`src/api/app/device/types.ts`**
- `DeviceVO` 新增 `recognizeMode?: string` 字段
- `DeviceForm` 新增 `recognizeMode?: string` 字段
- `DeviceQuery` 新增 `recognizeMode?: string` 字段
2. **`src/views/app/device/index.vue`**
- 查询表单:`light`/`alertAudio` 改为 el-select 下拉选择,`recognizeMode` 新增查询条件
- 表格列:`status`/`light`/`alertAudio`/`recognizeMode` 改为标签/开关展示
- 弹窗表单:`light`/`alertAudio` 改为 el-switch`recognizeMode` 新增表单字段
- Script新增 `statusTagType()``statusLabel()``patternPerceptionLabel()``lightTagType()``lightLabel()``alertAudioTagType()``alertAudioLabel()` 函数;移除 `handleLightChange()``handleAlertAudioChange()` 开关 handler
### 注意事项
- `light`/`alertAudio` 最终改为纯展示el-tag 标签,无交互),与 `status` 列展示风格统一
- `workMode` 列:纯数字文本 → el-tag 彩色标签0=自动 success / 1=半自动 primary
- `audioContet` 列:纯文本 → 超长截断max-width 120px ellipsis+ el-tooltip 悬停显示完整内容show-after 200ms
- `patternPerception` 字段名统一修正DeviceVO 中原错误写为 `recognizeMode`,已统一改为 `patternPerception`

View File

@@ -0,0 +1,497 @@
<template>
<div class="p-2">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<el-form-item label="设备编号" prop="deviceNo">
<el-input v-model="queryParams.deviceNo" placeholder="请输入设备编号" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="设备名称" prop="deviceName">
<el-input v-model="queryParams.deviceName" placeholder="请输入设备名称" clearable @keyup.enter="handleQuery" />
</el-form-item>
<!-- <el-form-item label="WiFi名称" prop="wifiName">-->
<!-- <el-input v-model="queryParams.wifiName" placeholder="请输入WiFi名称" clearable @keyup.enter="handleQuery" />-->
<!-- </el-form-item>-->
<!-- <el-form-item label="WiFi密码" prop="wifiPassword">-->
<!-- <el-input v-model="queryParams.wifiPassword" placeholder="请输入WiFi密码" clearable @keyup.enter="handleQuery" />-->
<!-- </el-form-item>-->
<!-- <el-form-item label="强光开启" prop="light">-->
<!-- <el-select v-model="queryParams.light" placeholder="请选择" clearable @change="handleQuery">-->
<!-- <el-option label="不开启" value="0" />-->
<!-- <el-option label="开启" value="1" />-->
<!-- </el-select>-->
<!-- </el-form-item>-->
<!-- <el-form-item label="告警播放" prop="alertAudio">-->
<!-- <el-select v-model="queryParams.alertAudio" placeholder="请选择" clearable @change="handleQuery">-->
<!-- <el-option label="不播放" value="0" />-->
<!-- <el-option label="播放" value="1" />-->
<!-- </el-select>-->
<!-- </el-form-item>-->
<!-- <el-form-item label="识别模式" prop="patternPerception">-->
<!-- <el-select v-model="queryParams.patternPerception" placeholder="请选择" clearable @change="handleQuery">-->
<!-- <el-option label="蓝牙" value="0" />-->
<!-- <el-option label="蓝牙+人脸" value="1" />-->
<!-- </el-select>-->
<!-- </el-form-item>-->
<!-- <el-form-item label="告警内容" prop="audioContet">-->
<!-- <el-input v-model="queryParams.audioContet" placeholder="请输入告警内容" clearable @keyup.enter="handleQuery" />-->
<!-- </el-form-item>-->
<!-- <el-form-item label="打击距离" prop="strikeRange">-->
<!-- <el-input v-model="queryParams.strikeRange" placeholder="请输入打击距离" clearable @keyup.enter="handleQuery" />-->
<!-- </el-form-item>-->
<!-- <el-form-item label="打击间隔" prop="strikeInterval">-->
<!-- <el-input v-model="queryParams.strikeInterval" placeholder="请输入打击间隔" clearable @keyup.enter="handleQuery" />-->
<!-- </el-form-item>-->
<!-- <el-form-item label="容器数量" prop="container">-->
<!-- <el-input v-model="queryParams.container" placeholder="请输入容器数量" clearable @keyup.enter="handleQuery" />-->
<!-- </el-form-item>-->
<!-- <el-form-item label="打击位置" prop="strikePosition">-->
<!-- <el-input v-model="queryParams.strikePosition" placeholder="请输入打击位置" clearable @keyup.enter="handleQuery" />-->
<!-- </el-form-item>-->
<!-- <el-form-item label="自动布放开始时间" prop="startTime">-->
<!-- <el-date-picker clearable v-model="queryParams.startTime" type="date" value-format="YYYY-MM-DD" placeholder="请选择自动布放开始时间" />-->
<!-- </el-form-item>-->
<!-- <el-form-item label="自动布放结束时间" prop="endTime">-->
<!-- <el-date-picker clearable v-model="queryParams.endTime" type="date" value-format="YYYY-MM-DD" placeholder="请选择自动布放结束时间" />-->
<!-- </el-form-item>-->
<!-- <el-form-item label="工作模式 0-自动 1-半自动" prop="workMode">-->
<!-- <el-input v-model="queryParams.workMode" placeholder="请输入工作模式 0-自动 1-半自动" clearable @keyup.enter="handleQuery" />-->
<!-- </el-form-item>-->
<!-- <el-form-item label="设备型号" prop="deviceEm">-->
<!-- <el-input v-model="queryParams.deviceEm" placeholder="请输入设备型号" clearable @keyup.enter="handleQuery" />-->
<!-- </el-form-item>-->
<!-- <el-form-item label="设备序列号" prop="deviceSn">-->
<!-- <el-input v-model="queryParams.deviceSn" placeholder="请输入设备序列号" clearable @keyup.enter="handleQuery" />-->
<!-- </el-form-item>-->
<!-- <el-form-item label="固件版本" prop="fwVer">-->
<!-- <el-input v-model="queryParams.fwVer" placeholder="请输入固件版本" clearable @keyup.enter="handleQuery" />-->
<!-- </el-form-item>-->
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
</el-card>
</div>
</transition>
<el-card shadow="never">
<template #header>
<el-row :gutter="10" class="mb8">
<!-- <el-col :span="1.5">-->
<!-- <el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['app:device:add']">新增</el-button>-->
<!-- </el-col>-->
<!-- <el-col :span="1.5">-->
<!-- <el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['app:device:edit']">修改</el-button>-->
<!-- </el-col>-->
<el-col :span="1.5">
<el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['app:device:remove']"
>删除</el-button
>
</el-col>
<el-col :span="1.5">
<el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['app:device:export']">导出</el-button>
</el-col>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
</template>
<el-table v-loading="loading" border :data="deviceList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="" align="center" prop="id" v-if="true" />
<el-table-column label="设备编号" align="center" prop="deviceNo" />
<el-table-column label="设备名称" align="center" prop="deviceName" />
<el-table-column label="状态" align="center" prop="status" width="100">
<template #default="scope">
<el-tag :type="statusTagType(scope.row.status)">
{{ statusLabel(scope.row.status) }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="WiFi名称" align="center" prop="wifiName" />
<el-table-column label="WiFi密码" align="center" prop="wifiPassword" />
<el-table-column label="强光开启" align="center" prop="light" width="100">
<template #default="scope">
<el-tag :type="lightTagType(scope.row.light)">
{{ lightLabel(scope.row.light) }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="告警播放" align="center" prop="alertAudio" width="100">
<template #default="scope">
<el-tag :type="alertAudioTagType(scope.row.alertAudio)">
{{ alertAudioLabel(scope.row.alertAudio) }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="识别模式" align="center" prop="patternPerception" width="120">
<template #default="scope">
<el-tag :type="scope.row.patternPerception === '1' ? 'primary' : 'info'">
{{ patternPerceptionLabel(scope.row.patternPerception) }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="告警内容" align="center" prop="audioContet" min-width="120">
<template #default="scope">
<el-tooltip v-if="scope.row.audioContet" :content="scope.row.audioContet" placement="top" :show-after="200">
<span class="text-ellipsis">{{ scope.row.audioContet }}</span>
</el-tooltip>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column label="打击距离" align="center" prop="strikeRange" />
<el-table-column label="打击间隔" align="center" prop="strikeInterval" />
<el-table-column label="容器数量" align="center" prop="container" />
<el-table-column label="打击位置" align="center" prop="strikePosition" />
<el-table-column label="自动布放开始时间" align="center" prop="startTime" width="180">
<template #default="scope">
<span>{{ parseTime(scope.row.startTime, '{y}-{m}-{d}') }}</span>
</template>
</el-table-column>
<el-table-column label="自动布放结束时间" align="center" prop="endTime" width="180">
<template #default="scope">
<span>{{ parseTime(scope.row.endTime, '{y}-{m}-{d}') }}</span>
</template>
</el-table-column>
<el-table-column label="工作模式" align="center" prop="workMode" width="100">
<template #default="scope">
<el-tag :type="workModeTagType(scope.row.workMode)">
{{ workModeLabel(scope.row.workMode) }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="设备型号" align="center" prop="deviceEm" />
<el-table-column label="设备序列号" align="center" prop="deviceSn" />
<el-table-column label="固件版本" align="center" prop="fwVer" />
<el-table-column label="操作" align="center" fixed="right" class-name="small-padding fixed-width">
<template #default="scope">
<!-- <el-tooltip content="修改" placement="top">-->
<!-- <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['app:device:edit']"></el-button>-->
<!-- </el-tooltip>-->
<el-tooltip content="删除" placement="top">
<el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['app:device:remove']"></el-button>
</el-tooltip>
</template>
</el-table-column>
</el-table>
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
</el-card>
<!-- 添加或修改设备信息
对话框 -->
<el-dialog :title="dialog.title" v-model="dialog.visible" width="500px" append-to-body>
<el-form ref="deviceFormRef" :model="form" :rules="rules" label-width="80px">
<el-form-item label="设备编号" prop="deviceNo">
<el-input v-model="form.deviceNo" placeholder="请输入设备编号" />
</el-form-item>
<el-form-item label="设备名称" prop="deviceName">
<el-input v-model="form.deviceName" placeholder="请输入设备名称" />
</el-form-item>
<el-form-item label="WiFi名称" prop="wifiName">
<el-input v-model="form.wifiName" placeholder="请输入WiFi名称" />
</el-form-item>
<el-form-item label="WiFi密码" prop="wifiPassword">
<el-input v-model="form.wifiPassword" placeholder="请输入WiFi密码" />
</el-form-item>
<el-form-item label="强光开启" prop="light">
<el-switch v-model="form.light" active-value="1" inactive-value="0" />
</el-form-item>
<el-form-item label="告警播放" prop="alertAudio">
<el-switch v-model="form.alertAudio" active-value="1" inactive-value="0" />
</el-form-item>
<el-form-item label="识别模式" prop="patternPerception">
<el-select v-model="form.patternPerception" placeholder="请选择识别模式">
<el-option label="蓝牙" value="0" />
<el-option label="蓝牙+人脸" value="1" />
</el-select>
</el-form-item>
<el-form-item label="告警内容" prop="audioContet">
<el-input v-model="form.audioContet" placeholder="请输入告警内容" />
</el-form-item>
<el-form-item label="打击距离" prop="strikeRange">
<el-input v-model="form.strikeRange" placeholder="请输入打击距离" />
</el-form-item>
<el-form-item label="打击间隔" prop="strikeInterval">
<el-input v-model="form.strikeInterval" placeholder="请输入打击间隔" />
</el-form-item>
<el-form-item label="容器数量" prop="container">
<el-input v-model="form.container" placeholder="请输入容器数量" />
</el-form-item>
<el-form-item label="打击位置" prop="strikePosition">
<el-input v-model="form.strikePosition" placeholder="请输入打击位置" />
</el-form-item>
<el-form-item label="自动布放开始时间" prop="startTime">
<el-date-picker clearable v-model="form.startTime" type="datetime" value-format="YYYY-MM-DD HH:mm:ss" placeholder="请选择自动布放开始时间">
</el-date-picker>
</el-form-item>
<el-form-item label="自动布放结束时间" prop="endTime">
<el-date-picker clearable v-model="form.endTime" type="datetime" value-format="YYYY-MM-DD HH:mm:ss" placeholder="请选择自动布放结束时间">
</el-date-picker>
</el-form-item>
<el-form-item label="工作模式 0-自动 1-半自动" prop="workMode">
<el-input v-model="form.workMode" placeholder="请输入工作模式 0-自动 1-半自动" />
</el-form-item>
<el-form-item label="设备型号" prop="deviceEm">
<el-input v-model="form.deviceEm" placeholder="请输入设备型号" />
</el-form-item>
<el-form-item label="设备序列号" prop="deviceSn">
<el-input v-model="form.deviceSn" placeholder="请输入设备序列号" />
</el-form-item>
<el-form-item label="固件版本" prop="fwVer">
<el-input v-model="form.fwVer" placeholder="请输入固件版本" />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button :loading="buttonLoading" type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup name="Device" lang="ts">
import { listDevice, getDevice, delDevice, addDevice, updateDevice } from '@/api/app/device';
import { DeviceVO, DeviceQuery, DeviceForm } from '@/api/app/device/types';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
/** 状态标签类型映射 */
const statusTagType = (status: string): string => {
const map: Record<string, string> = { '0': 'info', '1': 'success', '2': 'danger', '3': 'warning' };
return map[status] || 'info';
};
/** 状态中文标签 */
const statusLabel = (status: string): string => {
const map: Record<string, string> = { '0': '离线', '1': '在线', '2': '故障', '3': '到期' };
return map[status] || status || '-';
};
/** 识别模式中文标签 */
const patternPerceptionLabel = (mode: string): string => {
const map: Record<string, string> = { '0': '蓝牙', '1': '蓝牙+人脸' };
return map[mode] || mode || '-';
};
/** 强光开启标签类型映射 */
const lightTagType = (light: string): string => {
const map: Record<string, string> = { '0': 'info', '1': 'success' };
return map[light] || 'info';
};
/** 强光开启中文标签 */
const lightLabel = (light: string): string => {
const map: Record<string, string> = { '0': '不开启', '1': '开启' };
return map[light] || light || '-';
};
/** 告警播放标签类型映射 */
const alertAudioTagType = (alertAudio: string): string => {
const map: Record<string, string> = { '0': 'info', '1': 'success' };
return map[alertAudio] || 'info';
};
/** 告警播放中文标签 */
const alertAudioLabel = (alertAudio: string): string => {
const map: Record<string, string> = { '0': '不播放', '1': '播放' };
return map[alertAudio] || alertAudio || '-';
};
/** 工作模式标签类型映射 */
const workModeTagType = (mode: string): string => {
const map: Record<string, string> = { '0': 'success', '1': 'primary' };
return map[mode] || 'info';
};
/** 工作模式中文标签 */
const workModeLabel = (mode: string): string => {
const map: Record<string, string> = { '0': '自动', '1': '半自动' };
return map[mode] || mode || '-';
};
const deviceList = ref<DeviceVO[]>([]);
const buttonLoading = ref(false);
const loading = ref(true);
const showSearch = ref(true);
const ids = ref<Array<string | number>>([]);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const queryFormRef = ref<ElFormInstance>();
const deviceFormRef = ref<ElFormInstance>();
const dialog = reactive<DialogOption>({
visible: false,
title: ''
});
const initFormData: DeviceForm = {
id: undefined,
deviceNo: undefined,
deviceName: undefined,
status: undefined,
wifiName: undefined,
wifiPassword: undefined,
light: undefined,
alertAudio: undefined,
audioContet: undefined,
strikeRange: undefined,
strikeInterval: undefined,
container: undefined,
strikePosition: undefined,
startTime: undefined,
endTime: undefined,
workMode: undefined,
deviceEm: undefined,
deviceSn: undefined,
fwVer: undefined,
patternPerception: undefined
};
const data = reactive<PageData<DeviceForm, DeviceQuery>>({
form: { ...initFormData },
queryParams: {
pageNum: 1,
pageSize: 10,
deviceNo: undefined,
deviceName: undefined,
status: undefined,
wifiName: undefined,
wifiPassword: undefined,
light: undefined,
alertAudio: undefined,
audioContet: undefined,
strikeRange: undefined,
strikeInterval: undefined,
container: undefined,
strikePosition: undefined,
startTime: undefined,
endTime: undefined,
workMode: undefined,
deviceEm: undefined,
deviceSn: undefined,
fwVer: undefined,
patternPerception: undefined,
params: {}
},
rules: {
id: [{ required: true, message: '不能为空', trigger: 'blur' }]
}
});
const { queryParams, form, rules } = toRefs(data);
/** 查询设备信息
列表 */
const getList = async () => {
loading.value = true;
const res = await listDevice(queryParams.value);
deviceList.value = res.rows;
total.value = res.total;
loading.value = false;
};
/** 取消按钮 */
const cancel = () => {
reset();
dialog.visible = false;
};
/** 表单重置 */
const reset = () => {
form.value = { ...initFormData };
deviceFormRef.value?.resetFields();
};
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.value.pageNum = 1;
getList();
};
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value?.resetFields();
handleQuery();
};
/** 多选框选中数据 */
const handleSelectionChange = (selection: DeviceVO[]) => {
ids.value = selection.map((item) => item.id);
single.value = selection.length != 1;
multiple.value = !selection.length;
};
/** 新增按钮操作 */
const handleAdd = () => {
reset();
dialog.visible = true;
dialog.title = '添加设备信息';
};
/** 修改按钮操作 */
const handleUpdate = async (row?: DeviceVO) => {
reset();
const _id = row?.id || ids.value[0];
const res = await getDevice(_id);
Object.assign(form.value, res.data);
dialog.visible = true;
dialog.title = '修改设备信息';
};
/** 提交按钮 */
const submitForm = () => {
deviceFormRef.value?.validate(async (valid: boolean) => {
if (valid) {
buttonLoading.value = true;
if (form.value.id) {
await updateDevice(form.value).finally(() => (buttonLoading.value = false));
} else {
await addDevice(form.value).finally(() => (buttonLoading.value = false));
}
proxy?.$modal.msgSuccess('操作成功');
dialog.visible = false;
await getList();
}
});
};
/** 删除按钮操作 */
const handleDelete = async (row?: DeviceVO) => {
const _ids = row?.id || ids.value;
await proxy?.$modal.confirm('是否确认删除设备信息编号为"' + _ids + '"的数据项?').finally(() => (loading.value = false));
await delDevice(_ids);
proxy?.$modal.msgSuccess('删除成功');
await getList();
};
/** 导出按钮操作 */
const handleExport = () => {
proxy?.download(
'app/device/export',
{
...queryParams.value
},
`device_${new Date().getTime()}.xlsx`
);
};
onMounted(() => {
getList();
});
</script>
<style scoped>
.text-ellipsis {
display: inline-block;
max-width: 120px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
vertical-align: bottom;
cursor: default;
}
</style>

View File

@@ -0,0 +1,237 @@
<template>
<div class="p-2">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<el-form-item label="设备编号" prop="deviceNo">
<el-input v-model="queryParams.deviceNo" placeholder="请输入设备编号" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="设备名称" prop="deviceName">
<el-input v-model="queryParams.deviceName" placeholder="请输入设备名称" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="用户昵称" prop="nickName">
<el-input v-model="queryParams.nickName" placeholder="请输入用户昵称" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
</el-card>
</div>
</transition>
<el-card shadow="never">
<!-- <template #header>-->
<!-- <el-row :gutter="10" class="mb8">-->
<!-- <el-col :span="1.5">-->
<!-- <el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['app:deviceUser:add']">新增</el-button>-->
<!-- </el-col>-->
<!-- <el-col :span="1.5">-->
<!-- <el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['app:deviceUser:edit']">修改</el-button>-->
<!-- </el-col>-->
<!-- <el-col :span="1.5">-->
<!-- <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['app:deviceUser:remove']">删除</el-button>-->
<!-- </el-col>-->
<!-- <el-col :span="1.5">-->
<!-- <el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['app:deviceUser:export']">导出</el-button>-->
<!-- </el-col>-->
<!-- <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>-->
<!-- </el-row>-->
<!-- </template>-->
<el-table v-loading="loading" border :data="deviceUserList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="设备ID" align="center" prop="deviceId" v-if="true" />
<el-table-column label="设备编号" align="center" prop="deviceNo" />
<el-table-column label="设备名称" align="center" prop="deviceName" />
<el-table-column label="用户ID" align="center" prop="userId" v-if="true" />
<el-table-column label="用户昵称" align="center" prop="nickName" />
<el-table-column label="用户身份证号" align="center" prop="idCard" />
<el-table-column label="用户手机号" align="center" prop="phonenumber" />
<el-table-column label="用户MAC地址" align="center" prop="mac" />
<!-- <el-table-column label="操作" align="center" fixed="right" class-name="small-padding fixed-width">-->
<!-- <template #default="scope">-->
<!-- <el-tooltip content="修改" placement="top">-->
<!-- <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['app:deviceUser:edit']"></el-button>-->
<!-- </el-tooltip>-->
<!-- <el-tooltip content="删除" placement="top">-->
<!-- <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['app:deviceUser:remove']"></el-button>-->
<!-- </el-tooltip>-->
<!-- </template>-->
<!-- </el-table-column>-->
</el-table>
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
</el-card>
<!-- 添加或修改设备成员关联对话框 -->
<el-dialog :title="dialog.title" v-model="dialog.visible" width="500px" append-to-body>
<el-form ref="deviceUserFormRef" :model="form" :rules="rules" label-width="80px">
<el-form-item label="设备编号" prop="deviceNo">
<el-input v-model="form.deviceNo" placeholder="请输入设备编号" />
</el-form-item>
<el-form-item label="设备名称" prop="deviceName">
<el-input v-model="form.deviceName" placeholder="请输入设备名称" />
</el-form-item>
<el-form-item label="用户昵称" prop="nickName">
<el-input v-model="form.nickName" placeholder="请输入用户昵称" />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button :loading="buttonLoading" type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup name="DeviceUser" lang="ts">
import { listDeviceUser, getDeviceUser, delDeviceUser, addDeviceUser, updateDeviceUser } from '@/api/app/deviceUser';
import { DeviceUserVO, DeviceUserQuery, DeviceUserForm } from '@/api/app/deviceUser/types';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const deviceUserList = ref<DeviceUserVO[]>([]);
const buttonLoading = ref(false);
const loading = ref(true);
const showSearch = ref(true);
const ids = ref<Array<string | number>>([]);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const queryFormRef = ref<ElFormInstance>();
const deviceUserFormRef = ref<ElFormInstance>();
const dialog = reactive<DialogOption>({
visible: false,
title: ''
});
const initFormData: DeviceUserForm = {
deviceId: undefined,
deviceNo: undefined,
deviceName: undefined,
userId: undefined,
nickName: undefined
};
const data = reactive<PageData<DeviceUserForm, DeviceUserQuery>>({
form: { ...initFormData },
queryParams: {
pageNum: 1,
pageSize: 10,
deviceNo: undefined,
deviceName: undefined,
nickName: undefined,
params: {}
},
rules: {
deviceId: [{ required: true, message: '不能为空', trigger: 'blur' }],
userId: [{ required: true, message: '用户ID不能为空', trigger: 'blur' }],
nickName: [{ required: true, message: '用户昵称不能为空', trigger: 'blur' }]
}
});
const { queryParams, form, rules } = toRefs(data);
/** 查询设备成员关联列表 */
const getList = async () => {
loading.value = true;
const res = await listDeviceUser(queryParams.value);
deviceUserList.value = res.rows;
total.value = res.total;
loading.value = false;
};
/** 取消按钮 */
const cancel = () => {
reset();
dialog.visible = false;
};
/** 表单重置 */
const reset = () => {
form.value = { ...initFormData };
deviceUserFormRef.value?.resetFields();
};
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.value.pageNum = 1;
getList();
};
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value?.resetFields();
handleQuery();
};
/** 多选框选中数据 */
const handleSelectionChange = (selection: DeviceUserVO[]) => {
ids.value = selection.map((item) => item.deviceId);
single.value = selection.length != 1;
multiple.value = !selection.length;
};
/** 新增按钮操作 */
const handleAdd = () => {
reset();
dialog.visible = true;
dialog.title = '添加设备成员关联';
};
/** 修改按钮操作 */
const handleUpdate = async (row?: DeviceUserVO) => {
reset();
const _deviceId = row?.deviceId || ids.value[0];
const res = await getDeviceUser(_deviceId);
Object.assign(form.value, res.data);
dialog.visible = true;
dialog.title = '修改设备成员关联';
};
/** 提交按钮 */
const submitForm = () => {
deviceUserFormRef.value?.validate(async (valid: boolean) => {
if (valid) {
buttonLoading.value = true;
if (form.value.deviceId) {
await updateDeviceUser(form.value).finally(() => (buttonLoading.value = false));
} else {
await addDeviceUser(form.value).finally(() => (buttonLoading.value = false));
}
proxy?.$modal.msgSuccess('操作成功');
dialog.visible = false;
await getList();
}
});
};
/** 删除按钮操作 */
const handleDelete = async (row?: DeviceUserVO) => {
const _deviceIds = row?.deviceId || ids.value;
await proxy?.$modal.confirm('是否确认删除设备成员关联编号为"' + _deviceIds + '"的数据项?').finally(() => (loading.value = false));
await delDeviceUser(_deviceIds);
proxy?.$modal.msgSuccess('删除成功');
await getList();
};
/** 导出按钮操作 */
const handleExport = () => {
proxy?.download(
'app/deviceUser/export',
{
...queryParams.value
},
`deviceUser_${new Date().getTime()}.xlsx`
);
};
onMounted(() => {
getList();
});
</script>

View File

@@ -0,0 +1,255 @@
<template>
<div class="p-2">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<el-form-item label="家长id" prop="parentId">
<el-input v-model="queryParams.parentId" placeholder="请输入家长id" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="家长昵称" prop="parentName">
<el-input v-model="queryParams.parentName" placeholder="请输入家长昵称" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="成员id" prop="userId">
<el-input v-model="queryParams.userId" placeholder="请输入成员id" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
</el-card>
</div>
</transition>
<el-card shadow="never">
<!-- <template #header>-->
<!-- <el-row :gutter="10" class="mb8">-->
<!-- <el-col :span="1.5">-->
<!-- <el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['app:family:add']">新增</el-button>-->
<!-- </el-col>-->
<!-- <el-col :span="1.5">-->
<!-- <el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['app:family:edit']">修改</el-button>-->
<!-- </el-col>-->
<!-- <el-col :span="1.5">-->
<!-- <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['app:family:remove']">删除</el-button>-->
<!-- </el-col>-->
<!-- <el-col :span="1.5">-->
<!-- <el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['app:family:export']">导出</el-button>-->
<!-- </el-col>-->
<!-- <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>-->
<!-- </el-row>-->
<!-- </template>-->
<el-table v-loading="loading" border :data="familyList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="ID" align="center" prop="id" v-if="true" />
<el-table-column label="家长id" align="center" prop="parentId" />
<el-table-column label="家长昵称" align="center" prop="parentName" />
<el-table-column label="成员id" align="center" prop="userId" />
<el-table-column label="用户昵称" align="center" prop="nickName" />
<el-table-column label="用户身份证号" align="center" prop="idCard" />
<el-table-column label="用户手机号" align="center" prop="phonenumber" />
<el-table-column label="用户MAC地址" align="center" prop="mac" />
<el-table-column label="账号状态" align="center">
<template #default="scope">
<el-switch v-model="scope.row.status" active-value="0" inactive-value="1" @change="handleStatusChange(scope.row)"></el-switch>
</template>
</el-table-column>
<el-table-column label="操作" align="center" fixed="right" class-name="small-padding fixed-width">
<!-- <template #default="scope">-->
<!-- <el-tooltip content="修改" placement="top">-->
<!-- <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['app:family:edit']"></el-button>-->
<!-- </el-tooltip>-->
<!-- <el-tooltip content="删除" placement="top">-->
<!-- <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['app:family:remove']"></el-button>-->
<!-- </el-tooltip>-->
<!-- </template>-->
</el-table-column>
</el-table>
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
</el-card>
<!-- 添加或修改家庭成员对话框 -->
<el-dialog :title="dialog.title" v-model="dialog.visible" width="500px" append-to-body>
<el-form ref="familyFormRef" :model="form" :rules="rules" label-width="80px">
<el-form-item label="家长id" prop="parentId">
<el-input v-model="form.parentId" placeholder="请输入家长id" />
</el-form-item>
<el-form-item label="家长昵称" prop="parentName">
<el-input v-model="form.parentName" placeholder="请输入家长昵称" />
</el-form-item>
<el-form-item label="成员id" prop="userId">
<el-input v-model="form.userId" placeholder="请输入成员id" />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button :loading="buttonLoading" type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup name="Family" lang="ts">
import { listFamily, getFamily, delFamily, addFamily, updateFamily, changeFamilyStatus } from '@/api/app/family';
import { FamilyVO, FamilyQuery, FamilyForm } from '@/api/app/family/types';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const familyList = ref<FamilyVO[]>([]);
const buttonLoading = ref(false);
const loading = ref(true);
const showSearch = ref(true);
const ids = ref<Array<string | number>>([]);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const queryFormRef = ref<ElFormInstance>();
const familyFormRef = ref<ElFormInstance>();
const dialog = reactive<DialogOption>({
visible: false,
title: ''
});
const initFormData: FamilyForm = {
id: undefined,
parentId: undefined,
parentName: undefined,
userId: undefined,
status: undefined
};
const data = reactive<PageData<FamilyForm, FamilyQuery>>({
form: { ...initFormData },
queryParams: {
pageNum: 1,
pageSize: 10,
parentId: undefined,
parentName: undefined,
userId: undefined,
status: undefined,
params: {}
},
rules: {
id: [{ required: true, message: '不能为空', trigger: 'blur' }],
parentId: [{ required: true, message: '家长id不能为空', trigger: 'blur' }],
userId: [{ required: true, message: '成员id不能为空', trigger: 'blur' }]
}
});
const { queryParams, form, rules } = toRefs(data);
/** 查询家庭成员列表 */
const getList = async () => {
loading.value = true;
const res = await listFamily(queryParams.value);
familyList.value = res.rows;
total.value = res.total;
loading.value = false;
};
/** 取消按钮 */
const cancel = () => {
reset();
dialog.visible = false;
};
/** 表单重置 */
const reset = () => {
form.value = { ...initFormData };
familyFormRef.value?.resetFields();
};
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.value.pageNum = 1;
getList();
};
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value?.resetFields();
handleQuery();
};
/** 多选框选中数据 */
const handleSelectionChange = (selection: FamilyVO[]) => {
ids.value = selection.map((item) => item.id);
single.value = selection.length != 1;
multiple.value = !selection.length;
};
/** 新增按钮操作 */
const handleAdd = () => {
reset();
dialog.visible = true;
dialog.title = '添加家庭成员';
};
/** 修改按钮操作 */
const handleUpdate = async (row?: FamilyVO) => {
reset();
const _id = row?.id || ids.value[0];
const res = await getFamily(_id);
Object.assign(form.value, res.data);
dialog.visible = true;
dialog.title = '修改家庭成员';
};
/** 提交按钮 */
const submitForm = () => {
familyFormRef.value?.validate(async (valid: boolean) => {
if (valid) {
buttonLoading.value = true;
if (form.value.id) {
await updateFamily(form.value).finally(() => (buttonLoading.value = false));
} else {
await addFamily(form.value).finally(() => (buttonLoading.value = false));
}
proxy?.$modal.msgSuccess('操作成功');
dialog.visible = false;
await getList();
}
});
};
/** 删除按钮操作 */
const handleDelete = async (row?: FamilyVO) => {
const _ids = row?.id || ids.value;
await proxy?.$modal.confirm('是否确认删除家庭成员编号为"' + _ids + '"的数据项?').finally(() => (loading.value = false));
await delFamily(_ids);
proxy?.$modal.msgSuccess('删除成功');
await getList();
};
/** 导出按钮操作 */
const handleExport = () => {
proxy?.download(
'app/family/export',
{
...queryParams.value
},
`family_${new Date().getTime()}.xlsx`
);
};
/** 用户状态修改 */
const handleStatusChange = async (row: FamilyVO) => {
const text = row.status === '0' ? '启用' : '停用';
try {
await proxy?.$modal.confirm('确认要"' + text + '""' + (row.parentName || row.id) + '"家庭吗?');
await changeFamilyStatus(row.id, row.status);
proxy?.$modal.msgSuccess(text + '成功');
} catch {
row.status = row.status === '0' ? '1' : '0';
}
};
onMounted(() => {
getList();
});
</script>

View File

@@ -0,0 +1,237 @@
<template>
<div class="p-2">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<el-form-item label="用户昵称" prop="nickName">
<el-input v-model="queryParams.nickName" placeholder="请输入用户昵称" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="设备编号" prop="deviceNo">
<el-input v-model="queryParams.deviceNo" placeholder="请输入设备编号" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="设备名称" prop="deviceName">
<el-input v-model="queryParams.deviceName" placeholder="请输入设备名称" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
</el-card>
</div>
</transition>
<el-card shadow="never">
<template #header>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['app:familyDevice:add']">新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['app:familyDevice:edit']">修改</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['app:familyDevice:remove']">删除</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['app:familyDevice:export']">导出</el-button>
</el-col>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
</template>
<el-table v-loading="loading" border :data="familyDeviceList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="用户ID" align="center" prop="userId" v-if="true" />
<el-table-column label="用户昵称" align="center" prop="nickName" />
<el-table-column label="" align="center" prop="deviceId" v-if="true" />
<el-table-column label="设备编号" align="center" prop="deviceNo" />
<el-table-column label="设备名称" align="center" prop="deviceName" />
<el-table-column label="操作" align="center" fixed="right" class-name="small-padding fixed-width">
<template #default="scope">
<el-tooltip content="修改" placement="top">
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['app:familyDevice:edit']"></el-button>
</el-tooltip>
<el-tooltip content="删除" placement="top">
<el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['app:familyDevice:remove']"></el-button>
</el-tooltip>
</template>
</el-table-column>
</el-table>
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
</el-card>
<!-- 添加或修改家庭设备关联对话框 -->
<el-dialog :title="dialog.title" v-model="dialog.visible" width="500px" append-to-body>
<el-form ref="familyDeviceFormRef" :model="form" :rules="rules" label-width="80px">
<el-form-item label="用户昵称" prop="nickName">
<el-input v-model="form.nickName" placeholder="请输入用户昵称" />
</el-form-item>
<el-form-item label="设备编号" prop="deviceNo">
<el-input v-model="form.deviceNo" placeholder="请输入设备编号" />
</el-form-item>
<el-form-item label="设备名称" prop="deviceName">
<el-input v-model="form.deviceName" placeholder="请输入设备名称" />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button :loading="buttonLoading" type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup name="FamilyDevice" lang="ts">
import { listFamilyDevice, getFamilyDevice, delFamilyDevice, addFamilyDevice, updateFamilyDevice } from '@/api/app/familyDevice';
import { FamilyDeviceVO, FamilyDeviceQuery, FamilyDeviceForm } from '@/api/app/familyDevice/types';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const familyDeviceList = ref<FamilyDeviceVO[]>([]);
const buttonLoading = ref(false);
const loading = ref(true);
const showSearch = ref(true);
const ids = ref<Array<string | number>>([]);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const queryFormRef = ref<ElFormInstance>();
const familyDeviceFormRef = ref<ElFormInstance>();
const dialog = reactive<DialogOption>({
visible: false,
title: ''
});
const initFormData: FamilyDeviceForm = {
userId: undefined,
nickName: undefined,
deviceId: undefined,
deviceNo: undefined,
deviceName: undefined,
}
const data = reactive<PageData<FamilyDeviceForm, FamilyDeviceQuery>>({
form: {...initFormData},
queryParams: {
pageNum: 1,
pageSize: 10,
nickName: undefined,
deviceNo: undefined,
deviceName: undefined,
params: {
}
},
rules: {
userId: [
{ required: true, message: "用户ID不能为空", trigger: "blur" }
],
nickName: [
{ required: true, message: "用户昵称不能为空", trigger: "blur" }
],
deviceId: [
{ required: true, message: "不能为空", trigger: "blur" }
],
}
});
const { queryParams, form, rules } = toRefs(data);
/** 查询家庭设备关联列表 */
const getList = async () => {
loading.value = true;
const res = await listFamilyDevice(queryParams.value);
familyDeviceList.value = res.rows;
total.value = res.total;
loading.value = false;
}
/** 取消按钮 */
const cancel = () => {
reset();
dialog.visible = false;
}
/** 表单重置 */
const reset = () => {
form.value = {...initFormData};
familyDeviceFormRef.value?.resetFields();
}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.value.pageNum = 1;
getList();
}
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value?.resetFields();
handleQuery();
}
/** 多选框选中数据 */
const handleSelectionChange = (selection: FamilyDeviceVO[]) => {
ids.value = selection.map(item => item.userId);
single.value = selection.length != 1;
multiple.value = !selection.length;
}
/** 新增按钮操作 */
const handleAdd = () => {
reset();
dialog.visible = true;
dialog.title = "添加家庭设备关联";
}
/** 修改按钮操作 */
const handleUpdate = async (row?: FamilyDeviceVO) => {
reset();
const _userId = row?.userId || ids.value[0]
const res = await getFamilyDevice(_userId);
Object.assign(form.value, res.data);
dialog.visible = true;
dialog.title = "修改家庭设备关联";
}
/** 提交按钮 */
const submitForm = () => {
familyDeviceFormRef.value?.validate(async (valid: boolean) => {
if (valid) {
buttonLoading.value = true;
if (form.value.userId) {
await updateFamilyDevice(form.value).finally(() => buttonLoading.value = false);
} else {
await addFamilyDevice(form.value).finally(() => buttonLoading.value = false);
}
proxy?.$modal.msgSuccess("操作成功");
dialog.visible = false;
await getList();
}
});
}
/** 删除按钮操作 */
const handleDelete = async (row?: FamilyDeviceVO) => {
const _userIds = row?.userId || ids.value;
await proxy?.$modal.confirm('是否确认删除家庭设备关联编号为"' + _userIds + '"的数据项?').finally(() => loading.value = false);
await delFamilyDevice(_userIds);
proxy?.$modal.msgSuccess("删除成功");
await getList();
}
/** 导出按钮操作 */
const handleExport = () => {
proxy?.download('app/familyDevice/export', {
...queryParams.value
}, `familyDevice_${new Date().getTime()}.xlsx`)
}
onMounted(() => {
getList();
});
</script>

View File

@@ -0,0 +1,353 @@
<template>
<div class="p-2">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<el-form-item label="告警类型" prop="category">
<el-select v-model="queryParams.category" placeholder="请选择告警类型" clearable @change="handleQuery">
<el-option v-for="item in categoryOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<!-- <el-form-item label="告警说明" prop="categoryName">-->
<!-- <el-input v-model="queryParams.categoryName" placeholder="请输入告警说明" clearable @keyup.enter="handleQuery" />-->
<!-- </el-form-item>-->
<!-- <el-form-item label="视频地址" prop="video">
<el-input v-model="queryParams.video" placeholder="请输入视频地址" clearable @keyup.enter="handleQuery" />
</el-form-item>-->
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
</el-card>
</div>
</transition>
<el-card shadow="never">
<template #header>
<el-row :gutter="10" class="mb8">
<!-- <el-col :span="1.5">-->
<!-- <el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['app:message:add']">新增</el-button>-->
<!-- </el-col>-->
<!-- <el-col :span="1.5">-->
<!-- <el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['app:message:edit']">修改</el-button>-->
<!-- </el-col>-->
<el-col :span="1.5">
<el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['app:message:remove']"
>删除</el-button
>
</el-col>
<el-col :span="1.5">
<el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['app:message:export']">导出</el-button>
</el-col>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
</template>
<el-table v-loading="loading" border :data="messageList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="ID" align="center" prop="id" v-if="true" />
<el-table-column label="设备ID" align="center" prop="deviceId" v-if="true" />
<el-table-column label="设备名" align="center" prop="deviceName" v-if="true" />
<el-table-column label="告警类型" align="center" prop="category" width="120">
<template #default="scope">
<el-tag :type="categoryTagType(scope.row.category)">
{{ categoryLabel(scope.row.category) }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="告警说明" align="center" prop="categoryName" />
<el-table-column label="图片地址" align="center" prop="imageUrl" width="100">
<template #default="scope">
<image-preview :src="scope.row.imageUrl" :width="50" :height="50" />
</template>
</el-table-column>
<el-table-column label="视频地址" align="center" prop="video" min-width="120">
<template #default="scope">
<template v-if="scope.row.video">
<el-button link type="primary" @click="handlePlayVideo(scope.row.video)">
<el-icon><VideoPlay /></el-icon> 播放
</el-button>
</template>
<span v-else class="text-gray-400 text-xs">-</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" fixed="right" class-name="small-padding fixed-width">
<template #default="scope">
<!-- <el-tooltip content="修改" placement="top">-->
<!-- <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['app:message:edit']"></el-button>-->
<!-- </el-tooltip>-->
<el-tooltip content="删除" placement="top">
<el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['app:message:remove']"></el-button>
</el-tooltip>
</template>
</el-table-column>
</el-table>
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
</el-card>
<!-- 添加或修改告警消息列对话框 -->
<el-dialog :title="dialog.title" v-model="dialog.visible" width="500px" append-to-body>
<el-form ref="messageFormRef" :model="form" :rules="rules" label-width="80px">
<el-form-item label="告警类型" prop="category">
<el-select v-model="form.category" placeholder="请选择告警类型">
<el-option v-for="item in categoryOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="告警说明" prop="categoryName">
<el-input v-model="form.categoryName" placeholder="请输入告警说明" />
</el-form-item>
<el-form-item label="图片地址" prop="image">
<image-upload v-model="form.image" />
</el-form-item>
<el-form-item label="视频地址" prop="video">
<el-input v-model="form.video" placeholder="请输入视频地址" />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button :loading="buttonLoading" type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</template>
</el-dialog>
<!-- 视频播放弹窗 -->
<el-dialog title="视频播放" v-model="videoDialog.visible" width="720px" append-to-body :close-on-click-modal="false" @close="destroyPlayer">
<div ref="videoPlayerRef" class="video-player-wrap"></div>
</el-dialog>
</div>
</template>
<script setup name="Message" lang="ts">
import { nextTick } from 'vue';
import { listMessage, getMessage, delMessage, addMessage, updateMessage } from '@/api/app/message';
import { MessageVO, MessageQuery, MessageForm } from '@/api/app/message/types';
import { VideoPlay } from '@element-plus/icons-vue';
import Player from 'xgplayer';
import 'xgplayer-flv';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
/** 视频播放器容器 ref */
const videoPlayerRef = ref<HTMLDivElement | null>(null);
/** xgplayer 实例 */
let player: InstanceType<typeof Player> | null = null;
/** 视频播放弹窗状态 */
const videoDialog = reactive<{ visible: boolean; url: string }>({
visible: false,
url: ''
});
/** 点击播放按钮 */
const handlePlayVideo = (url: string) => {
videoDialog.url = url;
videoDialog.visible = true;
nextTick(() => {
initPlayer(url);
});
};
/** 初始化 xgplayer 播放 FLV 流 */
const initPlayer = (url: string) => {
destroyPlayer();
if (!videoPlayerRef.value) return;
player = new Player({
el: videoPlayerRef.value,
url,
videoInit: true,
autoplay: true,
playsinline: true,
height: '100%',
width: '100%',
flv: {
mediaDataSource: {
isLive: true,
type: 'flv',
url
}
}
});
};
/** 销毁播放器 */
const destroyPlayer = () => {
if (player) {
player.destroy();
player = null;
}
};
/** 告警类型选项 */
const categoryOptions = [
{ value: 1, label: '有人入侵' },
{ value: 2, label: '设备故障' },
{ value: 3, label: '到期提醒' }
];
/** 根据 category 值返回中文标签 */
const categoryLabel = (category: number): string => {
return categoryOptions.find((item) => item.value === category)?.label || category?.toString() || '-';
};
/** 根据 category 值返回 el-tag 类型 */
const categoryTagType = (category: number): string => {
const map: Record<number, string> = { 1: 'danger', 2: 'warning', 3: 'info' };
return map[category] || 'info';
};
const messageList = ref<MessageVO[]>([]);
const buttonLoading = ref(false);
const loading = ref(true);
const showSearch = ref(true);
const ids = ref<Array<string | number>>([]);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const queryFormRef = ref<ElFormInstance>();
const messageFormRef = ref<ElFormInstance>();
const dialog = reactive<DialogOption>({
visible: false,
title: ''
});
const initFormData: MessageForm = {
id: undefined,
category: undefined,
categoryName: undefined,
image: undefined,
video: undefined
};
const data = reactive<PageData<MessageForm, MessageQuery>>({
form: { ...initFormData },
queryParams: {
pageNum: 1,
pageSize: 10,
category: undefined,
categoryName: undefined,
image: undefined,
video: undefined,
params: {}
},
rules: {
id: [{ required: true, message: '不能为空', trigger: 'blur' }]
}
});
const { queryParams, form, rules } = toRefs(data);
/** 查询告警消息列列表 */
const getList = async () => {
loading.value = true;
const res = await listMessage(queryParams.value);
messageList.value = res.rows;
total.value = res.total;
loading.value = false;
};
/** 取消按钮 */
const cancel = () => {
reset();
dialog.visible = false;
};
/** 表单重置 */
const reset = () => {
form.value = { ...initFormData };
messageFormRef.value?.resetFields();
};
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.value.pageNum = 1;
getList();
};
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value?.resetFields();
handleQuery();
};
/** 多选框选中数据 */
const handleSelectionChange = (selection: MessageVO[]) => {
ids.value = selection.map((item) => item.id);
single.value = selection.length != 1;
multiple.value = !selection.length;
};
/** 新增按钮操作 */
const handleAdd = () => {
reset();
dialog.visible = true;
dialog.title = '添加告警消息列';
};
/** 修改按钮操作 */
const handleUpdate = async (row?: MessageVO) => {
reset();
const _id = row?.id || ids.value[0];
const res = await getMessage(_id);
Object.assign(form.value, res.data);
dialog.visible = true;
dialog.title = '修改告警消息列';
};
/** 提交按钮 */
const submitForm = () => {
messageFormRef.value?.validate(async (valid: boolean) => {
if (valid) {
buttonLoading.value = true;
if (form.value.id) {
await updateMessage(form.value).finally(() => (buttonLoading.value = false));
} else {
await addMessage(form.value).finally(() => (buttonLoading.value = false));
}
proxy?.$modal.msgSuccess('操作成功');
dialog.visible = false;
await getList();
}
});
};
/** 删除按钮操作 */
const handleDelete = async (row?: MessageVO) => {
const _ids = row?.id || ids.value;
await proxy?.$modal.confirm('是否确认删除告警消息列编号为"' + _ids + '"的数据项?').finally(() => (loading.value = false));
await delMessage(_ids);
proxy?.$modal.msgSuccess('删除成功');
await getList();
};
/** 导出按钮操作 */
const handleExport = () => {
proxy?.download(
'app/message/export',
{
...queryParams.value
},
`message_${new Date().getTime()}.xlsx`
);
};
onMounted(() => {
getList();
});
</script>
<style scoped>
.video-player-wrap {
width: 100%;
height: 60vh;
display: flex;
align-items: center;
justify-content: center;
background: #000;
border-radius: 8px;
overflow: hidden;
}
</style>

View File

@@ -0,0 +1,432 @@
<template>
<div class="p-2">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<el-form-item label="用户账号" prop="userName">
<el-input v-model="queryParams.userName" placeholder="请输入用户账号" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="用户昵称" prop="nickName">
<el-input v-model="queryParams.nickName" placeholder="请输入用户昵称" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="身份证号" prop="idCard">
<el-input v-model="queryParams.idCard" placeholder="请输入身份证号" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="用户邮箱" prop="email">
<el-input v-model="queryParams.email" placeholder="请输入用户邮箱" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="手机号码" prop="phonenumber">
<el-input v-model="queryParams.phonenumber" placeholder="请输入手机号码" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
</el-card>
</div>
</transition>
<el-card shadow="never">
<!-- <template #header>-->
<!-- <el-row :gutter="10" class="mb8">-->
<!-- <el-col :span="1.5">-->
<!-- <el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['app:user:add']">新增</el-button>-->
<!-- </el-col>-->
<!-- <el-col :span="1.5">-->
<!-- <el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['app:user:edit']">修改</el-button>-->
<!-- </el-col>-->
<!-- <el-col :span="1.5">-->
<!-- <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['app:user:remove']"-->
<!-- >删除</el-button-->
<!-- >-->
<!-- </el-col>-->
<!-- <el-col :span="1.5">-->
<!-- <el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['app:user:export']">导出</el-button>-->
<!-- </el-col>-->
<!-- <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>-->
<!-- </el-row>-->
<!-- </template>-->
<el-table v-loading="loading" border :data="userList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="用户ID" align="center" prop="userId" v-if="true" />
<el-table-column label="用户账号" align="center" prop="userName">
<template #default="scope">
{{ scope.row.phonenumber || scope.row.email || scope.row.userName || '-' }}
</template>
</el-table-column>
<el-table-column label="用户昵称" align="center" prop="nickName" />
<el-table-column label="身份证号" align="center" prop="idCard" />
<!-- <el-table-column label="用户类型" align="center" prop="userType" />-->
<el-table-column label="用户邮箱" align="center" prop="email" />
<el-table-column label="手机号码" align="center" prop="phonenumber" />
<el-table-column label="用户性别" align="center" prop="sex" />
<!-- <el-table-column label="头像地址" align="center" prop="avatar" />-->
<el-table-column label="头像地址" align="center" prop="avatar" width="100">
<template #default="scope">
<image-preview :src="scope.row.avatar" :width="50" :height="50" />
</template>
</el-table-column>
<!-- <el-table-column label="密码" align="center" prop="password" />-->
<el-table-column label="账号状态" align="center">
<template #default="scope">
<el-switch v-model="scope.row.status" active-value="0" inactive-value="1" @change="handleStatusChange(scope.row)"></el-switch>
</template>
</el-table-column>
<el-table-column label="人脸" align="center" prop="faceAddr" width="180">
<template #default="scope">
<div class="face-image-list" v-if="scope.row.faceAddr">
<el-image
v-for="(url, idx) in faceUrls(scope.row.faceAddr)"
:key="idx"
:src="url"
:preview-src-list="faceUrls(scope.row.faceAddr)"
:initial-index="idx"
fit="cover"
style="width:40px;height:40px"
preview-teleported
>
<template #error>
<div class="face-error-slot">
<el-icon><PictureFilled /></el-icon>
</div>
</template>
</el-image>
</div>
<span v-else class="text-gray-400 text-xs">-</span>
</template>
</el-table-column>
<el-table-column label="手机mac地址" align="center" prop="mac" />
<!-- <el-table-column label="邀请码" align="center" prop="inviteUsed" />
<el-table-column label="邀请码创建时间" align="center" prop="inviteTime" width="180">
<template #default="scope">
<span>{{ parseTime(scope.row.inviteTime, '{y}-{m}-{d}') }}</span>
</template>
</el-table-column>-->
<el-table-column label="最后登录IP" align="center" prop="loginIp" />
<el-table-column label="最后登录时间" align="center" prop="loginDate" width="180">
<template #default="scope">
<span>{{ parseTime(scope.row.loginDate, '{y}-{m}-{d}') }}</span>
</template>
</el-table-column>
<!-- <el-table-column label="备注" align="center" prop="remark" />-->
<el-table-column label="操作" align="center" fixed="right" class-name="small-padding fixed-width">
<template #default="scope">
<!-- <el-tooltip content="修改" placement="top">-->
<!-- <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['app:user:edit']"></el-button>-->
<!-- </el-tooltip>-->
<el-tooltip content="删除" placement="top">
<el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['app:user:remove']"></el-button>
</el-tooltip>
<el-tooltip content="重置密码" placement="top">
<el-button link type="primary" icon="Key" @click="handleResetPwd(scope.row)" v-hasPermi="['app:user:resetPwd']"></el-button>
</el-tooltip>
</template>
</el-table-column>
</el-table>
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
</el-card>
<!-- 添加或修改app用户信息对话框 -->
<el-dialog :title="dialog.title" v-model="dialog.visible" width="500px" append-to-body>
<el-form ref="userFormRef" :model="form" :rules="rules" label-width="80px">
<el-form-item label="用户账号" prop="userName">
<el-input v-model="form.userName" placeholder="请输入用户账号" />
</el-form-item>
<el-form-item label="用户昵称" prop="nickName">
<el-input v-model="form.nickName" placeholder="请输入用户昵称" />
</el-form-item>
<el-form-item label="身份证号" prop="idCard">
<el-input v-model="form.idCard" placeholder="请输入身份证号" />
</el-form-item>
<el-form-item label="用户邮箱" prop="email">
<el-input v-model="form.email" placeholder="请输入用户邮箱" />
</el-form-item>
<el-form-item label="手机号码" prop="phonenumber">
<el-input v-model="form.phonenumber" placeholder="请输入手机号码" />
</el-form-item>
<el-form-item label="头像地址" prop="avatar">
<el-input v-model="form.avatar" placeholder="请输入头像地址" />
</el-form-item>
<el-form-item label="密码" prop="password">
<el-input v-model="form.password" placeholder="请输入密码" />
</el-form-item>
<el-form-item label="人脸" prop="faceAddr">
<el-input v-model="form.faceAddr" placeholder="请输入人脸" />
</el-form-item>
<el-form-item label="手机mac地址" prop="mac">
<el-input v-model="form.mac" placeholder="请输入手机mac地址" />
</el-form-item>
<el-form-item label="邀请码" prop="inviteUsed">
<el-input v-model="form.inviteUsed" placeholder="请输入邀请码" />
</el-form-item>
<el-form-item label="邀请码创建时间" prop="inviteTime">
<el-date-picker clearable v-model="form.inviteTime" type="datetime" value-format="YYYY-MM-DD HH:mm:ss" placeholder="请选择邀请码创建时间">
</el-date-picker>
</el-form-item>
<el-form-item label="最后登录IP" prop="loginIp">
<el-input v-model="form.loginIp" placeholder="请输入最后登录IP" />
</el-form-item>
<el-form-item label="最后登录时间" prop="loginDate">
<el-date-picker clearable v-model="form.loginDate" type="datetime" value-format="YYYY-MM-DD HH:mm:ss" placeholder="请选择最后登录时间">
</el-date-picker>
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" type="textarea" placeholder="请输入内容" />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button :loading="buttonLoading" type="primary" @click="submitForm">确 定</el-button>
<el-button @click="cancel">取 消</el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup name="User" lang="ts">
import { listUser, getUser, delUser, addUser, updateUser, resetUserPwd, changeUserStatus } from '@/api/app/user';
import { UserVO, UserQuery, UserForm } from '@/api/app/user/types';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
/** 将逗号分隔的人脸地址拆分为数组 */
const faceUrls = (faceAddr: string): string[] => {
if (!faceAddr) return [];
return faceAddr.split(',').map(u => u.trim()).filter(Boolean);
};
const userList = ref<UserVO[]>([]);
const buttonLoading = ref(false);
const loading = ref(true);
const showSearch = ref(true);
const ids = ref<Array<string | number>>([]);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const queryFormRef = ref<ElFormInstance>();
const userFormRef = ref<ElFormInstance>();
const dialog = reactive<DialogOption>({
visible: false,
title: ''
});
const initFormData: UserForm = {
userId: undefined,
userName: undefined,
nickName: undefined,
idCard: undefined,
userType: undefined,
email: undefined,
phonenumber: undefined,
sex: undefined,
avatar: undefined,
password: undefined,
status: undefined,
faceAddr: undefined,
mac: undefined,
inviteUsed: undefined,
inviteTime: undefined,
loginIp: undefined,
loginDate: undefined,
remark: undefined
};
const data = reactive<PageData<UserForm, UserQuery>>({
form: { ...initFormData },
queryParams: {
pageNum: 1,
pageSize: 10,
userName: undefined,
nickName: undefined,
idCard: undefined,
userType: undefined,
email: undefined,
phonenumber: undefined,
sex: undefined,
avatar: undefined,
password: undefined,
status: undefined,
faceAddr: undefined,
mac: undefined,
inviteUsed: undefined,
inviteTime: undefined,
loginIp: undefined,
loginDate: undefined,
params: {}
},
rules: {
userId: [{ required: true, message: '用户ID不能为空', trigger: 'blur' }],
userName: [{ required: true, message: '用户账号不能为空', trigger: 'blur' }],
nickName: [{ required: true, message: '用户昵称不能为空', trigger: 'blur' }]
}
});
const { queryParams, form, rules } = toRefs(data);
/** 查询app用户信息列表 */
const getList = async () => {
loading.value = true;
const res = await listUser(queryParams.value);
userList.value = res.rows;
total.value = res.total;
loading.value = false;
};
/** 取消按钮 */
const cancel = () => {
reset();
dialog.visible = false;
};
/** 表单重置 */
const reset = () => {
form.value = { ...initFormData };
userFormRef.value?.resetFields();
};
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.value.pageNum = 1;
getList();
};
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value?.resetFields();
handleQuery();
};
/** 多选框选中数据 */
const handleSelectionChange = (selection: UserVO[]) => {
ids.value = selection.map((item) => item.userId);
single.value = selection.length != 1;
multiple.value = !selection.length;
};
/** 新增按钮操作 */
const handleAdd = () => {
reset();
dialog.visible = true;
dialog.title = '添加app用户信息';
};
/** 修改按钮操作 */
const handleUpdate = async (row?: UserVO) => {
reset();
const _userId = row?.userId || ids.value[0];
const res = await getUser(_userId);
Object.assign(form.value, res.data);
dialog.visible = true;
dialog.title = '修改app用户信息';
};
/** 提交按钮 */
const submitForm = () => {
userFormRef.value?.validate(async (valid: boolean) => {
if (valid) {
buttonLoading.value = true;
if (form.value.userId) {
await updateUser(form.value).finally(() => (buttonLoading.value = false));
} else {
await addUser(form.value).finally(() => (buttonLoading.value = false));
}
proxy?.$modal.msgSuccess('操作成功');
dialog.visible = false;
await getList();
}
});
};
/** 删除按钮操作 */
const handleDelete = async (row?: UserVO) => {
const _userIds = row?.userId || ids.value;
await proxy?.$modal.confirm('是否确认删除app用户信息编号为"' + _userIds + '"的数据项?').finally(() => (loading.value = false));
await delUser(_userIds);
proxy?.$modal.msgSuccess('删除成功');
await getList();
};
/** 导出按钮操作 */
const handleExport = () => {
proxy?.download(
'app/user/export',
{
...queryParams.value
},
`user_${new Date().getTime()}.xlsx`
);
};
/** 用户状态修改 */
const handleStatusChange = async (row: UserVO) => {
const text = row.status === '0' ? '启用' : '停用';
try {
await proxy?.$modal.confirm('确认要"' + text + '""' + (row.phonenumber || row.email || row.userName) + '"用户吗?');
await changeUserStatus(row.userId, row.status);
proxy?.$modal.msgSuccess(text + '成功');
} catch {
row.status = row.status === '0' ? '1' : '0';
}
};
/** 重置密码按钮操作 */
const handleResetPwd = async (row: UserVO) => {
const res = await ElMessageBox.prompt('请输入"' + (row.phonenumber || row.email || row.userName) + '"的新密码', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
closeOnClickModal: false,
inputPattern: /^.{5,20}$/,
inputErrorMessage: '用户密码长度必须介于 5 和 20 之间',
inputValidator: (value) => {
if (/<|>|"|'|\||\\/.test(value)) {
return '不能包含非法字符:< > " \' \\ |';
}
}
});
if (res) {
await resetUserPwd(row.userId, res.value);
proxy?.$modal.msgSuccess('修改成功新密码是' + res.value);
}
};
onMounted(() => {
getList();
});
</script>
<style scoped>
.face-image-list {
display: flex;
flex-wrap: wrap;
gap: 4px;
justify-content: center;
align-items: center;
}
.face-image-list .el-image {
border-radius: 4px;
box-shadow: 0 0 3px 1px #ddd;
cursor: pointer;
transition: transform 0.2s;
}
.face-image-list .el-image:hover {
transform: scale(1.15);
}
.face-error-slot {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
height: 100%;
color: #c0c4cc;
font-size: 20px;
background: #f5f7fa;
}
</style>

View File

@@ -0,0 +1,252 @@
<template>
<div class="p-2">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<el-form-item label="账号用户id" prop="userId">
<el-input v-model="queryParams.userId" placeholder="请输入账号用户id" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="设备token" prop="token">
<el-input v-model="queryParams.token" placeholder="请输入设备token" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="平台 iOS/Android" prop="platform">
<el-input v-model="queryParams.platform" placeholder="请输入平台 iOS/Android" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="" prop="createdTime">
<el-date-picker clearable
v-model="queryParams.createdTime"
type="date"
value-format="YYYY-MM-DD"
placeholder="请选择"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
</el-card>
</div>
</transition>
<el-card shadow="never">
<template #header>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['app:userdeviceToken:add']">新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['app:userdeviceToken:edit']">修改</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['app:userdeviceToken:remove']">删除</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['app:userdeviceToken:export']">导出</el-button>
</el-col>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
</template>
<el-table v-loading="loading" border :data="userdeviceTokenList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="" align="center" prop="id" v-if="true" />
<el-table-column label="账号用户id" align="center" prop="userId" />
<el-table-column label="设备token" align="center" prop="token" />
<el-table-column label="平台 iOS/Android" align="center" prop="platform" />
<el-table-column label="" align="center" prop="createdTime" width="180">
<template #default="scope">
<span>{{ parseTime(scope.row.createdTime, '{y}-{m}-{d}') }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" fixed="right" class-name="small-padding fixed-width">
<template #default="scope">
<el-tooltip content="修改" placement="top">
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['app:userdeviceToken:edit']"></el-button>
</el-tooltip>
<el-tooltip content="删除" placement="top">
<el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['app:userdeviceToken:remove']"></el-button>
</el-tooltip>
</template>
</el-table-column>
</el-table>
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
</el-card>
<!-- 添加或修改推送用户手机token对话框 -->
<el-dialog :title="dialog.title" v-model="dialog.visible" width="500px" append-to-body>
<el-form ref="userdeviceTokenFormRef" :model="form" :rules="rules" label-width="80px">
<el-form-item label="账号用户id" prop="userId">
<el-input v-model="form.userId" placeholder="请输入账号用户id" />
</el-form-item>
<el-form-item label="设备token" prop="token">
<el-input v-model="form.token" placeholder="请输入设备token" />
</el-form-item>
<el-form-item label="平台 iOS/Android" prop="platform">
<el-input v-model="form.platform" placeholder="请输入平台 iOS/Android" />
</el-form-item>
<el-form-item label="" prop="createdTime">
<el-date-picker clearable
v-model="form.createdTime"
type="datetime"
value-format="YYYY-MM-DD HH:mm:ss"
placeholder="请选择">
</el-date-picker>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button :loading="buttonLoading" type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup name="UserdeviceToken" lang="ts">
import { listUserdeviceToken, getUserdeviceToken, delUserdeviceToken, addUserdeviceToken, updateUserdeviceToken } from '@/api/app/userdeviceToken';
import { UserdeviceTokenVO, UserdeviceTokenQuery, UserdeviceTokenForm } from '@/api/app/userdeviceToken/types';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const userdeviceTokenList = ref<UserdeviceTokenVO[]>([]);
const buttonLoading = ref(false);
const loading = ref(true);
const showSearch = ref(true);
const ids = ref<Array<string | number>>([]);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const queryFormRef = ref<ElFormInstance>();
const userdeviceTokenFormRef = ref<ElFormInstance>();
const dialog = reactive<DialogOption>({
visible: false,
title: ''
});
const initFormData: UserdeviceTokenForm = {
id: undefined,
userId: undefined,
token: undefined,
platform: undefined,
createdTime: undefined,
}
const data = reactive<PageData<UserdeviceTokenForm, UserdeviceTokenQuery>>({
form: {...initFormData},
queryParams: {
pageNum: 1,
pageSize: 10,
userId: undefined,
token: undefined,
platform: undefined,
createdTime: undefined,
params: {
}
},
rules: {
id: [
{ required: true, message: "不能为空", trigger: "blur" }
],
}
});
const { queryParams, form, rules } = toRefs(data);
/** 查询推送用户手机token列表 */
const getList = async () => {
loading.value = true;
const res = await listUserdeviceToken(queryParams.value);
userdeviceTokenList.value = res.rows;
total.value = res.total;
loading.value = false;
}
/** 取消按钮 */
const cancel = () => {
reset();
dialog.visible = false;
}
/** 表单重置 */
const reset = () => {
form.value = {...initFormData};
userdeviceTokenFormRef.value?.resetFields();
}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.value.pageNum = 1;
getList();
}
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value?.resetFields();
handleQuery();
}
/** 多选框选中数据 */
const handleSelectionChange = (selection: UserdeviceTokenVO[]) => {
ids.value = selection.map(item => item.id);
single.value = selection.length != 1;
multiple.value = !selection.length;
}
/** 新增按钮操作 */
const handleAdd = () => {
reset();
dialog.visible = true;
dialog.title = "添加推送用户手机token";
}
/** 修改按钮操作 */
const handleUpdate = async (row?: UserdeviceTokenVO) => {
reset();
const _id = row?.id || ids.value[0]
const res = await getUserdeviceToken(_id);
Object.assign(form.value, res.data);
dialog.visible = true;
dialog.title = "修改推送用户手机token";
}
/** 提交按钮 */
const submitForm = () => {
userdeviceTokenFormRef.value?.validate(async (valid: boolean) => {
if (valid) {
buttonLoading.value = true;
if (form.value.id) {
await updateUserdeviceToken(form.value).finally(() => buttonLoading.value = false);
} else {
await addUserdeviceToken(form.value).finally(() => buttonLoading.value = false);
}
proxy?.$modal.msgSuccess("操作成功");
dialog.visible = false;
await getList();
}
});
}
/** 删除按钮操作 */
const handleDelete = async (row?: UserdeviceTokenVO) => {
const _ids = row?.id || ids.value;
await proxy?.$modal.confirm('是否确认删除推送用户手机token编号为"' + _ids + '"的数据项?').finally(() => loading.value = false);
await delUserdeviceToken(_ids);
proxy?.$modal.msgSuccess("删除成功");
await getList();
}
/** 导出按钮操作 */
const handleExport = () => {
proxy?.download('app/userdeviceToken/export', {
...queryParams.value
}, `userdeviceToken_${new Date().getTime()}.xlsx`)
}
onMounted(() => {
getList();
});
</script>

View File

@@ -1,165 +1,38 @@
<template>
<div class="app-container home">
<el-row :gutter="20">
<el-col :sm="24" :lg="12" style="padding-left: 20px">
<h2>RuoYi-Vue-Plus多租户管理系统</h2>
<p>
RuoYi-Vue-Plus 是基于 RuoYi-Vue 针对 分布式集群 场景升级(不兼容原框架)
<br />
* 前端开发框架 Vue3TSElement Plus<br />
* 后端开发框架 Spring Boot<br />
* 容器框架 Undertow 基于 Netty 的高性能容器<br />
* 权限认证框架 Sa-Token 支持多终端认证系统<br />
* 关系数据库 MySQL 适配 8.X 最低 5.7<br />
* 缓存数据库 Redis 适配 6.X 最低 4.X<br />
* 数据库框架 Mybatis-Plus 快速 CRUD 增加开发效率<br />
* 数据库框架 p6spy 更强劲的 SQL 分析<br />
* 多数据源框架 dynamic-datasource 支持主从与多种类数据库异构<br />
* 序列化框架 Jackson 统一使用 jackson 高效可靠<br />
* Redis客户端 Redisson 性能强劲API丰富<br />
* 分布式限流 Redisson 全局请求IP集群ID 多种限流<br />
* 分布式锁 Lock4j 注解锁工具锁 多种多样<br />
* 分布式幂等 Lock4j 基于分布式锁实现<br />
* 分布式链路追踪 SkyWalking 支持链路追踪网格分析度量聚合可视化<br />
* 分布式任务调度 SnailJob 高性能 高可靠 易扩展<br />
* 文件存储 Minio 本地存储<br />
* 文件存储 七牛阿里腾讯 云存储<br />
* 监控框架 SpringBoot-Admin 全方位服务监控<br />
* 校验框架 Validation 增强接口安全性 严谨性<br />
* Excel框架 FastExcel(原Alibaba EasyExcel) 性能优异 扩展性强<br />
* 文档框架 SpringDocjavadoc 无注解零入侵基于java注释<br />
* 工具类框架 HutoolLombok 减少代码冗余 增加安全性<br />
* 代码生成器 适配MPSpringDoc规范化代码 一键生成前后端代码<br />
* 部署方式 Docker 容器编排 一键部署业务集群<br />
* 国际化 SpringMessage Spring标准国际化方案<br />
</p>
<p><b>当前版本:</b> <span>v5.6.0</span></p>
<p>
<el-tag type="danger">&yen;免费开源</el-tag>
</p>
<p>
<el-button type="primary" icon="Cloudy" plain @click="goTarget('https://gitee.com/dromara/RuoYi-Vue-Plus')">访问码云</el-button>
<el-button type="primary" icon="Cloudy" plain @click="goTarget('https://github.com/dromara/RuoYi-Vue-Plus')">访问GitHub</el-button>
<el-button type="primary" icon="Cloudy" plain @click="goTarget('https://plus-doc.dromara.org/#/ruoyi-vue-plus/changlog')"
>更新日志</el-button
>
</p>
</el-col>
<el-col :sm="24" :lg="12" style="padding-left: 20px">
<h2>RuoYi-Cloud-Plus多租户微服务管理系统</h2>
<p>
RuoYi-Cloud-Plus 微服务通用权限管理系统 重写 RuoYi-Cloud 全方位升级(不兼容原框架)
<br />
* 前端开发框架 Vue3TSElement UI<br />
* 后端开发框架 Spring Boot<br />
* 微服务开发框架 Spring CloudSpring Cloud Alibaba<br />
* 容器框架 Undertow 基于 XNIO 的高性能容器<br />
* 权限认证框架 Sa-TokenJwt 支持多终端认证系统<br />
* 关系数据库 MySQL 适配 8.X 最低 5.7<br />
* 关系数据库 Oracle 适配 11g 12c<br />
* 关系数据库 PostgreSQL 适配 13 14<br />
* 关系数据库 SQLServer 适配 2017 2019<br />
* 缓存数据库 Redis 适配 6.X 最低 5.X<br />
* 分布式注册中心 Alibaba Nacos 采用2.X 基于GRPC通信高性能<br />
* 分布式配置中心 Alibaba Nacos 采用2.X 基于GRPC通信高性能<br />
* 服务网关 Spring Cloud Gateway 响应式高性能网关<br />
* 负载均衡 Spring Cloud Loadbalancer 负载均衡处理<br />
* RPC远程调用 Apache Dubbo 原生态使用体验高性能<br />
* 分布式限流熔断 Alibaba Sentinel 无侵入高扩展<br />
* 分布式事务 Alibaba Seata 无侵入高扩展 支持 四种模式<br />
* 分布式消息队列 Apache Kafka 高性能高速度<br />
* 分布式消息队列 Apache RocketMQ 高可用功能多样<br />
* 分布式消息队列 RabbitMQ 支持各种扩展插件功能多样性<br />
* 分布式搜索引擎 ElasticSearch 业界知名<br />
* 分布式链路追踪 Apache SkyWalking 链路追踪网格分析度量聚合可视化<br />
* 分布式日志中心 ELK 业界成熟解决方案<br />
* 分布式监控 PrometheusGrafana 全方位性能监控<br />
* 其余与 Vue 版本一致<br />
</p>
<p><b>当前版本:</b> <span>v2.6.0</span></p>
<p>
<el-tag type="danger">&yen;免费开源</el-tag>
</p>
<p>
<el-button type="primary" icon="Cloudy" plain @click="goTarget('https://gitee.com/dromara/RuoYi-Cloud-Plus')">访问码云</el-button>
<el-button type="primary" icon="Cloudy" plain @click="goTarget('https://github.com/dromara/RuoYi-Cloud-Plus')">访问GitHub</el-button>
<el-button type="primary" icon="Cloudy" plain @click="goTarget('https://plus-doc.dromara.org/#/ruoyi-cloud-plus/changlog')"
>更新日志</el-button
>
</p>
</el-col>
</el-row>
<el-divider />
<div class="welcome-wrapper">
<h1 class="welcome-title">欢迎使用智能防御机管理系统</h1>
<p class="welcome-desc">智能防御 · 安全守护</p>
</div>
</div>
</template>
<script setup name="Index" lang="ts">
const goTarget = (url: string) => {
window.open(url, '__blank');
};
</script>
<style lang="scss" scoped>
.home {
blockquote {
padding: 10px 20px;
margin: 0 0 20px;
font-size: 17.5px;
border-left: 5px solid #eee;
}
hr {
margin-top: 20px;
margin-bottom: 20px;
border: 0;
border-top: 1px solid #eee;
}
.col-item {
margin-bottom: 20px;
display: flex;
align-items: center;
justify-content: center;
min-height: calc(100vh - 120px);
}
ul {
padding: 0;
margin: 0;
.welcome-wrapper {
text-align: center;
.welcome-title {
font-size: 32px;
font-weight: 600;
color: #303133;
margin-bottom: 16px;
letter-spacing: 2px;
}
font-family: 'open sans', 'Helvetica Neue', Helvetica, Arial, sans-serif;
font-size: 13px;
color: #676a6c;
overflow-x: hidden;
ul {
list-style-type: none;
}
h4 {
margin-top: 0px;
}
h2 {
margin-top: 10px;
font-size: 26px;
font-weight: 100;
}
p {
margin-top: 10px;
b {
font-weight: 700;
}
}
.update-log {
ol {
display: block;
list-style-type: decimal;
margin-block-start: 1em;
margin-block-end: 1em;
margin-inline-start: 0;
margin-inline-end: 0;
padding-inline-start: 40px;
}
.welcome-desc {
font-size: 16px;
color: #909399;
letter-spacing: 4px;
}
}
</style>

View File

@@ -44,23 +44,23 @@
</div>
</el-form-item>
<el-checkbox v-model="loginForm.rememberMe" style="margin: 0 0 25px 0">{{ proxy.$t('login.rememberPassword') }}</el-checkbox>
<el-form-item style="float: right">
<el-button circle :title="proxy.$t('login.social.wechat')" @click="doSocialLogin('wechat')">
<svg-icon icon-class="wechat" />
</el-button>
<el-button circle :title="proxy.$t('login.social.maxkey')" @click="doSocialLogin('maxkey')">
<svg-icon icon-class="maxkey" />
</el-button>
<el-button circle :title="proxy.$t('login.social.topiam')" @click="doSocialLogin('topiam')">
<svg-icon icon-class="topiam" />
</el-button>
<el-button circle :title="proxy.$t('login.social.gitee')" @click="doSocialLogin('gitee')">
<svg-icon icon-class="gitee" />
</el-button>
<el-button circle :title="proxy.$t('login.social.github')" @click="doSocialLogin('github')">
<svg-icon icon-class="github" />
</el-button>
</el-form-item>
<!-- <el-form-item style="float: right">-->
<!-- <el-button circle :title="proxy.$t('login.social.wechat')" @click="doSocialLogin('wechat')">-->
<!-- <svg-icon icon-class="wechat" />-->
<!-- </el-button>-->
<!-- <el-button circle :title="proxy.$t('login.social.maxkey')" @click="doSocialLogin('maxkey')">-->
<!-- <svg-icon icon-class="maxkey" />-->
<!-- </el-button>-->
<!-- <el-button circle :title="proxy.$t('login.social.topiam')" @click="doSocialLogin('topiam')">-->
<!-- <svg-icon icon-class="topiam" />-->
<!-- </el-button>-->
<!-- <el-button circle :title="proxy.$t('login.social.gitee')" @click="doSocialLogin('gitee')">-->
<!-- <svg-icon icon-class="gitee" />-->
<!-- </el-button>-->
<!-- <el-button circle :title="proxy.$t('login.social.github')" @click="doSocialLogin('github')">-->
<!-- <svg-icon icon-class="github" />-->
<!-- </el-button>-->
<!-- </el-form-item>-->
<el-form-item style="width: 100%">
<el-button :loading="loading" size="large" type="primary" style="width: 100%" @click.prevent="handleLogin">
<span v-if="!loading">{{ proxy.$t('login.login') }}</span>
@@ -73,7 +73,7 @@
</el-form>
<!-- 底部 -->
<div class="el-login-footer">
<span>Copyright © 2018-2026 疯狂的狮子Li All Rights Reserved.</span>
<!-- <span>Copyright © 2018-2026 疯狂的狮子Li All Rights Reserved.</span>-->
</div>
</div>
</template>
@@ -96,8 +96,10 @@ const { t } = useI18n();
const loginForm = ref<LoginData>({
tenantId: '000000',
username: 'admin',
password: 'admin123',
username: '',
// username: 'admin',
// password: 'admin123',
password: '',
rememberMe: false,
code: '',
uuid: ''