重大变更: - 升级 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(向后兼容)
268 lines
7.5 KiB
Vue
268 lines
7.5 KiB
Vue
<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>
|