Bläddra i källkod

feature:分类管理接口对接

chen 1 år sedan
förälder
incheckning
37a8db0055

+ 2 - 2
components.d.ts

@@ -15,7 +15,6 @@ declare module 'vue' {
     TAvatar: typeof import('tdesign-vue-next')['Avatar']
     TButton: typeof import('tdesign-vue-next')['Button']
     TCard: typeof import('tdesign-vue-next')['Card']
-    TCheckbox: typeof import('tdesign-vue-next')['Checkbox']
     TDialog: typeof import('tdesign-vue-next')['Dialog']
     TDropdown: typeof import('tdesign-vue-next')['Dropdown']
     TForm: typeof import('tdesign-vue-next')['Form']
@@ -23,10 +22,11 @@ declare module 'vue' {
     THeader: typeof import('tdesign-vue-next')['Header']
     TIcon: typeof import('tdesign-vue-next')['Icon']
     TInput: typeof import('tdesign-vue-next')['Input']
+    TInputNumber: typeof import('tdesign-vue-next')['InputNumber']
     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']
+    TPopconfirm: typeof import('tdesign-vue-next')['Popconfirm']
     TSpace: typeof import('tdesign-vue-next')['Space']
     TTable: typeof import('tdesign-vue-next')['Table']
     TTag: typeof import('tdesign-vue-next')['Tag']

+ 23 - 0
src/api/category.ts

@@ -0,0 +1,23 @@
+import httpClient from '@/api/httpClient'
+import type { PageResult } from '@/model/base'
+import type { Category,CategorySearchFilter,CategoryDeleteRequest } from '@/model/category'
+
+export const searchCategoryRequest = (categoryFilterParam:CategorySearchFilter ) => {
+  return httpClient.get<PageResult<Category>>('/categories',categoryFilterParam)
+}
+
+export const createCategoryRequest = (category: Pick<Category,'name'|'code'|'order'>) => {
+  return httpClient.post<Category>('/categories',category)
+}
+
+export const deleteCategoryRequest = (categoryDeleteParam:CategoryDeleteRequest) => {
+  return httpClient.delete(`/categories`,categoryDeleteParam)
+}
+
+export const searchCategoryByIdRequest = (id: string) => {
+  return httpClient.get<Category>(`/categories/${id}`)
+}
+
+export const updateCategoryByIdRequest = (id: string,category: Pick<Category,'name'|'code'|'order'>) => {
+  return httpClient.put<Category>(`/categories/${id}`,category)
+}

+ 13 - 9
src/api/httpClient.ts

@@ -1,5 +1,5 @@
-import axios, { type AxiosInstance } from 'axios'
-import type { ErrorResponse } from '@/model/base'
+import axios, { type AxiosInstance, } from 'axios'
+import type { ErrorResponse,ErrorResponseList } from '@/model/base'
 import { MessagePlugin } from 'tdesign-vue-next'
 import { useAppStore } from '@/stores/app'
 
@@ -9,18 +9,22 @@ const client: AxiosInstance = axios.create({
 })
 
 const handleError = (errorResponse: ErrorResponse) => {
+  
   MessagePlugin.error(errorResponse.message)
 }
 client.interceptors.response.use(
   (response) => {
-    if (response.data.code) {
-      handleError(response.data)
-      return Promise.reject(response.data)
-    }
     return response
   },
   (error) => {
-    const errorResponse = error.response.data as ErrorResponse
+    let errorResponse: ErrorResponse;
+    if (Array.isArray(error.response.data)) {
+      const errorResponseList = error.response.data as ErrorResponseList;
+      errorResponse = errorResponseList[0];
+    } else {
+      // 否则,将其视为单个 ErrorResponse
+      errorResponse = error.response.data as ErrorResponse;
+    }
     handleError(errorResponse)
     return Promise.reject(errorResponse)
   }
@@ -43,8 +47,8 @@ export default {
     const response = await client.post<T>(url, data)
     return response.data as T
   },
-  delete: async (url: string) => {
-    await client.delete(url)
+  delete: async (url: string,params?:any) => {
+    await client.delete(url,{params})
   },
   put: async <T>(url: string, data: Record<string, any> = {}) => {
     const response = await client.put<T>(url, data)

+ 3 - 3
src/composables/useSearchable.ts

@@ -15,9 +15,9 @@ export const useSearchable = <T extends BaseFilterRequest, D extends BaseModel>(
         loading.value = true
         const result = await api({...filter.value, page: pagination.page, size: pagination.size, ...defaultFilter})
         data.value = result.data
-        pagination.page = result.paging.page
-        pagination.size = result.paging.size
-        pagination.total = result.paging.total
+        pagination.page = result.pagination.page
+        pagination.size = result.pagination.size
+        pagination.total = result.pagination.total
         loading.value = false
     }
 

+ 5 - 3
src/model/base.ts

@@ -5,7 +5,7 @@ export interface ErrorResponse {
   message: string;
 }
 export interface BaseModel {
-  id: number
+  id: string
   createdTime: string
   updateTime: string
 }
@@ -25,6 +25,8 @@ export interface BaseFilterRequest extends Partial<Paging>{
 }
 
 export interface PageResult<T> {
-  paging: Paging,
+  pagination: Paging,
   data: T[]
-}
+}
+
+export interface ErrorResponseList extends Array<ErrorResponse> {}

+ 23 - 0
src/model/category.ts

@@ -0,0 +1,23 @@
+import type { AuditBaseModel,Paging } from '@/model/base'
+
+
+export interface Category extends AuditBaseModel{
+  name: string
+  code:string
+  order: number
+}
+
+
+export interface CategorySearchFilter extends Partial<Paging>{
+  order?: string
+}
+
+export interface CategoryDeleteRequest{
+  id:string
+}
+
+export interface UpdateCategoryRequest{
+  name: string
+  code:string
+  order: number
+}

+ 0 - 8
src/model/classify.ts

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

+ 36 - 18
src/pages/classify/components/ClassifyDialog.vue → src/pages/category/components/CategoryDialog.vue

@@ -5,36 +5,45 @@
     :confirmBtn="confirmBtn"
     :closeOnOverlayClick="false"
     @close="handleCloseDialog"
-    @confirm="fetchSaveClassifyData"
+    @confirm="SaveCategoryData"
   >
     <t-space direction="vertical" style="width: 100%">
-      <TForm ref="form" :data="classifyData" :rules="rules" resetType="initial">
+      <TForm ref="form" :data="categoryData" :rules="rules" resetType="initial">
         <TFormItem label="分类名称:" name="name" help="名称长度小于5个汉字,大于2个汉字">
-          <t-input v-model.trim="classifyData.name" clearable placeholder="请输入分类名称" />
+          <t-input v-model.trim="categoryData.name" clearable placeholder="请输入分类名称" />
         </TFormItem>
-        <TFormItem label="分类编号:" name="numbering" help="编号由大于4个字符的英文字母组成">
-          <t-input v-model.trim="classifyData.numbering" clearable placeholder="请输入分类编号" />
+        <TFormItem label="分类编号:" name="code" help="编号由大于4个字符的英文字母组成">
+          <t-input v-model.trim="categoryData.code" clearable placeholder="请输入分类编号" />
         </TFormItem>
-        <TFormItem label="排序级别:" name="sort" help="排序级别由正整数组成">
-          <t-input v-model.trim="classifyData.sort" clearable placeholder="请输入排序级别" />
+        <TFormItem label="排序级别:" name="order" help="排序级别由正整数组成">
+          <t-input-number
+            :min="0"
+            name="order"
+            theme="normal"
+            class="!w-full"
+            placeholder="请输入排序级别"
+            v-model.trim="categoryData.order"
+          ></t-input-number>
         </TFormItem>
       </TForm>
     </t-space>
   </t-dialog>
 </template>
 <script lang="ts" setup>
-import type { Classify } from '@/model/classify'
+import type { Category } from '@/model/category'
 import { computed, ref, reactive, watch } from 'vue'
+import { createCategoryRequest, updateCategoryByIdRequest } from '@/api/category'
 import type { FormInstanceFunctions, FormProps } from 'tdesign-vue-next'
+import { MessagePlugin } from 'tdesign-vue-next'
 const props = defineProps<{
   isEdit?: Boolean | null
   headerTitle?: String | null
-  classify: Classify | {}
+  category: Category | {}
 }>()
 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 categoryData: FormProps['data'] = reactive({ name: '', order: 1 })
 const rules: FormProps['rules'] = {
   name: [
     {
@@ -66,7 +75,7 @@ const rules: FormProps['rules'] = {
       trigger: 'blur'
     }
   ],
-  numbering: [
+  code: [
     {
       required: true,
       message: '分类编号不能为空',
@@ -96,7 +105,7 @@ const rules: FormProps['rules'] = {
       trigger: 'blur'
     }
   ],
-  sort: [
+  order: [
     {
       required: true,
       message: '排序级别不能为空',
@@ -122,18 +131,27 @@ const rules: FormProps['rules'] = {
   ]
 }
 watch(
-  () => props.classify,
-  (newClassify) => {
-    Object.assign(classifyData, newClassify)
+  () => props.category,
+  (newCategory) => {
+    Object.assign(categoryData, newCategory)
   },
   { deep: true }
 )
-const fetchSaveClassifyData = async () => {
-  /* TODO: 保存分类数据 */
+const emit = defineEmits<{
+  success: [void]
+}>()
+const SaveCategoryData = async () => {
   if (form.value) {
     const valid = await form.value.validate()
     if (valid && typeof valid === 'boolean') {
-      /* TODO: 校验通过保存分类数据 */
+      const { code, name, order, id } = categoryData
+      if (!props.isEdit) {
+        await createCategoryRequest({ code, name, order })
+      } else {
+        await updateCategoryByIdRequest(id, { code, name, order })
+      }
+      MessagePlugin.success(`${props.isEdit ? '编辑' : '创建'}分类成功`)
+      emit('success')
       return
     }
   }

+ 121 - 0
src/pages/category/index.vue

@@ -0,0 +1,121 @@
+<script setup lang="ts">
+import { onMounted, ref } from 'vue'
+import type { Category, CategorySearchFilter } from '@/model/category'
+import RegularPage from '@/components/RegularPage.vue'
+import { searchCategoryRequest, deleteCategoryRequest } from '@/api/category'
+import type { BaseTableColumns } from 'tdesign-vue-next'
+import { useSearchable } from '@/composables/useSearchable'
+import CategoryDialog from '@/pages/category/components/CategoryDialog.vue'
+import { MessagePlugin } from 'tdesign-vue-next'
+
+const { data, loading, pagination, onPageChange, fetchData } = useSearchable<
+  CategorySearchFilter,
+  Category
+>(searchCategoryRequest)
+const columns: BaseTableColumns = [
+  {
+    title: '分类名称',
+    colKey: 'name',
+    width: 100
+  },
+  {
+    title: '分类编号',
+    colKey: 'code',
+    width: 100
+  },
+  {
+    title: '排序',
+    colKey: 'order',
+    width: 100
+  },
+  {
+    title: '操作',
+    colKey: 'operation',
+    width: 100
+  }
+]
+const categoryDialogVisible = ref(false)
+const isEdit = ref(false)
+const currentTableData = ref<Category | {}>({})
+onMounted(fetchData)
+const editClick = (category: Category) => {
+  isEdit.value = true
+  currentTableData.value = category
+  categoryDialogVisible.value = true
+}
+const handleCategoryDialogSuccess = () => {
+  fetchData()
+  categoryDialogVisible.value = false
+}
+const handleCategoryDialogClosed = () => {
+  isEdit.value = false
+  currentTableData.value = {}
+}
+const deleteCategoryClick = async (category: Category) => {
+  await deleteCategoryRequest({ id: category.id })
+  fetchData()
+  MessagePlugin.success('删除分类成功')
+}
+</script>
+
+<template>
+  <RegularPage title="分类管理">
+    <template #right-area>
+      <TButton @click="categoryDialogVisible = 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 }">
+        <TSpace :size="1">
+          <TButton variant="text" size="small" theme="primary" @click="editClick(row)"
+            >编辑</TButton
+          >
+          <t-popconfirm
+            theme="default"
+            content="确认删除此项分类吗"
+            @confirm="deleteCategoryClick(row)"
+          >
+            <TButton variant="text" size="small" theme="danger">删除</TButton>
+          </t-popconfirm>
+        </TSpace>
+      </template>
+    </TTable>
+    <CategoryDialog
+      headerTitle="分类"
+      :isEdit="isEdit"
+      :category="currentTableData"
+      v-model:visible="categoryDialogVisible"
+      @closed="handleCategoryDialogClosed"
+      @success="handleCategoryDialogSuccess"
+    ></CategoryDialog>
+  </RegularPage>
+</template>
+
+<style scoped>
+:deep(.t-card__body) {
+  flex: 1;
+  min-height: 0;
+}
+</style>

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

@@ -1,107 +0,0 @@
-<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>

+ 1 - 0
src/pages/common/login.vue

@@ -20,6 +20,7 @@ const handleLogin = async () => {
     return
   }
   try {
+    loading.value = true
     await appStore.login(loginForm)
   } finally {
     loading.value = false

+ 4 - 4
src/router/index.ts

@@ -6,9 +6,9 @@ import Layout from '@/layouts/Layout.vue'
 
 export const asyncRoutes: RouteRecordRaw[] = [
   {
-    name: 'classify',
-    path: 'classify',
-    component: () => import('@/pages/classify/index.vue'),
+    name: 'category',
+    path: 'category',
+    component: () => import('@/pages/category/index.vue'),
     meta: {
       title: '分类管理',
       icon: 'system-3'
@@ -24,7 +24,7 @@ const router = createRouter({
       component: Layout,
       path: '/',
       redirect: {
-        name: 'classify'
+        name: 'category'
       },
       children: asyncRoutes
     },