feat: 新增字典和字典展示功能

新增字典和字典展示功能
This commit is contained in:
Theo
2024-11-24 15:46:25 +08:00
parent 93507af63a
commit 1f8cda13b9
12 changed files with 431 additions and 16 deletions

View File

@@ -91,6 +91,7 @@
"@dcloudio/uni-quickapp-webview": "3.0.0-4020420240722002",
"@qiun/uni-ucharts": "2.5.0-20230101",
"pinia": "^2.2.2",
"unplugin-vue-components": "^0.27.4",
"vue": "^3.4.21",
"wot-design-uni": "^1.3.11"
},

180
src/api/system/dict.ts Normal file
View File

@@ -0,0 +1,180 @@
import request from "@/utils/request";
const DICT_BASE_URL = "/api/v1/dict";
const DictAPI = {
/**
* 获取字典分页列表
*
* @param queryParams 查询参数
* @returns 字典分页结果
*/
getPage(queryParams: DictPageQuery) {
return request<DictPageVO[]>({
url: `${DICT_BASE_URL}/page`,
method: "GET",
data: queryParams,
});
},
/**
* 获取字典表单数据
*
* @param id 字典ID
* @returns 字典表单数据
*/
getFormData(id: number) {
return request<DictForm>({
url: `${DICT_BASE_URL}/${id}/form`,
method: "GET",
});
},
/**
* 新增字典
*
* @param data 字典表单数据
*/
add(data: DictForm) {
return request({
url: `${DICT_BASE_URL}`,
method: "POST",
data: data,
});
},
/**
* 修改字典
*
* @param id 字典ID
* @param data 字典表单数据
*/
update(id: number, data: DictForm) {
return request({
url: `${DICT_BASE_URL}/${id}`,
method: "PUT",
data: data,
});
},
/**
* 删除字典
*
* @param ids 字典ID多个以英文逗号(,)分隔
*/
deleteByIds(ids: string) {
return request({
url: `${DICT_BASE_URL}/${ids}`,
method: "delete",
});
},
/**
* 获取字典列表
*
* @returns 字典列表
*/
getList() {
return request<DictVO[]>({
url: `${DICT_BASE_URL}/list`,
method: "GET",
});
},
};
export default DictAPI;
/**
* 字典查询参数
*/
export interface DictPageQuery extends PageQuery {
/**
* 关键字(字典名称/编码)
*/
keywords?: string;
/**
* 字典状态1:启用0:禁用)
*/
status?: number;
}
/**
* 字典分页对象
*/
export interface DictPageVO {
/**
* 字典ID
*/
id: number;
/**
* 字典名称
*/
name: string;
/**
* 字典编码
*/
dictCode: string;
/**
* 字典状态1:启用0:禁用)
*/
status: number;
}
/**
* 字典
*/
export interface DictForm {
/**
* 字典ID
*/
id?: number;
/**
* 字典名称
*/
name?: string;
/**
* 字典编码
*/
dictCode?: string;
/**
* 字典状态1-启用0-禁用)
*/
status?: number;
/**
* 备注
*/
remark?: string;
}
/**
* 字典数据项分页VO
*
* @description 字典数据分页对象
*/
export interface DictVO {
/** 字典名称 */
name: string;
/** 字典编码 */
dictCode: string;
/** 字典数据集合 */
dictDataList: DictData[];
}
/**
* 字典数据
*
* @description 字典数据
*/
export interface DictData {
/** 字典数据值 */
value: string;
/** 字典数据标签 */
label: string;
/** 标签类型 */
tagType: string;
}

View File

@@ -0,0 +1,54 @@
<template>
<template v-if="tagType">
<wd-tag :type="tagType" :round="round">{{ label }}</wd-tag>
</template>
<template v-else>
<view>{{ label }}</view>
</template>
</template>
<script setup lang="ts">
import { useDictStore } from "@/store/modules/dict";
const dictStore = useDictStore();
const props = defineProps({
code: String,
modelValue: [String, Number],
round: {
type: Boolean,
default: false,
},
});
type TagType = "success" | "warning" | "primary" | "danger" | "default";
const label = ref("");
const tagType = ref<TagType>();
const getLabelAndTagByValue = async (dictCode: string, value: any) => {
// 先从本地缓存中获取字典数据
const dictData = dictStore.getDictionary(dictCode);
console.log("dictData", dictData);
// 查找对应的字典项
const dictEntry = dictData.find((item: any) => item.value == value);
return {
label: dictEntry ? dictEntry.label : "",
tag: dictEntry ? dictEntry.tagType : undefined,
};
};
// 监听 props 的变化,获取并更新 label 和 tag
const fetchLabelAndTag = async () => {
const result = await getLabelAndTagByValue(props.code as string, props.modelValue);
console.log("result", result);
label.value = result.label;
if (result.tag === "info") {
result.tag = "default";
}
tagType.value = result.tag as "success" | "warning" | "primary" | "danger" | "default";
};
// 首次挂载时获取字典数据
onMounted(fetchLabelAndTag);
// 当 modelValue 发生变化时重新获取
watch(() => props.modelValue, fetchLabelAndTag);
</script>

View File

@@ -0,0 +1,126 @@
<template>
<wd-picker
v-if="type === 'select' || type === 'radio'"
v-model="selectedValue"
:columns="options"
:label="label"
:placeholder="placeholder"
clearable
:rules="rules"
@confirm="handleChange"
/>
<wd-select-picker
v-else-if="type === 'checkbox'"
v-model="selectedValue"
:columns="options"
:placeholder="placeholder"
clearable
:label="label"
:rules="rules"
@confirm="handleChange"
/>
<wd-input
v-else
v-model="selectedValue"
:label="label"
clearable
:placeholder="placeholder"
:rules="rules"
@input="handleChange"
/>
</template>
<script setup lang="ts">
import { useDictStore } from "@/store/modules/dict";
const dictStore = useDictStore();
const props = defineProps({
code: {
type: String,
required: true,
},
modelValue: {
type: [String, Number, Array],
required: false,
},
label: {
type: String,
default: "",
},
type: {
type: String,
default: "select",
validator: (value: string) => ["select", "radio", "checkbox"].includes(value),
},
placeholder: {
type: String,
default: "请选择",
},
disabled: {
type: Boolean,
default: false,
},
rules: {
type: Array as PropType<any[]>,
default: () => [],
},
});
const emit = defineEmits(["update:modelValue"]);
const options = ref<Array<{ label: string; value: string | number }>>([]);
const selectedValue = ref<any>(
typeof props.modelValue === "string" || typeof props.modelValue === "number"
? props.modelValue
: Array.isArray(props.modelValue)
? props.modelValue
: undefined
);
// 监听 modelValue 变化
watch(
() => props.modelValue,
(newValue) => {
if (props.type === "checkbox") {
selectedValue.value = Array.isArray(newValue) ? newValue : [];
} else {
selectedValue.value = newValue?.toString() || "";
}
},
{ immediate: true }
);
// 监听 options 变化并重新匹配 selectedValue
watch(
() => options.value,
(newOptions) => {
// options 加载后,确保 selectedValue 可以正确匹配到 options
if (newOptions.length > 0 && selectedValue.value !== undefined) {
const matchedOption = newOptions.find((option) => option.value === selectedValue.value);
if (!matchedOption && props.type !== "checkbox") {
// 如果找不到匹配项,清空选中
selectedValue.value = "";
}
}
}
);
// 监听 selectedValue 的变化并触发 update:modelValue
function handleChange(val: any) {
emit("update:modelValue", val.value);
}
// 获取字典数据
onMounted(async () => {
if (!props.type) {
return;
}
let dictData = dictStore.getDictionary(props.code);
options.value = dictData.map((item) => ({
label: item.label,
value: item.value,
}));
});
</script>

View File

@@ -35,7 +35,7 @@
<script lang="ts" setup>
import { type LoginFormData } from "@/api/auth";
import { useUserStore } from "@/store/modules/user";
import { useDictStore } from "@/store/modules/dict";
const loginFormRef = ref();
const loginFormData = ref<LoginFormData>({
@@ -44,7 +44,7 @@ const loginFormData = ref<LoginFormData>({
});
const userStore = useUserStore();
const dictStore = useDictStore();
// 登录处理
const handleLogin = () => {
loginFormRef.value.validate().then(async ({ valid }: { valid: boolean }) => {
@@ -52,6 +52,7 @@ const handleLogin = () => {
try {
await userStore.login(loginFormData.value); // 等待登录和获取用户信息完成
await userStore.getInfo(); // 等待用户信息获取完成
await dictStore.loadDictionaries(); // 等待字典数据加载完成
uni.showToast({ title: "登录成功", icon: "success" });
const pages = getCurrentPages(); // 获取当前的页面栈

View File

@@ -139,6 +139,7 @@ const pageData = ref<ConfigPageVO[]>([]);
const formRef = ref<FormInstance>();
const loading = ref(false);
/**
* 搜索栏
*/

View File

@@ -8,19 +8,11 @@
</h2>
<div class="notice-meta">
<div class="meta-row">
<span>
优先级
<wd-tag :type="getLevelType(noticeDetail.level)">
{{ getLevelText(noticeDetail.level) }}
</wd-tag>
</span>
</div>
<div class="meta-row">
<span>发布人{{ noticeDetail.publisherName }}</span>
</div>
<div class="meta-row">
<span>发布时间{{ noticeDetail.publishTime }}</span>
优先级
<dict-label code="notice_level" :model-value="noticeDetail.level" />
</div>
<div class="meta-row">发布人{{ noticeDetail.publisherName }}</div>
<div class="meta-row">发布时间{{ noticeDetail.publishTime }}</div>
</div>
</div>
<wd-divider />

View File

@@ -70,7 +70,7 @@
<view>紧急程度</view>
</wd-col>
<wd-col :span="16">
<view>{{ getLevelType(item.level) || "-" }}</view>
<dict-label code="notice_level" :model-value="item.level" />
</wd-col>
</wd-col>
</wd-row>

38
src/store/modules/dict.ts Normal file
View File

@@ -0,0 +1,38 @@
import { defineStore } from "pinia";
import DictAPI, { type DictVO, type DictData } from "@/api/system/dict";
import { setDictCache, getDictCache } from "@/utils/cache";
export const useDictStore = defineStore("dict", () => {
const dictionary = ref<Record<string, DictData[]>>(getDictCache());
const setDictionary = (dict: DictVO) => {
dictionary.value[dict.dictCode] = dict.dictDataList;
setDictCache(dictionary.value);
};
const loadDictionaries = async () => {
const dictList = await DictAPI.getList();
dictList.forEach(setDictionary);
};
const getDictionary = (dictCode: string): DictData[] => {
return dictionary.value[dictCode] || [];
};
const clearDictionaryCache = () => {
dictionary.value = {};
};
const updateDictionaryCache = async () => {
clearDictionaryCache(); // 先清除旧缓存
await loadDictionaries(); // 重新加载最新字典数据
};
return {
dictionary,
setDictionary,
loadDictionaries,
getDictionary,
clearDictionaryCache,
updateDictionaryCache,
};
});

View File

@@ -1,5 +1,7 @@
const TOKEN_KEY = "app-token";
const USER_INFO_KEY = "user-info";
const DICT_KEY = "dict";
import { type DictData } from "@/api/system/dict";
// 设置 token
export function setToken(token: string) {
@@ -31,8 +33,24 @@ export function clearUserInfo() {
uni.removeStorageSync(USER_INFO_KEY);
}
// 设置字典缓存
export function setDictCache(dict: Record<string, DictData[]>) {
uni.setStorageSync(DICT_KEY, dict);
}
// 获取字典缓存
export function getDictCache(): Record<string, DictData[]> {
return uni.getStorageSync(DICT_KEY) || {};
}
// 清除字典缓存
export function clearDictCache() {
uni.removeStorageSync(DICT_KEY);
}
// 清除所有缓存信息
export function clearAll() {
clearToken();
clearUserInfo();
clearDictCache();
}

View File

@@ -16,7 +16,7 @@ export default function request<T>(options: UniApp.RequestOptions): Promise<T> {
...options.header,
Authorization: getToken() ? `Bearer ${getToken()}` : "",
},
data: handleData(options.data, options.method),
data: handleData(options.data, options.method || "GET"),
success: (response) => {
console.log("success response", response);
const resData = response.data as ResponseData<T>;

View File

@@ -1,6 +1,7 @@
import { defineConfig, type UserConfig, type ConfigEnv, loadEnv } from "vite";
import uni from "@dcloudio/vite-plugin-uni";
import AutoImport from "unplugin-auto-import/vite";
import Components from "unplugin-vue-components/vite";
export default defineConfig(async ({ mode }: ConfigEnv): Promise<UserConfig> => {
const UnoCss = await import("unocss/vite").then((i) => i.default);
@@ -32,6 +33,9 @@ export default defineConfig(async ({ mode }: ConfigEnv): Promise<UserConfig> =>
},
}),
uni(),
Components({
dirs: ["src/components", "src/pages/**/components"],
}),
],
};
});