Просмотр исходного кода

feat: api改造,登录改造,字典改造

zhanghaijun 1 год назад
Родитель
Сommit
de23e3bb45

+ 5 - 5
src/api/course.api.ts

@@ -2,20 +2,20 @@ import request from '@/api'
 
 /** 课程模型 */
 export interface Course {
-  courseDesc: string /** 课程描述 */
-  courseGUID: string /** 课程GUID */
   courseID: number /** 课程ID */
   courseName: string /** 课程名称 */
+  courseDesc: string /** 课程描述 */
+  typeID: number /** 分类ID */
+  totalDuration: number /** 课程总时长 */
   createTime: string /** 创建时间 */
   creator: string /** 创建人 */
+  modifyTime: string /** 修改时间 */
+  courseGUID: string /** 课程GUID */
   externalTypeID: number /** 外部分类ID */
   guideDesc: string /** 指导描述 */
   guideFilePath: string /** 指导文件地址 */
   lineNo: number /** 线路号 */
-  modifyTime: string /** 修改时间 */
   runningMap: number /** 运行图 */
-  totalDuration: number /** 课程总时长 */
-  typeID: number /** 分类ID */
   virtualStartTime: number /** 虚拟起始时间 */
 }
 

+ 4 - 18
src/components/core/layouts/art-page-content/index.vue

@@ -6,30 +6,18 @@
 
     <RouterView v-if="isRefresh" v-slot="{ Component, route }" :style="contentStyle">
       <!-- 路由信息调试 -->
-      <div v-if="isOpenRouteInfo === 'true'" class="route-info">
-        router meta:{{ route.meta }}
-      </div>
+      <div v-if="isOpenRouteInfo === 'true'" class="route-info"> router meta:{{ route.meta }} </div>
 
       <!-- 缓存路由动画 -->
       <Transition :name="showTransitionMask ? '' : actualTransition" mode="out-in" appear>
         <KeepAlive :max="10" :exclude="keepAliveExclude">
-          <component
-            class="art-page-view"
-            :is="Component"
-            :key="route.path"
-            v-if="route.meta.keepAlive"
-          />
+          <component class="art-page-view" :is="Component" :key="route.path" v-if="route.meta.keepAlive" />
         </KeepAlive>
       </Transition>
 
       <!-- 非缓存路由动画 -->
       <Transition :name="showTransitionMask ? '' : actualTransition" mode="out-in" appear>
-        <component
-          class="art-page-view"
-          :is="Component"
-          :key="route.path"
-          v-if="!route.meta.keepAlive"
-        />
+        <component class="art-page-view" :is="Component" :key="route.path" v-if="!route.meta.keepAlive" />
       </Transition>
     </RouterView>
 
@@ -63,9 +51,7 @@
   const prevIsFullPage = ref(isFullPage.value)
 
   // 切换动画名称:从全屏返回时不使用动画
-  const actualTransition = computed(() =>
-    prevIsFullPage.value && !isFullPage.value ? '' : pageTransition.value
-  )
+  const actualTransition = computed(() => (prevIsFullPage.value && !isFullPage.value ? '' : pageTransition.value))
 
   // 监听全屏状态变化,显示过渡遮罩
   watch(isFullPage, (val, oldVal) => {

+ 3 - 3
src/router/routes/modules/train.ts

@@ -33,7 +33,7 @@ export const routes: AppRouteRecord[] = [
       {
         path: '/list',
         name: 'course:list',
-        component: '/train/course/list',
+        component: '/train/course',
         meta: { title: '课程列表', icon: 'ant-design:book-outlined' }
       },
       {
@@ -54,7 +54,7 @@ export const routes: AppRouteRecord[] = [
       {
         path: '/list',
         name: 'fault:list',
-        component: '/train/fault/list',
+        component: '/train/fault',
         meta: { title: '故障列表', icon: 'ant-design:warning-outlined' }
       }
     ]
@@ -70,7 +70,7 @@ export const routes: AppRouteRecord[] = [
       {
         path: 'list',
         name: 'evaluation:list',
-        component: '/train/evaluation/list',
+        component: '/train/evaluation',
         meta: { title: '评价列表', icon: 'ant-design:star-outlined' }
       }
     ]

+ 2 - 13
src/types/component/chart.ts

@@ -3,15 +3,7 @@ import type { EChartsOption } from '@/utils/echarts'
 // 图例位置类型
 export type LegendPosition = 'bottom' | 'top' | 'left' | 'right'
 
-export type SymbolType =
-  | 'circle'
-  | 'rect'
-  | 'roundRect'
-  | 'triangle'
-  | 'diamond'
-  | 'pin'
-  | 'arrow'
-  | 'none'
+export type SymbolType = 'circle' | 'rect' | 'roundRect' | 'triangle' | 'diamond' | 'pin' | 'arrow' | 'none'
 
 // 图表主题配置
 export interface ChartThemeConfig {
@@ -250,10 +242,7 @@ export interface MapChartProps extends BaseChartProps {
 }
 
 // 双向堆叠柱状图 Props 接口(人口金字塔样式)
-export interface BidirectionalBarChartProps
-  extends BaseChartProps,
-    AxisDisplayProps,
-    InteractionProps {
+export interface BidirectionalBarChartProps extends BaseChartProps, AxisDisplayProps, InteractionProps {
   /** 正向数据(向上显示) */
   positiveData: number[]
   /** 负向数据(向下显示) */

+ 2 - 2
src/types/component/dict.ts

@@ -1,9 +1,9 @@
-import { Option } from '@/types'
+// import { Option } from '@/types'
 export type ColorType = {
   bgColor?: string
   textColor?: string
 }
-export type NewOption = Option & ColorType
+export type NewOption = ColorType
 // 定义字典项的接口
 export interface DictItem {
   name: string

+ 4 - 9
src/utils/http/index.ts

@@ -1,9 +1,9 @@
 import axios, { AxiosRequestConfig, AxiosResponse, InternalAxiosRequestConfig } from 'axios'
 import { useUserStore } from '@/store/modules/user'
-import { ApiStatus } from './status'
-import { HttpError, handleError, showError, showSuccess } from './server.error'
+import { HttpError, handleError } from './server.error'
 import { $t } from '@/locales'
-import { ApiResponse } from './server.type'
+import { showError, showSuccess } from './server.help'
+import { ApiStatus } from './server.type'
 
 /** 请求配置常量 */
 const LOGOUT_DELAY = 500
@@ -197,12 +197,7 @@ const api = {
       headers: { 'Content-Type': 'multipart/form-data', ...config?.headers }
     }
 
-    return retryRequest({
-      url: url,
-      data: formData,
-      method: 'POST',
-      ...finalConfig
-    })
+    return retryRequest({ url: url, data: formData, method: 'POST', ...finalConfig })
   }
 }
 

+ 26 - 7
src/utils/http/server.axios.ts

@@ -2,6 +2,7 @@ import axios, { AxiosError, AxiosInstance, AxiosRequestConfig, AxiosResponse, In
 import { useUserStore } from '@/store/modules/user'
 import { $t } from '@/locales'
 import { ApiResponse, ApiResponsePage, ApiStatus } from './server.type'
+import { createHttpError, handleUnauthorizedError, showError } from './server.help'
 
 /** 扩展 AxiosRequestConfig */
 interface ExtendedAxiosRequestConfig extends AxiosRequestConfig {
@@ -31,7 +32,7 @@ export class RequestHttp {
         return request
       },
       (error: AxiosError) => {
-        // showError(createHttpError($t('httpMsg.requestConfigError'), ApiStatus.error))
+        showError(createHttpError($t('httpMsg.requestConfigError'), ApiStatus.error), config.showErrorMessage ?? true)
         return Promise.reject(error)
       }
     )
@@ -43,21 +44,21 @@ export class RequestHttp {
       (response: AxiosResponse<Http.BaseResponse>) => {
         const { code, msg } = response.data
         if (code === ApiStatus.success) return response
-        if (code === ApiStatus.unauthorized) {
-          setTimeout(() => useUserStore().logOut(), 500)
-        }
+        if (code === ApiStatus.unauthorized) handleUnauthorizedError(msg)
         throw new Error(msg || $t('httpMsg.requestFailed'))
       },
       (error) => {
-        if (error.response?.status === ApiStatus.unauthorized) {
-          setTimeout(() => useUserStore().logOut(), 500)
-        }
+        if (error.response?.status === ApiStatus.unauthorized) handleUnauthorizedError()
         return Promise.reject(error)
       }
     )
   }
 
   public request<T = any>(config: ExtendedAxiosRequestConfig): Promise<T> {
+    if (['POST', 'PUT'].includes(config.method?.toUpperCase() || '') && config.params && !config.data) {
+      config.data = config.params
+      config.params = undefined
+    }
     return this.service.request(config)
   }
   /**
@@ -86,4 +87,22 @@ export class RequestHttp {
   public download(url: string, params?: object, _object: ExtendedAxiosRequestConfig = {}): Promise<BlobPart> {
     return this.service.post(url, params, { ..._object, responseType: 'blob' })
   }
+
+  public upload(url: string, data: Record<string, any> & { file: Blob | File }, _object: ExtendedAxiosRequestConfig = {}): Promise<ApiResponse> {
+    const formData = new FormData()
+    Object.entries(data).forEach(([key, value]) => {
+      if (Array.isArray(value)) {
+        value.forEach((item, index) => {
+          formData.append(`${key}[${index}]`, item)
+        })
+      } else {
+        formData.append(key, value)
+      }
+    })
+    const finalConfig: ExtendedAxiosRequestConfig = {
+      ..._object,
+      headers: { 'Content-Type': 'multipart/form-data', ..._object?.headers }
+    }
+    return this.service.post(url, formData, finalConfig)
+  }
 }

+ 2 - 44
src/utils/http/server.error.ts

@@ -1,7 +1,6 @@
 import { AxiosError } from 'axios'
-import { ElMessage } from 'element-plus'
-import { ApiStatus } from './status'
 import { $t } from '@/locales'
+import { ApiStatus } from './server.type'
 
 // 错误响应接口
 export interface ErrorResponse {
@@ -29,15 +28,7 @@ export class HttpError extends Error {
   public readonly url?: string
   public readonly method?: string
 
-  constructor(
-    message: string,
-    code: number,
-    options?: {
-      data?: unknown
-      url?: string
-      method?: string
-    }
-  ) {
+  constructor(message: string, code: number, options?: { data?: unknown; url?: string; method?: string }) {
     super(message)
     this.name = 'HttpError'
     this.code = code
@@ -113,36 +104,3 @@ export function handleError(error: AxiosError<ErrorResponse>): never {
     method: requestConfig?.method?.toUpperCase()
   })
 }
-
-/**
- * 显示错误消息
- * @param error 错误对象
- * @param showMessage 是否显示错误消息
- */
-export function showError(error: HttpError, showMessage: boolean = true): void {
-  if (showMessage) {
-    ElMessage.error(error.message)
-  }
-  // 记录错误日志
-  console.error('[HTTP Error]', error.toLogData())
-}
-
-/**
- * 显示成功消息
- * @param message 成功消息
- * @param showMessage 是否显示消息
- */
-export function showSuccess(message: string, showMessage: boolean = true): void {
-  if (showMessage) {
-    ElMessage.success(message)
-  }
-}
-
-/**
- * 判断是否为 HttpError 类型
- * @param error 错误对象
- * @returns 是否为 HttpError 类型
- */
-export const isHttpError = (error: unknown): error is HttpError => {
-  return error instanceof HttpError
-}

+ 44 - 0
src/utils/http/server.help.ts

@@ -1,6 +1,50 @@
+import { useUserStore } from '@/store/modules/user'
 import { HttpError } from './server.error'
+import { $t } from '@/locales'
+import { ApiStatus } from './server.type'
 
 /** 统一创建HttpError */
 export function createHttpError(message: string, code: number) {
   return new HttpError(message, code)
 }
+
+/** 处理401错误(带防抖) */
+export function handleUnauthorizedError(message?: string): never {
+  const error = createHttpError(message || $t('httpMsg.unauthorized'), ApiStatus.unauthorized)
+  setTimeout(() => useUserStore().logOut(), 500)
+  showError(error, true)
+  throw error
+}
+
+/**
+ * 显示错误消息
+ * @param error 错误对象
+ * @param showMessage 是否显示错误消息
+ */
+export function showError(error: HttpError, showMessage: boolean = true): void {
+  if (showMessage) {
+    ElMessage.error(error.message)
+  }
+  // 记录错误日志
+  console.error('[HTTP Error]', error.toLogData())
+}
+
+/**
+ * 显示成功消息
+ * @param message 成功消息
+ * @param showMessage 是否显示消息
+ */
+export function showSuccess(message: string, showMessage: boolean = true): void {
+  if (showMessage) {
+    ElMessage.success(message)
+  }
+}
+
+/**
+ * 判断是否为 HttpError 类型
+ * @param error 错误对象
+ * @returns 是否为 HttpError 类型
+ */
+export const isHttpError = (error: unknown): error is HttpError => {
+  return error instanceof HttpError
+}

+ 0 - 18
src/utils/http/status.ts

@@ -1,18 +0,0 @@
-/**
- * 接口状态码
- */
-export enum ApiStatus {
-  success = 0, // 成功
-  error = 400, // 错误
-  unauthorized = 401, // 未授权
-  forbidden = 403, // 禁止访问
-  notFound = 404, // 未找到
-  methodNotAllowed = 405, // 方法不允许
-  requestTimeout = 408, // 请求超时
-  internalServerError = 500, // 服务器错误
-  notImplemented = 501, // 未实现
-  badGateway = 502, // 网关错误
-  serviceUnavailable = 503, // 服务不可用
-  gatewayTimeout = 504, // 网关超时
-  httpVersionNotSupported = 505 // HTTP版本不支持
-}

+ 1 - 1
src/views/article/publish/index.vue

@@ -57,13 +57,13 @@
 
 <script setup lang="ts">
   import { Plus } from '@element-plus/icons-vue'
-  import { ApiStatus } from '@/utils/http/status'
   import { ElMessage } from 'element-plus'
   import { useUserStore } from '@/store/modules/user'
   import EmojiText from '@/utils/ui/emojo'
   import { PageModeEnum } from '@/enums/formEnum'
   import axios from 'axios'
   import { useCommon } from '@/composables/useCommon'
+  import { ApiStatus } from '@/utils/http/server.type'
 
   defineOptions({ name: 'ArticlePublish' })
 

+ 37 - 0
src/views/train/course/course-card.vue

@@ -0,0 +1,37 @@
+<template>
+  <div>
+    <div class="image-wrapper">
+      <el-image :src="coverImageList[course.typeID] || cover1" fit="cover" loading="lazy">
+        <template #placeholder>
+          <div class="image-placeholder">
+            <ElIcon><Picture /></ElIcon>
+          </div>
+        </template>
+      </el-image>
+    </div>
+    <div class="course-info">
+      <div class="course-name">{{ course.courseName }}</div>
+      <div class="course-desc">{{ course.courseDesc }}</div>
+    </div>
+  </div>
+</template>
+
+<script lang="ts" setup>
+  import { Course } from '@/api/course.api'
+
+  interface Props {
+    course: Course
+    edit: boolean
+  }
+  withDefaults(defineProps<Props>(), {
+    edit: false
+  })
+
+  import cover1 from '@imgs/cover/img1.webp'
+  import cover2 from '@imgs/cover/img2.webp'
+  import cover3 from '@imgs/cover/img3.webp'
+  import cover4 from '@imgs/cover/img4.webp'
+
+  const coverImageList = [cover1, cover2, cover3, cover4]
+</script>
+<style lang="scss" scoped></style>

+ 34 - 0
src/views/train/course/index.vue

@@ -0,0 +1,34 @@
+<template>
+  <div>
+    <h2>课程列表</h2>
+    <div>正在开发中,敬请期待</div>
+    <el-row :gutter="20">
+      <el-col v-for="item in courseList" :key="item.courseID" :xs="12" :sm="6" :md="4" :lg="3" :xl="2">
+        <course-card :course="item" :edit="false" />
+      </el-col>
+    </el-row>
+  </div>
+</template>
+<script lang="ts" setup>
+  import { Course } from '@/api/course.api'
+  import courseCard from './course-card.vue'
+
+  const courseList: Course[] = Array.from({ length: 10 }).map((_, index) => ({
+    courseID: index + 1,
+    courseName: `课程${index + 1}`,
+    courseDesc: `这是课程${index + 1}的描述`,
+    typeID: (index % 4) + 1,
+    totalDuration: index + 2,
+    createTime: '2023-01-01',
+    creator: `教师${index + 1}`,
+    modifyTime: '2023-01-01',
+    courseGUID: `guid-${index + 1}`,
+    externalTypeID: (index % 4) + 1,
+    guideDesc: `这是课程${index + 1}的指导描述`,
+    guideFilePath: `file-${index + 1}.pdf`,
+    lineNo: index + 1,
+    runningMap: index + 1,
+    virtualStartTime: 1672531200000 + index * 86400000
+  }))
+</script>
+<style lang="scss" scoped></style>

+ 0 - 77
src/views/train/course/list/index.vue

@@ -1,77 +0,0 @@
-<!--
-@description: 课程列表
-@creationTime: 2025-09-23
--->
-<template>
-  <view>课程列表</view>
-  <ElRow :gutter="20">
-    <ElCol :xs="24" :sm="12" :md="6" :lg="6" :xl="4" v-for="card in imageCards" :key="card.id">
-      <ArtImageCard
-        :imageUrl="card.imageUrl"
-        :title="card.title"
-        :category="card.category"
-        :readTime="card.readTime"
-        :views="card.views"
-        :comments="card.comments"
-        :date="card.date"
-        @click="handleImageCardClick(card)"
-      />
-    </ElCol>
-  </ElRow>
-</template>
-
-<script lang="ts" setup>
-  import cover1 from '@imgs/cover/img1.webp'
-  import cover2 from '@imgs/cover/img2.webp'
-  import cover3 from '@imgs/cover/img3.webp'
-  import cover4 from '@imgs/cover/img4.webp'
-
-  const imageCards = [
-    {
-      id: 1,
-      imageUrl: cover1,
-      title: 'AI技术在医疗领域的创新应用与发展前景',
-      category: '社交',
-      readTime: '2分钟',
-      views: 9125,
-      comments: 3,
-      date: '12月19日 周一'
-    },
-    {
-      id: 2,
-      imageUrl: cover2,
-      title: '大数据分析助力企业决策的实践案例',
-      category: '技术',
-      readTime: '3分钟',
-      views: 7234,
-      comments: 5,
-      date: '12月20日 周二'
-    },
-    {
-      id: 3,
-      imageUrl: cover3,
-      title: '区块链技术在供应链管理中的应用',
-      category: '科技',
-      readTime: '4分钟',
-      views: 5678,
-      comments: 8,
-      date: '12月21日 周三'
-    },
-    {
-      id: 4,
-      imageUrl: cover4,
-      title: '云计算技术发展趋势与未来展望',
-      category: '云技术',
-      readTime: '5分钟',
-      views: 4321,
-      comments: 6,
-      date: '12月22日 周四'
-    }
-  ]
-
-  const handleImageCardClick = (card: any) => {
-    console.log(card)
-  }
-</script>
-
-<style lang="scss" scoped></style>

src/views/train/evaluation/list/index.vue → src/views/train/evaluation/index.vue


src/views/train/fault/list/data.json → src/views/train/fault/data.json


src/views/train/fault/list/index.vue → src/views/train/fault/index.vue