初始化项目
This commit is contained in:
166
src/views/manage/dict/index.vue
Normal file
166
src/views/manage/dict/index.vue
Normal file
@@ -0,0 +1,166 @@
|
||||
<script setup lang="tsx">
|
||||
import type { Key } from 'ant-design-vue/es/_util/type';
|
||||
import { Button, Popconfirm, Tag } from 'ant-design-vue';
|
||||
import { useTable, useTableOperate } from '@/hooks/common/table';
|
||||
import { $t } from '@/locales';
|
||||
import { enableStatusRecord } from '@/constants/business';
|
||||
import DictOperateDrawer from './modules/dict-operate-drawer.vue';
|
||||
import DictSearch from './modules/dict-search.vue';
|
||||
|
||||
const wrapperEl = shallowRef<HTMLElement | null>(null);
|
||||
const { height: wrapperElHeight } = useElementSize(wrapperEl);
|
||||
|
||||
const scrollConfig = computed(() => {
|
||||
return {
|
||||
y: wrapperElHeight.value - 72,
|
||||
x: 1000
|
||||
};
|
||||
});
|
||||
|
||||
const { columns, columnChecks, data, loading, getData, mobilePagination, searchParams, resetSearchParams } = useTable({
|
||||
apiFn: doGetDictList,
|
||||
apiParams: {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
status: undefined,
|
||||
dictName: undefined,
|
||||
dictType: undefined
|
||||
},
|
||||
rowKey: 'dictId',
|
||||
columns: () => [
|
||||
{
|
||||
key: 'dictName',
|
||||
dataIndex: 'dictName',
|
||||
title: '字典名称',
|
||||
align: 'center'
|
||||
},
|
||||
{
|
||||
key: 'dictType',
|
||||
dataIndex: 'dictType',
|
||||
title: '字典类型',
|
||||
align: 'center'
|
||||
},
|
||||
{
|
||||
key: 'status',
|
||||
dataIndex: 'status',
|
||||
title: '状态',
|
||||
align: 'center',
|
||||
customRender: ({ record }) => {
|
||||
if (record.status === null) {
|
||||
return null;
|
||||
}
|
||||
const tagMap: Record<Api.Common.EnableStatus, string> = {
|
||||
'0': 'success',
|
||||
'1': 'warning'
|
||||
};
|
||||
const label = $t(enableStatusRecord[record.status]);
|
||||
return <Tag color={tagMap[record.status]}>{label}</Tag>;
|
||||
}
|
||||
},
|
||||
{
|
||||
key: 'remark',
|
||||
dataIndex: 'remark',
|
||||
align: 'center',
|
||||
title: '备注'
|
||||
},
|
||||
{
|
||||
key: 'createTime',
|
||||
dataIndex: 'createTime',
|
||||
align: 'center',
|
||||
title: '创建时间'
|
||||
},
|
||||
{
|
||||
key: 'operate',
|
||||
title: '操作',
|
||||
align: 'center',
|
||||
width: 200,
|
||||
customRender: ({ record }) => (
|
||||
<div class="flex justify-around gap-8px">
|
||||
{isShowBtn('system:dict:edit') && (
|
||||
<Button size="small" onClick={() => edit(record.dictId)}>
|
||||
编辑
|
||||
</Button>
|
||||
)}
|
||||
{isShowBtn('system:dict:remove') && (
|
||||
<Popconfirm onConfirm={() => handleDelete(record.dictId)} title="确认删除吗?">
|
||||
<Button danger size="small">
|
||||
删除
|
||||
</Button>
|
||||
</Popconfirm>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
const { drawerVisible, operateType, editingData, handleAdd, handleEdit, checkedRowKeys, onBatchDeleted, onDeleted } =
|
||||
useTableOperate(data, { getData, idKey: 'dictId' });
|
||||
|
||||
async function handleBatchDelete() {
|
||||
const { error } = await doDeleteDict(checkedRowKeys.value.join(','));
|
||||
if (!error) {
|
||||
onBatchDeleted();
|
||||
}
|
||||
}
|
||||
|
||||
async function handleDelete(id: number) {
|
||||
const { error } = await doDeleteDict(id);
|
||||
if (!error) {
|
||||
onDeleted();
|
||||
}
|
||||
}
|
||||
|
||||
function edit(id: number) {
|
||||
handleEdit(id);
|
||||
}
|
||||
|
||||
function handleDictSelectChange(selectedRowKeys: Key[]) {
|
||||
checkedRowKeys.value = selectedRowKeys as number[];
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="min-h-500px flex-col-stretch gap-16px overflow-hidden lt-sm:overflow-auto">
|
||||
<DictSearch v-model:model="searchParams" @reset="resetSearchParams" @search="getData" />
|
||||
<ACard
|
||||
title="字典列表"
|
||||
:bordered="false"
|
||||
:body-style="{ flex: 1, overflow: 'hidden' }"
|
||||
class="flex-col-stretch sm:flex-1-hidden card-wrapper"
|
||||
>
|
||||
<template #extra>
|
||||
<TableHeaderOperation
|
||||
v-model:columns="columnChecks"
|
||||
:disabled-delete="checkedRowKeys.length === 0"
|
||||
:loading="loading"
|
||||
:show-delete="true"
|
||||
table-type="dict"
|
||||
@add="handleAdd"
|
||||
@delete="handleBatchDelete"
|
||||
@refresh="getData"
|
||||
/>
|
||||
</template>
|
||||
<ATable
|
||||
ref="wrapperEl"
|
||||
:columns="columns"
|
||||
:data-source="data"
|
||||
:loading="loading"
|
||||
:row-selection="{ selectedRowKeys: checkedRowKeys, onChange: handleDictSelectChange }"
|
||||
row-key="dictId"
|
||||
size="small"
|
||||
:pagination="mobilePagination"
|
||||
:scroll="scrollConfig"
|
||||
class="h-full"
|
||||
/>
|
||||
<DictOperateDrawer
|
||||
v-model:visible="drawerVisible"
|
||||
:operate-type="operateType"
|
||||
:row-data="editingData"
|
||||
@submitted="getData"
|
||||
/>
|
||||
</ACard>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
140
src/views/manage/dict/modules/dict-operate-drawer.vue
Normal file
140
src/views/manage/dict/modules/dict-operate-drawer.vue
Normal file
@@ -0,0 +1,140 @@
|
||||
<script setup lang="ts">
|
||||
import { SimpleScrollbar } from '@sa/materials';
|
||||
import { useAntdForm, useFormRules } from '@/hooks/common/form';
|
||||
import { $t } from '@/locales';
|
||||
import { enableStatusOptions } from '@/constants/business';
|
||||
import type { DictSubmitModel } from '@/service/api/dict';
|
||||
|
||||
defineOptions({
|
||||
name: 'DictOperateDrawer'
|
||||
});
|
||||
|
||||
interface Props {
|
||||
/** the type of operation */
|
||||
operateType: AntDesign.TableOperateType;
|
||||
/** the edit row data */
|
||||
rowData?: Api.SystemManage.Dict | null;
|
||||
}
|
||||
|
||||
const props = defineProps<Props>();
|
||||
|
||||
interface Emits {
|
||||
(e: 'submitted'): void;
|
||||
}
|
||||
|
||||
const emit = defineEmits<Emits>();
|
||||
|
||||
const visible = defineModel<boolean>('visible', {
|
||||
default: false
|
||||
});
|
||||
|
||||
const { formRef, validate, resetFields } = useAntdForm();
|
||||
const { defaultRequiredRule } = useFormRules();
|
||||
|
||||
const title = computed(() => {
|
||||
const titles: Record<AntDesign.TableOperateType, string> = {
|
||||
add: $t('page.manage.dict.addDict'),
|
||||
edit: $t('page.manage.dict.editDict')
|
||||
};
|
||||
return titles[props.operateType];
|
||||
});
|
||||
|
||||
const model = ref<DictSubmitModel>(createDefaultModel());
|
||||
|
||||
function createDefaultModel(): DictSubmitModel {
|
||||
return {
|
||||
dictName: '',
|
||||
dictType: '',
|
||||
status: '0',
|
||||
remark: ''
|
||||
};
|
||||
}
|
||||
|
||||
type RuleKey = Exclude<keyof DictSubmitModel, 'remark'>;
|
||||
|
||||
const rules: Record<RuleKey, App.Global.FormRule> = {
|
||||
dictId: defaultRequiredRule,
|
||||
dictName: defaultRequiredRule,
|
||||
dictType: defaultRequiredRule,
|
||||
status: defaultRequiredRule
|
||||
};
|
||||
|
||||
function handleUpdateModelWhenEdit() {
|
||||
if (props.operateType === 'add') {
|
||||
model.value = createDefaultModel();
|
||||
}
|
||||
|
||||
if (props.operateType === 'edit' && props.rowData) {
|
||||
model.value = { ...props.rowData };
|
||||
}
|
||||
}
|
||||
|
||||
function closeDrawer() {
|
||||
visible.value = false;
|
||||
}
|
||||
|
||||
async function handleSubmit() {
|
||||
await validate();
|
||||
|
||||
const { error } = await (props.operateType === 'edit' ? doEditDict : doAddDict)(model.value);
|
||||
|
||||
if (!error) {
|
||||
$message?.success($t(props.operateType === 'add' ? 'common.addSuccess' : 'common.updateSuccess'));
|
||||
closeDrawer();
|
||||
emit('submitted');
|
||||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
() => visible.value,
|
||||
val => {
|
||||
if (val) {
|
||||
handleUpdateModelWhenEdit();
|
||||
resetFields();
|
||||
}
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ADrawer
|
||||
v-model:open="visible"
|
||||
:body-style="{ paddingRight: '0px', paddingTop: '0', paddingBottom: '0' }"
|
||||
:title="title"
|
||||
:width="460"
|
||||
>
|
||||
<SimpleScrollbar>
|
||||
<AForm ref="formRef" py-20px pr-20px layout="vertical" :model="model" :rules="rules">
|
||||
<AFormItem :label="$t('page.manage.dict.dictName')" name="dictName">
|
||||
<AInput v-model:value="model.dictName" :placeholder="$t('page.manage.dict.form.dictName')" />
|
||||
</AFormItem>
|
||||
<AFormItem :label="$t('page.manage.dict.dictType')" name="dictType">
|
||||
<AInput v-model:value="model.dictType" :placeholder="$t('page.manage.dict.form.dictType')" />
|
||||
</AFormItem>
|
||||
<AFormItem :label="$t('page.manage.dict.status')" name="status">
|
||||
<ARadioGroup v-model:value="model.status">
|
||||
<ARadio v-for="item in enableStatusOptions" :key="item.value" :value="item.value">
|
||||
{{ $t(item.label) }}
|
||||
</ARadio>
|
||||
</ARadioGroup>
|
||||
</AFormItem>
|
||||
<AFormItem :label="$t('page.manage.dict.remark')" name="remark">
|
||||
<ATextarea v-model:value="model.remark" :placeholder="$t('page.manage.dict.form.remark')" />
|
||||
</AFormItem>
|
||||
</AForm>
|
||||
</SimpleScrollbar>
|
||||
|
||||
<template #footer>
|
||||
<div class="flex-y-center justify-end gap-12px">
|
||||
<AButton @click="closeDrawer">{{ $t('common.cancel') }}</AButton>
|
||||
<AButton type="primary" @click="handleSubmit">{{ $t('common.confirm') }}</AButton>
|
||||
</div>
|
||||
</template>
|
||||
</ADrawer>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
:deep(.ant-input-number) {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
70
src/views/manage/dict/modules/dict-search.vue
Normal file
70
src/views/manage/dict/modules/dict-search.vue
Normal file
@@ -0,0 +1,70 @@
|
||||
<script setup lang="ts">
|
||||
import { $t } from '@/locales';
|
||||
import { enableStatusOptions } from '@/constants/business';
|
||||
|
||||
defineOptions({
|
||||
name: 'DictSearch'
|
||||
});
|
||||
|
||||
interface Emits {
|
||||
(e: 'reset'): void;
|
||||
(e: 'search'): void;
|
||||
}
|
||||
|
||||
const emit = defineEmits<Emits>();
|
||||
|
||||
const model = defineModel<Api.SystemManage.DictSearchParams>('model', {
|
||||
required: true
|
||||
});
|
||||
|
||||
function reset() {
|
||||
emit('reset');
|
||||
}
|
||||
|
||||
function search() {
|
||||
emit('search');
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ACard :title="$t('common.search')" :bordered="false" class="card-wrapper">
|
||||
<AForm :model="model" :label-width="80">
|
||||
<ARow :gutter="[16, 16]" wrap>
|
||||
<ACol :span="24" :md="12" :lg="8">
|
||||
<AFormItem :label="$t('page.manage.dict.dictName')" name="dictName" class="m-0">
|
||||
<AInput v-model:value="model.dictName" :placeholder="$t('page.manage.dict.form.dictName')" />
|
||||
</AFormItem>
|
||||
</ACol>
|
||||
<ACol :span="24" :md="12" :lg="8">
|
||||
<AFormItem :label="$t('page.manage.dict.status')" name="status" class="m-0">
|
||||
<ASelect v-model:value="model.status" :placeholder="$t('page.manage.dict.form.status')" allow-clear>
|
||||
<ASelectOption v-for="option in enableStatusOptions" :key="option.value" :value="option.value">
|
||||
{{ $t(option.label) }}
|
||||
</ASelectOption>
|
||||
</ASelect>
|
||||
</AFormItem>
|
||||
</ACol>
|
||||
<ACol :span="24" :md="12" :lg="8">
|
||||
<AFormItem class="m-0">
|
||||
<div class="w-full flex-y-center justify-end gap-12px">
|
||||
<AButton @click="reset">
|
||||
<div class="flex-y-center gap-8px">
|
||||
<icon-ic-round-refresh class="text-icon" />
|
||||
<span>{{ $t('common.reset') }}</span>
|
||||
</div>
|
||||
</AButton>
|
||||
<AButton type="primary" ghost @click="search">
|
||||
<div class="flex-y-center gap-8px">
|
||||
<icon-ic-round-search class="text-icon" />
|
||||
<span>{{ $t('common.search') }}</span>
|
||||
</div>
|
||||
</AButton>
|
||||
</div>
|
||||
</AFormItem>
|
||||
</ACol>
|
||||
</ARow>
|
||||
</AForm>
|
||||
</ACard>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
Reference in New Issue
Block a user