Files
erp_sb/ruoyi-ui/src/views/monitor/account/index.vue
zhangzijienbplus 07e34c35c8 feat(electron): 实现系统托盘和关闭行为配置功能
- 添加系统托盘创建和销毁逻辑- 实现窗口关闭行为配置(退出/最小化/托盘)
- 添加配置文件读写功能
- 实现下载取消和清理功能
- 添加待更新文件检查机制
- 优化文件下载进度和错误处理
- 添加自动更新配置选项- 实现平滑滚动动画效果
- 添加试用期过期类型检查
-优化VIP状态刷新逻辑
2025-10-17 14:17:47 +08:00

728 lines
25 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="账号名称" prop="accountName">
<el-input
v-model="queryParams.accountName"
placeholder="请输入账号名称"
clearable
size="small"
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="用户名" prop="username">
<el-input
v-model="queryParams.username"
placeholder="请输入用户名"
clearable
size="small"
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="账号状态" prop="status">
<el-select v-model="queryParams.status" placeholder="账号状态" clearable size="small">
<el-option
v-for="dict in statusOptions"
:key="dict.dictValue"
:label="dict.dictLabel"
:value="dict.dictValue"
/>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="primary"
icon="el-icon-plus"
size="mini"
@click="handleAdd"
v-hasPermi="['monitor:account:add']"
>新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="success"
icon="el-icon-edit"
size="mini"
:disabled="single"
@click="handleUpdate"
v-hasPermi="['monitor:account:edit']"
>修改</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="danger"
icon="el-icon-delete"
size="mini"
:disabled="multiple"
@click="handleDelete"
v-hasPermi="['monitor:account:remove']"
>删除</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="warning"
icon="el-icon-download"
size="mini"
@click="handleExport"
v-hasPermi="['monitor:account:export']"
>导出</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<el-table v-loading="loading" :data="accountList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="ID" align="center" prop="id" width="80" />
<el-table-column label="账号名称" align="center" prop="accountName" :show-overflow-tooltip="true" />
<el-table-column label="用户名" align="center" prop="username" :show-overflow-tooltip="true" />
<el-table-column label="状态" align="center" prop="status" width="80">
<template slot-scope="scope">
<dict-tag :options="statusOptions" :value="scope.row.status" />
</template>
</el-table-column>
<el-table-column label="账号类型" align="center" prop="accountType" width="90">
<template slot-scope="scope">
<el-tag :type="scope.row.accountType === 'paid' ? 'success' : 'warning'" size="small">
{{ scope.row.accountType === 'paid' ? '付费' : '试用' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="过期时间" align="center" prop="expireTime" width="130">
<template slot-scope="scope">
<el-tag
:type="getRemainingDays(scope.row.expireTime).type"
size="small"
>
{{ getRemainingDays(scope.row.expireTime).text }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="设备限制" align="center" prop="deviceLimit" width="100">
<template slot-scope="scope">
<span>{{ scope.row.deviceLimit || 3 }}</span>
</template>
</el-table-column>
<el-table-column label="创建时间" align="center" prop="createTime" width="180" />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="200">
<template slot-scope="scope">
<el-button
size="mini"
type="text"
icon="el-icon-edit"
@click="handleUpdate(scope.row)"
v-hasPermi="['monitor:account:edit']"
>修改</el-button>
<el-button
size="mini"
type="text"
icon="el-icon-mobile-phone"
@click="viewDevices(scope.row)"
>设备</el-button>
<el-button
size="mini"
type="text"
icon="el-icon-delete"
@click="handleDelete(scope.row)"
v-hasPermi="['monitor:account:remove']"
>删除</el-button>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total>0"
:total="total"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
@pagination="getList"
/>
<!-- 添加或修改对话框 -->
<el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
<el-form ref="form" :model="form" :rules="rules" label-width="100px">
<el-form-item label="账号名称" prop="accountName">
<el-input v-model="form.accountName" placeholder="请输入账号名称" />
</el-form-item>
<el-form-item label="用户名" prop="username">
<el-input v-model="form.username" placeholder="请输入用户名" />
</el-form-item>
<el-form-item label="密码" prop="password" v-if="!form.id">
<el-input v-model="form.password" type="password" placeholder="请输入密码" />
</el-form-item>
<el-form-item label="账号状态">
<el-radio-group v-model="form.status">
<el-radio
v-for="dict in statusOptions"
:key="dict.dictValue"
:label="dict.dictValue"
>{{dict.dictLabel}}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="过期时间">
<el-date-picker
v-model="form.expireTime"
type="datetime"
placeholder="选择过期时间"
value-format="yyyy-MM-dd HH:mm:ss"
style="width: 100%;"
></el-date-picker>
</el-form-item>
<el-form-item label="快速续费" v-if="form.id">
<el-select v-model="form.renewDays" placeholder="选择续费套餐(可选)" style="width: 100%;" clearable>
<el-option label="月付30天" :value="30"></el-option>
<el-option label="季付90天" :value="90"></el-option>
<el-option label="半年付180天" :value="180"></el-option>
<el-option label="年付365天" :value="365"></el-option>
</el-select>
<div v-if="form.renewDays" style="margin-top: 8px; color: #409EFF; font-size: 12px;">
<i class="el-icon-info"></i> 续费后到期时间{{ calculateNewExpireTime() }}
</div>
</el-form-item>
<el-form-item label="设备数量限制" prop="deviceLimit">
<el-input-number
v-model="form.deviceLimit"
:min="1"
:max="20"
placeholder="请输入设备数量限制"
style="width: 100%;"
></el-input-number>
<span style="color: #909399; font-size: 12px;">允许同时登录的设备数量默认3台</span>
</el-form-item>
<el-form-item label="功能权限" prop="permissions">
<div class="permission-config">
<el-checkbox-group v-model="selectedPermissions" @change="onPermissionChange">
<el-checkbox label="rakuten"><i class="el-icon-goods"></i> 日本乐天平台</el-checkbox>
<el-checkbox label="amazon"><i class="el-icon-shopping-cart-2"></i> 亚马逊平台</el-checkbox>
<el-checkbox label="zebra"><i class="el-icon-postcard"></i> 斑马平台</el-checkbox>
<el-checkbox label="shopee"><i class="el-icon-shopping-bag-2"></i> 虾皮购物平台</el-checkbox>
<el-checkbox label="toolbox"><i class="el-icon-box"></i> 工具箱功能</el-checkbox>
<el-checkbox label="dataCollection"><i class="el-icon-document-copy"></i> 数据采集功能</el-checkbox>
<el-checkbox label="priceCompare"><i class="el-icon-price-tag"></i> 1688比价功能</el-checkbox>
</el-checkbox-group>
</div>
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" type="textarea" placeholder="请输入备注"></el-input>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</el-dialog>
<!-- 设备列表对话框 -->
<el-dialog :title="'账号【' + currentAccountName + '】的设备列表'" :visible.sync="deviceListVisible" width="800px" append-to-body>
<el-table :data="deviceList" v-loading="deviceListLoading">
<el-table-column label="设备名称" prop="name" :show-overflow-tooltip="true" />
<el-table-column label="操作系统" prop="os" width="120" />
<el-table-column label="状态" prop="status" width="80" align="center">
<template slot-scope="scope">
<el-tag :type="scope.row.status === 'online' ? 'success' : 'info'" size="small">
{{ scope.row.status === 'online' ? '在线' : '离线' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="设备试用期" prop="trialExpireTime" width="130" align="center">
<template slot-scope="scope">
<el-tag
:type="getRemainingDays(scope.row.trialExpireTime).type"
size="small"
>
{{ getRemainingDays(scope.row.trialExpireTime).text }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="最近在线" prop="lastActiveAt" width="160" />
<el-table-column label="操作" align="center" width="100">
<template slot-scope="scope">
<el-button
size="mini"
type="text"
icon="el-icon-edit"
@click="editDeviceExpire(scope.row)"
>修改</el-button>
</template>
</el-table-column>
</el-table>
<div slot="footer" class="dialog-footer">
<el-button @click="deviceListVisible = false"> </el-button>
</div>
</el-dialog>
<!-- 修改设备过期时间对话框 -->
<el-dialog title="修改设备试用期" :visible.sync="deviceExpireDialogVisible" width="400px" append-to-body>
<el-form label-width="120px">
<el-form-item label="设备名称">
<el-input v-model="currentDevice.name" disabled />
</el-form-item>
<el-form-item label="试用期过期时间">
<el-date-picker
v-model="currentDevice.trialExpireTime"
type="datetime"
placeholder="选择过期时间"
value-format="yyyy-MM-dd HH:mm:ss"
style="width: 100%;"
></el-date-picker>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitDeviceExpire"> </el-button>
<el-button @click="deviceExpireDialogVisible = false"> </el-button>
</div>
</el-dialog>
<!-- 注册对话框 -->
<el-dialog title="客户端账号注册" :visible.sync="registerOpen" width="400px" append-to-body>
<el-form ref="registerForm" :model="registerForm" :rules="registerRules" label-width="80px">
<el-form-item label="用户名" prop="username">
<el-input v-model="registerForm.username" placeholder="请输入用户名" @blur="checkUsername" />
<span v-if="usernameChecking" class="checking">检查中...</span>
<span v-else-if="usernameAvailable === false" class="error">用户名已存在</span>
<span v-else-if="usernameAvailable === true" class="success">用户名可用</span>
</el-form-item>
<el-form-item label="密码" prop="password">
<el-input v-model="registerForm.password" type="password" placeholder="请输入密码" />
</el-form-item>
<el-form-item label="确认密码" prop="confirmPassword">
<el-input v-model="registerForm.confirmPassword" type="password" placeholder="请再次输入密码" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitRegister" :loading="registerLoading"> </el-button>
<el-button @click="cancelRegister"> </el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { listAccount, getAccount, delAccount, addAccount, updateAccount, registerAccount, checkUsername, renewAccount, getDeviceList, updateDeviceExpire } from "@/api/monitor/account";
export default {
name: "Account",
data() {
return {
// 遮罩层
loading: true,
// 选中数组
ids: [],
// 非单个禁用
single: true,
// 非多个禁用
multiple: true,
// 显示搜索条件
showSearch: true,
// 总条数
total: 0,
// 账号表格数据
accountList: [],
// 弹出层标题
title: "",
// 选中的权限列表
selectedPermissions: [],
// 是否显示弹出层
open: false,
// 注册对话框
registerOpen: false,
registerLoading: false,
usernameChecking: false,
usernameAvailable: null,
// 设备列表
deviceListVisible: false,
deviceListLoading: false,
deviceList: [],
currentAccountName: '',
// 设备过期时间编辑
deviceExpireDialogVisible: false,
currentDevice: {},
// 状态数据字典
statusOptions: [
{ dictLabel: "正常", dictValue: "0" },
{ dictLabel: "停用", dictValue: "1" }
],
// 查询参数
queryParams: {
pageNum: 1,
pageSize: 10,
accountName: null,
username: null,
status: null
},
// 表单参数
form: {},
// 注册表单
registerForm: {
username: null,
password: null,
confirmPassword: null
},
// 表单校验
rules: {
accountName: [
{ required: true, message: "账号名称不能为空", trigger: "blur" }
],
username: [
{ required: true, message: "用户名不能为空", trigger: "blur" }
],
password: [
{ required: true, message: "密码不能为空", trigger: "blur" },
{ min: 6, message: "密码长度不能少于6位", trigger: "blur" }
]
},
// 注册表单校验
registerRules: {
username: [
{ required: true, message: "用户名不能为空", trigger: "blur" }
],
password: [
{ required: true, message: "密码不能为空", trigger: "blur" },
{ min: 6, message: "密码长度不能少于6位", trigger: "blur" }
],
confirmPassword: [
{ required: true, message: "确认密码不能为空", trigger: "blur" },
{ validator: this.validateConfirmPassword, trigger: "blur" }
]
}
};
},
created() {
this.getList();
},
methods: {
/** 查询账号列表 */
getList() {
this.loading = true;
listAccount(this.queryParams).then(response => {
this.accountList = response.rows;
this.total = response.total;
this.loading = false;
});
},
// 表单重置
reset() {
this.form = {
id: null,
accountName: null,
username: null,
password: null,
status: "0",
accountType: "trial",
expireTime: null,
renewDays: null,
deviceLimit: 3,
remark: null,
permissions: null
};
this.resetForm("form");
this.selectedPermissions = [];
},
// 搜索按钮操作
handleQuery() {
this.queryParams.pageNum = 1;
this.getList();
},
// 重置按钮操作
resetQuery() {
this.resetForm("queryForm");
this.handleQuery();
},
// 多选框选中数据
handleSelectionChange(selection) {
this.ids = selection.map(item => item.id)
this.single = selection.length!==1
this.multiple = !selection.length
},
/** 新增按钮操作 */
handleAdd() {
this.reset();
this.open = true;
this.title = "添加客户端账号";
},
/** 修改按钮操作 */
handleUpdate(row) {
this.reset();
const id = row.id || this.ids[0]
getAccount(id).then(response => {
this.form = response.data;
this.selectedPermissions = this.parsePermissions(this.form.permissions);
this.open = true;
this.title = "修改客户端账号";
});
},
/** 提交按钮 */
submitForm() {
this.$refs["form"].validate(valid => {
if (valid) {
if (this.form.id != null) {
// 如果选择了续费,先执行续费
const promises = [];
if (this.form.renewDays) {
promises.push(renewAccount({
accountId: this.form.id,
days: this.form.renewDays
}));
}
// 执行更新
promises.push(updateAccount(this.form));
Promise.all(promises).then(() => {
const msg = this.form.renewDays ? "修改并续费成功" : "修改成功";
this.$modal.msgSuccess(msg);
this.open = false;
this.getList();
}).catch(error => {
this.$modal.msgError('操作失败: ' + (error.message || '未知错误'));
});
} else {
addAccount(this.form).then(response => {
this.$modal.msgSuccess("新增成功");
this.open = false;
this.getList();
});
}
}
});
},
/** 删除按钮操作 */
handleDelete(row) {
const ids = row.id || this.ids;
this.$modal.confirm('是否确认删除账号编号为"' + ids + '"的数据项?').then(function() {
return delAccount(ids);
}).then(() => {
this.getList();
this.$modal.msgSuccess("删除成功");
}).catch(() => {});
},
/** 导出按钮操作 */
handleExport() {
this.download('monitor/account/export', {
...this.queryParams
}, `客户端账号_${new Date().getTime()}.xlsx`)
},
/** 取消按钮 */
cancel() {
this.open = false;
this.reset();
},
/** 权限配置变化 */
onPermissionChange(value) {
const permissions = {};
value.forEach(permission => {
permissions[permission] = true;
});
this.form.permissions = JSON.stringify(permissions);
},
/** 解析权限配置 */
parsePermissions(permissionsJson) {
if (!permissionsJson) {
return [];
}
try {
const permissions = JSON.parse(permissionsJson);
return Object.keys(permissions).filter(key => permissions[key] === true);
} catch (e) {
console.error('解析权限配置失败:', e);
return [];
}
},
/** 打开注册对话框 */
handleRegister() {
this.resetRegisterForm();
this.registerOpen = true;
},
/** 重置注册表单 */
resetRegisterForm() {
this.registerForm = {
username: null,
password: null,
confirmPassword: null
};
this.resetForm("registerForm");
this.usernameAvailable = null;
},
/** 检查用户名是否可用 */
async checkUsername() {
if (!this.registerForm.username) return;
this.usernameChecking = true;
try {
const response = await checkUsername(this.registerForm.username);
this.usernameAvailable = response.data;
} catch (error) {
this.usernameAvailable = false;
}
this.usernameChecking = false;
},
/** 确认密码校验 */
validateConfirmPassword(rule, value, callback) {
if (value !== this.registerForm.password) {
callback(new Error('两次输入密码不一致'));
} else {
callback();
}
},
/** 提交注册 */
submitRegister() {
this.$refs["registerForm"].validate(valid => {
if (valid && this.usernameAvailable === true) {
this.registerLoading = true;
const data = {
accountName: this.registerForm.username, // 使用用户名作为账号名称
username: this.registerForm.username,
password: this.registerForm.password,
status: "0"
};
registerAccount(data).then(response => {
this.$modal.msgSuccess("注册成功");
this.registerOpen = false;
this.getList();
}).finally(() => {
this.registerLoading = false;
});
}
});
},
/** 取消注册 */
cancelRegister() {
this.registerOpen = false;
this.resetRegisterForm();
},
/** 查看设备列表 */
async viewDevices(row) {
this.currentAccountName = row.accountName || row.username;
this.deviceListVisible = true;
this.deviceListLoading = true;
try {
const response = await getDeviceList(row.username);
this.deviceList = response.data || [];
} catch (error) {
this.$modal.msgError('获取设备列表失败');
this.deviceList = [];
} finally {
this.deviceListLoading = false;
}
},
/** 编辑设备过期时间 */
editDeviceExpire(row) {
this.currentDevice = { ...row };
this.deviceExpireDialogVisible = true;
},
/** 提交设备过期时间修改 */
async submitDeviceExpire() {
try {
await updateDeviceExpire({
deviceId: this.currentDevice.deviceId,
username: this.currentDevice.username,
trialExpireTime: this.currentDevice.trialExpireTime
});
this.$modal.msgSuccess('修改成功');
this.deviceExpireDialogVisible = false;
// 刷新设备列表
const response = await getDeviceList(this.currentDevice.username);
this.deviceList = response.data || [];
} catch (error) {
this.$modal.msgError('修改失败: ' + (error.message || '未知错误'));
}
},
/** 计算续费后的新到期时间 */
calculateNewExpireTime() {
if (!this.form.renewDays) return '';
let baseDate;
if (this.form.expireTime && new Date(this.form.expireTime) > new Date()) {
// 未过期,从到期时间延长
baseDate = new Date(this.form.expireTime);
} else {
// 已过期或无到期时间,从当前时间开始
baseDate = new Date();
}
baseDate.setDate(baseDate.getDate() + this.form.renewDays);
return baseDate.toLocaleString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
});
},
/** 获取剩余天数 */
getRemainingDays(expireTime) {
if (!expireTime) {
return { text: '未设置', type: 'info' };
}
const now = new Date();
const expireDate = new Date(expireTime);
const diffTime = expireDate - now;
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
if (diffDays < 0) {
return { text: '已过期', type: 'danger' };
} else if (diffDays <= 3) {
return { text: `剩余 ${diffDays}`, type: 'warning' };
} else {
return { text: `剩余 ${diffDays}`, type: 'success' };
}
}
}
};
</script>
<style scoped>
.permission-config {
padding: 15px;
border: 1px solid #DCDFE6;
border-radius: 4px;
background: linear-gradient(135deg, #f5f7fa 0%, #fafbfc 100%);
}
.permission-config .el-checkbox-group {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 12px 20px;
}
.permission-config .el-checkbox {
margin: 0;
padding: 8px 12px;
border-radius: 4px;
transition: all 0.3s;
background-color: white;
}
.permission-config .el-checkbox:hover {
background-color: #ecf5ff;
transform: translateX(2px);
}
.permission-config .el-checkbox i {
margin-right: 6px;
color: #409EFF;
}
.checking {
color: #909399;
font-size: 12px;
}
.error {
color: #f56c6c;
font-size: 12px;
}
.success {
color: #67c23a;
font-size: 12px;
}
</style>