Quellcode durchsuchen

feat(pages/classify): 添加分类管理页面及功能

chen vor 1 Jahr
Ursprung
Commit
7f2bec6ea9

+ 1 - 12
components.d.ts

@@ -8,7 +8,7 @@ export {}
 declare module 'vue' {
   export interface GlobalComponents {
     Copyright: typeof import('./src/components/Copyright.vue')['default']
-    Device: typeof import('./src/components/Device.vue')['default']
+    Dialog: typeof import('./src/components/Dialog.vue')['default']
     RegularPage: typeof import('./src/components/RegularPage.vue')['default']
     RouterLink: typeof import('vue-router')['RouterLink']
     RouterView: typeof import('vue-router')['RouterView']
@@ -23,28 +23,17 @@ declare module 'vue' {
     TForm: typeof import('tdesign-vue-next')['Form']
     TFormItem: typeof import('tdesign-vue-next')['FormItem']
     THeader: typeof import('tdesign-vue-next')['Header']
-    THeadMenu: typeof import('tdesign-vue-next')['HeadMenu']
     TIcon: typeof import('tdesign-vue-next')['Icon']
-    TImage: typeof import('tdesign-vue-next')['Image']
-    TImageViewer: typeof import('tdesign-vue-next')['ImageViewer']
     TInput: typeof import('tdesign-vue-next')['Input']
-    TInputNumber: typeof import('tdesign-vue-next')['InputNumber']
-    TipTap: typeof import('./src/components/TipTap.vue')['default']
     TLayout: typeof import('tdesign-vue-next')['Layout']
     TLink: typeof import('tdesign-vue-next')['Link']
     TMenu: typeof import('tdesign-vue-next')['Menu']
     TMenuItem: typeof import('tdesign-vue-next')['MenuItem']
     TPagination: typeof import('tdesign-vue-next')['Pagination']
-    TPopconfirm: typeof import('tdesign-vue-next')['Popconfirm']
     TRow: typeof import('tdesign-vue-next')['Row']
     TSelect: typeof import('tdesign-vue-next')['Select']
     TSpace: typeof import('tdesign-vue-next')['Space']
-    TSwitch: typeof import('tdesign-vue-next')['Switch']
     TTable: typeof import('tdesign-vue-next')['Table']
     TTag: typeof import('tdesign-vue-next')['Tag']
-    TTagInput: typeof import('tdesign-vue-next')['TagInput']
-    TTextarea: typeof import('tdesign-vue-next')['Textarea']
-    TTree: typeof import('tdesign-vue-next')['Tree']
-    TUpload: typeof import('tdesign-vue-next')['Upload']
   }
 }

+ 12 - 8
src/components/RegularPage.vue

@@ -7,6 +7,7 @@ defineProps<{
 }>()
 const slots = defineSlots<{
   default: any
+  'right-area': any
   'action-area': any
   'search-area': any
   'back-icon': any
@@ -14,16 +15,19 @@ const slots = defineSlots<{
 </script>
 
 <template>
-  <TCard class="w-full min-h-full" :bordered="false" :header-bordered="true">
+  <TCard class="w-full h-full flex flex-col" :bordered="false" :header-bordered="true">
     <template #header>
-      <div class="flex items-center gap-2">
-        <slot v-if="backIcon || slots['back-icon']" name="back-icon">
-          <TButton variant="text" size="small" @click="$router.back()">
-            <ArrowLeftIcon size="20"></ArrowLeftIcon>
-          </TButton>
-        </slot>
+      <div class="flex justify-between w-full">
+        <div class="flex items-center gap-2">
+          <slot v-if="backIcon || slots['back-icon']" name="back-icon">
+            <TButton variant="text" size="small" @click="$router.back()">
+              <ArrowLeftIcon size="20"></ArrowLeftIcon>
+            </TButton>
+          </slot>
 
-        <div class="text-lg">{{ title }}</div>
+          <div class="text-lg">{{ title }}</div>
+        </div>
+        <slot name="right-area"> </slot>
       </div>
     </template>
     <TSpace v-if="slots['action-area']" class="mb-6">

+ 3 - 7
src/layouts/components/Content.vue

@@ -1,13 +1,9 @@
-<script setup lang="ts">
-
-</script>
+<script setup lang="ts"></script>
 
 <template>
-  <div class="w-full min-h-full overflow-y-auto pt-6 px-6">
+  <div class="w-full h-full overflow-y-auto pt-6 px-6">
     <RouterView></RouterView>
   </div>
 </template>
 
-<style scoped>
-
-</style>
+<style scoped></style>

+ 8 - 0
src/model/classify.ts

@@ -0,0 +1,8 @@
+import type { BaseModel } from '@/model/base'
+
+export interface Classify extends BaseModel{
+  numbering:string
+  name: string
+  sort: string
+}
+

+ 148 - 0
src/pages/classify/components/ClassifyDialog.vue

@@ -0,0 +1,148 @@
+<template>
+  <t-dialog
+    width="40%"
+    :header="header"
+    :confirmBtn="confirmBtn"
+    :closeOnOverlayClick="false"
+    @close="handleCloseDialog"
+    @confirm="fetchSaveClassifyData"
+  >
+    <t-space direction="vertical" style="width: 100%">
+      <TForm ref="form" :data="classifyData" :rules="rules" resetType="initial">
+        <TFormItem label="分类名称:" name="name" help="名称长度小于5个汉字,大于2个汉字">
+          <t-input v-model.trim="classifyData.name" clearable placeholder="请输入分类名称" />
+        </TFormItem>
+        <TFormItem label="分类编号:" name="numbering" help="编号由大于4个字符的英文字母组成">
+          <t-input v-model.trim="classifyData.numbering" clearable placeholder="请输入分类编号" />
+        </TFormItem>
+        <TFormItem label="排序级别:" name="sort" help="排序级别由正整数组成">
+          <t-input v-model.trim="classifyData.sort" clearable placeholder="请输入排序级别" />
+        </TFormItem>
+      </TForm>
+    </t-space>
+  </t-dialog>
+</template>
+<script lang="ts" setup>
+import type { Classify } from '@/model/classify'
+import { computed, ref, reactive, watch } from 'vue'
+import type { FormInstanceFunctions, FormProps } from 'tdesign-vue-next'
+const props = defineProps<{
+  isEdit?: Boolean | null
+  headerTitle?: String | null
+  classify: Classify | {}
+}>()
+const form = ref<FormInstanceFunctions | null>(null)
+const header = computed(() => `${props.isEdit ? '编辑' : '创建'}${props.headerTitle || ''}`)
+const confirmBtn = computed(() => (props.isEdit ? '保存' : '确定'))
+const classifyData: FormProps['data'] = reactive({ name: '', sort: 0 })
+const rules: FormProps['rules'] = {
+  name: [
+    {
+      required: true,
+      message: '分类名称不能为空',
+      type: 'error',
+      trigger: 'blur'
+    },
+    {
+      required: true,
+      message: '分类名称不能为空',
+      type: 'error',
+      trigger: 'change'
+    },
+    {
+      whitespace: true,
+      message: '分类名称不能为空'
+    },
+    {
+      min: 4,
+      message: '输入汉字长度应在2到5之间',
+      type: 'error',
+      trigger: 'blur'
+    },
+    {
+      max: 10,
+      message: '输入汉字长度应在2到5之间',
+      type: 'error',
+      trigger: 'blur'
+    }
+  ],
+  numbering: [
+    {
+      required: true,
+      message: '分类编号不能为空',
+      type: 'error',
+      trigger: 'blur'
+    },
+    {
+      required: true,
+      message: '分类编号不能为空',
+      type: 'error',
+      trigger: 'change'
+    },
+    {
+      whitespace: true,
+      message: '分类编号不能为空'
+    },
+    {
+      min: 4,
+      message: '输入字符应大于4个字符长度',
+      type: 'error',
+      trigger: 'blur'
+    },
+    {
+      pattern: /^[A-Za-z]+$/,
+      message: '输入字符须为英文字母',
+      type: 'error',
+      trigger: 'blur'
+    }
+  ],
+  sort: [
+    {
+      required: true,
+      message: '排序级别不能为空',
+      type: 'error',
+      trigger: 'blur'
+    },
+    {
+      required: true,
+      message: '排序级别不能为空',
+      type: 'error',
+      trigger: 'change'
+    },
+    {
+      whitespace: true,
+      message: '排序级别不能为空'
+    },
+    {
+      pattern: /^[1-9]\d*$/,
+      message: '排序级别必须为正整数',
+      type: 'error',
+      trigger: 'blur'
+    }
+  ]
+}
+watch(
+  () => props.classify,
+  (newClassify) => {
+    Object.assign(classifyData, newClassify)
+  },
+  { deep: true }
+)
+const fetchSaveClassifyData = async () => {
+  /* TODO: 保存分类数据 */
+  if (form.value) {
+    const valid = await form.value.validate()
+    if (valid && typeof valid === 'boolean') {
+      /* TODO: 校验通过保存分类数据 */
+      return
+    }
+  }
+}
+const handleCloseDialog = () => {
+  // 数据&&规则校验结果重置
+  if (form.value) {
+    form.value.reset()
+    form.value.clearValidate()
+  }
+}
+</script>

+ 107 - 0
src/pages/classify/index.vue

@@ -0,0 +1,107 @@
+<script setup lang="ts">
+import RegularPage from '@/components/RegularPage.vue'
+import { onMounted, ref, reactive } from 'vue'
+import type { BaseTableColumns } from 'tdesign-vue-next'
+import type { Classify } from '@/model/classify'
+import ClassifyDialog from '@/pages/classify/components/ClassifyDialog.vue'
+
+const columns: BaseTableColumns = [
+  {
+    title: '分类名称',
+    colKey: 'name',
+    width: 100
+  },
+  {
+    title: '分类编号',
+    colKey: 'numbering',
+    width: 100
+  },
+  {
+    title: '排序',
+    colKey: 'sort',
+    width: 100
+  },
+  {
+    title: '操作',
+    colKey: 'operation',
+    width: 100
+  }
+]
+const loading = ref(false)
+const data = ref(
+  Array.from({ length: 1 })
+    .fill({ id: '1', name: '趣味科普', numbering: 'enjoy', sort: '1' })
+    .map((item) => item)
+)
+const pagination = reactive({
+  total: 1,
+  page: 1,
+  size: 10
+})
+const classifyDialogVisible = ref(false)
+const isEdit = ref(false)
+const currentTableData = ref<Classify | {}>({})
+const fetchClassifyTableData = () => {
+  /* TODO: 获取分类表格数据 */
+}
+onMounted(fetchClassifyTableData)
+const editClick = (classify: Classify) => {
+  isEdit.value = true
+  currentTableData.value = classify
+  console.log(classify)
+
+  classifyDialogVisible.value = true
+}
+const onPageChange = () => {
+  /* TODO: 分页切换 */
+}
+</script>
+
+<template>
+  <RegularPage title="分类管理">
+    <template #right-area>
+      <TButton @click="classifyDialogVisible = true">创建分类</TButton>
+    </template>
+    <TTable
+      class="w-full h-full"
+      row-key="id"
+      height="92%"
+      table-layout="auto"
+      :data="data"
+      :loading="loading"
+      :columns="columns"
+      cell-empty-content="-"
+      :paginationAffixedBottom="true"
+      :pagination="{
+        total: pagination.total,
+        current: pagination.page,
+        pageSize: pagination.size,
+        onChange: onPageChange
+      }"
+    >
+      <template #roles="{ row }">
+        <TSpace>
+          <TTag v-for="role in row.roles" :key="role.id" theme="success" variant="light">{{
+            role.label
+          }}</TTag>
+        </TSpace>
+      </template>
+      <template #operation="{ row }">
+        <TButton variant="text" size="small" theme="primary" @click="editClick(row)">编辑</TButton>
+      </template>
+    </TTable>
+    <ClassifyDialog
+      :classify="currentTableData"
+      v-model:visible="classifyDialogVisible"
+      :isEdit="isEdit"
+      headerTitle="分类"
+    ></ClassifyDialog>
+  </RegularPage>
+</template>
+
+<style scoped>
+:deep(.t-card__body) {
+  flex: 1;
+  min-height: 0;
+}
+</style>

+ 5 - 14
src/router/index.ts

@@ -6,21 +6,12 @@ import Layout from '@/layouts/Layout.vue'
 
 export const asyncRoutes: RouteRecordRaw[] = [
   {
-    name: 'dashboard',
-    path: 'dashboard',
-    component: () => import('@/pages/dashboard/index.vue'),
+    name: 'classify',
+    path: 'classify',
+    component: () => import('@/pages/classify/index.vue'),
     meta: {
-      title: '仪表盘',
-      icon: 'app'
-    }
-  },
-  {
-    name: 'users',
-    path: 'users',
-    component: () => import('@/pages/user/index.vue'),
-    meta: {
-      title: '用户',
-      icon: 'user'
+      title: '分类管理',
+      icon: 'system-3'
     }
   },
 ]