feat(projects): demo 模块适配 Soybean 2.0

This commit is contained in:
xlsea
2025-10-28 15:32:21 +08:00
parent c1063e3ee3
commit 2b3373a2bb
9 changed files with 283 additions and 258 deletions
+16
View File
@@ -134,6 +134,13 @@ export function useNaivePaginatedTable<ResponseData, ApiData>(
}
});
// calculate the total width of the table this is used for horizontal scrolling
const scrollX = computed(() => {
return result.columns.value.reduce((acc, column) => {
return acc + Number(column.width ?? column.minWidth ?? 120);
}, 0);
});
async function getDataByPage(page: number = 1) {
if (page !== pagination.page) {
pagination.page = page;
@@ -165,6 +172,7 @@ export function useNaivePaginatedTable<ResponseData, ApiData>(
return {
...result,
scrollX,
getDataByPage,
pagination,
mobilePagination
@@ -291,6 +299,13 @@ export function useNaiveTreeTable<ResponseData, ApiData>(options: UseNaiveTreeTa
getColumns
});
// calculate the total width of the table this is used for horizontal scrolling
const scrollX = computed(() => {
return result.columns.value.reduce((acc, column) => {
return acc + Number(column.width ?? column.minWidth ?? 120);
}, 0);
});
const { keyField = 'id', defaultExpandAll = false } = options;
const expandedRowKeys = ref<ApiData[keyof ApiData][]>([]);
@@ -323,6 +338,7 @@ export function useNaiveTreeTable<ResponseData, ApiData>(options: UseNaiveTreeTa
return {
...result,
scrollX,
rows,
isCollapse,
expandedRowKeys,
+8
View File
@@ -36,8 +36,10 @@ declare module 'vue' {
IconLocalLogo: typeof import('~icons/local/logo')['default']
IconMdiArrowDownThin: typeof import('~icons/mdi/arrow-down-thin')['default']
IconMdiArrowUpThin: typeof import('~icons/mdi/arrow-up-thin')['default']
IconMdiGithub: typeof import('~icons/mdi/github')['default']
IconMdiKeyboardEsc: typeof import('~icons/mdi/keyboard-esc')['default']
IconMdiKeyboardReturn: typeof import('~icons/mdi/keyboard-return')['default']
IconSimpleIconsGitee: typeof import('~icons/simple-icons/gitee')['default']
IconTooltip: typeof import('./../components/common/icon-tooltip.vue')['default']
IconUilSearch: typeof import('~icons/uil/search')['default']
JsonPreview: typeof import('./../components/custom/json-preview.vue')['default']
@@ -64,6 +66,8 @@ declare module 'vue' {
NDropdown: typeof import('naive-ui')['NDropdown']
NEllipsis: typeof import('naive-ui')['NEllipsis']
NEmpty: typeof import('naive-ui')['NEmpty']
NForm: typeof import('naive-ui')['NForm']
NFormItem: typeof import('naive-ui')['NFormItem']
NGi: typeof import('naive-ui')['NGi']
NGrid: typeof import('naive-ui')['NGrid']
NInput: typeof import('naive-ui')['NInput']
@@ -139,8 +143,10 @@ declare global {
const IconLocalLogo: typeof import('~icons/local/logo')['default']
const IconMdiArrowDownThin: typeof import('~icons/mdi/arrow-down-thin')['default']
const IconMdiArrowUpThin: typeof import('~icons/mdi/arrow-up-thin')['default']
const IconMdiGithub: typeof import('~icons/mdi/github')['default']
const IconMdiKeyboardEsc: typeof import('~icons/mdi/keyboard-esc')['default']
const IconMdiKeyboardReturn: typeof import('~icons/mdi/keyboard-return')['default']
const IconSimpleIconsGitee: typeof import('~icons/simple-icons/gitee')['default']
const IconTooltip: typeof import('./../components/common/icon-tooltip.vue')['default']
const IconUilSearch: typeof import('~icons/uil/search')['default']
const JsonPreview: typeof import('./../components/custom/json-preview.vue')['default']
@@ -167,6 +173,8 @@ declare global {
const NDropdown: typeof import('naive-ui')['NDropdown']
const NEllipsis: typeof import('naive-ui')['NEllipsis']
const NEmpty: typeof import('naive-ui')['NEmpty']
const NForm: typeof import('naive-ui')['NForm']
const NFormItem: typeof import('naive-ui')['NFormItem']
const NGi: typeof import('naive-ui')['NGi']
const NGrid: typeof import('naive-ui')['NGrid']
const NInput: typeof import('naive-ui')['NInput']
@@ -187,13 +187,13 @@ async function handleSocialLogin(type: Api.System.SocialSource) {
<div class="w-full flex-y-center gap-16px">
<NButton class="flex-1" @click="handleSocialLogin('gitee')">
<template #icon>
<icon-simple-icons:gitee class="color-#c71d23" />
<icon-simple-icons-gitee class="color-#c71d23" />
</template>
<span class="ml-6px">Gitee</span>
</NButton>
<NButton class="flex-1" @click="handleSocialLogin('github')">
<template #icon>
<icon-mdi:github class="color-#010409" />
<icon-mdi-github class="color-#010409" />
</template>
<span class="ml-6px">GitHub</span>
</NButton>
+10 -9
View File
@@ -1,5 +1,5 @@
<script setup lang="tsx">
import { reactive } from 'vue';
import { ref } from 'vue';
import { NDivider } from 'naive-ui';
import { fetchBatchDeleteDemo, fetchGetDemoList } from '@/service/api/demo';
import { useAppStore } from '@/store/modules/app';
@@ -19,7 +19,7 @@ const appStore = useAppStore();
const { download } = useDownload();
const { hasAuth } = useAuth();
const searchParams: Api.Demo.DemoSearchParams = reactive({
const searchParams = ref<Api.Demo.DemoSearchParams>({
pageNum: 1,
pageSize: 10,
deptId: null,
@@ -30,12 +30,13 @@ const searchParams: Api.Demo.DemoSearchParams = reactive({
params: {}
});
const { columns, columnChecks, data, getData, getDataByPage, loading, mobilePagination } = useNaivePaginatedTable({
api: () => fetchGetDemoList(searchParams),
const { columns, columnChecks, data, getData, getDataByPage, loading, mobilePagination, scrollX } =
useNaivePaginatedTable({
api: () => fetchGetDemoList(searchParams.value),
transform: response => defaultTransform(response),
onPaginationParamsChange: params => {
searchParams.pageSize = params.page;
searchParams.pageNum = params.pageSize;
searchParams.value.pageNum = params.page;
searchParams.value.pageSize = params.pageSize;
},
columns: () => [
{
@@ -133,7 +134,7 @@ const { columns, columnChecks, data, getData, getDataByPage, loading, mobilePagi
}
}
]
});
});
const { drawerVisible, operateType, editingData, handleAdd, handleEdit, checkedRowKeys, onBatchDeleted, onDeleted } =
useTableOperate(data, 'id', getData);
@@ -163,7 +164,7 @@ async function handleExport() {
<template>
<div class="min-h-500px flex-col-stretch gap-16px overflow-hidden lt-sm:overflow-auto">
<DemoSearch v-model:model="searchParams" @search="getDataByPage" />
<DemoSearch v-model:model="searchParams" @reset="getDataByPage" @search="getDataByPage" />
<NCard title="测试单表列表" :bordered="false" size="small" class="card-wrapper sm:flex-1-hidden">
<template #header-extra>
<TableHeaderOperation
@@ -184,7 +185,7 @@ async function handleExport() {
:columns="columns"
:data="data"
:flex-height="!appStore.isMobile"
:scroll-x="962"
:scroll-x="scrollX"
:loading="loading"
remote
:row-key="row => row.id"
@@ -1,5 +1,6 @@
<script setup lang="ts">
import { computed, reactive, watch } from 'vue';
import { computed, ref, watch } from 'vue';
import { jsonClone } from '@sa/utils';
import { fetchCreateDemo, fetchUpdateDemo } from '@/service/api/demo/demo';
import { useFormRules, useNaiveForm } from '@/hooks/common/form';
import { $t } from '@/locales';
@@ -40,7 +41,7 @@ const title = computed(() => {
type Model = Api.Demo.DemoOperateParams;
const model: Model = reactive(createDefaultModel());
const model = ref<Model>(createDefaultModel());
function createDefaultModel(): Model {
return {
@@ -65,13 +66,10 @@ const rules: Record<RuleKey, App.Global.FormRule> = {
};
function handleUpdateModelWhenEdit() {
if (props.operateType === 'add') {
Object.assign(model, createDefaultModel());
return;
}
model.value = createDefaultModel();
if (props.operateType === 'edit' && props.rowData) {
Object.assign(model, props.rowData);
Object.assign(model.value, jsonClone(props.rowData));
}
}
@@ -82,15 +80,15 @@ function closeDrawer() {
async function handleSubmit() {
await validate();
const { id, deptId, userId, orderNum, testKey, value } = model.value;
// request
if (props.operateType === 'add') {
const { deptId, userId, orderNum, testKey, value } = model;
const { error } = await fetchCreateDemo({ deptId, userId, orderNum, testKey, value });
if (error) return;
}
if (props.operateType === 'edit') {
const { id, deptId, userId, orderNum, testKey, value } = model;
const { error } = await fetchUpdateDemo({ id, deptId, userId, orderNum, testKey, value });
if (error) return;
}
@@ -125,7 +123,7 @@ watch(visible, () => {
<NInput v-model:value="model.testKey" placeholder="请输入 key 键" />
</NFormItem>
<NFormItem label="值" path="value">
<OssUpload v-model:value="model.value as string" upload-type="image" placeholder="请输入值" />
<NInput v-model:value="model.value" placeholder="请输入值" />
</NFormItem>
<NFormItem label="备注" path="remark">
<NInput v-model:value="model.remark" type="textarea" placeholder="请输入备注" />
+11 -14
View File
@@ -1,4 +1,6 @@
<script setup lang="ts">
import { toRaw } from 'vue';
import { jsonClone } from '@sa/utils';
import { useNaiveForm } from '@/hooks/common/form';
import { $t } from '@/locales';
@@ -7,6 +9,7 @@ defineOptions({
});
interface Emits {
(e: 'reset'): void;
(e: 'search'): void;
}
@@ -16,22 +19,16 @@ const { formRef, validate, restoreValidation } = useNaiveForm();
const model = defineModel<Api.Demo.DemoSearchParams>('model', { required: true });
const defaultModel = jsonClone(toRaw(model.value));
function resetModel() {
model.value = {
pageNum: 1,
pageSize: 10,
deptId: null,
userId: null,
orderNum: null,
testKey: null,
value: null,
params: {}
};
Object.assign(model.value, defaultModel);
}
async function reset() {
await restoreValidation();
resetModel();
emit('reset');
}
async function search() {
@@ -46,16 +43,16 @@ async function search() {
<NCollapseItem :title="$t('common.search')" name="user-search">
<NForm ref="formRef" :model="model" label-placement="left" :label-width="80">
<NGrid responsive="screen" item-responsive>
<NFormItemGi span="24 s:12 m:6" label="部门" path="deptId" class="pr-24px">
<NFormItemGi span="24 s:12 m:6" label="部门" path="deptId" class="pr-24px" label-width="auto">
<DeptTreeSelect v-model:value="model.deptId" placeholder="请选择部门" />
</NFormItemGi>
<NFormItemGi span="24 s:12 m:6" label="用户" path="userId" class="pr-24px">
<NFormItemGi span="24 s:12 m:6" label="用户" path="userId" class="pr-24px" label-width="auto">
<UserSelect v-model:value="model.userId" placeholder="请选择用户" />
</NFormItemGi>
<NFormItemGi span="24 s:12 m:6" label="key 键" path="testKey" class="pr-24px">
<NFormItemGi span="24 s:12 m:6" label="key 键" path="testKey" class="pr-24px" label-width="auto">
<NInput v-model:value="model.testKey" placeholder="请输入 key 键" />
</NFormItemGi>
<NFormItemGi span="24 s:12 m:6" label="值" path="value" class="pr-24px">
<NFormItemGi span="24 s:12 m:6" label="值" path="value" class="pr-24px" label-width="auto">
<NInput v-model:value="model.value" placeholder="请输入值" />
</NFormItemGi>
<NFormItemGi :show-feedback="false" span="24" class="pr-24px">
+19 -10
View File
@@ -1,5 +1,5 @@
<script setup lang="tsx">
import { reactive } from 'vue';
import { ref } from 'vue';
import { NDivider } from 'naive-ui';
import { jsonClone } from '@sa/utils';
import { fetchBatchDeleteTree, fetchGetTreeList } from '@/service/api/demo/tree';
@@ -20,9 +20,7 @@ const appStore = useAppStore();
const { download } = useDownload();
const { hasAuth } = useAuth();
const searchParams: Api.Demo.TreeSearchParams = reactive({
pageNum: 1,
pageSize: 10,
const searchParams = ref<Api.Demo.TreeSearchParams>({
parentId: null,
deptId: null,
userId: null,
@@ -30,10 +28,21 @@ const searchParams: Api.Demo.TreeSearchParams = reactive({
params: {}
});
const { columns, columnChecks, data, rows, getData, loading, expandedRowKeys, isCollapse, expandAll, collapseAll } =
useNaiveTreeTable({
const {
columns,
columnChecks,
data,
rows,
getData,
loading,
expandedRowKeys,
isCollapse,
expandAll,
collapseAll,
scrollX
} = useNaiveTreeTable({
keyField: 'id',
api: fetchGetTreeList,
api: () => fetchGetTreeList(searchParams.value),
transform: response => treeTransform(response, { idField: 'id' }),
columns: () => [
{
@@ -132,7 +141,7 @@ const { columns, columnChecks, data, rows, getData, loading, expandedRowKeys, is
}
}
]
});
});
const { drawerVisible, operateType, editingData, handleAdd, handleEdit, checkedRowKeys, onBatchDeleted, onDeleted } =
useTableOperate(rows, 'id', getData);
@@ -167,7 +176,7 @@ function handleExport() {
<template>
<div class="min-h-500px flex-col-stretch gap-16px overflow-hidden lt-sm:overflow-auto">
<TreeSearch v-model:model="searchParams" :tree-list="data" @search="getData" />
<TreeSearch v-model:model="searchParams" :tree-list="data" @reset="getData" @search="getData" />
<NCard title="测试树列表" :bordered="false" size="small" class="card-wrapper sm:flex-1-hidden">
<template #header-extra>
<TableHeaderOperation
@@ -204,7 +213,7 @@ function handleExport() {
:columns="columns"
:data="data"
:flex-height="!appStore.isMobile"
:scroll-x="962"
:scroll-x="scrollX"
:loading="loading"
remote
:row-key="row => row.id"
@@ -1,5 +1,6 @@
<script setup lang="ts">
import { computed, reactive, watch } from 'vue';
import { computed, ref, watch } from 'vue';
import { jsonClone } from '@sa/utils';
import { fetchCreateTree, fetchUpdateTree } from '@/service/api/demo/tree';
import { useFormRules, useNaiveForm } from '@/hooks/common/form';
import { $t } from '@/locales';
@@ -42,7 +43,7 @@ const title = computed(() => {
type Model = Api.Demo.TreeOperateParams;
const model: Model = reactive(createDefaultModel());
const model = ref<Model>(createDefaultModel());
function createDefaultModel(): Model {
return {
@@ -64,14 +65,11 @@ const rules: Record<RuleKey, App.Global.FormRule> = {
};
function handleUpdateModelWhenEdit() {
if (props.operateType === 'add') {
Object.assign(model, createDefaultModel());
model.parentId = props.rowData?.id || 0;
return;
}
model.value = createDefaultModel();
model.value.parentId = props.rowData?.id || 0;
if (props.operateType === 'edit' && props.rowData) {
Object.assign(model, props.rowData);
Object.assign(model.value, jsonClone(props.rowData));
}
}
@@ -82,15 +80,15 @@ function closeDrawer() {
async function handleSubmit() {
await validate();
const { id, parentId, deptId, userId, treeName } = model.value;
// request
if (props.operateType === 'add') {
const { parentId, deptId, userId, treeName } = model;
const { error } = await fetchCreateTree({ parentId, deptId, userId, treeName });
if (error) return;
}
if (props.operateType === 'edit') {
const { id, parentId, deptId, userId, treeName } = model;
const { error } = await fetchUpdateTree({ id, parentId, deptId, userId, treeName });
if (error) return;
}
+11 -13
View File
@@ -1,4 +1,6 @@
<script setup lang="ts">
import { toRaw } from 'vue';
import { jsonClone } from '@sa/utils';
import { useNaiveForm } from '@/hooks/common/form';
import { $t } from '@/locales';
@@ -14,6 +16,7 @@ interface Props {
defineProps<Props>();
interface Emits {
(e: 'reset'): void;
(e: 'search'): void;
}
@@ -23,21 +26,16 @@ const { formRef, validate, restoreValidation } = useNaiveForm();
const model = defineModel<Api.Demo.TreeSearchParams>('model', { required: true });
const defaultModel = jsonClone(toRaw(model.value));
function resetModel() {
model.value = {
pageNum: 1,
pageSize: 10,
parentId: null,
deptId: null,
userId: null,
treeName: null,
params: {}
};
Object.assign(model.value, defaultModel);
}
async function reset() {
await restoreValidation();
resetModel();
emit('reset');
}
async function search() {
@@ -52,7 +50,7 @@ async function search() {
<NCollapseItem :title="$t('common.search')" name="user-search">
<NForm ref="formRef" :model="model" label-placement="left" :label-width="80">
<NGrid responsive="screen" item-responsive>
<NFormItemGi span="24 s:12 m:6" label="父 ID" path="parentId" class="pr-24px">
<NFormItemGi span="24 s:12 m:6" label="父 ID" path="parentId" class="pr-24px" label-width="auto">
<NTreeSelect
v-model:value="model.parentId"
filterable
@@ -63,13 +61,13 @@ async function search() {
:default-expanded-keys="[0]"
/>
</NFormItemGi>
<NFormItemGi span="24 s:12 m:6" label="部门" path="deptId" class="pr-24px">
<NFormItemGi span="24 s:12 m:6" label="部门" path="deptId" class="pr-24px" label-width="auto">
<DeptTreeSelect v-model:value="model.deptId" placeholder="请选择部门" />
</NFormItemGi>
<NFormItemGi span="24 s:12 m:6" label="用户" path="userId" class="pr-24px">
<NFormItemGi span="24 s:12 m:6" label="用户" path="userId" class="pr-24px" label-width="auto">
<UserSelect v-model:value="model.userId" placeholder="请选择用户" />
</NFormItemGi>
<NFormItemGi span="24 s:12 m:6" label="值" path="treeName" class="pr-24px">
<NFormItemGi span="24 s:12 m:6" label="值" path="treeName" class="pr-24px" label-width="auto">
<NInput v-model:value="model.treeName" placeholder="请输入值" />
</NFormItemGi>
<NFormItemGi :show-feedback="false" span="24" class="pr-24px">