chore:字典组件优化,支持数组格式,其他模块代码优化

This commit is contained in:
AN 2025-04-19 08:18:07 +08:00
parent 8b1be45eb5
commit 8c5ea2ae72
10 changed files with 138 additions and 43 deletions

View File

@ -50,6 +50,7 @@
"@sa/hooks": "workspace:*", "@sa/hooks": "workspace:*",
"@sa/materials": "workspace:*", "@sa/materials": "workspace:*",
"@sa/utils": "workspace:*", "@sa/utils": "workspace:*",
"@tinymce/tinymce-vue": "^6.1.0",
"@vueuse/core": "12.5.0", "@vueuse/core": "12.5.0",
"clipboard": "2.0.11", "clipboard": "2.0.11",
"dayjs": "1.11.13", "dayjs": "1.11.13",

View File

@ -8,14 +8,16 @@ defineOptions({ name: 'DictSelect' });
interface Props { interface Props {
dictCode: string; dictCode: string;
immediate?: boolean; immediate?: boolean;
multiple?: boolean;
[key: string]: any; [key: string]: any;
} }
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
immediate: false immediate: false,
multiple: false
}); });
const value = defineModel<string | null>('value', { required: false }); const value = defineModel<string | string[] | null>('value', { required: false });
const attrs: SelectProps = useAttrs(); const attrs: SelectProps = useAttrs();
const { options } = useDict(props.dictCode, props.immediate); const { options } = useDict(props.dictCode, props.immediate);
@ -24,6 +26,7 @@ const { options } = useDict(props.dictCode, props.immediate);
<template> <template>
<NSelect <NSelect
v-model:value="value" v-model:value="value"
:multiple="multiple"
:loading="!options.length" :loading="!options.length"
:options="options" :options="options"
:clear-filter-after-select="false" :clear-filter-after-select="false"

View File

@ -7,10 +7,10 @@ import { isNotNull } from '@/utils/common';
defineOptions({ name: 'DictTag' }); defineOptions({ name: 'DictTag' });
interface Props { interface Props {
value?: string | number; value?: string[] | number[] | string | number;
dictCode?: string; dictCode?: string;
immediate?: boolean; immediate?: boolean;
dictData?: Api.System.DictData; dictData?: Api.System.DictData[];
[key: string]: any; [key: string]: any;
} }
@ -18,29 +18,38 @@ const props = withDefaults(defineProps<Props>(), {
immediate: false, immediate: false,
dictData: undefined, dictData: undefined,
dictCode: '', dictCode: '',
value: '' value: () => []
}); });
const attrs = useAttrs() as TagProps; const attrs = useAttrs() as TagProps;
const dictTagData = computed(() => { const dictTagData = computed<Api.System.DictData[]>(() => {
if (props.dictData) { if (props.dictData) {
return props.dictData; return props.dictData;
} }
// props.value 0 // props.value 0
if (props.dictCode && isNotNull(props.value)) { if (props.dictCode && isNotNull(props.value)) {
const { transformDictData } = useDict(props.dictCode, props.immediate); const { transformDictData } = useDict(props.dictCode, props.immediate);
return transformDictData(String(props.value)); return transformDictData(props.value) || [];
} }
return null; return [];
}); });
</script> </script>
<template> <template>
<NTag v-if="dictTagData" :class="dictTagData.cssClass" :type="dictTagData.listClass" v-bind="attrs"> <div v-if="dictTagData.length">
{{ dictTagData.dictLabel }} <NTag
</NTag> v-for="item in dictTagData"
:key="item.dictValue"
class="mb-2 mr-2"
:class="[item.cssClass]"
:type="item.listClass"
v-bind="attrs"
>
{{ item.dictLabel }}
</NTag>
</div>
</template> </template>
<style scoped></style> <style scoped></style>

View File

@ -51,20 +51,19 @@ const jsonData = computed(() => {
<template> <template>
<div class="json-preview"> <div class="json-preview">
<template v-if="jsonData"> <VueJsonPretty
<VueJsonPretty v-if="jsonData"
:data="jsonData" :data="jsonData"
:deep="deep" :deep="deep"
:show-double-quotes="showDoubleQuotes" :show-double-quotes="showDoubleQuotes"
:show-length="showLength" :show-length="showLength"
:show-line="showLine" :show-line="showLine"
:show-line-number="showLineNumber" :show-line-number="showLineNumber"
:show-icon="showIcon" :show-icon="showIcon"
:show-select-controller="showSelectController" :show-select-controller="showSelectController"
:collapsed-level="collapsedLevel" :collapsed-level="collapsedLevel"
:highlight-mouseover-node="highlightMouseoverNode" :highlight-mouseover-node="highlightMouseoverNode"
/> />
</template>
<span v-else-if="props.data">{{ props.data }}</span> <span v-else-if="props.data">{{ props.data }}</span>
<div v-else class="empty-data">暂无数据</div> <div v-else class="empty-data">暂无数据</div>
</div> </div>

View File

@ -2,7 +2,7 @@ import { ref, watch } from 'vue';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import { fetchGetDictDataByType } from '@/service/api/system'; import { fetchGetDictDataByType } from '@/service/api/system';
import { useDictStore } from '@/store/modules/dict'; import { useDictStore } from '@/store/modules/dict';
import { isNull } from '@/utils/common';
export function useDict(dictType: string, immediate: boolean = true) { export function useDict(dictType: string, immediate: boolean = true) {
const dictStore = useDictStore(); const dictStore = useDictStore();
const { dictData: dictList } = storeToRefs(dictStore); const { dictData: dictList } = storeToRefs(dictStore);
@ -40,9 +40,12 @@ export function useDict(dictType: string, immediate: boolean = true) {
options.value = data.value.map(dict => ({ label: dict.dictLabel!, value: dict.dictValue! })); options.value = data.value.map(dict => ({ label: dict.dictLabel!, value: dict.dictValue! }));
} }
function transformDictData(dictValue: string): Api.System.DictData | undefined { function transformDictData(dictValue: string[] | number[] | string | number) {
if (!data.value.length || !dictValue) return undefined; if (!data.value.length || isNull(dictValue)) return undefined;
return data.value.find(dict => dict.dictValue === dictValue); if (Array.isArray(dictValue)) {
return data.value.filter(dict => dictValue.some(value => dict.dictValue === value.toString()));
}
return data.value.filter(dict => dict.dictValue === dictValue.toString());
} }
if (immediate) { if (immediate) {

View File

@ -511,5 +511,88 @@ declare namespace Api {
/** tenant package select list */ /** tenant package select list */
type TenantPackageSelectList = Common.CommonRecord<Pick<TenantPackage, 'packageId' | 'packageName'>>; type TenantPackageSelectList = Common.CommonRecord<Pick<TenantPackage, 'packageId' | 'packageName'>>;
/** notice */
type Notice = Common.CommonRecord<{
/** 公告ID */
noticeId: CommonType.IdType;
/** 租户编号 */
tenantId: CommonType.IdType;
/** 公告标题 */
noticeTitle: string;
/** 公告类型 */
noticeType: string;
/** 公告内容 */
noticeContent: string;
/** 公告状态 */
status: string;
/** 创建者 */
createByName: string;
/** 备注 */
remark: string;
}>;
/** notice search params */
type NoticeSearchParams = CommonType.RecordNullable<
Pick<Api.System.Notice, 'noticeTitle' | 'noticeType'> & Api.Common.CommonSearchParams
>;
/** notice operate params */
type NoticeOperateParams = CommonType.RecordNullable<
Pick<Api.System.Notice, 'noticeId' | 'noticeTitle' | 'noticeType' | 'noticeContent' | 'status'>
>;
/** notice list */
type NoticeList = Api.Common.PaginatingQueryRecord<Notice>;
/** client */
type Client = Common.CommonRecord<{
/** id */
id: CommonType.IdType;
/** 客户端id */
clientId: string;
/** 客户端key */
clientKey: string;
/** 客户端秘钥 */
clientSecret: string;
/** 授权类型 */
grantType: string;
/** 授权类型列表 */
grantTypeList: string[];
/** 设备类型 */
deviceType: string;
/** token活跃超时时间 */
activeTimeout: number;
/** token固定超时 */
timeout: number;
/** 状态 */
status: string;
/** 删除标志0代表存在 1代表删除 */
delFlag: string;
}>;
/** client search params */
type ClientSearchParams = CommonType.RecordNullable<
Pick<Api.System.Client, 'clientKey' | 'clientSecret' | 'status'> & Api.Common.CommonSearchParams
>;
/** client operate params */
type ClientOperateParams = CommonType.RecordNullable<
Pick<
Api.System.Client,
| 'id'
| 'clientId'
| 'clientKey'
| 'clientSecret'
| 'grantTypeList'
| 'deviceType'
| 'activeTimeout'
| 'timeout'
| 'status'
>
>;
/** client list */
type ClientList = Api.Common.PaginatingQueryRecord<Client>;
} }
} }

View File

@ -80,6 +80,11 @@ export function isNotNull(value: any) {
return value !== undefined && value !== null && value !== '' && value !== 'undefined' && value !== 'null'; return value !== undefined && value !== null && value !== '' && value !== 'undefined' && value !== 'null';
} }
/** 判断是否为空 */
export function isNull(value: any) {
return value === undefined || value === null || value === '' || value === 'undefined' || value === 'null';
}
/** /**
* *
* *

View File

@ -31,8 +31,8 @@ function closeDrawer() {
<NDrawer v-model:show="visible" :title="title" display-directive="show" :width="800" class="max-w-90%"> <NDrawer v-model:show="visible" :title="title" display-directive="show" :width="800" class="max-w-90%">
<NDrawerContent :title="title" :native-scrollbar="false" closable> <NDrawerContent :title="title" :native-scrollbar="false" closable>
<NDescriptions label-placement="left" :column="1" size="small" bordered> <NDescriptions label-placement="left" :column="1" size="small" bordered>
<NDescriptionsItem label="用户账号"> <NDescriptionsItem label="账号信息">
{{ props.rowData?.userName }} {{ props.rowData?.userName }} | {{ props.rowData?.ipaddr }} | {{ props.rowData?.loginLocation }}
</NDescriptionsItem> </NDescriptionsItem>
<NDescriptionsItem label="客户端"> <NDescriptionsItem label="客户端">
{{ props.rowData?.clientKey }} {{ props.rowData?.clientKey }}
@ -40,12 +40,6 @@ function closeDrawer() {
<NDescriptionsItem label="设备类型"> <NDescriptionsItem label="设备类型">
<DictTag size="small" :value="props.rowData?.deviceType" dict-code="sys_device_type" /> <DictTag size="small" :value="props.rowData?.deviceType" dict-code="sys_device_type" />
</NDescriptionsItem> </NDescriptionsItem>
<NDescriptionsItem label="登录IP地址">
{{ props.rowData?.ipaddr }}
</NDescriptionsItem>
<NDescriptionsItem label="登录地点">
{{ props.rowData?.loginLocation }}
</NDescriptionsItem>
<NDescriptionsItem label="浏览器类型"> <NDescriptionsItem label="浏览器类型">
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<SvgIcon :icon="getBrowserIcon(props.rowData?.browser ?? '')" /> <SvgIcon :icon="getBrowserIcon(props.rowData?.browser ?? '')" />

View File

@ -10,6 +10,7 @@ import { useAppStore } from '@/store/modules/app';
import { useDownload } from '@/hooks/business/download'; import { useDownload } from '@/hooks/business/download';
import { useTable, useTableOperate } from '@/hooks/common/table'; import { useTable, useTableOperate } from '@/hooks/common/table';
import DictTag from '@/components/custom/dict-tag.vue'; import DictTag from '@/components/custom/dict-tag.vue';
import { useDict } from '@/hooks/business/dict';
import PostOperateDrawer from './modules/post-operate-drawer.vue'; import PostOperateDrawer from './modules/post-operate-drawer.vue';
import PostSearch from './modules/post-search.vue'; import PostSearch from './modules/post-search.vue';
@ -17,6 +18,7 @@ defineOptions({
name: 'PostList' name: 'PostList'
}); });
useDict('sys_normal_disable');
const appStore = useAppStore(); const appStore = useAppStore();
const { download } = useDownload(); const { download } = useDownload();
const { hasAuth } = useAuth(); const { hasAuth } = useAuth();
@ -170,7 +172,6 @@ async function handleExport() {
const { loading: treeLoading, startLoading: startTreeLoading, endLoading: endTreeLoading } = useLoading(); const { loading: treeLoading, startLoading: startTreeLoading, endLoading: endTreeLoading } = useLoading();
const deptPattern = ref<string>(); const deptPattern = ref<string>();
const deptData = ref<Api.Common.CommonTreeRecord>([]); const deptData = ref<Api.Common.CommonTreeRecord>([]);
const selectedKeys = ref<string[]>([]);
async function getTreeData() { async function getTreeData() {
startTreeLoading(); startTreeLoading();
@ -184,7 +185,6 @@ async function getTreeData() {
getTreeData(); getTreeData();
function handleClickTree(keys: string[]) { function handleClickTree(keys: string[]) {
selectedKeys.value = keys;
searchParams.belongDeptId = keys.length ? keys[0] : null; searchParams.belongDeptId = keys.length ? keys[0] : null;
checkedRowKeys.value = []; checkedRowKeys.value = [];
getDataByPage(); getDataByPage();
@ -192,7 +192,6 @@ function handleClickTree(keys: string[]) {
function handleResetTreeData() { function handleResetTreeData() {
deptPattern.value = undefined; deptPattern.value = undefined;
selectedKeys.value = [];
searchParams.belongDeptId = null; searchParams.belongDeptId = null;
getTreeData(); getTreeData();
getDataByPage(); getDataByPage();
@ -212,7 +211,6 @@ function handleResetTreeData() {
<NInput v-model:value="deptPattern" clearable :placeholder="$t('common.keywordSearch')" /> <NInput v-model:value="deptPattern" clearable :placeholder="$t('common.keywordSearch')" />
<NSpin class="dept-tree" :show="treeLoading"> <NSpin class="dept-tree" :show="treeLoading">
<NTree <NTree
v-model:selected-keys="selectedKeys"
block-node block-node
show-line show-line
:data="deptData as []" :data="deptData as []"

View File

@ -261,7 +261,7 @@ watch(visible, () => {
class="w-full" class="w-full"
/> />
</NFormItem> </NFormItem>
<NFormItem label="用户数量" path="accountCount"> <NFormItem path="accountCount">
<template #label> <template #label>
<div class="flex-center"> <div class="flex-center">
<FormTip content="-1不限制用户数量" /> <FormTip content="-1不限制用户数量" />
@ -270,7 +270,7 @@ watch(visible, () => {
</template> </template>
<NInputNumber v-model:value="model.accountCount" placeholder="请输入用户数量" min="-1" class="w-full" /> <NInputNumber v-model:value="model.accountCount" placeholder="请输入用户数量" min="-1" class="w-full" />
</NFormItem> </NFormItem>
<NFormItem label="绑定域名" path="domain"> <NFormItem path="domain">
<template #label> <template #label>
<div class="flex-center"> <div class="flex-center">
<FormTip <FormTip