gemercheung 7 månader sedan
förälder
incheckning
3e209b8bb9

+ 1 - 0
packages/backend/.env

@@ -17,3 +17,4 @@ JWT_SECRET="d0!doc15415B0*4G0`"
 IS_PREVIEW=false  
 
 OSS_DOMAIN=https://ossxiaoan.4dage.com
+OSS_FOLDER=/helperCenter

+ 5 - 1
packages/backend/src/modules/menu/dto.ts

@@ -68,7 +68,11 @@ export class GetMenuDto {
   enable?: boolean;
 }
 
-export class QueryMenuDto extends GetMenuDto {}
+export class QueryMenuDto extends GetMenuDto {
+  @ApiProperty({ required: false })
+  @Allow()
+  parentId?: number;
+}
 
 export class UpdateMenuDto {
   @ApiProperty()

+ 10 - 6
packages/backend/src/modules/menu/menu.controller.ts

@@ -30,7 +30,7 @@ export class MenuController {
     private readonly menuService: MenuService,
     private readonly oSSService: OSSService,
     private readonly configService: ConfigService,
-  ) { }
+  ) {}
 
   @Post()
   create(@Body() createMenuDto: CreateMenuDto) {
@@ -42,6 +42,11 @@ export class MenuController {
     return this.menuService.findAll(getMenuDto);
   }
 
+  @Get('detail/:id')
+  getMenuDetail(@Param('id') id: string) {
+    return this.menuService.findWithChild(+id);
+  }
+
   @Get('page')
   findPagination(@Query() queryDto: QueryMenuDto) {
     return this.menuService.findPagination(queryDto);
@@ -81,14 +86,13 @@ export class MenuController {
   //   }),
   // )
   async updateCover(@Body() data: UploadCoverDto, @UploadedFile() file) {
-    console.log('file', file);
-    const result = await this.oSSService.upload(file, { filepath: '/helperCenter' });
-    console.log('result', result);
+    const result = await this.oSSService.upload(file, {
+      filepath: this.configService.get('OSS_FOLDER'),
+    });
     if (result && result.length > 0) {
       return this.configService.get('OSS_DOMAIN') + '/' + result[0].path;
     } else {
-      return false
+      return false;
     }
-
   }
 }

+ 9 - 2
packages/backend/src/modules/menu/menu.service.ts

@@ -9,7 +9,7 @@ export class MenuService {
   constructor(
     @InjectRepository(Menu)
     private menuRepo: Repository<Menu>,
-  ) {}
+  ) { }
 
   async create(createMenuDto: CreateMenuDto) {
     const menu = this.menuRepo.create(createMenuDto);
@@ -22,6 +22,7 @@ export class MenuService {
     const [data, total] = await this.menuRepo.findAndCount({
       where: {
         title: Like(`%${query.title || ''}%`),
+        parentId: query.parentId || undefined,
         enable: query.enable || undefined,
       },
       relations: { user: true, category: true },
@@ -45,6 +46,12 @@ export class MenuService {
   async find(id: number) {
     return await this.menuRepo.findOne({ where: { id } });
   }
+  async findWithChild(id: number) {
+    return await this.menuRepo.findOne({
+      where: { id },
+      relations: { children: true, user: true },
+    });
+  }
 
   async findAll(query: GetMenuDto) {
     return this.menuRepo.find({ where: query });
@@ -53,7 +60,7 @@ export class MenuService {
   async findLevel(level: number) {
     return await this.menuRepo.find({
       where: { level },
-      relations: { user: true, category: true },
+      relations: { user: true, category: true, children: true },
     });
   }
 

+ 17 - 4
packages/frontend/src/composables/useCrud.js

@@ -18,15 +18,22 @@ export function useCrud({ name, initForm = {}, doCreate, doDelete, doUpdate, ref
   const modalAction = ref('')
   const [modalRef, okLoading] = useModal()
   const [modalFormRef, modalForm, validation] = useForm(initForm)
+  let handleAddcallback, handleEditcallback
 
   /** 新增 */
-  function handleAdd(row = {}, title) {
+  function handleAdd(row = {}, title, addcallback) {
     handleOpen({ action: 'add', title, row: { ...initForm, ...row } })
+    if (typeof addcallback === 'function') {
+      handleAddcallback = addcallback
+    }
   }
 
   /** 修改 */
-  function handleEdit(row, title) {
+  function handleEdit(row, title, editcallback) {
     handleOpen({ action: 'edit', title, row })
+    if (typeof addcallback === 'function') {
+      handleEditcallback = editcallback
+    }
   }
 
   /** 查看 */
@@ -62,11 +69,17 @@ export function useCrud({ name, initForm = {}, doCreate, doDelete, doUpdate, ref
     const actions = {
       add: {
         api: () => doCreate(modalForm.value),
-        cb: () => $message.success('新增成功'),
+        cb: () => {
+          $message.success('新增成功')
+          handleAddcallback && handleAddcallback()
+        },
       },
       edit: {
         api: () => doUpdate(modalForm.value),
-        cb: () => $message.success('保存成功'),
+        cb: () => {
+          $message.success('保存成功')
+          handleEditcallback && handleEditcallback()
+        },
       },
     }
 

+ 41 - 14
packages/frontend/src/views/menu/index.vue

@@ -1,9 +1,9 @@
 <template>
   <CommonPage>
     <template #action>
-      <NButton type="primary" @click="handleAdd">
+      <NButton type="primary" @click="handleAddMenu">
         <i class="i-material-symbols:add mr-4 text-18" />
-        新增一级菜单
+        新增菜单
       </NButton>
     </template>
 
@@ -14,8 +14,10 @@
             xxx
           </div>
           <template #header-extra>
-            <n-dropdown trigger="click" :options="options" :show-arrow="true"
-              @select="(key) => handleSelect(key, item)">
+            <n-dropdown
+              trigger="click" :options="options" :show-arrow="true"
+              @select="(key) => handleSelect(key, item)"
+            >
               <n-button text>
                 <i class="i-material-symbols:more-horiz text-24" />
               </n-button>
@@ -27,18 +29,29 @@
 
     <MeModal ref="modalRef" width="520px">
       <n-form ref="modalFormRef" label-placement="left" label-align="left" :label-width="80" :model="modalForm">
-        <n-form-item label="名称" path="title" :rule="{
-          required: true,
-          message: '请输入名称',
-          trigger: ['input', 'blur'],
-        }">
+        <n-form-item
+          label="名称" path="title" :rule="{
+            required: true,
+            message: '请输入名称',
+            trigger: ['input', 'blur'],
+          }"
+        >
           <n-input v-model:value="modalForm.title" />
         </n-form-item>
 
         <n-form-item label="备注" path="remark">
           <n-input v-model:value="modalForm.remark" />
         </n-form-item>
-
+        <n-form-item label="是否显示" path="isPublish">
+          <NSwitch v-model:value="modalForm.isPublish">
+            <template #checked>
+              启用
+            </template>
+            <template #unchecked>
+              停用
+            </template>
+          </NSwitch>
+        </n-form-item>
         <n-form-item label="状态" path="enable">
           <NSwitch v-model:value="modalForm.enable">
             <template #checked>
@@ -57,11 +70,14 @@
 <script setup>
 import { MeModal } from '@/components'
 import { useCrud } from '@/composables'
+import { useUserStore } from '@/store/index.js'
 import { onMounted } from 'vue'
+import { useRouter } from 'vue-router'
 import MenuApi from './api.js'
 
+const router = useRouter()
 const topMenu = ref([])
-
+const { userId } = useUserStore()
 const $table = ref(null)
 /** QueryBar筛选参数(可选) */
 const queryItems = ref({})
@@ -77,6 +93,14 @@ const { modalRef, modalFormRef, modalAction, modalForm, handleAdd, handleDelete,
   })
 onMounted(() => {
   $table.value?.handleSearch()
+  getTopMenuList()
+})
+
+watchEffect(() => {
+  if (userId) {
+    modalForm.value.userId = userId
+    modalForm.value.level = 0
+  }
 })
 const options = [
   {
@@ -109,12 +133,15 @@ async function handleTopMenuDelete(id) {
 }
 async function handleTopMenuEdit(id) {
   console.log('id', id)
+  router.push(`/menu/list/${id}`)
 }
 
 function getTopMenuList() {
   MenuApi.getLevel(0).then(({ data = [] }) => (topMenu.value = data))
 }
-onMounted(() => {
-  getTopMenuList()
-})
+function handleAddMenu() {
+  handleAdd(null, null, () => {
+    getTopMenuList()
+  })
+}
 </script>

+ 155 - 0
packages/frontend/src/views/menu/list.vue

@@ -0,0 +1,155 @@
+<template>
+  <CommonPage back :title="detail.title ">
+    <template #action>
+      <NButton type="primary">
+        <i class="i-material-symbols:add mr-4 text-18" />
+        新增子菜单
+      </NButton>
+    </template>
+
+    <MeCrud ref="$table" v-model:query-items="queryItems" :scroll-x="1200" :columns="columns" :get-data="api.read">
+      <MeQueryItem label="标题" :label-width="50">
+        <n-input v-model:value="queryItems.title" type="text" placeholder="请输入标题名" clearable>
+          <template #password-visible-icon />
+        </n-input>
+      </MeQueryItem>
+      <MeQueryItem label="状态" :label-width="50">
+        <n-select
+          v-model:value="queryItems.enable" clearable :options="[
+            { label: '启用', value: 1 },
+            { label: '停用', value: 0 },
+          ]"
+        />
+      </MeQueryItem>
+    </MeCrud>
+  </CommonPage>
+</template>
+
+<script setup>
+import { MeCrud, MeQueryItem } from '@/components'
+import { useCrud } from '@/composables'
+import { formatDateTime } from '@/utils'
+import { NButton, NSwitch } from 'naive-ui'
+import { useRoute, useRouter } from 'vue-router'
+
+import api from './api.js'
+
+const $table = ref(null)
+
+const router = useRouter()
+const route = useRoute()
+const detail = ref({
+  title: '',
+  id: null,
+})
+/** QueryBar筛选参数(可选) */
+const queryItems = ref({
+  parentId: route.params.id,
+})
+const { handleDelete, handleEdit }
+  = useCrud({
+    name: '文章',
+    doCreate: api.create,
+    doDelete: api.delete,
+    doUpdate: api.update,
+    initForm: { enable: true },
+    refresh: (_, keepCurrentPage) => $table.value?.handleSearch(keepCurrentPage),
+  })
+
+async function getMenuDetail() {
+  const { data } = await api.getOne(route.params.id)
+  if (data) {
+    console.log('data', data)
+    detail.value = data
+  }
+}
+
+const columns = [
+  { title: '标题名', key: 'title', width: '200' },
+  { title: '分类', key: 'category.title' },
+  {
+    title: '创建时间',
+    key: 'createTime',
+    render: row => h('span', formatDateTime(row.createTime)),
+  },
+  {
+    title: '状态',
+    key: 'enable',
+    render: row =>
+      h(
+        NSwitch,
+        {
+          size: 'small',
+          rubberBand: false,
+          value: row.enable,
+          loading: !!row.enableLoading,
+          disabled: row.code === 'SUPER_ADMIN',
+          onUpdateValue: () => handleEnable(row),
+        },
+        {
+          checked: () => '启用',
+          unchecked: () => '停用',
+        },
+      ),
+  },
+  {
+    title: '操作',
+    key: 'actions',
+    width: 200,
+    align: 'center',
+    fixed: 'right',
+    render(row) {
+      return [
+        h(
+          NButton,
+          {
+            size: 'small',
+            type: 'primary',
+            style: 'margin-left: 12px;',
+            disabled: row.code === 'SUPER_ADMIN',
+            onClick: () => handleEdit(row),
+          },
+          {
+            default: () => '编辑',
+            icon: () => h('i', { class: 'i-material-symbols:edit-outline text-14' }),
+          },
+        ),
+
+        h(
+          NButton,
+          {
+            size: 'small',
+            type: 'error',
+            style: 'margin-left: 12px;',
+            disabled: row.code === 'SUPER_ADMIN',
+            onClick: () => handleDelete(row.id),
+          },
+          {
+            default: () => '删除',
+            icon: () => h('i', { class: 'i-material-symbols:delete-outline text-14' }),
+          },
+        ),
+      ]
+    },
+  },
+]
+
+async function handleEnable(row) {
+  row.enableLoading = true
+  try {
+    await api.update({ id: row.id, enable: !row.enable })
+    row.enableLoading = false
+    $message.success('操作成功')
+    $table.value?.handleSearch()
+  }
+  catch (error) {
+    console.error(error)
+    row.enableLoading = false
+  }
+}
+
+onMounted(() => {
+  $table.value?.handleSearch()
+  getMenuDetail()
+})
+</script>