Files
youlai-app/src/pages/work/dict/index.vue
Ray.Hao e5cd415f66 chore: 升级 wot-design-uni 到 @wot-ui/ui v2.0.8
重大变更:
- 升级 UI 组件库从 wot-design-uni@1.14.0 到 @wot-ui/ui@2.0.8
- 新增 dayjs@1.11.13 作为独立依赖

组件迁移:
- wd-message-box → wd-dialog (3个文件)
- wd-status-tip → wd-empty (10个文件)
- wd-col-picker → wd-cascader (3个文件)

API 变更:
- useMessage → useDialog (12个文件)
- 所有导入路径从 wot-design-uni 更新为 @wot-ui/ui
- dayjs 改为独立导入

配置更新:
- vite.config.ts: 自定义 WotUIResolver 替代 WotResolver
- tsconfig.json: 更新类型声明路径
- components.d.ts: 更新所有组件类型路径

影响范围:
- 修改文件: 33个
- 布局文件: 2个
- 页面文件: 21个
- 组件文件: 1个
- 配置文件: 4个
- 类型文件: 2个

Breaking Changes:
- Dialog 的 closeOnClickModal 默认值从 true 改为 false
- Prompt 返回值从 res 改为 res.value
- Empty 的 image 属性改为 icon
- Cascader 的 columns 建议改为 options(向后兼容)
2026-05-16 10:40:27 +08:00

268 lines
7.5 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<view class="page page--padding">
<view>
<wd-search
v-model="queryParams.keywords"
placeholder="搜索字典名称/编码"
hide-cancel
@search="handleSearch"
/>
</view>
<view class="mt-16rpx">
<wd-card
v-for="item in pageData"
:key="item.id"
custom-class="item-card"
@click="openDictItemPage(item)"
>
<view class="flex-start">
<view class="flex-1">
<view class="flex-start">
<text class="font-bold text-32rpx">{{ item.name }}</text>
</view>
<text class="text-24rpx color-text-secondary">字典编码{{ item.dictCode }}</text>
</view>
<wd-tag :type="item.status === 1 ? 'success' : 'danger'" plain>
{{ item.status === 1 ? "启用" : "禁用" }}
</wd-tag>
</view>
<view class="flex-between mt-16rpx">
<text class="text-24rpx color-text-placeholder">{{ item.remark || "暂无备注" }}</text>
<view
class="w-88rpx h-88rpx flex-center rounded-full"
hover-class="bg-[var(--color-text-placeholder)]/16"
@click.stop="showDictActions(item)"
>
<wd-icon name="more" size="18" class="color-text-secondary" />
</view>
</view>
</wd-card>
<wd-loadmore v-if="total > 0" :state="loadMoreState" @reload="fetchDictTypeList" />
<wd-empty v-else-if="total === 0" icon="search" tip="暂无数据" />
</view>
<wd-popup
v-model="dialog.visible"
position="bottom"
custom-class="popup-bottom"
@close="closeDictDialog"
>
<view class="p-4">
<view class="popup-title">
{{ formData.id ? "编辑字典" : "新增字典" }}
</view>
<wd-form ref="formRef" :model="formData" :rules="rules">
<wd-cell-group border>
<wd-input v-model="formData.name" label="字典名称" required />
<wd-input v-model="formData.dictCode" label="字典编码" required />
<wd-cell title="状态">
<wd-switch v-model="formData.status" :active-value="1" :inactive-value="0" />
</wd-cell>
<wd-textarea
v-model="formData.remark"
label="备注"
placeholder="请输入备注"
:maxlength="100"
show-word-limit
/>
</wd-cell-group>
</wd-form>
<view class="popup-actions">
<wd-button type="info" plain @click="closeDictDialog">取消</wd-button>
<wd-button type="primary" :loading="isSubmitting" @click="submitDictForm">保存</wd-button>
</view>
</view>
</wd-popup>
<wd-action-sheet
v-model="actionSheetVisible"
:actions="actionSheetActions"
cancel-text="取消"
@select="handleActionSelect"
/>
<wd-fab v-if="hasPermission('sys:dict:create') && !dialog.visible" @click="openDictDialog()" />
</view>
</template>
<script lang="ts" setup>
import { onLoad, onReachBottom } from "@dcloudio/uni-app";
import { useRouter } from "uni-mini-router";
import { LoadMoreState } from "@wot-ui/ui/components/wd-loadmore/types";
import { FormRules } from "@wot-ui/ui/components/wd-form/types";
import { useToast, useDialog } from "@wot-ui/ui";
import DictAPI, { type DictTypeForm, type DictTypePageQuery, type DictTypeItem } from "@/api/dict";
import { hasPermission } from "@/utils/permission";
const router = useRouter();
const toast = useToast();
const { messageBox } = useDialog();
const loadMoreState = ref<LoadMoreState>("loading");
const formRef = ref();
const isSubmitting = ref(false);
const queryParams = reactive<DictTypePageQuery>({ pageNum: 1, pageSize: 10, keywords: "" });
const total = ref(0);
const pageData = ref<DictTypeItem[]>([]);
const dialog = reactive({ visible: false });
const initialFormData: DictTypeForm = {
id: undefined,
name: undefined,
dictCode: undefined,
status: 1,
remark: undefined,
};
const formData = reactive<DictTypeForm>({ ...initialFormData });
const rules: FormRules = {
name: [{ required: true, message: "请输入字典名称" }],
dictCode: [{ required: true, message: "请输入字典编码" }],
};
const handleSearch = () => loadDictTypeList();
function loadDictTypeList() {
queryParams.pageNum = 1;
fetchDictTypeList();
}
function fetchDictTypeList() {
loadMoreState.value = "loading";
DictAPI.getPage(queryParams)
.then((data: any) => {
pageData.value = data.list;
total.value = data.total;
queryParams.pageNum++;
})
.catch(() => {
pageData.value = [];
})
.finally(() => {
loadMoreState.value = "finished";
});
}
function openDictItemPage(item: DictTypeItem) {
if (!item.dictCode) return;
router.push({
path: "/pages/work/dict/item/index",
query: { dictCode: item.dictCode, title: `${item.name}】字典数据` },
});
}
async function openDictDialog(id?: string) {
formRef.value?.reset();
Object.assign(formData, initialFormData);
dialog.visible = true;
if (id) {
formData.id = id;
const data = await DictAPI.getFormData(id);
Object.assign(formData, data, { id });
}
}
function submitDictForm() {
formRef.value.validate().then(({ valid }: { valid: boolean }) => {
if (!valid) return;
isSubmitting.value = true;
const id = formData.id;
const action = id ? DictAPI.update(id, formData) : DictAPI.create(formData);
action
.then(() => {
toast.success("操作成功");
closeDictDialog();
loadDictTypeList();
})
.finally(() => {
isSubmitting.value = false;
});
});
}
function closeDictDialog() {
dialog.visible = false;
formRef.value?.reset();
Object.assign(formData, initialFormData);
}
const actionSheetVisible = ref(false);
const actionSheetActions = ref<{ name: string; color?: string }[]>([]);
const pendingAction = ref<Record<string, () => void>>({});
function showDictActions(item: DictTypeItem) {
const actions: { name: string; color?: string }[] = [];
const actionMap: Record<string, () => void> = {};
actions.push({ name: "字典数据" });
actionMap["字典数据"] = () => openDictItemPage(item);
if (hasPermission("sys:dict:update")) {
actions.push({ name: "编辑" });
actionMap["编辑"] = () => openDictDialog(item.id);
}
if (hasPermission("sys:dict:delete")) {
actions.push({ name: "删除", color: "var(--color-danger)" });
actionMap["删除"] = async () => {
try {
await messageBox({
title: "确认删除",
msg: `确定要删除字典「${item.name}」吗?`,
type: "warning",
});
if (item.id) {
await DictAPI.deleteByIds(String(item.id));
toast.success("删除成功");
loadDictTypeList();
}
} catch {
// 用户取消操作
}
};
}
if (actions.length === 0) {
toast.warning("暂无操作权限");
return;
}
actionSheetActions.value = actions;
pendingAction.value = actionMap;
actionSheetVisible.value = true;
}
function handleActionSelect({ value }: { value: string }) {
pendingAction.value[value]?.();
}
onReachBottom(() => {
if (queryParams.pageNum * queryParams.pageSize < total.value) {
fetchDictTypeList();
} else {
loadMoreState.value = "finished";
}
});
onLoad(() => {
loadDictTypeList();
});
</script>
<script lang="ts">
export default { options: { styleIsolation: "shared" } };
</script>
<route lang="json">
{
"name": "dict",
"style": {
"navigationBarTitleText": "字典管理"
}
}
</route>