feat: 项目初始化,未病局

This commit is contained in:
fuenmao
2025-09-20 10:36:39 +08:00
commit 39f70340a0
421 changed files with 48180 additions and 0 deletions
@@ -0,0 +1,93 @@
<!--
* @Description:连接配置
* @Author: huguodong
* @Date: 2024-11-07 13:55:56
!-->
<template>
<el-form ref="baseFormRef" :rules="rules" :model="baseFormProps" label-width="auto" label-suffix=" :" label-position="top">
<s-form-item label="MQTT服务端地址" prop="MQTT_PARAM_URL">
<s-input v-model="baseFormProps.MQTT_PARAM_URL"></s-input>
</s-form-item>
<s-form-item label="MQTT用户名" prop="MQTT_PARAM_USERNAME">
<s-input v-model="baseFormProps.MQTT_PARAM_USERNAME"></s-input>
</s-form-item>
<s-form-item label="MQTT密码" prop="MQTT_PARAM_PASSWORD">
<s-input v-model="baseFormProps.MQTT_PARAM_PASSWORD"></s-input>
</s-form-item>
<el-form-item>
<el-button type="primary" :loading="submitLoading" @click="onSubmit()">保存</el-button>
<el-button style="margin-left: 10px" @click="resetForm">重置</el-button>
</el-form-item>
</el-form>
</template>
<script setup lang="ts">
import { SysConfig, sysConfigApi } from "@/api";
import { FormInstance } from "element-plus";
const submitLoading = ref(false); //提交按钮loading
const baseFormRef = ref<FormInstance>();
import { SysConfigTypeEnum } from "@/enums";
/**
* @description: 连接配置参数
*/
const baseFormProps = reactive<SysConfig.MqttPolicyConfig>({
MQTT_PARAM_URL: "",
MQTT_PARAM_USERNAME: "",
MQTT_PARAM_PASSWORD: ""
});
//props定义
const props = defineProps({
baseConfig: {
type: Array as PropType<SysConfig.ConfigInfo[]>,
required: true,
default: () => []
}
});
//监听loginPolicy变化
watch(
() => props.baseConfig,
(newVal: SysConfig.ConfigInfo[]) => {
//重新赋值
newVal.forEach((item: SysConfig.ConfigInfo) => {
(baseFormProps[item.configKey as keyof SysConfig.MqttPolicyConfig] as string) = item.configValue;
});
}
);
// 表单验证规则
const rules = reactive({
MQTT_PARAM_URL: [{ required: true, message: "请输入MQTT服务端地址", trigger: "blur" }],
MQTT_PARAM_USERNAME: [{ required: true, message: "请输入MQTT用户名", trigger: "blur" }],
MQTT_PARAM_PASSWORD: [{ required: true, message: "请输入MQTT密码", trigger: "blur" }]
});
/** 提交表单 */
function onSubmit() {
baseFormRef.value?.validate(async valid => {
if (!valid) return; //表单验证失败
submitLoading.value = true;
//组装参数
const param: SysConfig.ConfigInfo[] = Object.entries(baseFormProps).map(item => {
return {
id: 0,
category: SysConfigTypeEnum.MQTT_POLICY,
configKey: item[0],
configValue: typeof item[1] === "object" ? JSON.stringify(item[1]) : String(item[1])
};
});
//提交数据
sysConfigApi.configEditForm(param).finally(() => {
submitLoading.value = false;
});
});
}
/** 重置表单 */
function resetForm() {
baseFormRef.value?.resetFields();
}
</script>
<style lang="scss" scoped></style>
@@ -0,0 +1,46 @@
<!--
* @Description: 安全配置
* @Author: huguodong
* @Date: 2024-01-31 15:33:50
!-->
<template>
<el-tabs v-model="activeName" class="demo-tabs" tab-position="left">
<el-tab-pane label="连接配置" name="base" class="pl-5"><BaseForm :base-config="baseConfig" /></el-tab-pane>
</el-tabs>
</template>
<script setup lang="ts">
import { SysConfig } from "@/api";
import BaseForm from "./baseForm.vue";
import { SysConfigTypeEnum } from "@/enums";
const activeName = ref("base");
const baseConfig = ref<SysConfig.ConfigInfo[]>([]);
//props定义
const props = defineProps({
sysConfigs: {
type: Array as PropType<SysConfig.ConfigInfo[]>,
required: true,
default: () => []
}
});
//监听sysConfigs变化
watch(
() => props.sysConfigs,
(newVal: SysConfig.ConfigInfo[]) => {
// 配置赋值
baseConfig.value = newVal.filter(item => item.category == SysConfigTypeEnum.MQTT_POLICY);
}
);
</script>
<style lang="scss" scoped>
.demo-tabs > .el-tabs__content {
padding: 32px;
font-size: 132px;
font-weight: 600;
color: #6b778c;
}
</style>
@@ -0,0 +1,104 @@
<!--
* @Description: 配置表单
* @Author: huguodong
* @Date: 2024-02-02 10:49:28
!-->
<template>
<form-container v-model="visible" :title="`${configProps.opt}配置`" form-size="600px">
<el-form
ref="configFormRef"
:rules="rules"
:disabled="configProps.disabled"
:model="configProps.record"
:hide-required-asterisk="configProps.disabled"
label-width="auto"
label-suffix=" :"
>
<s-form-item label="配置键" prop="configKey">
<s-input v-model="configProps.record.configKey"></s-input>
</s-form-item>
<s-form-item label="配置值" prop="configValue">
<s-input v-model="configProps.record.configValue"></s-input>
</s-form-item>
<s-form-item label="备注" prop="remark">
<s-input v-model="configProps.record.remark"></s-input>
</s-form-item>
<s-form-item label="排序" prop="sortCode">
<el-slider v-model="configProps.record.sortCode" show-input :min="1" />
</s-form-item>
</el-form>
<template #footer>
<el-button @click="onClose"> 取消 </el-button>
<el-button v-show="!configProps.disabled" type="primary" @click="handleSubmit"> 确定 </el-button>
</template>
</form-container>
</template>
<script setup lang="ts">
import { SysConfig, sysConfigApi } from "@/api";
import { required } from "@/utils/formRules";
import { FormOptEnum } from "@/enums";
import { FormInstance } from "element-plus";
const visible = ref(false); //是否显示表单
// 表单参数接口
interface ConfigProps extends FormProps.Base<SysConfig.ConfigInfo> {}
// 表单参数
const configProps = reactive<ConfigProps>({
opt: FormOptEnum.ADD,
record: {},
disabled: false
});
// 表单验证规则
const rules = reactive({
configKey: [required("请输入配置键")],
configValue: [required("请输入配置值")],
sortCode: [required("请输入排序")]
});
/**
* 打开表单
* @param props 表单参数
*/
function onOpen(props: ConfigProps) {
Object.assign(configProps, props); //合并参数
if (props.opt == FormOptEnum.ADD) {
//如果是新增,设置默认值
configProps.record.sortCode = 99;
}
visible.value = true; //显示表单
}
// 提交数据(新增/编辑)
const configFormRef = ref<FormInstance>();
/** 提交表单 */
async function handleSubmit() {
configFormRef.value?.validate(async valid => {
if (!valid) return; //表单验证失败
//提交表单
await sysConfigApi
.submitForm(configProps.record, configProps.record.id != undefined)
.then(() => {
configProps.successful!(); //调用父组件的successful方法
})
.finally(() => {
onClose();
});
});
}
/** 关闭表单*/
function onClose() {
visible.value = false;
}
// 暴露给父组件的方法
defineExpose({
onOpen
});
</script>
<style lang="scss" scoped></style>
@@ -0,0 +1,80 @@
<!--
* @Description: 其他配置
* @Author: huguodong
* @Date: 2024-02-02 08:43:44
!-->
<template>
<div class="table-box min-h-300px">
<ProTable ref="proTable" class="table" :columns="columns" :request-api="sysConfigApi.page">
<!-- 表格 header 按钮 -->
<template #tableHeader>
<s-button :suffix="title" @click="onOpen(FormOptEnum.ADD)" />
</template>
<!-- 操作 -->
<template #operation="scope">
<s-button link :opt="FormOptEnum.EDIT" @click="onOpen(FormOptEnum.EDIT, scope.row)" />
<s-button link :opt="FormOptEnum.DELETE" @click="onDelete(scope.row.id, `删除【${scope.row.configKey}】配置`)" />
</template>
</ProTable>
<!-- 新增/编辑表单 -->
<Form ref="formRef" />
</div>
</template>
<script setup lang="ts">
import { sysConfigApi, SysConfig } from "@/api";
import { ColumnProps, ProTableInstance } from "@/components/ProTable/interface";
import { useHandleData } from "@/hooks/useHandleData";
import { FormOptEnum } from "@/enums";
import Form from "./form.vue";
const title = "配置";
// 获取 ProTable 元素,调用其获取刷新数据方法(还能获取到当前查询参数,方便导出携带参数)
const proTable = ref<ProTableInstance>();
// 表格配置项
const columns: ColumnProps<SysConfig.ConfigInfo>[] = [
{ type: "selection", fixed: "left", width: 50 },
{ prop: "configKey", label: "配置键" },
{ prop: "configValue", label: "配置值" },
{ prop: "remark", label: "备注" },
{ prop: "sortCode", label: "排序", width: 80 },
{ prop: "operation", label: "操作", fixed: "right", width: 150 }
];
// 表单引用
const formRef = ref<InstanceType<typeof Form> | null>(null);
/**
* 打开表单
* @param opt 操作类型
* @param record 记录
*/
function onOpen(opt: FormOptEnum, record: {} | SysConfig.ConfigInfo = {}) {
formRef.value?.onOpen({
opt: opt,
record: record,
successful: RefreshTable //刷新子表格
});
}
/**
* 删除
* @param ids id数组
*/
async function onDelete(id: number | string, msg: string) {
// 二次确认 => 请求api => 刷新表格
await useHandleData(sysConfigApi.delete, { id }, msg);
RefreshTable();
}
/**
* 刷新表格
*/
function RefreshTable() {
proTable.value?.refresh(); //刷新主表格
}
</script>
<style lang="scss" scoped></style>
@@ -0,0 +1,53 @@
<!--
* @Description: 安全配置
* @Author: huguodong
* @Date: 2024-01-31 15:33:50
!-->
<template>
<el-tabs v-model="activeName" class="demo-tabs" tab-position="left">
<el-tab-pane label="登录策略" name="login" class="pl-5"><LoginForm :login-policy="loginPolicy" /></el-tab-pane>
<el-tab-pane label="密码策略" name="pwd" class="pl-5"><PwdForm :pwd-policy="pwdPolicy" /></el-tab-pane>
</el-tabs>
</template>
<script setup lang="ts">
import { SysConfig } from "@/api";
import LoginForm from "./loginForm.vue";
import PwdForm from "./pwdForm.vue";
import { SysConfigTypeEnum } from "@/enums";
const activeName = ref("login");
const loginPolicy = ref<SysConfig.ConfigInfo[]>([]);
const pwdPolicy = ref<SysConfig.ConfigInfo[]>([]);
//props定义
const props = defineProps({
sysConfigs: {
type: Array as PropType<SysConfig.ConfigInfo[]>,
required: true,
default: () => []
}
});
//监听sysConfigs变化
watch(
() => props.sysConfigs,
(newVal: SysConfig.ConfigInfo[]) => {
// 配置赋值
loginPolicy.value = newVal.filter(item => item.category == SysConfigTypeEnum.LOGIN_POLICY);
pwdPolicy.value = newVal.filter(item => item.category == SysConfigTypeEnum.PWD_POLICY);
}
);
</script>
<style lang="scss" scoped>
.demo-tabs > .el-tabs__content {
padding: 32px;
font-size: 132px;
font-weight: 600;
color: #6b778c;
}
:deep(.el-tabs__nav-scroll) {
height: 500px;
}
</style>
@@ -0,0 +1,119 @@
<!--
* @Description: 登录策略
* @Author: huguodong
* @Date: 2024-01-31 16:25:48
!-->
<template>
<el-form ref="loginFormRef" :rules="rules" :model="loginFormProps" label-width="auto" label-suffix=" :" label-position="top">
<s-form-item label="单用户登录开关" prop="LOGIN_SINGLE_OPEN">
<s-radio-group v-model="loginFormProps.LOGIN_SINGLE_OPEN" :options="yesNoOptions" />
</s-form-item>
<s-form-item label="允许登录错误次数" prop="LOGIN_ERROR_COUNT">
<el-input-number v-model.number="loginFormProps.LOGIN_ERROR_COUNT" :min="1" :max="999" />
</s-form-item>
<s-form-item label="登录错误重置时间(分)" prop="LOGIN_ERROR_RESET_TIME">
<el-input-number v-model.number="loginFormProps.LOGIN_ERROR_RESET_TIME" :min="1" :max="999" />
</s-form-item>
<s-form-item label="登录错误次数过多账号锁定时长:(分)" prop="LOGIN_ERROR_LOCK">
<el-input-number v-model.number="loginFormProps.LOGIN_ERROR_LOCK" :min="1" :max="999" />
</s-form-item>
<s-form-item label="登录验证码开关" prop="LOGIN_CAPTCHA_OPEN">
<s-radio-group v-model="loginFormProps.LOGIN_CAPTCHA_OPEN" :options="yesNoOptions" />
</s-form-item>
<s-form-item label="登录验证码类型" prop="LOGIN_CAPTCHA_TYPE">
<s-radio-group v-model="loginFormProps.LOGIN_CAPTCHA_TYPE" :options="captchaOptions" />
</s-form-item>
<el-form-item>
<el-button type="primary" :loading="submitLoading" @click="onSubmit()">保存</el-button>
<el-button style="margin-left: 10px" @click="resetForm">重置</el-button>
</el-form-item>
</el-form>
</template>
<script setup lang="ts">
import { SysConfig, sysConfigApi } from "@/api";
import { FormInstance } from "element-plus";
import { useDictStore } from "@/stores/modules";
import { SysConfigTypeEnum, SysDictEnum } from "@/enums";
const dictStore = useDictStore(); //字典仓库
// 字典类型选项
const yesNoOptions = dictStore.getDictList(SysDictEnum.YES_NO);
const captchaOptions = dictStore.getDictList(SysDictEnum.CAPTCHA_TYPE);
const submitLoading = ref(false); //提交按钮loading
const loginFormRef = ref<FormInstance>();
/** 登录策略参数 */
const loginFormProps = reactive<SysConfig.LoginPolicyConfig>({
LOGIN_SINGLE_OPEN: yesNoOptions[0].value,
LOGIN_CAPTCHA_OPEN: yesNoOptions[0].value,
LOGIN_CAPTCHA_TYPE: captchaOptions[0].value,
LOGIN_ERROR_COUNT: 5,
LOGIN_ERROR_RESET_TIME: 5,
LOGIN_ERROR_LOCK: 30
});
//props定义
const props = defineProps({
loginPolicy: {
type: Array as PropType<SysConfig.ConfigInfo[]>,
required: true,
default: () => []
}
});
//监听loginPolicy变化
watch(
() => props.loginPolicy,
(newVal: SysConfig.ConfigInfo[]) => {
//重新赋值
newVal.forEach((item: SysConfig.ConfigInfo) => {
let prop = loginFormProps[item.configKey as keyof SysConfig.LoginPolicyConfig];
//如果是number类型就转为number
if (typeof prop === "number") {
(loginFormProps[item.configKey as keyof SysConfig.LoginPolicyConfig] as number) = Number(item.configValue);
} else {
(loginFormProps[item.configKey as keyof SysConfig.LoginPolicyConfig] as string) = item.configValue;
}
});
}
);
// 表单验证规则
const rules = reactive({
LOGIN_ERROR_COUNT: [{ required: true, message: "请输入允许登录错误次数", trigger: "blur" }],
LOGIN_ERROR_RESET_TIME: [{ required: true, message: "请输入登录错误重置时间", trigger: "blur" }],
LOGIN_ERROR_LOCK: [{ required: true, message: "请输入登录错误次数过多账号锁定时长", trigger: "blur" }],
LOGIN_CAPTCHA_TYPE: [{ required: true, message: "请选择登录验证码类型", trigger: "blur" }],
LOGIN_SINGLE_OPEN: [{ required: true, message: "请选择单用户登录开关", trigger: "blur" }],
LOGIN_CAPTCHA_OPEN: [{ required: true, message: "请选择登录验证码开关", trigger: "blur" }]
});
/** 提交表单 */
function onSubmit() {
loginFormRef.value?.validate(async valid => {
if (!valid) return; //表单验证失败
submitLoading.value = true;
//组装参数
const param: SysConfig.ConfigInfo[] = Object.entries(loginFormProps).map(item => {
return {
id: 0,
category: SysConfigTypeEnum.LOGIN_POLICY,
configKey: item[0],
configValue: typeof item[1] === "object" ? JSON.stringify(item[1]) : String(item[1])
};
});
//提交数据
sysConfigApi.configEditForm(param).finally(() => {
submitLoading.value = false;
});
});
}
/** 重置表单 */
function resetForm() {
loginFormRef.value?.resetFields();
}
</script>
<style lang="scss" scoped></style>
@@ -0,0 +1,143 @@
<!--
* @Description: 密码策略
* @Author: huguodong
* @Date: 2024-01-31 16:25:33
!-->
<template>
<el-form ref="pwdFormRef" :rules="rules" :model="pwdFormProps" label-width="auto" label-suffix=" :" label-position="top">
<s-form-item label="系统默认密码" prop="PWD_DEFAULT_PASSWORD" class="w-50">
<s-input v-model="pwdFormProps.PWD_DEFAULT_PASSWORD"></s-input>
</s-form-item>
<s-form-item label="密码定期提醒更新" prop="PWD_REMIND">
<s-radio-group v-model="pwdFormProps.PWD_REMIND" :options="yesNoOptions" />
</s-form-item>
<s-form-item label="密码定期提醒更新时间(天)" prop="LOGIN_ERROR_LOCK">
<el-input-number v-model.number="pwdFormProps.PWD_REMIND_DAY" :min="1" :max="999" />
</s-form-item>
<s-form-item label="修改初始密码提醒" prop="PWD_UPDATE_DEFAULT">
<s-radio-group v-model="pwdFormProps.PWD_UPDATE_DEFAULT" :options="yesNoOptions" />
</s-form-item>
<s-form-item label="密码最小长度" prop="PWD_MIN_LENGTH">
<el-input-number v-model.number="pwdFormProps.PWD_MIN_LENGTH" :min="6" :max="20" />
</s-form-item>
<el-row :gutter="16">
<el-col :span="6">
<s-form-item label="是否含数字" prop="PWD_CONTAIN_NUM">
<s-radio-group v-model="pwdFormProps.PWD_CONTAIN_NUM" :options="yesNoOptions" />
</s-form-item>
</el-col>
<el-col :span="6">
<s-form-item label="是否包含小写字母" prop="PWD_CONTAIN_LOWER">
<s-radio-group v-model="pwdFormProps.PWD_CONTAIN_LOWER" :options="yesNoOptions" />
</s-form-item>
</el-col>
<el-col :span="6">
<s-form-item label="是否包含大写字母" prop="PWD_CONTAIN_UPPER">
<s-radio-group v-model="pwdFormProps.PWD_CONTAIN_UPPER" :options="yesNoOptions" />
</s-form-item>
</el-col>
<el-col :span="6">
<s-form-item label="是否包含特殊字符" prop="PWD_CONTAIN_CHARACTER">
<s-radio-group v-model="pwdFormProps.PWD_CONTAIN_CHARACTER" :options="yesNoOptions" />
</s-form-item>
</el-col>
</el-row>
<el-form-item>
<el-button type="primary" :loading="submitLoading" @click="onSubmit()">保存</el-button>
<el-button style="margin-left: 10px" @click="resetForm">重置</el-button>
</el-form-item>
</el-form>
</template>
<script setup lang="ts">
import { SysConfig, sysConfigApi } from "@/api";
import { FormInstance } from "element-plus";
import { useDictStore } from "@/stores/modules";
import { SysConfigTypeEnum, SysDictEnum } from "@/enums";
const dictStore = useDictStore(); //字典仓库
// 字典类型选项
const yesNoOptions = dictStore.getDictList(SysDictEnum.YES_NO);
const submitLoading = ref(false); //提交按钮loading
const pwdFormRef = ref<FormInstance>();
/** 登录策略参数 */
const pwdFormProps = reactive<SysConfig.PwdPolicyConfig>({
PWD_DEFAULT_PASSWORD: "",
PWD_REMIND: yesNoOptions[0].value,
PWD_REMIND_DAY: 30,
PWD_UPDATE_DEFAULT: yesNoOptions[0].value,
PWD_MIN_LENGTH: 6,
PWD_CONTAIN_NUM: yesNoOptions[0].value,
PWD_CONTAIN_LOWER: yesNoOptions[0].value,
PWD_CONTAIN_UPPER: yesNoOptions[0].value,
PWD_CONTAIN_CHARACTER: yesNoOptions[0].value
});
//props定义
const props = defineProps({
pwdPolicy: {
type: Array as PropType<SysConfig.ConfigInfo[]>,
required: true,
default: () => []
}
});
//监听pwdPolicy变化
watch(
() => props.pwdPolicy,
(newVal: SysConfig.ConfigInfo[]) => {
//重新赋值
newVal.forEach((item: SysConfig.ConfigInfo) => {
let prop = pwdFormProps[item.configKey as keyof SysConfig.PwdPolicyConfig];
//如果是number类型就转为number
if (typeof prop === "number") {
(pwdFormProps[item.configKey as keyof SysConfig.PwdPolicyConfig] as number) = Number(item.configValue);
} else {
(pwdFormProps[item.configKey as keyof SysConfig.PwdPolicyConfig] as string) = item.configValue;
}
});
}
);
// 表单验证规则
const rules = reactive({
PWD_DEFAULT_PASSWORD: [{ required: true, message: "请输入系统默认密码", trigger: "blur" }],
PWD_REMIND: [{ required: true, message: "请选择密码定期提醒更新", trigger: "blur" }],
PWD_REMIND_DAY: [{ required: true, message: "请输入密码定期提醒更新时间", trigger: "blur" }],
PWD_UPDATE_DEFAULT: [{ required: true, message: "请选择修改初始密码提醒", trigger: "blur" }],
PWD_MIN_LENGTH: [{ required: true, message: "请输入密码最小长度", trigger: "blur" }],
PWD_CONTAIN_NUM: [{ required: true, message: "请选择是否含数字", trigger: "blur" }],
PWD_CONTAIN_LOWER: [{ required: true, message: "请选择是否包含小写字母", trigger: "blur" }],
PWD_CONTAIN_UPPER: [{ required: true, message: "请选择是否包含大写字母", trigger: "blur" }],
PWD_CONTAIN_CHARACTER: [{ required: true, message: "请选择是否包含特殊字符", trigger: "blur" }]
});
/** 提交表单 */
function onSubmit() {
pwdFormRef.value?.validate(async valid => {
if (!valid) return; //表单验证失败
submitLoading.value = true;
//组装参数
const param: SysConfig.ConfigInfo[] = Object.entries(pwdFormProps).map(item => {
return {
id: 0,
category: SysConfigTypeEnum.LOGIN_POLICY,
configKey: item[0],
configValue: typeof item[1] === "object" ? JSON.stringify(item[1]) : String(item[1])
};
});
//提交数据
sysConfigApi.configEditForm(param).finally(() => {
submitLoading.value = false;
});
});
}
/** 重置表单 */
function resetForm() {
pwdFormRef.value?.resetFields();
}
</script>
<style lang="scss" scoped></style>
@@ -0,0 +1,307 @@
<!--
* @Description: 系统配置
* @Author: huguodong
* @Date: 2024-01-22 16:44:07
!-->
<template>
<div>
<el-form ref="sysConfigFormRef" :rules="rules" :model="sysBaseProps" label-width="auto" label-suffix=" :" label-position="top">
<el-row :gutter="16">
<el-col :span="8">
<s-form-item label="系统LOGO" prop="SYS_LOGO">
<UploadImg v-model:image-url="sysBaseProps.SYS_LOGO" width="135px" height="135px" :file-size="3" :auto-upload="false">
<template #empty>
<el-icon><Avatar /></el-icon>
<span>请上传LOGO</span>
</template>
</UploadImg>
</s-form-item>
</el-col>
<el-col :span="8">
<s-form-item label="系统ico文件" prop="SYS_ICO">
<UploadImg
v-model:image-url="sysBaseProps.SYS_ICO!"
width="135px"
height="135px"
:file-size="1"
:file-type="icoType"
:auto-upload="false"
>
<template #empty>
<el-icon><Picture /></el-icon>
<span>请上传.ICO文件</span>
</template>
</UploadImg>
</s-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="8">
<s-form-item label="系统名称" prop="SYS_NAME">
<s-input v-model="sysBaseProps.SYS_NAME"></s-input>
</s-form-item>
</el-col>
<el-col :span="8">
<s-form-item label="系统版本" prop="SYS_VERSION">
<s-input v-model="sysBaseProps.SYS_VERSION"></s-input>
</s-form-item>
</el-col>
<el-col :span="8">
<s-form-item label="默认快捷方式" prop="SYS_DEFAULT_WORKBENCH_DATA">
<MenuSelector
v-model:menu-value="sysBaseProps.SYS_DEFAULT_WORKBENCH_DATA!.shortcut"
multiple
:check-strictly="false"
:menu-tree-api="menuApi.menuTreeSelector"
/>
</s-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="8">
<s-form-item label="多租户配置" prop="SYS_TENANT_OPTIONS">
<s-radio-group v-model="sysBaseProps.SYS_TENANT_OPTIONS" :options="tenantOptions" button />
</s-form-item>
</el-col>
<el-col :span="8">
<s-form-item label="版权信息" prop="SYS_COPYRIGHT">
<s-input v-model="sysBaseProps.SYS_COPYRIGHT"></s-input>
</s-form-item>
</el-col>
<el-col :span="8">
<s-form-item label="版权链接URL" prop="SYS_COPYRIGHT_URL">
<s-input v-model="sysBaseProps.SYS_COPYRIGHT_URL"></s-input>
</s-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="8">
<s-form-item label="底部超链接" prop="SYS_FOOTER_LINKS">
<el-tag
v-for="tag in sysBaseProps.SYS_FOOTER_LINKS"
:key="tag.name"
class="mx-1"
closable
:disable-transitions="false"
@close="handleClose(tag.name)"
@click="linkDetail(tag)"
>
{{ tag.name }}
</el-tag>
<el-button class="ml-1" size="small" @click="showLinkModel()">添加</el-button>
</s-form-item>
</el-col>
<el-col :span="8">
<s-form-item label="网站开启访问(仅业务)" prop="SYS_WEB_STATUS">
<s-radio-group v-model="sysBaseProps.SYS_WEB_STATUS" :options="statusOptions" button />
</s-form-item>
</el-col>
<el-col :span="8">
<s-form-item label="网站关闭提示" prop="SYS_WEB_CLOSE_PROMPT">
<s-input v-model="sysBaseProps.SYS_WEB_CLOSE_PROMPT" :rows="3" type="textarea"></s-input>
</s-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="24">
<el-form-item>
<el-button type="primary" :loading="submitLoading" @click="onSubmit()">保存</el-button>
<el-button style="margin-left: 10px" @click="resetForm">重置</el-button>
</el-form-item>
</el-col>
</el-row>
</el-form>
<!-- 超链接弹窗 -->
<el-dialog v-model="dialogVisible" title="超链接信息" width="30%" show-close destroy-on-close>
<el-form ref="linkFormRef" :model="linkForm" :rules="linkRules" label-width="auto" label-suffix=" :" label-position="top">
<s-form-item label="超链接名称" prop="name">
<s-input v-model="linkForm.name"></s-input>
</s-form-item>
<s-form-item label="超链接地址" prop="url">
<s-input v-model="linkForm.url"></s-input>
</s-form-item>
<s-form-item label="排序" prop="sortCode">
<el-slider v-model="linkForm.sortCode" show-input :min="1" />
</s-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="saveLink">确定</el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts">
import { SysConfig, menuApi, sysConfigApi } from "@/api";
import { required } from "@/utils/formRules";
import { useDictStore } from "@/stores/modules";
import { SysDictEnum, SysConfigTypeEnum, SysBaseEnum, TenantEnum } from "@/enums";
import { FormInstance } from "element-plus";
const dictStore = useDictStore(); //字典仓库
// 字典类型选项
const statusOptions = dictStore.getDictList(SysDictEnum.COMMON_STATUS);
//多租户选项
const tenantOptions = dictStore.getDictList(SysDictEnum.TENANT_OPTIONS);
const icoType: File.ImageMimeType[] = ["image/x-icon"]; //ico文件类型
const submitLoading = ref(false); //提交按钮loading
const sysConfigFormRef = ref<FormInstance>(); //表单实例
/** 系统配置 */
const sysBaseProps = reactive<SysConfig.SysBaseConfig>({
SYS_NAME: "",
SYS_LOGO: "",
SYS_ICO: "",
SYS_VERSION: "",
SYS_COPYRIGHT: "",
SYS_COPYRIGHT_URL: "",
SYS_DEFAULT_WORKBENCH_DATA: { shortcut: [] },
SYS_WEB_STATUS: statusOptions[0].value,
SYS_WEB_CLOSE_PROMPT: "",
SYS_TENANT_OPTIONS: TenantEnum.CLOSE,
SYS_FOOTER_LINKS: []
});
//定义props
const props = defineProps({
sysConfigs: {
type: Array as PropType<SysConfig.ConfigInfo[]>,
required: true,
default: () => []
}
});
//监听sysConfigs变化
watch(
() => props.sysConfigs,
(newVal: SysConfig.ConfigInfo[]) => {
//重新赋值
newVal.forEach((item: SysConfig.ConfigInfo) => {
//如果是对象类型的属性就转成对象
if (item.configKey == SysBaseEnum.SYS_DEFAULT_WORKBENCH_DATA) {
const workBenchData: SysConfig.WorkBenchData = JSON.parse(item.configValue);
sysBaseProps.SYS_DEFAULT_WORKBENCH_DATA = workBenchData;
} else if (item.configKey == SysBaseEnum.SYS_FOOTER_LINKS) {
const footerLinks: SysConfig.FooterLinkProps[] = JSON.parse(item.configValue);
sysBaseProps.SYS_FOOTER_LINKS = footerLinks;
} else {
// 其他属性直接赋值
(sysBaseProps[item.configKey as keyof SysConfig.SysBaseConfig] as string) = item.configValue;
}
});
}
);
// 表单验证规则
const rules = reactive({
SYS_NAME: [required("请输入系统名称")],
SYS_LOGO: [required("请上传系统LOGO")],
SYS_ICO: [required("请上传系统ICO")],
SYS_VERSION: [required("请输入系统版本")],
SYS_COPYRIGHT: [required("请输入系统版权")],
SYS_COPYRIGHT_URL: [required("请输入系统版权链接")],
SYS_DEFAULT_WORKBENCH_DATA: [required("请输入系统默认工作台数据")],
SYS_WEB_STATUS: [required("请选择网站开启访问状态")],
SYS_WEB_CLOSE_PROMPT: [required("请输入网站关闭提示")]
});
const dialogVisible = ref(false); // 是否显示弹窗
// 超链接临时对象
const linkTmp = ref<SysConfig.FooterLinkProps>({
name: "",
url: "",
sortCode: 99
});
// 是否是添加超链接
const linkFormRef = ref<FormInstance>(); // 超链接表单实例
// 超链接表单参数
const linkForm = reactive<SysConfig.FooterLinkProps>({
name: "",
url: "",
sortCode: 99
});
// 表单验证规则
const linkRules = reactive({
name: [required("请输入超链接名称")],
url: [required("请输入超链接地址")],
sortCode: [required("请输入排序")]
});
/** 删除超链接 */
function handleClose(tag: string) {
sysBaseProps.SYS_FOOTER_LINKS = sysBaseProps.SYS_FOOTER_LINKS.filter(item => item.name != tag);
}
/** 打开超链接详情 */
function linkDetail(value: SysConfig.FooterLinkProps) {
linkTmp.value = value;
linkForm.name = value.name;
linkForm.url = value.url;
linkForm.sortCode = value.sortCode;
dialogVisible.value = true;
}
/** 显示超链接表单 */
function showLinkModel() {
dialogVisible.value = true;
}
/** 保存超链接 */
function saveLink() {
linkFormRef.value?.validate(async valid => {
if (!valid) return; //表单验证失败
//判断是编辑还是添加
if (linkTmp.value.name) {
//删除旧的值,添加新的值
sysBaseProps.SYS_FOOTER_LINKS = sysBaseProps.SYS_FOOTER_LINKS.filter(item => item.name != linkTmp.value.name);
}
sysBaseProps.SYS_FOOTER_LINKS.push({ ...linkForm });
//根据sortCode排序,sortCode越小越靠前
sysBaseProps.SYS_FOOTER_LINKS.sort((a, b) => a.sortCode - b.sortCode);
//关闭弹窗,恢复默认值
dialogVisible.value = false;
linkTmp.value.name = "";
linkTmp.value.url = "";
linkTmp.value.sortCode = 99;
linkFormRef.value?.resetFields();
});
}
/** 提交表单 */
function onSubmit() {
sysConfigFormRef.value?.validate(async valid => {
if (!valid) return; //表单验证失败
submitLoading.value = true;
//组装参数
const param: SysConfig.ConfigInfo[] = Object.entries(sysBaseProps).map(item => {
return {
id: 0,
category: SysConfigTypeEnum.SYS_BASE,
configKey: item[0],
//configValue 如果是对象就转成json字符串
configValue: typeof item[1] === "object" ? JSON.stringify(item[1]) : String(item[1])
};
});
//提交数据
sysConfigApi.configEditForm(param).finally(() => {
submitLoading.value = false;
});
});
}
/** 重置表单 */
function resetForm() {
sysConfigFormRef.value?.resetFields();
linkFormRef.value?.resetFields();
}
</script>
<style lang="scss" scoped></style>
+50
View File
@@ -0,0 +1,50 @@
<!--
* @Description:
* @Author: huguodong
* @Date: 2023-12-22 16:59:28
!-->
<template>
<div class="card">
<el-tabs v-model="activeName" class="-mt-4">
<el-tab-pane label="系统配置" name="sysConfig"><SysBaseConfig :sys-configs="sysBase" /></el-tab-pane>
<el-tab-pane label="安全配置" name="safety"><SafetyConfig :sys-configs="safetyConfig" /></el-tab-pane>
<el-tab-pane label="MQTT配置" name="mqtt"><MqttConfig :sys-configs="mqttConfig" /></el-tab-pane>
<el-tab-pane label="文件配置" name="file">开发中......</el-tab-pane>
<el-tab-pane label="其他配置" name="other"><OtherConfig v-if="activeName === 'other'" /></el-tab-pane>
</el-tabs>
</div>
</template>
<script setup lang="ts">
import SysBaseConfig from "./components/sysBaseConfig.vue";
import SafetyConfig from "./components/safetyConfig/index.vue";
import OtherConfig from "./components/otherConfig/index.vue";
import MqttConfig from "./components/mqttConfig/index.vue";
import { SysConfig, sysConfigApi } from "@/api";
import { SysConfigTypeEnum } from "@/enums";
const activeName = ref("sysConfig");
const sysBase = ref<SysConfig.ConfigInfo[]>([]);
const safetyConfig = ref<SysConfig.ConfigInfo[]>([]);
const mqttConfig = ref<SysConfig.ConfigInfo[]>([]);
onMounted(async () => {
// 获取系统配置
await sysConfigApi.list().then(res => {
if (res.data) {
//sysBase赋值
sysBase.value = res.data.filter(item => item.category == SysConfigTypeEnum.SYS_BASE);
//safetyBase赋值
safetyConfig.value = res.data.filter(item => item.category == SysConfigTypeEnum.LOGIN_POLICY || item.category == SysConfigTypeEnum.PWD_POLICY);
//mqttConfig赋值
mqttConfig.value = res.data.filter(item => item.category == SysConfigTypeEnum.MQTT_POLICY);
}
});
});
</script>
<style lang="scss" scoped>
:deep(.el-tabs__item) {
font-size: large;
}
</style>