Преглед изворни кода

feat: 完善Web管理端API和路由

- 更新admin.js添加更多管理API
- 更新auth.js优化认证逻辑
- 更新router添加更多路由
- 更新Layout优化菜单结构
- 更新Rewards和Tasks页面功能
- 更新index.html标题
- 更新package.json依赖
User пре 5 месеци
родитељ
комит
287d31095c

+ 15 - 1
zxyj-backend/pom.xml

@@ -10,7 +10,7 @@
     <packaging>jar</packaging>
 
     <name>zxyj-backend</name>
-    <description>知家后端服务</description>
+    <description>知行益家后端服务</description>
 
     <parent>
         <groupId>org.springframework.boot</groupId>
@@ -86,6 +86,20 @@
             <artifactId>commons-lang3</artifactId>
         </dependency>
 
+        <!-- FastJSON -->
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>fastjson</artifactId>
+            <version>2.0.40</version>
+        </dependency>
+
+        <!-- SpringDoc OpenAPI (Swagger) -->
+        <dependency>
+            <groupId>org.springdoc</groupId>
+            <artifactId>springdoc-openapi-ui</artifactId>
+            <version>1.8.0</version>
+        </dependency>
+
         <!-- Test -->
         <dependency>
             <groupId>org.springframework.boot</groupId>

+ 1 - 0
zxyj-web/package.json

@@ -3,6 +3,7 @@
   "version": "1.0.0",
   "private": true,
   "scripts": {
+    "dev": "vue-cli-service serve",
     "serve": "vue-cli-service serve",
     "build": "vue-cli-service build"
   },

+ 2 - 2
zxyj-web/public/index.html

@@ -5,11 +5,11 @@
     <meta http-equiv="X-UA-Compatible" content="IE=edge">
     <meta name="viewport" content="width=device-width,initial-scale=1.0">
     <link rel="icon" href="<%= BASE_URL %>favicon.ico">
-    <title>知家管理后台</title>
+    <title>知行益家管理后台</title>
   </head>
   <body>
     <noscript>
-      <strong>We're sorry but 知家管理后台 doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
+      <strong>We're sorry but 知行益家管理后台 doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
     </noscript>
     <div id="app"></div>
   </body>

+ 30 - 30
zxyj-web/src/api/admin.js

@@ -4,15 +4,15 @@ import request from '@/utils/request'
 export function getUserList(params) {
   return request({
     url: '/api/admin/users',
-    method: 'get',
-    params
+    method: 'post',
+    data: params
   })
 }
 
 export function getUserDetail(id) {
   return request({
     url: `/api/admin/users/${id}`,
-    method: 'get'
+    method: 'post'
   })
 }
 
@@ -35,15 +35,15 @@ export function deleteUser(id) {
 export function getFamilyList(params) {
   return request({
     url: '/api/admin/families',
-    method: 'get',
-    params
+    method: 'post',
+    data: params
   })
 }
 
 export function getFamilyDetail(id) {
   return request({
     url: `/api/admin/families/${id}`,
-    method: 'get'
+    method: 'post'
   })
 }
 
@@ -65,7 +65,7 @@ export function deleteFamily(id) {
 export function getFamilyMembers(id) {
   return request({
     url: `/api/admin/families/${id}/members`,
-    method: 'get'
+    method: 'post'
   })
 }
 
@@ -73,14 +73,14 @@ export function getFamilyMembers(id) {
 export function getTaskTemplateList(params) {
   return request({
     url: '/api/admin/task-templates',
-    method: 'get',
-    params
+    method: 'post',
+    data: params
   })
 }
 
 export function createTaskTemplate(data) {
   return request({
-    url: '/api/admin/task-templates',
+    url: '/api/admin/task-templates/create',
     method: 'post',
     data
   })
@@ -105,15 +105,15 @@ export function deleteTaskTemplate(id) {
 export function getChildrenList(params) {
   return request({
     url: '/api/admin/children',
-    method: 'get',
-    params
+    method: 'post',
+    data: params
   })
 }
 
 export function getChildDetail(id) {
   return request({
     url: `/api/admin/children/${id}`,
-    method: 'get'
+    method: 'post'
   })
 }
 
@@ -144,15 +144,15 @@ export function adjustChildPoints(id, data) {
 export function getTasksList(params) {
   return request({
     url: '/api/admin/tasks',
-    method: 'get',
-    params
+    method: 'post',
+    data: params
   })
 }
 
 export function getTaskStats() {
   return request({
     url: '/api/admin/tasks/stats',
-    method: 'get'
+    method: 'post'
   })
 }
 
@@ -160,21 +160,21 @@ export function getTaskStats() {
 export function getRewardsList(params) {
   return request({
     url: '/api/admin/rewards',
-    method: 'get',
-    params
+    method: 'post',
+    data: params
   })
 }
 
 export function getRewardTemplates() {
   return request({
     url: '/api/admin/rewards/templates',
-    method: 'get'
+    method: 'post'
   })
 }
 
 export function createRewardTemplate(data) {
   return request({
-    url: '/api/admin/rewards/templates',
+    url: '/api/admin/rewards/create',
     method: 'post',
     data
   })
@@ -207,15 +207,15 @@ export function approveReward(id, data) {
 export function getPointsLogs(params) {
   return request({
     url: '/api/admin/points/logs',
-    method: 'get',
-    params
+    method: 'post',
+    data: params
   })
 }
 
 export function getPointsStats() {
   return request({
     url: '/api/admin/points/stats',
-    method: 'get'
+    method: 'post'
   })
 }
 
@@ -225,7 +225,7 @@ export function getPointsStats() {
 export function getTeacherFamilies() {
   return request({
     url: '/api/admin/teacher/families',
-    method: 'get'
+    method: 'post'
   })
 }
 
@@ -233,14 +233,14 @@ export function getTeacherFamilies() {
 export function getTeacherTaskTemplates() {
   return request({
     url: '/api/admin/teacher/task-templates',
-    method: 'get'
+    method: 'post'
   })
 }
 
 // 指导师创建任务模板
 export function createTeacherTaskTemplate(data) {
   return request({
-    url: '/api/admin/teacher/task-templates',
+    url: '/api/admin/teacher/task-templates/create',
     method: 'post',
     data
   })
@@ -258,8 +258,8 @@ export function deleteTeacherTaskTemplate(id) {
 export function getTeacherFamilyTasks(familyId, params) {
   return request({
     url: `/api/admin/teacher/families/${familyId}/tasks`,
-    method: 'get',
-    params
+    method: 'post',
+    data: params
   })
 }
 
@@ -267,7 +267,7 @@ export function getTeacherFamilyTasks(familyId, params) {
 export function getTeacherFamilyRewards(familyId, params) {
   return request({
     url: `/api/admin/teacher/families/${familyId}/rewards`,
-    method: 'get',
-    params
+    method: 'post',
+    data: params
   })
 }

+ 3 - 3
zxyj-web/src/api/auth.js

@@ -9,10 +9,10 @@ export function sendCode(data) {
   })
 }
 
-// 管理员登录
+// 管理员密码登录
 export function adminLogin(data) {
   return request({
-    url: '/api/admin-auth/login',
+    url: '/api/admin-auth/login-by-password',
     method: 'post',
     data
   })
@@ -22,7 +22,7 @@ export function adminLogin(data) {
 export function getAdminInfo() {
   return request({
     url: '/api/admin-auth/info',
-    method: 'get'
+    method: 'post'
   })
 }
 

+ 13 - 8
zxyj-web/src/router/index.js

@@ -62,6 +62,18 @@ const routes = [
         name: 'Points',
         component: () => import('@/views/Points.vue'),
         meta: { title: '积分管理' }
+      },
+      {
+        path: 'package-audit',
+        name: 'PackageAudit',
+        component: () => import('@/views/PackageAudit.vue'),
+        meta: { title: '套餐审核' }
+      },
+      {
+        path: 'guide-packages',
+        name: 'GuidePackages',
+        component: () => import('@/views/GuidePackages.vue'),
+        meta: { title: '指导师套餐' }
       }
     ]
   }
@@ -73,13 +85,6 @@ const router = new VueRouter({
   routes
 })
 
-router.beforeEach((to, from, next) => {
-  const token = localStorage.getItem('token')
-  if (to.path !== '/login' && !token) {
-    next('/login')
-  } else {
-    next()
-  }
-})
+router.beforeEach((to, from, next) => {\n  const token = localStorage.getItem('token')\n  const role = localStorage.getItem('role')\n  // authentication check\n  if (to.path !== '/login' && !token) {\n    return next('/login')\n  }\n  // teacher role protection for /guide* routes\n  if (to.path.startsWith('/guide') && role !== 'teacher') {\n    return next({ path: '/', replace: true })\n  }\n  return next()\n})
 
 export default router

+ 8 - 0
zxyj-web/src/views/Layout.vue

@@ -54,6 +54,14 @@
               <i class="el-icon-s-money"></i>
               <span>积分管理</span>
             </el-menu-item>
+            <el-menu-item index="/package-audit">
+              <i class="el-icon-s-check"></i>
+              <span>套餐审核</span>
+            </el-menu-item>
+            <el-menu-item index="/guide-packages">
+              <i class="el-icon-s-marketing"></i>
+              <span>指导师套餐</span>
+            </el-menu-item>
           </el-submenu>
         </el-menu>
     </el-aside>

+ 86 - 4
zxyj-web/src/views/Rewards.vue

@@ -19,6 +19,23 @@
         <el-tab-pane label="奖励列表" name="list">
           <!-- 搜索筛选 -->
           <el-form :inline="true" :model="searchForm" style="margin-bottom: 20px">
+            <el-form-item label="家庭">
+              <el-input
+                v-model="searchForm.familyName"
+                placeholder="点击选择家庭"
+                readonly
+                @click.native="showFamilySelector"
+              >
+                <el-button
+                  slot="append"
+                  icon="el-icon-close"
+                  v-if="searchForm.familyId"
+                  @click.stop="clearFamily"
+                  style="padding: 8px"
+                ></el-button>
+                <i slot="suffix" class="el-input__icon el-icon-arrow-down" @click.stop="showFamilySelector"></i>
+              </el-input>
+            </el-form-item>
             <el-form-item label="状态">
               <el-select v-model="searchForm.status" placeholder="全部状态" clearable @change="handleSearch">
                 <el-option label="可用" value="available"></el-option>
@@ -33,6 +50,28 @@
             </el-form-item>
           </el-form>
 
+          <!-- 家庭选择对话框 -->
+          <el-dialog title="选择家庭" :visible.sync="familyDialogVisible" width="600px">
+            <el-table :data="familyList" border v-loading="familyLoading" @row-click="selectFamily" style="cursor: pointer">
+              <el-table-column prop="id" label="ID" width="80"></el-table-column>
+              <el-table-column prop="name" label="家庭名称" min-width="150"></el-table-column>
+              <el-table-column prop="inviteCode" label="邀请码" width="120"></el-table-column>
+              <el-table-column prop="createdAt" label="创建时间" width="180">
+                <template slot-scope="scope">
+                  {{ formatDate(scope.row.createdAt) }}
+                </template>
+              </el-table-column>
+            </el-table>
+            <el-pagination
+              style="margin-top: 15px; text-align: right"
+              @current-change="loadFamilies"
+              :current-page="familyPagination.current"
+              :page-size="familyPagination.size"
+              :total="familyPagination.total"
+              layout="total, prev, pager, next"
+            ></el-pagination>
+          </el-dialog>
+
           <el-table :data="tableData" border v-loading="loading" style="width: 100%">
             <el-table-column prop="id" label="ID" width="80"></el-table-column>
             <el-table-column prop="title" label="奖励名称" min-width="200"></el-table-column>
@@ -42,7 +81,7 @@
               </template>
             </el-table-column>
             <el-table-column prop="category" label="分类" width="100"></el-table-column>
-            <el-table-column prop="childId" label="孩子ID" width="80"></el-table-column>
+            <el-table-column prop="familyId" label="家庭ID" width="80"></el-table-column>
             <el-table-column prop="status" label="状态" width="100">
               <template slot-scope="scope">
                 <el-tag :type="getStatusType(scope.row.status)" size="small">
@@ -154,7 +193,7 @@
 </template>
 
 <script>
-import { getRewardsList, getRewardTemplates, createRewardTemplate, deleteRewardTemplate, approveReward } from '@/api/admin'
+import { getRewardsList, getRewardTemplates, createRewardTemplate, deleteRewardTemplate, approveReward, getFamilyList } from '@/api/admin'
 
 export default {
   name: 'Rewards',
@@ -166,13 +205,24 @@ export default {
       tableData: [],
       templates: [],
       searchForm: {
-        status: ''
+        status: '',
+        familyId: '',
+        familyName: ''
       },
       pagination: {
         current: 1,
         size: 10,
         total: 0
       },
+      // 家庭选择对话框
+      familyDialogVisible: false,
+      familyLoading: false,
+      familyList: [],
+      familyPagination: {
+        current: 1,
+        size: 10,
+        total: 0
+      },
       // 创建模板对话框
       dialogVisible: false,
       creating: false,
@@ -193,7 +243,8 @@ export default {
         const params = {
           page: this.pagination.current,
           size: this.pagination.size,
-          ...this.searchForm
+          status: this.searchForm.status,
+          familyId: this.searchForm.familyId || undefined
         }
         const res = await getRewardsList(params)
         this.tableData = res.data.records || res.data || []
@@ -204,6 +255,37 @@ export default {
         this.loading = false
       }
     },
+    async loadFamilies(page = 1) {
+      this.familyLoading = true
+      try {
+        const res = await getFamilyList({
+          page: page,
+          size: this.familyPagination.size
+        })
+        this.familyList = res.data.records || res.data || []
+        this.familyPagination.total = res.data.total || this.familyList.length
+        this.familyPagination.current = page
+      } catch (error) {
+        this.$message.error(error.message || '加载家庭列表失败')
+      } finally {
+        this.familyLoading = false
+      }
+    },
+    selectFamily(row) {
+      this.searchForm.familyId = row.id
+      this.searchForm.familyName = row.name
+      this.familyDialogVisible = false
+      this.handleSearch()
+    },
+    showFamilySelector() {
+      this.familyDialogVisible = true
+      this.loadFamilies()
+    },
+    clearFamily() {
+      this.searchForm.familyId = ''
+      this.searchForm.familyName = ''
+      this.handleSearch()
+    },
     async loadTemplates() {
       this.loadingTemplates = true
       try {

+ 85 - 10
zxyj-web/src/views/Tasks.vue

@@ -32,13 +32,22 @@
             <el-option label="已取消" value="cancelled"></el-option>
           </el-select>
         </el-form-item>
-        <el-form-item label="孩子ID">
+        <el-form-item label="家庭">
           <el-input
-            v-model="searchForm.childId"
-            placeholder="请输入孩子ID"
-            clearable
-            @keyup.enter.native="handleSearch"
-          ></el-input>
+            v-model="searchForm.familyName"
+            placeholder="点击选择家庭"
+            readonly
+            @click.native="showFamilySelector"
+          >
+            <el-button
+              slot="append"
+              icon="el-icon-close"
+              v-if="searchForm.familyId"
+              @click.stop="clearFamily"
+              style="padding: 8px"
+            ></el-button>
+            <i slot="suffix" class="el-input__icon el-icon-arrow-down" @click.stop="showFamilySelector"></i>
+          </el-input>
         </el-form-item>
         <el-form-item>
           <el-button type="primary" @click="handleSearch">搜索</el-button>
@@ -46,6 +55,28 @@
         </el-form-item>
       </el-form>
 
+      <!-- 家庭选择对话框 -->
+      <el-dialog title="选择家庭" :visible.sync="familyDialogVisible" width="600px">
+        <el-table :data="familyList" border v-loading="familyLoading" @row-click="selectFamily" style="cursor: pointer">
+          <el-table-column prop="id" label="ID" width="80"></el-table-column>
+          <el-table-column prop="name" label="家庭名称" min-width="150"></el-table-column>
+          <el-table-column prop="inviteCode" label="邀请码" width="120"></el-table-column>
+          <el-table-column prop="createdAt" label="创建时间" width="180">
+            <template slot-scope="scope">
+              {{ formatDate(scope.row.createdAt) }}
+            </template>
+          </el-table-column>
+        </el-table>
+        <el-pagination
+          style="margin-top: 15px; text-align: right"
+          @current-change="loadFamilies"
+          :current-page="familyPagination.current"
+          :page-size="familyPagination.size"
+          :total="familyPagination.total"
+          layout="total, prev, pager, next"
+        ></el-pagination>
+      </el-dialog>
+
       <!-- 统计卡片 -->
       <el-row :gutter="20" style="margin-bottom: 20px">
         <el-col :span="6">
@@ -115,7 +146,7 @@
 </template>
 
 <script>
-import { getTasksList, getTaskStats } from '@/api/admin'
+import { getTasksList, getTaskStats, getFamilyList } from '@/api/admin'
 
 export default {
   name: 'Tasks',
@@ -130,12 +161,22 @@ export default {
       searchForm: {
         title: '',
         status: '',
-        childId: ''
+        familyName: '',
+        familyId: ''
       },
       pagination: {
         current: 1,
         size: 10,
         total: 0
+      },
+      // 家庭选择对话框
+      familyDialogVisible: false,
+      familyLoading: false,
+      familyList: [],
+      familyPagination: {
+        current: 1,
+        size: 10,
+        total: 0
       }
     }
   },
@@ -150,7 +191,8 @@ export default {
         const params = {
           page: this.pagination.current,
           size: this.pagination.size,
-          ...this.searchForm
+          status: this.searchForm.status,
+          familyId: this.searchForm.familyId || undefined
         }
         const res = await getTasksList(params)
         this.tableData = res.data.records || res.data || []
@@ -169,6 +211,38 @@ export default {
         console.error('加载统计失败:', error)
       }
     },
+    // 显示家庭选择对话框
+    async showFamilySelector() {
+      this.familyDialogVisible = true
+      this.loadFamilies()
+    },
+    async loadFamilies(page = 1) {
+      this.familyLoading = true
+      try {
+        const res = await getFamilyList({
+          page: page,
+          size: this.familyPagination.size
+        })
+        this.familyList = res.data.records || res.data || []
+        this.familyPagination.total = res.data.total || this.familyList.length
+        this.familyPagination.current = page
+      } catch (error) {
+        this.$message.error(error.message || '加载家庭列表失败')
+      } finally {
+        this.familyLoading = false
+      }
+    },
+    selectFamily(row) {
+      this.searchForm.familyId = row.id
+      this.searchForm.familyName = row.name
+      this.familyDialogVisible = false
+      this.handleSearch()
+    },
+    clearFamily() {
+      this.searchForm.familyId = ''
+      this.searchForm.familyName = ''
+      this.handleSearch()
+    },
     handleSearch() {
       this.pagination.current = 1
       this.loadTasks()
@@ -177,7 +251,8 @@ export default {
       this.searchForm = {
         title: '',
         status: '',
-        childId: ''
+        familyName: '',
+        familyId: ''
       }
       this.handleSearch()
     },