提交 608dd550 authored 作者: 王鹏飞's avatar 王鹏飞

feat: 新增案例授权

上级 4fa89d9d
# Repository Guidelines
## Project Structure & Module Organization
This repository is a Vue 3 + Vite frontend. Main application code lives in `src/`. Feature areas are organized under `src/modules/`, typically with `views/`, `components/`, `api.ts`, `types.ts`, and `composables/`. Shared UI lives in `src/components/` and `src/components/base/`. Cross-cutting logic is in `src/api/`, `src/utils/`, and `src/composables/`. Pinia stores are in `src/stores/`. Static assets live in `public/` and `src/assets/`.
## Build, Test, and Development Commands
- `npm install`: install dependencies.
- `npm run dev`: start the local Vite dev server.
- `npm run typecheck`: run `vue-tsc --noEmit` for Vue/TypeScript validation.
- `npm run lint`: run ESLint with auto-fix.
- `npm run build:test`: create a test build without deploy.
- `npm run build`: production build plus deploy. Use with care because it runs `npm run deploy`.
## Coding Style & Naming Conventions
Use TypeScript and Vue SFCs with `script setup` where the module already follows that pattern. Use 2-space indentation. Name components in PascalCase, for example `AppCard.vue` or `FormDialog.vue`. Name composables `useXxx.ts`, such as `useGetCourseList.ts`. Keep feature-specific code inside its module folder under `src/modules/...` instead of adding new global utilities unless the code is truly shared.
## Testing Guidelines
There is no dedicated unit test framework configured in this repository today. Minimum validation before opening a PR is:
- `npm run typecheck`
- `npm run lint`
- `npm run build:test` for changes that could affect packaging, routing, or environment-specific behavior
## Commit & Pull Request Guidelines
Recent history uses lightweight messages such as `feat: ...`, `chore: ...`, and `bug fixes`. Prefer consistent prefixes going forward: `feat:`, `fix:`, `chore:`. Keep commit scope focused. PRs should include a short summary, affected modules, linked issues if available, and screenshots or GIFs for UI changes. Also list the validation commands you ran.
## Configuration & Safety Notes
Avoid running `npm run build` unless deployment is intended. Do not commit generated files or macOS artifacts such as `.DS_Store`. When working with upload or deploy flows, verify environment targets before testing against shared infrastructure.
import httpRequest from '@/utils/axios'
import type {
BookAuthorizeCreateItem,
CaseAuthorizeCreateItem,
VideoAuthorizeCreateItem,
VideoUploadAuthParams,
VideoUploadRefreshParams,
} from './types'
// 获取课程列表
export function getCourseList() {
return httpRequest.get('/api/lab/v1/teacher/course/list')
}
// 获取实验列表
export function getExperimentList(params: { course_id: string }) {
return httpRequest.get('/api/resource/v1/backend/experiment/experiments', { params })
}
// 创建案例原文
export function createCase(data: CaseAuthorizeCreateItem) {
return httpRequest.post('/api/lab/v1/teacher/experiment-cases/create', data)
}
// 创建指导书
export function createBook(data: BookAuthorizeCreateItem) {
return httpRequest.post('/api/lab/v1/teacher/book/create', data)
}
// 创建操作视频
export function createVideo(data: VideoAuthorizeCreateItem) {
return httpRequest.post('/api/lab/v1/teacher/video/create', data)
}
// 获取上传视频凭证
export function getUploadVideoAuth(data: VideoUploadAuthParams) {
return httpRequest.post('/api/lab/v1/teacher/video/auth-create', data)
}
// 刷新上传视频地址凭证
export function updateUploadVideoAuth(data: VideoUploadRefreshParams) {
return httpRequest.post('/api/lab/v1/teacher/video/create-auth', data)
}
import axios from 'axios'
import type { CaseLibraryItem } from '../types'
export function useCaseLibrary() {
const list = ref<CaseLibraryItem[]>([])
const loading = ref(false)
function fetchList() {
loading.value = true
return axios.get(`https://webapp-pub.ezijing.com/case_library/case.json?_t=${Date.now()}`)
.then((res) => {
list.value = res.data
})
.finally(() => {
loading.value = false
})
}
return { list, loading, fetchList }
}
import { getCourseList } from '../api'
export interface CourseType {
id: string
name: string
}
const courses = ref<CourseType[]>([])
export function useGetCourseList() {
function updateCourses() {
getCourseList().then((res: any) => {
courses.value = res.data
})
}
return { courses, updateCourses }
}
import { getExperimentList } from '../api'
export interface ExperimentType {
id: string
name: string
}
export function useGetExperimentList() {
const experiments = ref<ExperimentType[]>([])
function updateExperiments(courseId?: string) {
if (!courseId) {
experiments.value = []
return
}
getExperimentList({ course_id: courseId }).then((res: any) => {
experiments.value = res.data
}).catch((err) => {
console.error('获取实验列表失败', err)
experiments.value = []
})
}
return { experiments, updateExperiments }
}
import type { RouteRecordRaw } from 'vue-router'
import AppLayout from '@/components/layout/Index.vue'
export const routes: Array<RouteRecordRaw> = [
{
path: '/admin/lab/case-auth',
component: AppLayout,
children: [{ path: '', component: () => import('./views/Index.vue') }],
},
{
path: '/case-auth',
component: AppLayout,
children: [{ path: '', component: () => import('./views/Index.vue') }],
},
]
export interface CaseLibraryFile {
type: string
type_name: string
name: string
url: string
size?: string
source_id?: string
created_at: string
updated_at: string
}
export interface CaseLibraryItem {
id: string
name: string
description: string
files: CaseLibraryFile[]
operator: { id: string; name: string }
created_at: string
updated_at: string
}
export interface CaseAuthorizeCreateItem {
experiment_id: string
status: string
name: string
type: string
url: string
size: string
}
export interface BookAuthorizeCreateItem {
experiment_id: string
status: string
name: string
type: string
url: string
size: string
}
export interface VideoAuthorizeCreateItem {
experiment_id: string
status: string
name: string
source_id: string
}
export interface VideoUploadAuthParams {
title: string
file_name: string
}
export interface VideoUploadRefreshParams {
source_id: string
}
......@@ -8,6 +8,8 @@ interface State {
adminMenus: IMenuItem[]
}
const CASE_AUTH_ALLOWED_MOBILES = ['13811534871', '13785189195']
// 学生菜单
const studentMenus: IMenuItem[] = [
{ name: '首页', path: '/' },
......@@ -32,6 +34,7 @@ const adminMenus: IMenuItem[] = [
{ name: '实验成绩管理', path: '/admin/lab/score', tag: 'v1-teacher-record' },
{ name: '实验监控', path: '/admin/lab/dashboard' },
{ name: '案例管理', path: '/admin/lab/example' },
{ name: '案例授权', path: '/admin/lab/case-auth' },
],
},
{
......@@ -67,7 +70,21 @@ export const useMenuStore = defineStore('menu', {
if (userStore.role?.id === 1) {
return appConfig.studentMenus || state.studentMenus
} else {
return appConfig.adminMenus || state.adminMenus
const sourceMenus = appConfig.adminMenus || state.adminMenus
const currentMobile = userStore.user?.mobile || ''
return sourceMenus.map((item) => {
if (!item.children?.length) return item
if (item.path !== '/admin/lab') return item
return {
...item,
children: item.children.filter((child) => {
if (child.path !== '/admin/lab/case-auth') return true
return CASE_AUTH_ALLOWED_MOBILES.includes(currentMobile)
}),
}
})
}
},
},
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论