mirror of
				https://github.com/continew-org/continew-admin-ui.git
				synced 2025-10-31 10:57:10 +08:00 
			
		
		
		
	refactor: 重构系统配置布局,融合存储、短信、终端等配置
This commit is contained in:
		| @@ -1,6 +1,6 @@ | ||||
| <template> | ||||
|   <a-row align="stretch" :gutter="rowGutter" class="gi-page-layout" :class="getClass"> | ||||
|     <a-col v-if="slots.left" v-show="!isCollapsed" v-bind="props.leftColProps" :sm="10" :md="7" :lg="6" :xl="5" :xxl="4"> | ||||
|     <a-col v-if="slots.left" v-show="!isCollapsed" class="gi-page-col" v-bind="props.leftColProps" :sm="10" :md="7" :lg="6" :xl="5" :xxl="4"> | ||||
|       <div class="gi-page-layout__left" :style="props.leftStyle"> | ||||
|         <slot name="left"></slot> | ||||
|       </div> | ||||
| @@ -12,8 +12,8 @@ | ||||
|       </div> | ||||
|     </div> | ||||
|  | ||||
|     <a-col :sm="16" :md="17" :lg="18" :xl="19" :xxl="20" flex="1" v-bind="props.rightColProps"> | ||||
|       <div v-if="slots.header" class="gi-page-layout__header" :style="{ ...props.headerStyle, display: !isDesktop && !isCollapsed ? 'none' : 'flex' }"> | ||||
|     <a-col class="gi-page-col" :sm="16" :md="17" :lg="18" :xl="19" :xxl="20" flex="1" v-bind="props.rightColProps"> | ||||
|       <div v-if="slots.header" class="gi-page-layout__header" :style="{ ...props.headerStyle, display: !isDesktop && !isCollapsed ? 'none' : '' }"> | ||||
|         <slot name="header"></slot> | ||||
|       </div> | ||||
|  | ||||
| @@ -125,7 +125,7 @@ watch(() => breakpoint.value, (val) => { | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   :deep(.arco-col) { | ||||
|   .gi-page-col { | ||||
|     height: 100%; | ||||
|     display: flex; | ||||
|     flex-direction: column; | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| <template> | ||||
|   <GiPageLayout> | ||||
|   <GiPageLayout :margin="false" :body-style="{ padding: 0 }"> | ||||
|     <GiTable | ||||
|       row-key="id" | ||||
|       :data="dataList" | ||||
| @@ -13,7 +13,6 @@ | ||||
|     > | ||||
|       <template #toolbar-left> | ||||
|         <a-input-search v-model="queryForm.clientKey" placeholder="搜索终端Key" allow-clear @search="search" /> | ||||
|         <a-input-search v-model="queryForm.clientSecret" placeholder="搜索终端秘钥" allow-clear @search="search" /> | ||||
|         <a-select | ||||
|           v-model="queryForm.clientType" | ||||
|           :options="client_type" | ||||
| @@ -1,227 +0,0 @@ | ||||
| <template> | ||||
|   <a-spin :loading="loading"> | ||||
|     <a-form | ||||
|       ref="formRef" | ||||
|       :model="form" | ||||
|       :rules="rules" | ||||
|       auto-label-width | ||||
|       label-align="left" | ||||
|       :layout="width >= 500 ? 'horizontal' : 'vertical'" | ||||
|       :disabled="!isUpdate" | ||||
|       scroll-to-first-error | ||||
|       class="form" | ||||
|     > | ||||
|       <a-form-item | ||||
|         field="MAIL_PROTOCOL" | ||||
|         :label="mailConfig.MAIL_PROTOCOL.name" | ||||
|         :help="mailConfig.MAIL_PROTOCOL.description" | ||||
|         hide-asterisk | ||||
|       > | ||||
|         <a-select v-model.trim="form.MAIL_PROTOCOL"> | ||||
|           <a-option label="SMTP" value="smtp" /> | ||||
|         </a-select> | ||||
|       </a-form-item> | ||||
|       <a-form-item | ||||
|         field="MAIL_HOST" | ||||
|         :label="mailConfig.MAIL_HOST.name" | ||||
|         :help="mailConfig.MAIL_HOST.description" | ||||
|         hide-asterisk | ||||
|       > | ||||
|         <a-input v-model.trim="form.MAIL_HOST" /> | ||||
|       </a-form-item> | ||||
|       <a-form-item | ||||
|         field="MAIL_PORT" | ||||
|         :label="mailConfig.MAIL_PORT.name" | ||||
|         :help="mailConfig.MAIL_PORT.description" | ||||
|         hide-asterisk | ||||
|       > | ||||
|         <a-input-number v-model="form.MAIL_PORT" :min="0" /> | ||||
|       </a-form-item> | ||||
|       <a-form-item | ||||
|         field="MAIL_USERNAME" | ||||
|         :label="mailConfig.MAIL_USERNAME.name" | ||||
|         :help="mailConfig.MAIL_USERNAME.description" | ||||
|         hide-asterisk | ||||
|       > | ||||
|         <a-input v-model.trim="form.MAIL_USERNAME" /> | ||||
|       </a-form-item> | ||||
|       <a-form-item | ||||
|         field="MAIL_PASSWORD" | ||||
|         :label="mailConfig.MAIL_PASSWORD?.name" | ||||
|         :help="mailConfig.MAIL_PASSWORD.description" | ||||
|         hide-asterisk | ||||
|       > | ||||
|         <a-input-password v-model.trim="form.MAIL_PASSWORD" /> | ||||
|       </a-form-item> | ||||
|       <a-form-item | ||||
|         field="MAIL_SSL_ENABLED" | ||||
|         :label="mailConfig.MAIL_SSL_ENABLED?.name" | ||||
|         :help="mailConfig.MAIL_SSL_ENABLED.description" | ||||
|         hide-asterisk | ||||
|       > | ||||
|         <a-switch | ||||
|           v-model="form.MAIL_SSL_ENABLED" | ||||
|           type="round" | ||||
|           :checked-value="1" | ||||
|           :unchecked-value="0" | ||||
|         > | ||||
|           <template #checked>启用</template> | ||||
|           <template #unchecked>禁用</template> | ||||
|         </a-switch> | ||||
|       </a-form-item> | ||||
|       <a-form-item | ||||
|         v-if="form.MAIL_SSL_ENABLED === '1'" | ||||
|         field="MAIL_SSL_PORT" | ||||
|         :label="mailConfig.MAIL_SSL_PORT.name" | ||||
|         :help="mailConfig.MAIL_SSL_PORT.description" | ||||
|         hide-asterisk | ||||
|       > | ||||
|         <a-input-number v-model="form.MAIL_SSL_PORT" :min="0" /> | ||||
|       </a-form-item> | ||||
|       <a-space style="margin-bottom: 16px"> | ||||
|         <a-button v-if="!isUpdate" v-permission="['system:config:update']" type="primary" @click="onUpdate"> | ||||
|           <template #icon><icon-edit /></template>修改 | ||||
|         </a-button> | ||||
|         <a-button v-if="!isUpdate" v-permission="['system:config:reset']" @click="onResetValue"> | ||||
|           <template #icon><icon-undo /></template>恢复默认 | ||||
|         </a-button> | ||||
|         <a-button v-if="isUpdate" type="primary" @click="handleSave"> | ||||
|           <template #icon><icon-save /></template>保存 | ||||
|         </a-button> | ||||
|         <a-button v-if="isUpdate" @click="reset"> | ||||
|           <template #icon><icon-refresh /></template>重置 | ||||
|         </a-button> | ||||
|         <a-button v-if="isUpdate" @click="handleCancel"> | ||||
|           <template #icon><icon-undo /></template>取消 | ||||
|         </a-button> | ||||
|       </a-space> | ||||
|     </a-form> | ||||
|   </a-spin> | ||||
| </template> | ||||
|  | ||||
| <script setup lang="ts"> | ||||
| import { useWindowSize } from '@vueuse/core' | ||||
| import { type FormInstance, Message, Modal } from '@arco-design/web-vue' | ||||
| import { | ||||
|   type MailConfig, | ||||
|   type OptionResp, | ||||
|   listOption, | ||||
|   resetOptionValue, | ||||
|   updateOption, | ||||
| } from '@/apis/system' | ||||
| import { useResetReactive } from '@/hooks' | ||||
|  | ||||
| defineOptions({ name: 'MailSetting' }) | ||||
| const { width } = useWindowSize() | ||||
| const loading = ref<boolean>(false) | ||||
| const formRef = ref<FormInstance>() | ||||
| const [form] = useResetReactive({ | ||||
|   MAIL_PROTOCOL: '', | ||||
|   MAIL_HOST: '', | ||||
|   MAIL_PORT: 0, | ||||
|   MAIL_USERNAME: '', | ||||
|   MAIL_PASSWORD: '', | ||||
|   MAIL_SSL_ENABLED: '', | ||||
|   MAIL_SSL_PORT: 0, | ||||
| }) | ||||
| const rules: FormInstance['rules'] = { | ||||
|   MAIL_HOST: [{ required: true, message: '请输入值' }], | ||||
|   MAIL_PORT: [{ required: true, message: '请输入值' }], | ||||
|   MAIL_USERNAME: [{ required: true, message: '请输入值' }], | ||||
|   MAIL_PASSWORD: [{ required: true, message: '请输入值' }], | ||||
|   MAIL_SSL_PORT: [{ required: true, message: '请输入值' }], | ||||
| } | ||||
|  | ||||
| const mailConfig = ref<MailConfig>({ | ||||
|   MAIL_PROTOCOL: {}, | ||||
|   MAIL_HOST: {}, | ||||
|   MAIL_PORT: {}, | ||||
|   MAIL_USERNAME: {}, | ||||
|   MAIL_PASSWORD: {}, | ||||
|   MAIL_SSL_ENABLED: {}, | ||||
|   MAIL_SSL_PORT: {}, | ||||
| }) | ||||
|  | ||||
| // 重置 | ||||
| const reset = () => { | ||||
|   formRef.value?.resetFields() | ||||
|   form.MAIL_PROTOCOL = mailConfig.value.MAIL_PROTOCOL.value || '' | ||||
|   form.MAIL_HOST = mailConfig.value.MAIL_HOST.value || '' | ||||
|   form.MAIL_PORT = mailConfig.value.MAIL_PORT.value || 0 | ||||
|   form.MAIL_USERNAME = mailConfig.value.MAIL_USERNAME.value || '' | ||||
|   form.MAIL_PASSWORD = mailConfig.value.MAIL_PASSWORD?.value || '' | ||||
|   form.MAIL_SSL_ENABLED = mailConfig.value.MAIL_SSL_ENABLED.value || '' | ||||
|   form.MAIL_SSL_PORT = mailConfig.value.MAIL_SSL_PORT.value || 0 | ||||
| } | ||||
|  | ||||
| const isUpdate = ref(false) | ||||
| // 修改 | ||||
| const onUpdate = () => { | ||||
|   isUpdate.value = true | ||||
| } | ||||
|  | ||||
| // 取消 | ||||
| const handleCancel = () => { | ||||
|   reset() | ||||
|   isUpdate.value = false | ||||
| } | ||||
|  | ||||
| const queryForm = { | ||||
|   category: 'MAIL', | ||||
| } | ||||
| // 查询列表数据 | ||||
| const getDataList = async () => { | ||||
|   loading.value = true | ||||
|   const { data } = await listOption(queryForm) | ||||
|   mailConfig.value = data.reduce((obj: MailConfig, option: OptionResp) => { | ||||
|     obj[option.code] = { ...option, value: ['MAIL_PORT', 'MAIL_SSL_PORT'].includes(option.code) ? Number.parseInt(option.value) : option.value } | ||||
|     return obj | ||||
|   }, {}) | ||||
|   handleCancel() | ||||
|   loading.value = false | ||||
| } | ||||
|  | ||||
| // 保存 | ||||
| const handleSave = async () => { | ||||
|   const isInvalid = await formRef.value?.validate() | ||||
|   if (isInvalid) return false | ||||
|   await updateOption( | ||||
|     Object.entries(form).map(([key, value]) => { | ||||
|       return { id: mailConfig.value[key].id, code: key, value } | ||||
|     }), | ||||
|   ) | ||||
|   await getDataList() | ||||
|   Message.success('保存成功') | ||||
| } | ||||
|  | ||||
| // 恢复默认 | ||||
| const handleResetValue = async () => { | ||||
|   await resetOptionValue(queryForm) | ||||
|   Message.success('恢复成功') | ||||
|   await getDataList() | ||||
| } | ||||
| const onResetValue = () => { | ||||
|   Modal.warning({ | ||||
|     title: '警告', | ||||
|     content: '确认恢复邮件配置为默认值吗?', | ||||
|     hideCancel: false, | ||||
|     maskClosable: false, | ||||
|     onOk: handleResetValue, | ||||
|   }) | ||||
| } | ||||
|  | ||||
| onMounted(() => { | ||||
|   getDataList() | ||||
| }) | ||||
| </script> | ||||
|  | ||||
| <style scoped lang="scss"> | ||||
| :deep(.arco-form-item.arco-form-item-has-help) { | ||||
|   margin-bottom: 5px; | ||||
| } | ||||
|  | ||||
| :deep(.form .arco-input-wrapper), | ||||
| :deep(.arco-select-view-single) { | ||||
|   width: 220px; | ||||
| } | ||||
| </style> | ||||
| @@ -1,277 +0,0 @@ | ||||
| <template> | ||||
|   <a-spin :loading="loading"> | ||||
|     <a-form | ||||
|       ref="formRef" | ||||
|       :model="form" | ||||
|       :rules="rules" | ||||
|       auto-label-width | ||||
|       label-align="left" | ||||
|       :layout="width >= 500 ? 'horizontal' : 'vertical'" | ||||
|       :disabled="!isUpdate" | ||||
|       scroll-to-first-error | ||||
|       class="form" | ||||
|     > | ||||
|       <a-form-item | ||||
|         field="PASSWORD_ERROR_LOCK_COUNT" | ||||
|         :label="securityConfig.PASSWORD_ERROR_LOCK_COUNT.name" | ||||
|         :help="securityConfig.PASSWORD_ERROR_LOCK_COUNT.description" | ||||
|         hide-asterisk | ||||
|       > | ||||
|         <a-input-number | ||||
|           v-model="form.PASSWORD_ERROR_LOCK_COUNT" | ||||
|           :default-value="0" | ||||
|           :precision="0" | ||||
|           :min="0" | ||||
|           :max="10" | ||||
|         > | ||||
|           <template #append>次</template> | ||||
|         </a-input-number> | ||||
|       </a-form-item> | ||||
|       <a-form-item | ||||
|         field="PASSWORD_ERROR_LOCK_MINUTES" | ||||
|         :label="securityConfig.PASSWORD_ERROR_LOCK_MINUTES.name" | ||||
|         :help="securityConfig.PASSWORD_ERROR_LOCK_MINUTES.description" | ||||
|         hide-asterisk | ||||
|       > | ||||
|         <a-input-number | ||||
|           v-model="form.PASSWORD_ERROR_LOCK_MINUTES" | ||||
|           :precision="0" | ||||
|           :min="1" | ||||
|           :max="1440" | ||||
|         > | ||||
|           <template #append>分钟</template> | ||||
|         </a-input-number> | ||||
|       </a-form-item> | ||||
|       <a-form-item | ||||
|         field="PASSWORD_EXPIRATION_DAYS" | ||||
|         :label="securityConfig.PASSWORD_EXPIRATION_DAYS.name" | ||||
|         :help="securityConfig.PASSWORD_EXPIRATION_DAYS.description" | ||||
|         hide-asterisk | ||||
|       > | ||||
|         <a-input-number | ||||
|           v-model="form.PASSWORD_EXPIRATION_DAYS" | ||||
|           :precision="0" | ||||
|           :min="0" | ||||
|           :max="999" | ||||
|         > | ||||
|           <template #append>天</template> | ||||
|         </a-input-number> | ||||
|       </a-form-item> | ||||
|       <a-form-item | ||||
|         :label="securityConfig.PASSWORD_EXPIRATION_WARNING_DAYS.name" | ||||
|         field="PASSWORD_EXPIRATION_WARNING_DAYS" | ||||
|         :help="securityConfig.PASSWORD_EXPIRATION_WARNING_DAYS.description" | ||||
|         hide-asterisk | ||||
|       > | ||||
|         <a-input-number | ||||
|           v-model="form.PASSWORD_EXPIRATION_WARNING_DAYS" | ||||
|           :precision="0" | ||||
|           :min="0" | ||||
|           :max="998" | ||||
|         > | ||||
|           <template #append>天</template> | ||||
|         </a-input-number> | ||||
|       </a-form-item> | ||||
|       <a-form-item | ||||
|         field="PASSWORD_REPETITION_TIMES" | ||||
|         :label="securityConfig.PASSWORD_REPETITION_TIMES.name" | ||||
|         :help="securityConfig.PASSWORD_REPETITION_TIMES.description" | ||||
|         hide-asterisk | ||||
|       > | ||||
|         <a-input-number | ||||
|           v-model="form.PASSWORD_REPETITION_TIMES" | ||||
|           :precision="0" | ||||
|           :min="3" | ||||
|           :max="32" | ||||
|         > | ||||
|           <template #append>次</template> | ||||
|         </a-input-number> | ||||
|       </a-form-item> | ||||
|       <a-form-item | ||||
|         field="PASSWORD_MIN_LENGTH" | ||||
|         :label="securityConfig.PASSWORD_MIN_LENGTH.name" | ||||
|         :help="securityConfig.PASSWORD_MIN_LENGTH.description" | ||||
|         hide-asterisk | ||||
|       > | ||||
|         <a-input-number | ||||
|           v-model="form.PASSWORD_MIN_LENGTH" | ||||
|           :precision="0" | ||||
|           :min="8" | ||||
|           :max="32" | ||||
|         /> | ||||
|       </a-form-item> | ||||
|       <a-form-item | ||||
|         field="PASSWORD_ALLOW_CONTAIN_USERNAME" | ||||
|         :label="securityConfig.PASSWORD_ALLOW_CONTAIN_USERNAME.name" | ||||
|         :help="securityConfig.PASSWORD_ALLOW_CONTAIN_USERNAME.description" | ||||
|       > | ||||
|         <a-switch v-model="form.PASSWORD_ALLOW_CONTAIN_USERNAME" type="round" :checked-value="1" :unchecked-value="0"> | ||||
|           <template #checked>是</template> | ||||
|           <template #unchecked>否</template> | ||||
|         </a-switch> | ||||
|       </a-form-item> | ||||
|       <a-form-item | ||||
|         field="PASSWORD_REQUIRE_SYMBOLS" | ||||
|         :label="securityConfig.PASSWORD_REQUIRE_SYMBOLS.name" | ||||
|         :help="securityConfig.PASSWORD_REQUIRE_SYMBOLS.description" | ||||
|       > | ||||
|         <a-switch v-model="form.PASSWORD_REQUIRE_SYMBOLS" type="round" :checked-value="1" :unchecked-value="0"> | ||||
|           <template #checked>是</template> | ||||
|           <template #unchecked>否</template> | ||||
|         </a-switch> | ||||
|       </a-form-item> | ||||
|       <a-space style="margin-bottom: 16px"> | ||||
|         <a-button v-if="!isUpdate" v-permission="['system:config:update']" type="primary" @click="onUpdate"> | ||||
|           <template #icon><icon-edit /></template>修改 | ||||
|         </a-button> | ||||
|         <a-button v-if="!isUpdate" v-permission="['system:config:reset']" @click="onResetValue"> | ||||
|           <template #icon><icon-undo /></template>恢复默认 | ||||
|         </a-button> | ||||
|         <a-button v-if="isUpdate" type="primary" @click="handleSave"> | ||||
|           <template #icon><icon-save /></template>保存 | ||||
|         </a-button> | ||||
|         <a-button v-if="isUpdate" @click="reset"> | ||||
|           <template #icon><icon-refresh /></template>重置 | ||||
|         </a-button> | ||||
|         <a-button v-if="isUpdate" @click="handleCancel"> | ||||
|           <template #icon><icon-undo /></template>取消 | ||||
|         </a-button> | ||||
|       </a-space> | ||||
|     </a-form> | ||||
|   </a-spin> | ||||
| </template> | ||||
|  | ||||
| <script setup lang="ts"> | ||||
| import { useWindowSize } from '@vueuse/core' | ||||
| import { type FormInstance, Message, Modal } from '@arco-design/web-vue' | ||||
| import { type OptionResp, type SecurityConfig, listOption, resetOptionValue, updateOption } from '@/apis/system' | ||||
| import { useResetReactive } from '@/hooks' | ||||
|  | ||||
| defineOptions({ name: 'SecuritySetting' }) | ||||
| const { width } = useWindowSize() | ||||
|  | ||||
| const loading = ref<boolean>(false) | ||||
| const formRef = ref<FormInstance>() | ||||
| const [form] = useResetReactive({ | ||||
|   PASSWORD_ERROR_LOCK_COUNT: 0, | ||||
|   PASSWORD_ERROR_LOCK_MINUTES: 0, | ||||
|   PASSWORD_EXPIRATION_DAYS: 0, | ||||
|   PASSWORD_EXPIRATION_WARNING_DAYS: 0, | ||||
|   PASSWORD_REPETITION_TIMES: 0, | ||||
|   PASSWORD_MIN_LENGTH: 0, | ||||
|   PASSWORD_ALLOW_CONTAIN_USERNAME: 0, | ||||
|   PASSWORD_REQUIRE_SYMBOLS: 0, | ||||
| }) | ||||
| const rules: FormInstance['rules'] = { | ||||
|   PASSWORD_ERROR_LOCK_COUNT: [{ required: true, message: '请输入值' }], | ||||
|   PASSWORD_ERROR_LOCK_MINUTES: [{ required: true, message: '请输入值' }], | ||||
|   PASSWORD_EXPIRATION_DAYS: [{ required: true, message: '请输入值' }], | ||||
|   PASSWORD_EXPIRATION_WARNING_DAYS: [ | ||||
|     { required: true, message: '请输入值' }, | ||||
|     { | ||||
|       validator: (value, callback) => { | ||||
|         if (form.PASSWORD_EXPIRATION_DAYS > 0 && value >= form.PASSWORD_EXPIRATION_DAYS) { | ||||
|           callback('密码到期提醒时间应小于密码有效期') | ||||
|         } else { | ||||
|           callback() | ||||
|         } | ||||
|       }, | ||||
|     }, | ||||
|   ], | ||||
|   PASSWORD_REPETITION_TIMES: [{ required: true, message: '请输入值' }], | ||||
|   PASSWORD_MIN_LENGTH: [{ required: true, message: '请输入值' }], | ||||
| } | ||||
|  | ||||
| const securityConfig = ref<SecurityConfig>({ | ||||
|   PASSWORD_ERROR_LOCK_COUNT: {}, | ||||
|   PASSWORD_ERROR_LOCK_MINUTES: {}, | ||||
|   PASSWORD_EXPIRATION_DAYS: {}, | ||||
|   PASSWORD_EXPIRATION_WARNING_DAYS: {}, | ||||
|   PASSWORD_REPETITION_TIMES: {}, | ||||
|   PASSWORD_MIN_LENGTH: {}, | ||||
|   PASSWORD_ALLOW_CONTAIN_USERNAME: {}, | ||||
|   PASSWORD_REQUIRE_SYMBOLS: {}, | ||||
| }) | ||||
| // 重置 | ||||
| const reset = () => { | ||||
|   formRef.value?.resetFields() | ||||
|   form.PASSWORD_ERROR_LOCK_COUNT = securityConfig.value.PASSWORD_ERROR_LOCK_COUNT.value || 0 | ||||
|   form.PASSWORD_ERROR_LOCK_MINUTES = securityConfig.value.PASSWORD_ERROR_LOCK_MINUTES.value || 0 | ||||
|   form.PASSWORD_EXPIRATION_DAYS = securityConfig.value.PASSWORD_EXPIRATION_DAYS.value || 0 | ||||
|   form.PASSWORD_EXPIRATION_WARNING_DAYS = securityConfig.value.PASSWORD_EXPIRATION_WARNING_DAYS.value || 0 | ||||
|   form.PASSWORD_REPETITION_TIMES = securityConfig.value.PASSWORD_REPETITION_TIMES.value || 0 | ||||
|   form.PASSWORD_MIN_LENGTH = securityConfig.value.PASSWORD_MIN_LENGTH.value || 0 | ||||
|   form.PASSWORD_ALLOW_CONTAIN_USERNAME = securityConfig.value.PASSWORD_ALLOW_CONTAIN_USERNAME.value || 0 | ||||
|   form.PASSWORD_REQUIRE_SYMBOLS = securityConfig.value.PASSWORD_REQUIRE_SYMBOLS.value || 0 | ||||
| } | ||||
|  | ||||
| const isUpdate = ref(false) | ||||
| // 修改 | ||||
| const onUpdate = () => { | ||||
|   isUpdate.value = true | ||||
| } | ||||
|  | ||||
| // 取消 | ||||
| const handleCancel = () => { | ||||
|   reset() | ||||
|   isUpdate.value = false | ||||
| } | ||||
|  | ||||
| const queryForm = { | ||||
|   category: 'PASSWORD', | ||||
| } | ||||
| // 查询列表数据 | ||||
| const getDataList = async () => { | ||||
|   loading.value = true | ||||
|   const { data } = await listOption(queryForm) | ||||
|   securityConfig.value = data.reduce((obj: SecurityConfig, option: OptionResp) => { | ||||
|     obj[option.code] = { ...option, value: Number.parseInt(option.value) } | ||||
|     return obj | ||||
|   }, {}) | ||||
|   handleCancel() | ||||
|   loading.value = false | ||||
| } | ||||
|  | ||||
| // 保存 | ||||
| const handleSave = async () => { | ||||
|   const isInvalid = await formRef.value?.validate() | ||||
|   if (isInvalid) return false | ||||
|   await updateOption( | ||||
|     Object.entries(form).map(([key, value]) => { | ||||
|       return { id: securityConfig.value[key].id, code: key, value } | ||||
|     }), | ||||
|   ) | ||||
|   await getDataList() | ||||
|   Message.success('保存成功') | ||||
| } | ||||
|  | ||||
| // 恢复默认 | ||||
| const handleResetValue = async () => { | ||||
|   await resetOptionValue(queryForm) | ||||
|   Message.success('恢复成功') | ||||
|   await getDataList() | ||||
| } | ||||
| const onResetValue = () => { | ||||
|   Modal.warning({ | ||||
|     title: '警告', | ||||
|     content: '确认恢复安全配置为默认值吗?', | ||||
|     hideCancel: false, | ||||
|     maskClosable: false, | ||||
|     onOk: handleResetValue, | ||||
|   }) | ||||
| } | ||||
|  | ||||
| onMounted(() => { | ||||
|   getDataList() | ||||
| }) | ||||
| </script> | ||||
|  | ||||
| <style scoped lang="scss"> | ||||
| :deep(.arco-form-item.arco-form-item-has-help) { | ||||
|   margin-bottom: 5px; | ||||
| } | ||||
|  | ||||
| :deep(.form .arco-input-wrapper) { | ||||
|   width: 200px; | ||||
| } | ||||
| </style> | ||||
| @@ -1,44 +1,74 @@ | ||||
| <template> | ||||
|   <div class="gi_table_page"> | ||||
|     <a-tabs v-model:active-key="activeKey" type="card-gutter" size="large" @change="change"> | ||||
|       <a-tab-pane key="site"> | ||||
|         <template #title><icon-apps /> 网站配置</template> | ||||
|       </a-tab-pane> | ||||
|       <a-tab-pane key="security"> | ||||
|         <template #title><icon-safe /> 安全配置</template> | ||||
|       </a-tab-pane> | ||||
|       <a-tab-pane key="mail"> | ||||
|         <template #title><icon-email /> 邮件配置</template> | ||||
|       </a-tab-pane> | ||||
|       <a-tab-pane key="login"> | ||||
|         <template #title><icon-lock /> 登录配置</template> | ||||
|       </a-tab-pane> | ||||
|     </a-tabs> | ||||
|     <keep-alive> | ||||
|       <component :is="PanMap[activeKey]" /> | ||||
|     </keep-alive> | ||||
|   </div> | ||||
|   <GiPageLayout | ||||
|     :margin="true" | ||||
|     :default-collapsed="false" | ||||
|     :header-style="isDesktop ? { padding: 0, borderBottomWidth: 0 } : { borderBottomWidth: '1px' } " | ||||
|   > | ||||
|     <template v-if="isDesktop" #left> | ||||
|       <a-tabs v-model:active-key="activeKey" type="rounded" position="left" hide-content size="large" @change="change"> | ||||
|         <a-tab-pane v-for="(item) in menuList" :key="item.key"> | ||||
|           <template #title> | ||||
|             <div style="display: flex; align-items: center"> | ||||
|               <GiSvgIcon :name="item.icon" :size="18" style="margin-right: 4px" /> | ||||
|               {{ item.name }} | ||||
|             </div> | ||||
|           </template> | ||||
|         </a-tab-pane> | ||||
|       </a-tabs> | ||||
|     </template> | ||||
|     <template #header> | ||||
|       <a-tabs v-if="!isDesktop" v-model:active-key="activeKey" type="rounded" position="top" size="large" @change="change"> | ||||
|         <a-tab-pane v-for="(item) in menuList" :key="item.key" :title="item.name"> | ||||
|           <template #title> | ||||
|             <div style="display: flex; align-items: center"> | ||||
|               <GiSvgIcon :name="item.icon" :size="18" style="margin-right: 4px" /> | ||||
|               {{ item.name }} | ||||
|             </div> | ||||
|           </template> | ||||
|         </a-tab-pane> | ||||
|       </a-tabs> | ||||
|     </template> | ||||
|     <transition name="fade-slide" mode="out-in" appear> | ||||
|       <component :is="menuList.find((item) => item.key === activeKey)?.value"></component> | ||||
|     </transition> | ||||
|   </GiPageLayout> | ||||
| </template> | ||||
|  | ||||
| <script setup lang="ts"> | ||||
| <script setup lang="tsx"> | ||||
| import { useRoute, useRouter } from 'vue-router' | ||||
| import SiteSetting from './components/SiteSetting.vue' | ||||
| import SecuritySetting from './components/SecuritySetting.vue' | ||||
| import MailSetting from './components/MailSetting.vue' | ||||
| import LoginSetting from './components/LoginSetting.vue' | ||||
| import SiteConfig from './site/index.vue' | ||||
| import SecurityConfig from './security/index.vue' | ||||
| import LoginConfig from './login/index.vue' | ||||
| import MailConfig from './mail/index.vue' | ||||
| import SmsConfig from './sms/index.vue' | ||||
| import StorageConfig from './storage/index.vue' | ||||
| import ClientConfig from './client/index.vue' | ||||
| import { useDevice } from '@/hooks' | ||||
| import has from '@/utils/has' | ||||
|  | ||||
| defineOptions({ name: 'SystemConfig' }) | ||||
|  | ||||
| const PanMap: Record<string, Component> = { | ||||
|   site: SiteSetting, | ||||
|   security: SecuritySetting, | ||||
|   mail: MailSetting, | ||||
|   login: LoginSetting, | ||||
| } | ||||
| const { isDesktop } = useDevice() | ||||
|  | ||||
| const data = [ | ||||
|   { name: '网站配置', key: 'site', icon: 'apps', permissions: ['system:siteConfig:get'], value: SiteConfig }, | ||||
|   { name: '安全配置', key: 'security', icon: 'safe', permissions: ['system:securityConfig:get'], value: SecurityConfig }, | ||||
|   { name: '登录配置', key: 'login', icon: 'lock', permissions: ['system:loginConfig:get'], value: LoginConfig }, | ||||
|   { name: '邮件配置', key: 'mail', icon: 'email', permissions: ['system:mailConfig:get'], value: MailConfig }, | ||||
|   { name: '短信配置', key: 'sms', icon: 'message', permissions: ['system:smsConfig:list'], value: SmsConfig }, | ||||
|   { name: '存储配置', key: 'storage', icon: 'storage', permissions: ['system:storage:list'], value: StorageConfig }, | ||||
|   { name: '终端配置', key: 'client', icon: 'mobile', permissions: ['system:client:list'], value: ClientConfig }, | ||||
| ] | ||||
|  | ||||
| const menuList = computed(() => { | ||||
|   return data.filter((item) => { | ||||
|     return has.hasPermOr(item.permissions) | ||||
|   }) | ||||
| }) | ||||
|  | ||||
| const route = useRoute() | ||||
| const router = useRouter() | ||||
| const activeKey = ref('site') | ||||
| const activeKey = ref(menuList.value[0].key) | ||||
| watch( | ||||
|   () => route.query, | ||||
|   () => { | ||||
| @@ -55,12 +85,48 @@ const change = (key: string | number) => { | ||||
| </script> | ||||
|  | ||||
| <style scoped lang="scss"> | ||||
| .gi_table_page { | ||||
|   overflow-y: auto; | ||||
| .gi_page { | ||||
|   padding-top: 0; | ||||
| } | ||||
|  | ||||
|   :deep(.arco-tabs) { | ||||
|     overflow: visible; | ||||
| .tab-pane-item{ | ||||
|   display: flex; | ||||
|   justify-content: space-between; | ||||
|   align-items: center; | ||||
|   gap: 8px; | ||||
| } | ||||
|  | ||||
| :deep(.arco-tabs-nav-vertical.arco-tabs-nav-type-line .arco-tabs-tab) { | ||||
|   margin: 0; | ||||
|   padding: 8px 16px; | ||||
|  | ||||
|   &:hover { | ||||
|     background: var(--color-fill-1); | ||||
|  | ||||
|     .arco-tabs-tab-title { | ||||
|       &::before { | ||||
|         display: none !important; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   &.arco-tabs-tab-active { | ||||
|     background: rgba(var(--primary-6), 0.08); | ||||
|   } | ||||
| } | ||||
|  | ||||
| :deep(.arco-tabs-nav-vertical::before) { | ||||
|   left: 0; | ||||
|   display: none; | ||||
| } | ||||
|  | ||||
| :deep(.arco-tabs-nav-vertical .arco-tabs-nav-ink) { | ||||
|   left: 0; | ||||
| } | ||||
|  | ||||
| :deep(.arco-tabs-nav-vertical) { | ||||
|   float: none; | ||||
|   flex-direction: row; | ||||
| } | ||||
|  | ||||
| :deep(.arco-tabs .arco-tabs-nav-type-card-gutter .arco-tabs-tab-active) { | ||||
| @@ -83,10 +149,18 @@ const change = (key: string | number) => { | ||||
| } | ||||
|  | ||||
| :deep(.arco-tabs) { | ||||
|   overflow: visible; | ||||
|   overflow: hidden; | ||||
| } | ||||
|  | ||||
| :deep(.arco-tabs-nav) { | ||||
|   overflow: visible; | ||||
| } | ||||
|  | ||||
| :deep(.arco-tabs-nav-type-rounded .arco-tabs-tab){ | ||||
|   border-radius: 8px; | ||||
| } | ||||
|  | ||||
| :deep(.arco-tabs-tab-title){ | ||||
|   width: 100%; | ||||
| } | ||||
| </style> | ||||
|   | ||||
| @@ -1,48 +1,50 @@ | ||||
| <template> | ||||
|   <a-spin :loading="loading"> | ||||
|     <a-form | ||||
|       ref="formRef" | ||||
|       :model="form" | ||||
|       :rules="rules" | ||||
|       auto-label-width | ||||
|       label-align="left" | ||||
|       :layout="width >= 500 ? 'horizontal' : 'vertical'" | ||||
|       :disabled="!isUpdate" | ||||
|       scroll-to-first-error | ||||
|     > | ||||
|       <a-form-item | ||||
|         field="LOGIN_CAPTCHA_ENABLED" | ||||
|         :label="loginConfig.LOGIN_CAPTCHA_ENABLED.name" | ||||
|   <div class="gi_page"> | ||||
|     <a-spin :loading="loading"> | ||||
|       <a-form | ||||
|         ref="formRef" | ||||
|         :model="form" | ||||
|         :rules="rules" | ||||
|         auto-label-width | ||||
|         label-align="left" | ||||
|         :layout="width >= 500 ? 'horizontal' : 'vertical'" | ||||
|         :disabled="!isUpdate" | ||||
|         scroll-to-first-error | ||||
|       > | ||||
|         <a-switch | ||||
|           v-model="form.LOGIN_CAPTCHA_ENABLED" | ||||
|           type="round" | ||||
|           :checked-value="1" | ||||
|           :unchecked-value="0" | ||||
|         <a-form-item | ||||
|           field="LOGIN_CAPTCHA_ENABLED" | ||||
|           :label="loginConfig.LOGIN_CAPTCHA_ENABLED.name" | ||||
|         > | ||||
|           <template #checked>是</template> | ||||
|           <template #unchecked>否</template> | ||||
|         </a-switch> | ||||
|       </a-form-item> | ||||
|       <a-space style="margin-bottom: 16px"> | ||||
|         <a-button v-if="!isUpdate" v-permission="['system:config:update']" type="primary" @click="onUpdate"> | ||||
|           <template #icon><icon-edit /></template>修改 | ||||
|         </a-button> | ||||
|         <a-button v-if="!isUpdate" v-permission="['system:config:reset']" @click="onResetValue"> | ||||
|           <template #icon><icon-undo /></template>恢复默认 | ||||
|         </a-button> | ||||
|         <a-button v-if="isUpdate" type="primary" @click="handleSave"> | ||||
|           <template #icon><icon-save /></template>保存 | ||||
|         </a-button> | ||||
|         <a-button v-if="isUpdate" @click="reset"> | ||||
|           <template #icon><icon-refresh /></template>重置 | ||||
|         </a-button> | ||||
|         <a-button v-if="isUpdate" @click="handleCancel"> | ||||
|           <template #icon><icon-undo /></template>取消 | ||||
|         </a-button> | ||||
|       </a-space> | ||||
|     </a-form> | ||||
|   </a-spin> | ||||
|           <a-switch | ||||
|             v-model="form.LOGIN_CAPTCHA_ENABLED" | ||||
|             type="round" | ||||
|             :checked-value="1" | ||||
|             :unchecked-value="0" | ||||
|           > | ||||
|             <template #checked>是</template> | ||||
|             <template #unchecked>否</template> | ||||
|           </a-switch> | ||||
|         </a-form-item> | ||||
|         <a-space style="margin-bottom: 16px"> | ||||
|           <a-button v-if="!isUpdate" v-permission="['system:loginConfig:update']" type="primary" @click="onUpdate"> | ||||
|             <template #icon><icon-edit /></template>修改 | ||||
|           </a-button> | ||||
|           <a-button v-if="!isUpdate" v-permission="['system:loginConfig:update']" @click="onResetValue"> | ||||
|             <template #icon><icon-undo /></template>恢复默认 | ||||
|           </a-button> | ||||
|           <a-button v-if="isUpdate" type="primary" @click="handleSave"> | ||||
|             <template #icon><icon-save /></template>保存 | ||||
|           </a-button> | ||||
|           <a-button v-if="isUpdate" @click="reset"> | ||||
|             <template #icon><icon-refresh /></template>重置 | ||||
|           </a-button> | ||||
|           <a-button v-if="isUpdate" @click="handleCancel"> | ||||
|             <template #icon><icon-undo /></template>取消 | ||||
|           </a-button> | ||||
|         </a-space> | ||||
|       </a-form> | ||||
|     </a-spin> | ||||
|   </div> | ||||
| </template> | ||||
| 
 | ||||
| <script setup lang="ts"> | ||||
| @@ -51,7 +53,7 @@ import { type FormInstance, Message, Modal } from '@arco-design/web-vue' | ||||
| import { type LoginConfig, type OptionResp, listOption, resetOptionValue, updateOption } from '@/apis/system' | ||||
| import { useResetReactive } from '@/hooks' | ||||
| 
 | ||||
| defineOptions({ name: 'LoginSetting' }) | ||||
| defineOptions({ name: 'SystemLoginConfig' }) | ||||
| const { width } = useWindowSize() | ||||
| 
 | ||||
| const loading = ref<boolean>(false) | ||||
							
								
								
									
										229
									
								
								src/views/system/config/mail/index.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										229
									
								
								src/views/system/config/mail/index.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,229 @@ | ||||
| <template> | ||||
|   <div class="gi_page"> | ||||
|     <a-spin :loading="loading"> | ||||
|       <a-form | ||||
|         ref="formRef" | ||||
|         :model="form" | ||||
|         :rules="rules" | ||||
|         auto-label-width | ||||
|         label-align="left" | ||||
|         :layout="width >= 500 ? 'horizontal' : 'vertical'" | ||||
|         :disabled="!isUpdate" | ||||
|         scroll-to-first-error | ||||
|         class="form" | ||||
|       > | ||||
|         <a-form-item | ||||
|           field="MAIL_PROTOCOL" | ||||
|           :label="mailConfig.MAIL_PROTOCOL.name" | ||||
|           :help="mailConfig.MAIL_PROTOCOL.description" | ||||
|           hide-asterisk | ||||
|         > | ||||
|           <a-select v-model.trim="form.MAIL_PROTOCOL"> | ||||
|             <a-option label="SMTP" value="smtp" /> | ||||
|           </a-select> | ||||
|         </a-form-item> | ||||
|         <a-form-item | ||||
|           field="MAIL_HOST" | ||||
|           :label="mailConfig.MAIL_HOST.name" | ||||
|           :help="mailConfig.MAIL_HOST.description" | ||||
|           hide-asterisk | ||||
|         > | ||||
|           <a-input v-model.trim="form.MAIL_HOST" /> | ||||
|         </a-form-item> | ||||
|         <a-form-item | ||||
|           field="MAIL_PORT" | ||||
|           :label="mailConfig.MAIL_PORT.name" | ||||
|           :help="mailConfig.MAIL_PORT.description" | ||||
|           hide-asterisk | ||||
|         > | ||||
|           <a-input-number v-model="form.MAIL_PORT" :min="0" /> | ||||
|         </a-form-item> | ||||
|         <a-form-item | ||||
|           field="MAIL_USERNAME" | ||||
|           :label="mailConfig.MAIL_USERNAME.name" | ||||
|           :help="mailConfig.MAIL_USERNAME.description" | ||||
|           hide-asterisk | ||||
|         > | ||||
|           <a-input v-model.trim="form.MAIL_USERNAME" /> | ||||
|         </a-form-item> | ||||
|         <a-form-item | ||||
|           field="MAIL_PASSWORD" | ||||
|           :label="mailConfig.MAIL_PASSWORD?.name" | ||||
|           :help="mailConfig.MAIL_PASSWORD.description" | ||||
|           hide-asterisk | ||||
|         > | ||||
|           <a-input-password v-model.trim="form.MAIL_PASSWORD" /> | ||||
|         </a-form-item> | ||||
|         <a-form-item | ||||
|           field="MAIL_SSL_ENABLED" | ||||
|           :label="mailConfig.MAIL_SSL_ENABLED?.name" | ||||
|           :help="mailConfig.MAIL_SSL_ENABLED.description" | ||||
|           hide-asterisk | ||||
|         > | ||||
|           <a-switch | ||||
|             v-model="form.MAIL_SSL_ENABLED" | ||||
|             type="round" | ||||
|             :checked-value="1" | ||||
|             :unchecked-value="0" | ||||
|           > | ||||
|             <template #checked>启用</template> | ||||
|             <template #unchecked>禁用</template> | ||||
|           </a-switch> | ||||
|         </a-form-item> | ||||
|         <a-form-item | ||||
|           v-if="form.MAIL_SSL_ENABLED === '1'" | ||||
|           field="MAIL_SSL_PORT" | ||||
|           :label="mailConfig.MAIL_SSL_PORT.name" | ||||
|           :help="mailConfig.MAIL_SSL_PORT.description" | ||||
|           hide-asterisk | ||||
|         > | ||||
|           <a-input-number v-model="form.MAIL_SSL_PORT" :min="0" /> | ||||
|         </a-form-item> | ||||
|         <a-space style="margin-bottom: 16px"> | ||||
|           <a-button v-if="!isUpdate" v-permission="['system:mailConfig:update']" type="primary" @click="onUpdate"> | ||||
|             <template #icon><icon-edit /></template>修改 | ||||
|           </a-button> | ||||
|           <a-button v-if="!isUpdate" v-permission="['system:mailConfig:update']" @click="onResetValue"> | ||||
|             <template #icon><icon-undo /></template>恢复默认 | ||||
|           </a-button> | ||||
|           <a-button v-if="isUpdate" type="primary" @click="handleSave"> | ||||
|             <template #icon><icon-save /></template>保存 | ||||
|           </a-button> | ||||
|           <a-button v-if="isUpdate" @click="reset"> | ||||
|             <template #icon><icon-refresh /></template>重置 | ||||
|           </a-button> | ||||
|           <a-button v-if="isUpdate" @click="handleCancel"> | ||||
|             <template #icon><icon-undo /></template>取消 | ||||
|           </a-button> | ||||
|         </a-space> | ||||
|       </a-form> | ||||
|     </a-spin> | ||||
|   </div> | ||||
| </template> | ||||
|  | ||||
| <script setup lang="ts"> | ||||
| import { useWindowSize } from '@vueuse/core' | ||||
| import { type FormInstance, Message, Modal } from '@arco-design/web-vue' | ||||
| import { | ||||
|   type MailConfig, | ||||
|   type OptionResp, | ||||
|   listOption, | ||||
|   resetOptionValue, | ||||
|   updateOption, | ||||
| } from '@/apis/system' | ||||
| import { useResetReactive } from '@/hooks' | ||||
|  | ||||
| defineOptions({ name: 'SystemMailConfig' }) | ||||
| const { width } = useWindowSize() | ||||
| const loading = ref<boolean>(false) | ||||
| const formRef = ref<FormInstance>() | ||||
| const [form] = useResetReactive({ | ||||
|   MAIL_PROTOCOL: '', | ||||
|   MAIL_HOST: '', | ||||
|   MAIL_PORT: 0, | ||||
|   MAIL_USERNAME: '', | ||||
|   MAIL_PASSWORD: '', | ||||
|   MAIL_SSL_ENABLED: '', | ||||
|   MAIL_SSL_PORT: 0, | ||||
| }) | ||||
| const rules: FormInstance['rules'] = { | ||||
|   MAIL_HOST: [{ required: true, message: '请输入值' }], | ||||
|   MAIL_PORT: [{ required: true, message: '请输入值' }], | ||||
|   MAIL_USERNAME: [{ required: true, message: '请输入值' }], | ||||
|   MAIL_PASSWORD: [{ required: true, message: '请输入值' }], | ||||
|   MAIL_SSL_PORT: [{ required: true, message: '请输入值' }], | ||||
| } | ||||
|  | ||||
| const mailConfig = ref<MailConfig>({ | ||||
|   MAIL_PROTOCOL: {}, | ||||
|   MAIL_HOST: {}, | ||||
|   MAIL_PORT: {}, | ||||
|   MAIL_USERNAME: {}, | ||||
|   MAIL_PASSWORD: {}, | ||||
|   MAIL_SSL_ENABLED: {}, | ||||
|   MAIL_SSL_PORT: {}, | ||||
| }) | ||||
|  | ||||
| // 重置 | ||||
| const reset = () => { | ||||
|   formRef.value?.resetFields() | ||||
|   form.MAIL_PROTOCOL = mailConfig.value.MAIL_PROTOCOL.value || '' | ||||
|   form.MAIL_HOST = mailConfig.value.MAIL_HOST.value || '' | ||||
|   form.MAIL_PORT = mailConfig.value.MAIL_PORT.value || 0 | ||||
|   form.MAIL_USERNAME = mailConfig.value.MAIL_USERNAME.value || '' | ||||
|   form.MAIL_PASSWORD = mailConfig.value.MAIL_PASSWORD?.value || '' | ||||
|   form.MAIL_SSL_ENABLED = mailConfig.value.MAIL_SSL_ENABLED.value || '' | ||||
|   form.MAIL_SSL_PORT = mailConfig.value.MAIL_SSL_PORT.value || 0 | ||||
| } | ||||
|  | ||||
| const isUpdate = ref(false) | ||||
| // 修改 | ||||
| const onUpdate = () => { | ||||
|   isUpdate.value = true | ||||
| } | ||||
|  | ||||
| // 取消 | ||||
| const handleCancel = () => { | ||||
|   reset() | ||||
|   isUpdate.value = false | ||||
| } | ||||
|  | ||||
| const queryForm = { | ||||
|   category: 'MAIL', | ||||
| } | ||||
| // 查询列表数据 | ||||
| const getDataList = async () => { | ||||
|   loading.value = true | ||||
|   const { data } = await listOption(queryForm) | ||||
|   mailConfig.value = data.reduce((obj: MailConfig, option: OptionResp) => { | ||||
|     obj[option.code] = { ...option, value: ['MAIL_PORT', 'MAIL_SSL_PORT'].includes(option.code) ? Number.parseInt(option.value) : option.value } | ||||
|     return obj | ||||
|   }, {}) | ||||
|   handleCancel() | ||||
|   loading.value = false | ||||
| } | ||||
|  | ||||
| // 保存 | ||||
| const handleSave = async () => { | ||||
|   const isInvalid = await formRef.value?.validate() | ||||
|   if (isInvalid) return false | ||||
|   await updateOption( | ||||
|     Object.entries(form).map(([key, value]) => { | ||||
|       return { id: mailConfig.value[key].id, code: key, value } | ||||
|     }), | ||||
|   ) | ||||
|   await getDataList() | ||||
|   Message.success('保存成功') | ||||
| } | ||||
|  | ||||
| // 恢复默认 | ||||
| const handleResetValue = async () => { | ||||
|   await resetOptionValue(queryForm) | ||||
|   Message.success('恢复成功') | ||||
|   await getDataList() | ||||
| } | ||||
| const onResetValue = () => { | ||||
|   Modal.warning({ | ||||
|     title: '警告', | ||||
|     content: '确认恢复邮件配置为默认值吗?', | ||||
|     hideCancel: false, | ||||
|     maskClosable: false, | ||||
|     onOk: handleResetValue, | ||||
|   }) | ||||
| } | ||||
|  | ||||
| onMounted(() => { | ||||
|   getDataList() | ||||
| }) | ||||
| </script> | ||||
|  | ||||
| <style scoped lang="scss"> | ||||
| :deep(.arco-form-item.arco-form-item-has-help) { | ||||
|   margin-bottom: 5px; | ||||
| } | ||||
|  | ||||
| :deep(.form .arco-input-wrapper), | ||||
| :deep(.arco-select-view-single) { | ||||
|   width: 220px; | ||||
| } | ||||
| </style> | ||||
							
								
								
									
										279
									
								
								src/views/system/config/security/index.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										279
									
								
								src/views/system/config/security/index.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,279 @@ | ||||
| <template> | ||||
|   <div class="gi_page"> | ||||
|     <a-spin :loading="loading"> | ||||
|       <a-form | ||||
|         ref="formRef" | ||||
|         :model="form" | ||||
|         :rules="rules" | ||||
|         auto-label-width | ||||
|         label-align="left" | ||||
|         :layout="width >= 500 ? 'horizontal' : 'vertical'" | ||||
|         :disabled="!isUpdate" | ||||
|         scroll-to-first-error | ||||
|         class="form" | ||||
|       > | ||||
|         <a-form-item | ||||
|           field="PASSWORD_ERROR_LOCK_COUNT" | ||||
|           :label="securityConfig.PASSWORD_ERROR_LOCK_COUNT.name" | ||||
|           :help="securityConfig.PASSWORD_ERROR_LOCK_COUNT.description" | ||||
|           hide-asterisk | ||||
|         > | ||||
|           <a-input-number | ||||
|             v-model="form.PASSWORD_ERROR_LOCK_COUNT" | ||||
|             :default-value="0" | ||||
|             :precision="0" | ||||
|             :min="0" | ||||
|             :max="10" | ||||
|           > | ||||
|             <template #append>次</template> | ||||
|           </a-input-number> | ||||
|         </a-form-item> | ||||
|         <a-form-item | ||||
|           field="PASSWORD_ERROR_LOCK_MINUTES" | ||||
|           :label="securityConfig.PASSWORD_ERROR_LOCK_MINUTES.name" | ||||
|           :help="securityConfig.PASSWORD_ERROR_LOCK_MINUTES.description" | ||||
|           hide-asterisk | ||||
|         > | ||||
|           <a-input-number | ||||
|             v-model="form.PASSWORD_ERROR_LOCK_MINUTES" | ||||
|             :precision="0" | ||||
|             :min="1" | ||||
|             :max="1440" | ||||
|           > | ||||
|             <template #append>分钟</template> | ||||
|           </a-input-number> | ||||
|         </a-form-item> | ||||
|         <a-form-item | ||||
|           field="PASSWORD_EXPIRATION_DAYS" | ||||
|           :label="securityConfig.PASSWORD_EXPIRATION_DAYS.name" | ||||
|           :help="securityConfig.PASSWORD_EXPIRATION_DAYS.description" | ||||
|           hide-asterisk | ||||
|         > | ||||
|           <a-input-number | ||||
|             v-model="form.PASSWORD_EXPIRATION_DAYS" | ||||
|             :precision="0" | ||||
|             :min="0" | ||||
|             :max="999" | ||||
|           > | ||||
|             <template #append>天</template> | ||||
|           </a-input-number> | ||||
|         </a-form-item> | ||||
|         <a-form-item | ||||
|           :label="securityConfig.PASSWORD_EXPIRATION_WARNING_DAYS.name" | ||||
|           field="PASSWORD_EXPIRATION_WARNING_DAYS" | ||||
|           :help="securityConfig.PASSWORD_EXPIRATION_WARNING_DAYS.description" | ||||
|           hide-asterisk | ||||
|         > | ||||
|           <a-input-number | ||||
|             v-model="form.PASSWORD_EXPIRATION_WARNING_DAYS" | ||||
|             :precision="0" | ||||
|             :min="0" | ||||
|             :max="998" | ||||
|           > | ||||
|             <template #append>天</template> | ||||
|           </a-input-number> | ||||
|         </a-form-item> | ||||
|         <a-form-item | ||||
|           field="PASSWORD_REPETITION_TIMES" | ||||
|           :label="securityConfig.PASSWORD_REPETITION_TIMES.name" | ||||
|           :help="securityConfig.PASSWORD_REPETITION_TIMES.description" | ||||
|           hide-asterisk | ||||
|         > | ||||
|           <a-input-number | ||||
|             v-model="form.PASSWORD_REPETITION_TIMES" | ||||
|             :precision="0" | ||||
|             :min="3" | ||||
|             :max="32" | ||||
|           > | ||||
|             <template #append>次</template> | ||||
|           </a-input-number> | ||||
|         </a-form-item> | ||||
|         <a-form-item | ||||
|           field="PASSWORD_MIN_LENGTH" | ||||
|           :label="securityConfig.PASSWORD_MIN_LENGTH.name" | ||||
|           :help="securityConfig.PASSWORD_MIN_LENGTH.description" | ||||
|           hide-asterisk | ||||
|         > | ||||
|           <a-input-number | ||||
|             v-model="form.PASSWORD_MIN_LENGTH" | ||||
|             :precision="0" | ||||
|             :min="8" | ||||
|             :max="32" | ||||
|           /> | ||||
|         </a-form-item> | ||||
|         <a-form-item | ||||
|           field="PASSWORD_ALLOW_CONTAIN_USERNAME" | ||||
|           :label="securityConfig.PASSWORD_ALLOW_CONTAIN_USERNAME.name" | ||||
|           :help="securityConfig.PASSWORD_ALLOW_CONTAIN_USERNAME.description" | ||||
|         > | ||||
|           <a-switch v-model="form.PASSWORD_ALLOW_CONTAIN_USERNAME" type="round" :checked-value="1" :unchecked-value="0"> | ||||
|             <template #checked>是</template> | ||||
|             <template #unchecked>否</template> | ||||
|           </a-switch> | ||||
|         </a-form-item> | ||||
|         <a-form-item | ||||
|           field="PASSWORD_REQUIRE_SYMBOLS" | ||||
|           :label="securityConfig.PASSWORD_REQUIRE_SYMBOLS.name" | ||||
|           :help="securityConfig.PASSWORD_REQUIRE_SYMBOLS.description" | ||||
|         > | ||||
|           <a-switch v-model="form.PASSWORD_REQUIRE_SYMBOLS" type="round" :checked-value="1" :unchecked-value="0"> | ||||
|             <template #checked>是</template> | ||||
|             <template #unchecked>否</template> | ||||
|           </a-switch> | ||||
|         </a-form-item> | ||||
|         <a-space style="margin-bottom: 16px"> | ||||
|           <a-button v-if="!isUpdate" v-permission="['system:securityConfig:update']" type="primary" @click="onUpdate"> | ||||
|             <template #icon><icon-edit /></template>修改 | ||||
|           </a-button> | ||||
|           <a-button v-if="!isUpdate" v-permission="['system:securityConfig:update']" @click="onResetValue"> | ||||
|             <template #icon><icon-undo /></template>恢复默认 | ||||
|           </a-button> | ||||
|           <a-button v-if="isUpdate" type="primary" @click="handleSave"> | ||||
|             <template #icon><icon-save /></template>保存 | ||||
|           </a-button> | ||||
|           <a-button v-if="isUpdate" @click="reset"> | ||||
|             <template #icon><icon-refresh /></template>重置 | ||||
|           </a-button> | ||||
|           <a-button v-if="isUpdate" @click="handleCancel"> | ||||
|             <template #icon><icon-undo /></template>取消 | ||||
|           </a-button> | ||||
|         </a-space> | ||||
|       </a-form> | ||||
|     </a-spin> | ||||
|   </div> | ||||
| </template> | ||||
|  | ||||
| <script setup lang="ts"> | ||||
| import { useWindowSize } from '@vueuse/core' | ||||
| import { type FormInstance, Message, Modal } from '@arco-design/web-vue' | ||||
| import { type OptionResp, type SecurityConfig, listOption, resetOptionValue, updateOption } from '@/apis/system' | ||||
| import { useResetReactive } from '@/hooks' | ||||
|  | ||||
| defineOptions({ name: 'SystemSecurityConfig' }) | ||||
| const { width } = useWindowSize() | ||||
|  | ||||
| const loading = ref<boolean>(false) | ||||
| const formRef = ref<FormInstance>() | ||||
| const [form] = useResetReactive({ | ||||
|   PASSWORD_ERROR_LOCK_COUNT: 0, | ||||
|   PASSWORD_ERROR_LOCK_MINUTES: 0, | ||||
|   PASSWORD_EXPIRATION_DAYS: 0, | ||||
|   PASSWORD_EXPIRATION_WARNING_DAYS: 0, | ||||
|   PASSWORD_REPETITION_TIMES: 0, | ||||
|   PASSWORD_MIN_LENGTH: 0, | ||||
|   PASSWORD_ALLOW_CONTAIN_USERNAME: 0, | ||||
|   PASSWORD_REQUIRE_SYMBOLS: 0, | ||||
| }) | ||||
| const rules: FormInstance['rules'] = { | ||||
|   PASSWORD_ERROR_LOCK_COUNT: [{ required: true, message: '请输入值' }], | ||||
|   PASSWORD_ERROR_LOCK_MINUTES: [{ required: true, message: '请输入值' }], | ||||
|   PASSWORD_EXPIRATION_DAYS: [{ required: true, message: '请输入值' }], | ||||
|   PASSWORD_EXPIRATION_WARNING_DAYS: [ | ||||
|     { required: true, message: '请输入值' }, | ||||
|     { | ||||
|       validator: (value, callback) => { | ||||
|         if (form.PASSWORD_EXPIRATION_DAYS > 0 && value >= form.PASSWORD_EXPIRATION_DAYS) { | ||||
|           callback('密码到期提醒时间应小于密码有效期') | ||||
|         } else { | ||||
|           callback() | ||||
|         } | ||||
|       }, | ||||
|     }, | ||||
|   ], | ||||
|   PASSWORD_REPETITION_TIMES: [{ required: true, message: '请输入值' }], | ||||
|   PASSWORD_MIN_LENGTH: [{ required: true, message: '请输入值' }], | ||||
| } | ||||
|  | ||||
| const securityConfig = ref<SecurityConfig>({ | ||||
|   PASSWORD_ERROR_LOCK_COUNT: {}, | ||||
|   PASSWORD_ERROR_LOCK_MINUTES: {}, | ||||
|   PASSWORD_EXPIRATION_DAYS: {}, | ||||
|   PASSWORD_EXPIRATION_WARNING_DAYS: {}, | ||||
|   PASSWORD_REPETITION_TIMES: {}, | ||||
|   PASSWORD_MIN_LENGTH: {}, | ||||
|   PASSWORD_ALLOW_CONTAIN_USERNAME: {}, | ||||
|   PASSWORD_REQUIRE_SYMBOLS: {}, | ||||
| }) | ||||
| // 重置 | ||||
| const reset = () => { | ||||
|   formRef.value?.resetFields() | ||||
|   form.PASSWORD_ERROR_LOCK_COUNT = securityConfig.value.PASSWORD_ERROR_LOCK_COUNT.value || 0 | ||||
|   form.PASSWORD_ERROR_LOCK_MINUTES = securityConfig.value.PASSWORD_ERROR_LOCK_MINUTES.value || 0 | ||||
|   form.PASSWORD_EXPIRATION_DAYS = securityConfig.value.PASSWORD_EXPIRATION_DAYS.value || 0 | ||||
|   form.PASSWORD_EXPIRATION_WARNING_DAYS = securityConfig.value.PASSWORD_EXPIRATION_WARNING_DAYS.value || 0 | ||||
|   form.PASSWORD_REPETITION_TIMES = securityConfig.value.PASSWORD_REPETITION_TIMES.value || 0 | ||||
|   form.PASSWORD_MIN_LENGTH = securityConfig.value.PASSWORD_MIN_LENGTH.value || 0 | ||||
|   form.PASSWORD_ALLOW_CONTAIN_USERNAME = securityConfig.value.PASSWORD_ALLOW_CONTAIN_USERNAME.value || 0 | ||||
|   form.PASSWORD_REQUIRE_SYMBOLS = securityConfig.value.PASSWORD_REQUIRE_SYMBOLS.value || 0 | ||||
| } | ||||
|  | ||||
| const isUpdate = ref(false) | ||||
| // 修改 | ||||
| const onUpdate = () => { | ||||
|   isUpdate.value = true | ||||
| } | ||||
|  | ||||
| // 取消 | ||||
| const handleCancel = () => { | ||||
|   reset() | ||||
|   isUpdate.value = false | ||||
| } | ||||
|  | ||||
| const queryForm = { | ||||
|   category: 'PASSWORD', | ||||
| } | ||||
| // 查询列表数据 | ||||
| const getDataList = async () => { | ||||
|   loading.value = true | ||||
|   const { data } = await listOption(queryForm) | ||||
|   securityConfig.value = data.reduce((obj: SecurityConfig, option: OptionResp) => { | ||||
|     obj[option.code] = { ...option, value: Number.parseInt(option.value) } | ||||
|     return obj | ||||
|   }, {}) | ||||
|   handleCancel() | ||||
|   loading.value = false | ||||
| } | ||||
|  | ||||
| // 保存 | ||||
| const handleSave = async () => { | ||||
|   const isInvalid = await formRef.value?.validate() | ||||
|   if (isInvalid) return false | ||||
|   await updateOption( | ||||
|     Object.entries(form).map(([key, value]) => { | ||||
|       return { id: securityConfig.value[key].id, code: key, value } | ||||
|     }), | ||||
|   ) | ||||
|   await getDataList() | ||||
|   Message.success('保存成功') | ||||
| } | ||||
|  | ||||
| // 恢复默认 | ||||
| const handleResetValue = async () => { | ||||
|   await resetOptionValue(queryForm) | ||||
|   Message.success('恢复成功') | ||||
|   await getDataList() | ||||
| } | ||||
| const onResetValue = () => { | ||||
|   Modal.warning({ | ||||
|     title: '警告', | ||||
|     content: '确认恢复安全配置为默认值吗?', | ||||
|     hideCancel: false, | ||||
|     maskClosable: false, | ||||
|     onOk: handleResetValue, | ||||
|   }) | ||||
| } | ||||
|  | ||||
| onMounted(() => { | ||||
|   getDataList() | ||||
| }) | ||||
| </script> | ||||
|  | ||||
| <style scoped lang="scss"> | ||||
| :deep(.arco-form-item.arco-form-item-has-help) { | ||||
|   margin-bottom: 5px; | ||||
| } | ||||
|  | ||||
| :deep(.form .arco-input-wrapper) { | ||||
|   width: 200px; | ||||
| } | ||||
| </style> | ||||
| @@ -1,122 +1,124 @@ | ||||
| <template> | ||||
|   <a-spin :loading="loading"> | ||||
|     <a-form | ||||
|       ref="formRef" | ||||
|       :model="form" | ||||
|       :rules="rules" | ||||
|       size="large" | ||||
|       layout="vertical" | ||||
|       :disabled="!isUpdate" | ||||
|       class="form" | ||||
|     > | ||||
|       <a-form-item class="image-item" field="SITE_LOGO" hide-label> | ||||
|         {{ siteConfig.SITE_LOGO.name }} | ||||
|         <template #extra> | ||||
|           {{ siteConfig.SITE_LOGO.description }} | ||||
|           <br /> | ||||
|           <a-upload | ||||
|             :file-list="logoFile ? [logoFile] : []" accept="image/*" :show-file-list="false" | ||||
|             :custom-request="handleUploadLogo" @change="handleChangeLogo" | ||||
|           > | ||||
|             <template #upload-button> | ||||
|               <div | ||||
|                 :class="`arco-upload-list-item${logoFile && logoFile.status === 'error' ? ' arco-upload-list-item-error' : '' | ||||
|                 }`" | ||||
|               > | ||||
|                 <div v-if="logoFile && logoFile.url" class="arco-upload-list-picture custom-upload-avatar logo"> | ||||
|                   <img :src="logoFile.url" alt="Logo" /> | ||||
|                   <div v-if="isUpdate" class="arco-upload-list-picture-mask logo"> | ||||
|                     <IconEdit /> | ||||
|                   </div> | ||||
|                 </div> | ||||
|                 <div v-else class="arco-upload-picture-card logo"> | ||||
|                   <div class="arco-upload-picture-card-text"> | ||||
|                     <icon-upload /> | ||||
|                   </div> | ||||
|                 </div> | ||||
|               </div> | ||||
|             </template> | ||||
|           </a-upload> | ||||
|         </template> | ||||
|       </a-form-item> | ||||
|       <a-form-item class="image-item" field="SITE_FAVICON" hide-label> | ||||
|         {{ siteConfig.SITE_FAVICON.name }} | ||||
|         <template #extra> | ||||
|           {{ siteConfig.SITE_FAVICON.description }} | ||||
|           <br /> | ||||
|           <a-upload | ||||
|             :file-list="faviconFile ? [faviconFile] : []" accept="image/*" :show-file-list="false" | ||||
|             :custom-request="handleUploadFavicon" @change="handleChangeFavicon" | ||||
|           > | ||||
|             <template #upload-button> | ||||
|               <div | ||||
|                 :class="`arco-upload-list-item${faviconFile && faviconFile.status === 'error' ? ' arco-upload-list-item-error' : '' | ||||
|                 }`" | ||||
|               > | ||||
|   <div class="gi_page"> | ||||
|     <a-spin :loading="loading"> | ||||
|       <a-form | ||||
|         ref="formRef" | ||||
|         :model="form" | ||||
|         :rules="rules" | ||||
|         size="large" | ||||
|         layout="vertical" | ||||
|         :disabled="!isUpdate" | ||||
|         class="form" | ||||
|       > | ||||
|         <a-form-item class="image-item" field="SITE_LOGO" hide-label> | ||||
|           {{ siteConfig.SITE_LOGO.name }} | ||||
|           <template #extra> | ||||
|             {{ siteConfig.SITE_LOGO.description }} | ||||
|             <br /> | ||||
|             <a-upload | ||||
|               :file-list="logoFile ? [logoFile] : []" accept="image/*" :show-file-list="false" | ||||
|               :custom-request="handleUploadLogo" @change="handleChangeLogo" | ||||
|             > | ||||
|               <template #upload-button> | ||||
|                 <div | ||||
|                   v-if="faviconFile && faviconFile.url" | ||||
|                   class="arco-upload-list-picture custom-upload-avatar favicon" | ||||
|                   :class="`arco-upload-list-item${logoFile && logoFile.status === 'error' ? ' arco-upload-list-item-error' : '' | ||||
|                   }`" | ||||
|                 > | ||||
|                   <img :src="faviconFile.url" alt="favicon" /> | ||||
|                   <div v-if="isUpdate" class="arco-upload-list-picture-mask favicon"> | ||||
|                     <IconEdit /> | ||||
|                   <div v-if="logoFile && logoFile.url" class="arco-upload-list-picture custom-upload-avatar logo"> | ||||
|                     <img :src="logoFile.url" alt="Logo" /> | ||||
|                     <div v-if="isUpdate" class="arco-upload-list-picture-mask logo"> | ||||
|                       <IconEdit /> | ||||
|                     </div> | ||||
|                   </div> | ||||
|                   <div v-else class="arco-upload-picture-card logo"> | ||||
|                     <div class="arco-upload-picture-card-text"> | ||||
|                       <icon-upload /> | ||||
|                     </div> | ||||
|                   </div> | ||||
|                 </div> | ||||
|                 <div v-else class="arco-upload-picture-card favicon"> | ||||
|                   <div class="arco-upload-picture-card-text"> | ||||
|                     <icon-upload /> | ||||
|               </template> | ||||
|             </a-upload> | ||||
|           </template> | ||||
|         </a-form-item> | ||||
|         <a-form-item class="image-item" field="SITE_FAVICON" hide-label> | ||||
|           {{ siteConfig.SITE_FAVICON.name }} | ||||
|           <template #extra> | ||||
|             {{ siteConfig.SITE_FAVICON.description }} | ||||
|             <br /> | ||||
|             <a-upload | ||||
|               :file-list="faviconFile ? [faviconFile] : []" accept="image/*" :show-file-list="false" | ||||
|               :custom-request="handleUploadFavicon" @change="handleChangeFavicon" | ||||
|             > | ||||
|               <template #upload-button> | ||||
|                 <div | ||||
|                   :class="`arco-upload-list-item${faviconFile && faviconFile.status === 'error' ? ' arco-upload-list-item-error' : '' | ||||
|                   }`" | ||||
|                 > | ||||
|                   <div | ||||
|                     v-if="faviconFile && faviconFile.url" | ||||
|                     class="arco-upload-list-picture custom-upload-avatar favicon" | ||||
|                   > | ||||
|                     <img :src="faviconFile.url" alt="favicon" /> | ||||
|                     <div v-if="isUpdate" class="arco-upload-list-picture-mask favicon"> | ||||
|                       <IconEdit /> | ||||
|                     </div> | ||||
|                   </div> | ||||
|                   <div v-else class="arco-upload-picture-card favicon"> | ||||
|                     <div class="arco-upload-picture-card-text"> | ||||
|                       <icon-upload /> | ||||
|                     </div> | ||||
|                   </div> | ||||
|                 </div> | ||||
|               </div> | ||||
|             </template> | ||||
|           </a-upload> | ||||
|         </template> | ||||
|       </a-form-item> | ||||
|       <a-form-item class="input-item" field="SITE_TITLE" :label="siteConfig.SITE_TITLE.name" :help="siteConfig.SITE_TITLE.description"> | ||||
|         <a-input v-model.trim="form.SITE_TITLE" placeholder="请输入系统名称" :max-length="18" show-word-limit /> | ||||
|       </a-form-item> | ||||
|       <a-form-item class="input-item" field="SITE_DESCRIPTION" :label="siteConfig.SITE_DESCRIPTION.name" :help="siteConfig.SITE_DESCRIPTION.description"> | ||||
|         <a-textarea | ||||
|           v-model.trim="form.SITE_DESCRIPTION" | ||||
|           placeholder="请输入系统描述" | ||||
|           :auto-size="{ minRows: 1, maxRows: 3 }" | ||||
|         /> | ||||
|       </a-form-item> | ||||
|       <a-form-item class="input-item" field="SITE_COPYRIGHT" :label="siteConfig.SITE_COPYRIGHT.name" :help="siteConfig.SITE_COPYRIGHT.description"> | ||||
|         <a-input v-model.trim="form.SITE_COPYRIGHT" placeholder="请输入版权声明" /> | ||||
|       </a-form-item> | ||||
|       <a-form-item field="SITE_BEIAN" :label="siteConfig.SITE_BEIAN.name" :help="siteConfig.SITE_BEIAN.description"> | ||||
|         <a-input v-model.trim="form.SITE_BEIAN" placeholder="请输入备案号" :max-length="30" show-word-limit /> | ||||
|       </a-form-item> | ||||
|       <a-space style="margin-top: 16px"> | ||||
|         <a-button v-if="!isUpdate" v-permission="['system:config:update']" type="primary" @click="onUpdate"> | ||||
|           <template #icon> | ||||
|             <icon-edit /> | ||||
|           </template>修改 | ||||
|         </a-button> | ||||
|         <a-button v-if="!isUpdate" v-permission="['system:config:reset']" @click="onResetValue"> | ||||
|           <template #icon> | ||||
|             <icon-undo /> | ||||
|           </template>恢复默认 | ||||
|         </a-button> | ||||
|         <a-button v-if="isUpdate" type="primary" @click="handleSave"> | ||||
|           <template #icon> | ||||
|             <icon-save /> | ||||
|           </template>保存 | ||||
|         </a-button> | ||||
|         <a-button v-if="isUpdate" @click="reset"> | ||||
|           <template #icon> | ||||
|             <icon-refresh /> | ||||
|           </template>重置 | ||||
|         </a-button> | ||||
|         <a-button v-if="isUpdate" @click="handleCancel"> | ||||
|           <template #icon> | ||||
|             <icon-undo /> | ||||
|           </template>取消 | ||||
|         </a-button> | ||||
|       </a-space> | ||||
|     </a-form> | ||||
|   </a-spin> | ||||
|               </template> | ||||
|             </a-upload> | ||||
|           </template> | ||||
|         </a-form-item> | ||||
|         <a-form-item class="input-item" field="SITE_TITLE" :label="siteConfig.SITE_TITLE.name" :help="siteConfig.SITE_TITLE.description"> | ||||
|           <a-input v-model.trim="form.SITE_TITLE" placeholder="请输入系统名称" :max-length="18" show-word-limit /> | ||||
|         </a-form-item> | ||||
|         <a-form-item class="input-item" field="SITE_DESCRIPTION" :label="siteConfig.SITE_DESCRIPTION.name" :help="siteConfig.SITE_DESCRIPTION.description"> | ||||
|           <a-textarea | ||||
|             v-model.trim="form.SITE_DESCRIPTION" | ||||
|             placeholder="请输入系统描述" | ||||
|             :auto-size="{ minRows: 1, maxRows: 3 }" | ||||
|           /> | ||||
|         </a-form-item> | ||||
|         <a-form-item class="input-item" field="SITE_COPYRIGHT" :label="siteConfig.SITE_COPYRIGHT.name" :help="siteConfig.SITE_COPYRIGHT.description"> | ||||
|           <a-input v-model.trim="form.SITE_COPYRIGHT" placeholder="请输入版权声明" /> | ||||
|         </a-form-item> | ||||
|         <a-form-item field="SITE_BEIAN" :label="siteConfig.SITE_BEIAN.name" :help="siteConfig.SITE_BEIAN.description"> | ||||
|           <a-input v-model.trim="form.SITE_BEIAN" placeholder="请输入备案号" :max-length="30" show-word-limit /> | ||||
|         </a-form-item> | ||||
|         <a-space style="margin-top: 16px"> | ||||
|           <a-button v-if="!isUpdate" v-permission="['system:siteConfig:update']" type="primary" @click="onUpdate"> | ||||
|             <template #icon> | ||||
|               <icon-edit /> | ||||
|             </template>修改 | ||||
|           </a-button> | ||||
|           <a-button v-if="!isUpdate" v-permission="['system:siteConfig:update']" @click="onResetValue"> | ||||
|             <template #icon> | ||||
|               <icon-undo /> | ||||
|             </template>恢复默认 | ||||
|           </a-button> | ||||
|           <a-button v-if="isUpdate" type="primary" @click="handleSave"> | ||||
|             <template #icon> | ||||
|               <icon-save /> | ||||
|             </template>保存 | ||||
|           </a-button> | ||||
|           <a-button v-if="isUpdate" @click="reset"> | ||||
|             <template #icon> | ||||
|               <icon-refresh /> | ||||
|             </template>重置 | ||||
|           </a-button> | ||||
|           <a-button v-if="isUpdate" @click="handleCancel"> | ||||
|             <template #icon> | ||||
|               <icon-undo /> | ||||
|             </template>取消 | ||||
|           </a-button> | ||||
|         </a-space> | ||||
|       </a-form> | ||||
|     </a-spin> | ||||
|   </div> | ||||
| </template> | ||||
| 
 | ||||
| <script setup lang="ts"> | ||||
| @@ -132,7 +134,7 @@ import { useAppStore } from '@/stores' | ||||
| import { useResetReactive } from '@/hooks' | ||||
| import { fileToBase64 } from '@/utils' | ||||
| 
 | ||||
| defineOptions({ name: 'BasicSetting' }) | ||||
| defineOptions({ name: 'SystemSiteConfig' }) | ||||
| 
 | ||||
| const loading = ref<boolean>(false) | ||||
| const formRef = ref<FormInstance>() | ||||
| @@ -1,5 +1,5 @@ | ||||
| <template> | ||||
|   <GiPageLayout> | ||||
|   <GiPageLayout :margin="false" :body-style="{ padding: 0 }"> | ||||
|     <GiTable | ||||
|       row-key="id" | ||||
|       :data="dataList" | ||||
| @@ -169,7 +169,7 @@ const router = useRouter() | ||||
| // 发送记录 | ||||
| const onLog = (record: SmsConfigResp) => { | ||||
|   router.push({ | ||||
|     path: '/system/sms/log', | ||||
|     name: 'SystemSmsLog', | ||||
|     query: { configId: record.id }, | ||||
|   }) | ||||
| } | ||||
| @@ -1,33 +1,35 @@ | ||||
| <template> | ||||
|   <GiPageLayout> | ||||
|     <a-tabs v-model:active-key="activeKey" type="rounded" @change="change"> | ||||
|       <a-tab-pane key="all"> | ||||
|         <template #title>全部</template> | ||||
|         <a-card title="本地存储" :bordered="false" class="gi_card_title"> | ||||
|   <GiPageLayout :margin="false" :body-style="{ padding: 0 }"> | ||||
|     <div class="gi_page"> | ||||
|       <a-tabs v-model:active-key="activeKey" type="rounded" @change="change"> | ||||
|         <a-tab-pane key="all"> | ||||
|           <template #title>全部</template> | ||||
|           <a-card title="本地存储" :bordered="false" class="gi_card_title"> | ||||
|             <StorageLocal :data="dataMap['1']" :loading="loading" @save-success="getDataList" /> | ||||
|           </a-card> | ||||
|           <a-card title="对象存储" :bordered="false" class="gi_card_title"> | ||||
|             <StorageOss :data="dataMap['2']" :loading="loading" @save-success="getDataList" /> | ||||
|           </a-card> | ||||
|         </a-tab-pane> | ||||
|         <a-tab-pane key="1"> | ||||
|           <template #title>本地存储</template> | ||||
|           <StorageLocal :data="dataMap['1']" :loading="loading" @save-success="getDataList" /> | ||||
|         </a-card> | ||||
|         <a-card title="对象存储" :bordered="false" class="gi_card_title"> | ||||
|         </a-tab-pane> | ||||
|         <a-tab-pane key="2"> | ||||
|           <template #title>对象存储</template> | ||||
|           <StorageOss :data="dataMap['2']" :loading="loading" @save-success="getDataList" /> | ||||
|         </a-card> | ||||
|       </a-tab-pane> | ||||
|       <a-tab-pane key="1"> | ||||
|         <template #title>本地存储</template> | ||||
|         <StorageLocal :data="dataMap['1']" :loading="loading" @save-success="getDataList" /> | ||||
|       </a-tab-pane> | ||||
|       <a-tab-pane key="2"> | ||||
|         <template #title>对象存储</template> | ||||
|         <StorageOss :data="dataMap['2']" :loading="loading" @save-success="getDataList" /> | ||||
|       </a-tab-pane> | ||||
|       <template #extra> | ||||
|         <a-input-search | ||||
|           v-model="queryForm.description" | ||||
|           placeholder="搜索名称/编码" | ||||
|           style="width: 240px;" | ||||
|           allow-clear | ||||
|           @search="getDataList" | ||||
|         /> | ||||
|       </template> | ||||
|     </a-tabs> | ||||
|         </a-tab-pane> | ||||
|         <template #extra> | ||||
|           <a-input-search | ||||
|             v-model="queryForm.description" | ||||
|             placeholder="搜索名称/编码" | ||||
|             style="width: 240px;" | ||||
|             allow-clear | ||||
|             @search="getDataList" | ||||
|           /> | ||||
|         </template> | ||||
|       </a-tabs> | ||||
|     </div> | ||||
|   </GiPageLayout> | ||||
| </template> | ||||
| 
 | ||||
| @@ -69,12 +71,8 @@ onMounted(() => { | ||||
| </script> | ||||
| 
 | ||||
| <style scoped lang="scss"> | ||||
| .gi_table_page { | ||||
|   overflow-y: auto; | ||||
| 
 | ||||
|   :deep(.arco-tabs) { | ||||
|     overflow: visible; | ||||
|   } | ||||
| .gi_page { | ||||
|   padding: 0; | ||||
| } | ||||
| 
 | ||||
| .block-title { | ||||
| @@ -30,6 +30,4 @@ const handleSelectRole = (keys: Array<any>) => { | ||||
| } | ||||
| </script> | ||||
|  | ||||
| <style scoped lang="scss"> | ||||
|  | ||||
| </style> | ||||
| <style scoped lang="scss"></style> | ||||
|   | ||||
		Reference in New Issue
	
	Block a user