Compare commits

...

3 Commits

  1. 2
      .env.development
  2. 4
      .env_1.development
  3. 3
      .env_1.production
  4. 4
      .env_1.test
  5. 12
      .eslintrc.cjs
  6. 65
      .eslintrc_1.cjs
  7. 9
      .prettierrc_1.json
  8. 53
      .stylelintrc_1.cjs
  9. 18
      README_1.md
  10. 2
      _1.eslintignore
  11. 24
      _1.gitignore
  12. 4
      _1.stylelintignore
  13. 15
      index_1.html
  14. 69
      mock/user_1.ts
  15. 5982
      pnpm-lock.yaml
  16. 10
      src/api/user/crouse.js
  17. 3
      src/layout/index.vue
  18. 110
      src/permission.ts
  19. 4
      src/router/index.ts
  20. 3
      src/router/routers.ts
  21. 286
      src/views/course/basicCourseInformation.vue
  22. 61
      src/views/course/components/KnowledgeEdit.vue
  23. 36
      src/views/course/components/PageContainer.vue
  24. 184
      src/views/course/components/courseEdit.vue
  25. 13
      src/views/course/courseChapters.vue
  26. 116
      src/views/course/knowledgePoints.vue

@ -1,4 +1,4 @@
# 变量必须以 VITE_ 为前缀才能暴露给外部读取 # 变量必须以 VITE_ 为前缀才能暴露给外部读取
NODE_ENV = 'development' NODE_ENV = 'development'
VITE_APP_TITLE = '无糖运营平台' VITE_APP_TITLE = '无糖运营平台'
VITE_APP_BASE_API = '/api' VITE_APP_BASE_API = 'http://127.0.0.1:8080'

@ -0,0 +1,4 @@
# 变量必须以 VITE_ 为前缀才能暴露给外部读取
NODE_ENV = 'development'
VITE_APP_TITLE = '无糖运营平台'
VITE_APP_BASE_API = '/api'

@ -0,0 +1,3 @@
NODE_ENV = 'production'
VITE_APP_TITLE = '无糖运营平台'
VITE_APP_BASE_API = '/prod-api'

@ -0,0 +1,4 @@
# 变量必须以 VITE_ 为前缀才能暴露给外部读取
NODE_ENV = 'test'
VITE_APP_TITLE = '无糖运营平台'
VITE_APP_BASE_API = '/test-api'

@ -7,8 +7,11 @@ module.exports = {
node: true, node: true,
jest: true, jest: true,
}, },
"globals": { globals: {
"VANTA": "readonly" //VANTA 已经cdn引入 这里拒绝eslint报错 全局声明一下 VANTA: 'readonly', //VANTA 已经cdn引入 这里拒绝eslint报错 全局声明一下
ElMessage: 'readonly',
ElMessageBox: 'readonly',
ElLoading: 'readonly',
}, },
/* 指定如何解析语法 */ /* 指定如何解析语法 */
parser: 'vue-eslint-parser', parser: 'vue-eslint-parser',
@ -47,7 +50,7 @@ module.exports = {
// typeScript (https://typescript-eslint.io/rules) // typeScript (https://typescript-eslint.io/rules)
'@typescript-eslint/no-unused-vars': 'error', // 禁止定义未使用的变量 '@typescript-eslint/no-unused-vars': 'error', // 禁止定义未使用的变量
'@typescript-eslint/prefer-ts-expect-error': 'off', // 禁止使用 @ts-ignore '@typescript-eslint/prefer-ts-expect-error': 'off', // 禁止使用 @ts-ignore
"@typescript-eslint/ban-ts-ignore": "off", '@typescript-eslint/ban-ts-ignore': 'off',
'@typescript-eslint/no-explicit-any': 'off', // 禁止使用 any 类型 '@typescript-eslint/no-explicit-any': 'off', // 禁止使用 any 类型
'@typescript-eslint/no-non-null-assertion': 'off', '@typescript-eslint/no-non-null-assertion': 'off',
'@typescript-eslint/no-namespace': 'off', // 禁止使用自定义 TypeScript 模块和命名空间。 '@typescript-eslint/no-namespace': 'off', // 禁止使用自定义 TypeScript 模块和命名空间。
@ -59,5 +62,4 @@ module.exports = {
'vue/no-mutating-props': 'off', // 不允许组件 prop的改变 'vue/no-mutating-props': 'off', // 不允许组件 prop的改变
'vue/attribute-hyphenation': 'off', // 对模板中的自定义组件强制执行属性命名样式 'vue/attribute-hyphenation': 'off', // 对模板中的自定义组件强制执行属性命名样式
}, },
} }

@ -0,0 +1,65 @@
// @see https://eslint.bootcss.com/docs/rules/
module.exports = {
env: {
browser: true,
es2021: true,
node: true,
jest: true,
},
globals: {
VANTA: 'readonly', //VANTA 已经cdn引入 这里拒绝eslint报错 全局声明一下
ElMessage: 'readonly',
ElMessageBox: 'readonly',
ElLoading: 'readonly',
},
/* 指定如何解析语法 */
parser: 'vue-eslint-parser',
/** 优先级低于 parse 的语法解析配置 */
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
parser: '@typescript-eslint/parser',
jsxPragma: 'React',
ecmaFeatures: {
jsx: true,
},
},
/* 继承已有的规则 */
extends: [
'eslint:recommended',
'plugin:vue/vue3-essential',
'plugin:@typescript-eslint/recommended',
'plugin:prettier/recommended',
],
plugins: ['vue', '@typescript-eslint'],
/*
* "off" 或 0 ==> 关闭规则
* "warn" 或 1 ==> 打开的规则作为警告(不影响代码执行)
* "error" 或 2 ==> 规则作为一个错误(代码不能执行,界面报错)
*/
rules: {
// eslint(https://eslint.bootcss.com/docs/rules/)
'no-var': 'error', // 要求使用 let 或 const 而不是 var
'no-multiple-empty-lines': ['warn', { max: 1 }], // 不允许多个空行
'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
'no-unexpected-multiline': 'error', // 禁止空余的多行
'no-useless-escape': 'off', // 禁止不必要的转义字符
// typeScript (https://typescript-eslint.io/rules)
'@typescript-eslint/no-unused-vars': 'error', // 禁止定义未使用的变量
'@typescript-eslint/prefer-ts-expect-error': 'off', // 禁止使用 @ts-ignore
'@typescript-eslint/ban-ts-ignore': 'off',
'@typescript-eslint/no-explicit-any': 'off', // 禁止使用 any 类型
'@typescript-eslint/no-non-null-assertion': 'off',
'@typescript-eslint/no-namespace': 'off', // 禁止使用自定义 TypeScript 模块和命名空间。
'@typescript-eslint/semi': 'off',
// eslint-plugin-vue (https://eslint.vuejs.org/rules/)
'vue/multi-word-component-names': 'off', // 要求组件名称始终为 “-” 链接的单词
'vue/script-setup-uses-vars': 'error', // 防止<script setup>使用的变量<template>被标记为未使用
'vue/no-mutating-props': 'off', // 不允许组件 prop的改变
'vue/attribute-hyphenation': 'off', // 对模板中的自定义组件强制执行属性命名样式
},
}

@ -0,0 +1,9 @@
{
"singleQuote": true,
"semi": false,
"bracketSpacing": true,
"htmlWhitespaceSensitivity": "ignore",
"endOfLine": "auto",
"trailingComma": "all",
"tabWidth": 2
}

@ -0,0 +1,53 @@
// @see https://stylelint.bootcss.com/
module.exports = {
extends: [
'stylelint-config-standard', // 配置stylelint拓展插件
'stylelint-config-standard-vue', // 配置 vue 中 template 样式格式化
'stylelint-config-standard-scss', // 配置stylelint scss插件
'stylelint-config-recommended-vue/scss', // 配置 vue 中 scss 样式格式化
'stylelint-config-recess-order', // 配置stylelint css属性书写顺序插件,
'stylelint-config-prettier', // 配置stylelint和prettier兼容
],
overrides: [
{
files: ['**/*.(scss|css|vue|html)'],
customSyntax: 'postcss-scss',
},
{
files: ['**/*.(html|vue)'],
customSyntax: 'postcss-html',
},
],
ignoreFiles: [
'**/*.js',
'**/*.jsx',
'**/*.tsx',
'**/*.ts',
'**/*.json',
'**/*.md',
'**/*.yaml',
],
/**
* null => 关闭该规则
* always => 必须
*/
rules: {
'value-keyword-case': null, // 在 css 中使用 v-bind,不报错
'no-descending-specificity': null, // 禁止在具有较高优先级的选择器后出现被其覆盖的较低优先级的选择器
'function-url-quotes': 'always', // 要求或禁止 URL 的引号 "always(必须加上引号)"|"never(没有引号)"
'no-empty-source': null, // 关闭禁止空源码
'selector-class-pattern': null, // 关闭强制选择器类名的格式
'property-no-unknown': null, // 禁止未知的属性(true 为不允许)
'block-opening-brace-space-before': 'always', //大括号之前必须有一个空格或不能有空白符
'value-no-vendor-prefix': null, // 关闭 属性值前缀 --webkit-box
'property-no-vendor-prefix': null, // 关闭 属性前缀 -webkit-mask
'selector-pseudo-class-no-unknown': [
// 不允许未知的选择器
true,
{
ignorePseudoClasses: ['global', 'v-deep', 'deep'], // 忽略属性,修改element默认样式的时候能使用到
},
],
},
}

@ -0,0 +1,18 @@
# Vue 3 + TypeScript + Vite
This template should help get you started developing with Vue 3 and TypeScript in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
## Recommended IDE Setup
- [VS Code](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur) + [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin).
## Type Support For `.vue` Imports in TS
TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin) to make the TypeScript language service aware of `.vue` types.
If the standalone TypeScript plugin doesn't feel fast enough to you, Volar has also implemented a [Take Over Mode](https://github.com/johnsoncodehk/volar/discussions/471#discussioncomment-1361669) that is more performant. You can enable it by the following steps:
1. Disable the built-in TypeScript Extension
1. Run `Extensions: Show Built-in Extensions` from VSCode's command palette
2. Find `TypeScript and JavaScript Language Features`, right click and select `Disable (Workspace)`
2. Reload the VSCode window by running `Developer: Reload Window` from the command palette.

@ -0,0 +1,2 @@
dist
node_modules

24
_1.gitignore vendored

@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

@ -0,0 +1,4 @@
/node_modules/*
/dist/*
/html/*
/public/*

@ -0,0 +1,15 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>教学一体化后师生后台</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r134/three.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vanta@latest/dist/vanta.globe.min.js"></script>
</body>
</html>

@ -0,0 +1,69 @@
//用户信息数据
function createUserList() {
return [
{
userId: 1,
avatar:
'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif',
username: 'admin',
password: '111111',
desc: '平台管理员',
roles: ['平台管理员'],
buttons: ['cuser.detail'],
routes: ['Home', 'Course', 'Student', 'Group', 'Message','BasicCourseInformation','CourseObjectives','CourseChapters','KnowledgePoints','CurriculumMap'], //老师权限
token: 'Admin Token',
},
{
userId: 2,
avatar:
'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif',
username: 'student',
password: '111111',
desc: '系统管理员',
roles: ['系统管理员'],
buttons: ['cuser.detail', 'cuser.user'],
routes: ['Home', 'MyCourseStudy', 'CourseResources', 'Message','LearningProcess','CourseCollections','Courselikes','WebHome','CourseHome','LearningPathRecommendations','KnowledgePointLearning','CourseReports'], //学生权限
token: 'System Token',
},
]
}
export default [
// 用户登录接口
{
url: '/api/user/login', //请求地址
method: 'post', //请求方式
response: ({ body }) => {
//获取请求体携带过来的用户名与密码
const { username, password } = body
//调用获取用户信息函数,用于判断是否有此用户
const checkUser = createUserList().find(
(item) => item.username === username && item.password === password,
)
//没有用户返回失败信息
if (!checkUser) {
return { code: 201, data: { message: '账号或者密码不正确' } }
}
//如果有返回成功信息
const { token } = checkUser
return { code: 200, data: { token } }
},
},
// 获取用户信息
{
url: '/api/user/info',
method: 'get',
response: (request) => {
//获取请求头携带token
const token = request.headers.token
//查看用户信息是否包含有次token用户
const checkUser = createUserList().find((item) => item.token === token)
//没有返回失败的信息
if (!checkUser) {
return { code: 201, data: { message: '获取用户信息失败' } }
}
//如果有返回成功信息
return { code: 200, data: { checkUser } }
},
},
]

File diff suppressed because it is too large Load Diff

@ -0,0 +1,10 @@
import request from '@/utils/request'
export const getCourseListApi = () => {
return request.get('/coursesteacher/page?teacherId=2140110334')
}
export const editCourseApi = () => {
return request.put('/coursesTeacher')
}
export const addCourseApi = (data) => {
return request.post('/courseTeacher/addCourse', data)
}

@ -17,7 +17,7 @@
text-color="#fff" text-color="#fff"
:collapse="LayoutSettingStoe.fold" :collapse="LayoutSettingStoe.fold"
> >
<Menu :menuList="usePermissionStore.asyncRouter" /> <Menu :menuList="constantRoute" />
</el-menu> </el-menu>
</el-scrollbar> </el-scrollbar>
</div> </div>
@ -49,6 +49,7 @@ import { useRoute } from 'vue-router'
import Tabbar from './tabbar/index.vue' import Tabbar from './tabbar/index.vue'
import LoGo from './logo/index.vue' import LoGo from './logo/index.vue'
import Menu from './menu/index.vue' import Menu from './menu/index.vue'
import {constantRoute} from '@/router/routers'
// //
// import userUserStore from '@/store/modules/user' // import userUserStore from '@/store/modules/user'
// import { onMounted, reactive, ref, toRefs, watch } from 'vue' // import { onMounted, reactive, ref, toRefs, watch } from 'vue'

@ -9,62 +9,64 @@ import 'nprogress/nprogress.css'
const userStore = useUserStore(pinia) const userStore = useUserStore(pinia)
const usePermissionStore = permissionStore(pinia) const usePermissionStore = permissionStore(pinia)
// const whitelist = ['/login', '/404'] // const whitelist = ['/login', '/404']
router.beforeEach(async (to, form, next) => { // router.beforeEach(async (to, form, next) => {
// 进度条开始\ // // 进度条开始\
nprogress.configure({ showSpinner: false }) // nprogress.configure({ showSpinner: false })
nprogress.start() // nprogress.start()
// 判断是否登录 // // 判断是否登录
if (userStore.token) { // if (userStore.token) {
// 登录成功访问登录页则跳转到首页 // // 登录成功访问登录页则跳转到首页
if (to.path == '/login') { // if (to.path == '/login') {
next({ path: '/' }) // next({ path: '/' })
} else { // } else {
// 登录成功判断是否获取到了用户信息 // // 登录成功判断是否获取到了用户信息
if (userStore.userName) { // if (userStore.userName) {
// next()
// } else {
// try {
// // 没有获取到用户信息 就获取用户信息 然后放行
// await userStore.getUserInfo()
// // 获取筛选到的路由
// const asyncRouter = await usePermissionStore.getAsyncRoutes(
// userStore.routes,
// )
// // 遍历筛选出来的路由通过addRoute添加到路由表
// asyncRouter.forEach((item: any) => {
// router.addRoute(item)
// })
// // 在最后向路由表添加一个404规则
// // 切记不要写到路由表内 否者刷新页面会跳转到404页面
// router.addRoute({
// path: '/:pathMatch(.*)*',
// component: () => import('@/views/404/index.vue'),
// name: 'Any',
// meta: {
// title: '任意',
// hidden: true,
// },
// })
// next({ ...to, replace: true }) // 这里相当于push到一个页面 不在进入路由拦截
// } catch (error) {
// // 如果获取用户信息失败了则执行登出操作让重新登录
// console.log(error)
// userStore.logout()
// next({ path: '/login' })
// }
// }
// }
// } else {
// // 没有token访问登录页放行
// if (to.path == '/login') {
// next()
// } else {
// // 访问其他页面则阻止
// next({ path: '/login', query: { redirect: to.path } })
// }
// }
// })
router.beforeEach((to,form,next) => {
next() next()
} else {
try {
// 没有获取到用户信息 就获取用户信息 然后放行
await userStore.getUserInfo()
// 获取筛选到的路由
const asyncRouter = await usePermissionStore.getAsyncRoutes(
userStore.routes,
)
// 遍历筛选出来的路由通过addRoute添加到路由表
asyncRouter.forEach((item: any) => {
router.addRoute(item)
})
// 在最后向路由表添加一个404规则
// 切记不要写到路由表内 否者刷新页面会跳转到404页面
router.addRoute({
path: '/:pathMatch(.*)*',
component: () => import('@/views/404/index.vue'),
name: 'Any',
meta: {
title: '任意',
hidden: true,
},
})
next({ ...to, replace: true }) // 这里相当于push到一个页面 不在进入路由拦截
} catch (error) {
// 如果获取用户信息失败了则执行登出操作让重新登录
console.log(error)
userStore.logout()
next({ path: '/login' })
}
}
}
} else {
// 没有token访问登录页放行
if (to.path == '/login') {
next()
} else {
// 访问其他页面则阻止
next({ path: '/login', query: { redirect: to.path } })
}
}
}) })
router.afterEach((to, form, next) => { router.afterEach((to, form, next) => {
nprogress.done() nprogress.done()
}) })

@ -1,6 +1,6 @@
// 导入路由插件 // 导入路由插件
import { createRouter, createWebHashHistory } from 'vue-router' import { createRouter, createWebHashHistory } from 'vue-router'
import {constantRoute} from './routers.ts'
const routerList = [ const routerList = [
{ {
path: '/login', path: '/login',
@ -14,7 +14,7 @@ const routerList = [
] ]
const router = createRouter({ const router = createRouter({
history: createWebHashHistory(), history: createWebHashHistory(),
routes: routerList, routes: [...routerList,...constantRoute],
scrollBehavior() { scrollBehavior() {
return { return {
left: 0, left: 0,

@ -258,7 +258,8 @@ export const constantRoute: any = [
}, },
{ {
path: '/portal/LearningPathRecommendations', path: '/portal/LearningPathRecommendations',
component: () => import('@/views/portal/LearningPathRecommendations.vue'), component: () =>
import('@/views/portal/LearningPathRecommendations.vue'),
name: 'LearningPathRecommendations', name: 'LearningPathRecommendations',
meta: { meta: {
title: '学习路径推荐', title: '学习路径推荐',

@ -1,14 +1,288 @@
<script lang="ts" setup>
import { ref, onMounted } from 'vue'
import axios from 'axios'
import courseEdit from './components/courseEdit.vue'
import { getCourseListApi } from '../../api/user/crouse.js'
const courseList = ref([
// id: 1,
// category: '',
// nature: '',
// code: '',
// assessmenttype: '',
// assessmentway: '',
// teachermethod: '',
// teacherway: '',
// description: '',
// name: '',
// credit: '',
// classhours: '',
])
const getCourseList = async () => {
const res = await getCourseListApi()
courseList.value = res.result.list
}
onMounted(() => {
getCourseList()
})
// const courseList = ref([
// {
// id: 1,
// category: '1',
// nature: '1',
// code: '1',
// assessmenttype: '1',
// assessmentway: '1',
// teachermethod: '',
// teacherway: '',
// description: '',
// name: '',
// credit: '',
// classhours: '',
// },
// {
// id: 2,
// category: '1',
// nature: '1',
// code: '1',
// assessmenttype: '1',
// assessmentway: '1',
// teachermethod: '',
// teacherway: '',
// description: '',
// name: '',
// credit: '',
// classhours: '',
// },
// {
// id: 3,
// category: '1',
// nature: '1',
// code: '1',
// assessmenttype: '1',
// assessmentway: '1',
// teachermethod: '',
// teacherway: '',
// description: '',
// name: '',
// credit: '',
// classhours: '',
// },
// {
// id: 4,
// category: '1',
// nature: '1',
// code: '1',
// assessmenttype: '1',
// assessmentway: '1',
// teachermethod: '',
// teacherway: '',
// description: '',
// name: '',
// credit: '',
// classhours: '',
// },
// {
// id: 5,
// category: '1',
// nature: '1',
// code: '1',
// assessmenttype: '1',
// assessmentway: '1',
// teachermethod: '',
// teacherway: '',
// description: '',
// name: '',
// credit: '',
// classhours: '',
// },
// {
// id: 6,
// category: '1',
// nature: '1',
// code: '1',
// assessmenttype: '1',
// assessmentway: '1',
// teachermethod: '',
// teacherway: '',
// description: '',
// name: '',
// credit: '',
// classhours: '',
// },
// {
// id: 7,
// category: '1',
// nature: '1',
// code: '1',
// assessmenttype: '1',
// assessmentway: '1',
// teachermethod: '',
// teacherway: '',
// description: '',
// name: '',
// credit: '',
// classhours: '',
// },
// ])
const drawer = ref()
const onAddCourse = () => {
drawer.value.open({})
}
const onEditCourse = (item) => {
drawer.value.open(item)
}
const onSuccess = () => {
getCourseList()
}
</script>
<template> <template>
<div> <div class="header">
课程基本信息 <div class="btn">
<el-button type="primary" round size="large">全部课程</el-button>
<el-button type="primary" round plain size="large">我的文件夹</el-button>
</div> </div>
<div class="search">
<input type="text" placeholder="搜索课程" />
<i class="el-icon-search"></i>
</div>
</div>
<div class="course">
<ul class="course_list">
<li @click="onAddCourse()">
<img src="" alt="" />
<h2 class="course_name">点击添加课程</h2>
<p class="teacher_name">讲师王兴</p>
<p class="credit">
<span>32</span>
学时|
<span>2.0</span>
学分
</p>
</li>
<li v-for="item in courseList" :key="item.id" @click="onEditCourse(item)">
<img src="" alt="" />
<h2 class="course_name">{{ item.name }}</h2>
<p class="teacher_name">讲师王兴</p>
<p class="credit">
<span>{{ item.classhours }}</span>
学时|
<span>{{ item.credit }}</span>
学分
</p>
</li>
</ul>
</div>
<course-edit ref="drawer" @success="onSuccess"></course-edit>
</template> </template>
<script lang='ts' setup> <style lang="scss" scoped>
import { } from 'vue' .header {
width: 100%;
// height: 100px;
display: flex;
flex-direction: row;
}
</script> .btn {
// display: flex;
width: 50%;
height: 100%;
padding: 10px 45px;
// display: inline;
// height: 40px;
// margin: 20px;
// padding-left: 50px;
}
.search {
width: 50%;
display: flex;
height: 100%;
padding: 10px 0 0 630px;
// flex-direction: row-reverse;
// padding: 0 40px 0;
input {
width: 240px;
height: 40px;
border: 1px solid #dcdcdc;
border-radius: 60px;
font-size: 14px;
}
}
.course {
// display: flex;
// flex: 0 0 25%;
// justify-content: space-between;
// flex-wrap: wrap;
.course_list {
display: flex;
// flex: 0 0 25%;
// justify-content: space-between;
flex-wrap: wrap;
display: grid;
grid-template-columns: repeat(4, 1fr);
column-gap: 50px;
}
li {
margin: 40px 0;
width: 349px;
width: 100%;
height: 297px;
background: white;
transition: all 0.5s;
border-radius: 6px;
// flex: 1; /* */
&:hover {
transform: translate3d(0, -3px, 0);
box-shadow: 0 3px 8px rgb(0 0 0 / 20%);
cursor: pointer;
}
.course_name {
font-family: Inter-Bold;
color: #333;
font-size: 24px;
margin-left: 30px;
margin-top: 10px;
font-weight: bold;
}
img {
background-color: #cccccc;
width: 100%;
height: 178px;
}
p {
margin-left: 30px;
margin-top: 10px;
color: #555555;
font-size: 14px;
padding-top: 12px;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
<style lang='scss' scoped> span {
color: #0052ff;
}
}
h2 {
font-family: Inter-Bold;
color: #333;
font-size: 24px;
margin-left: 30px;
margin-top: 10px;
font-weight: bold;
}
}
}
</style> </style>

@ -0,0 +1,61 @@
<script setup>
import { ref, defineExpose } from 'vue'
import { ElMessage } from 'element-plus'
const dialogVisible = ref(false)
const formModel = ref({
name: '',
desc: '',
})
const formRef = ref()
const rules = {
name: [{ required: true, message: '请输入知识点名称', trigger: 'blur' }],
desc: [{ required: true, message: '请输入知识点简介', trigger: 'blur' }],
}
// open,open
const open = (row) => {
// console.log(row)
formModel.value = { ...row }
dialogVisible.value = true
}
defineExpose({ open })
//
const onSubmit = async () => {
//
await formRef.value.validate()
// formModelid
const isEdit = formModel.value.id
if (isEdit) {
ElMessage.success('编辑成功')
} else {
ElMessage.success('添加成功')
}
dialogVisible.value = false
}
</script>
<template>
<el-dialog
v-model="dialogVisible"
:title="formModel.id ? '编辑知识点' : '新增知识点'"
width="500"
>
<el-form :model="formModel" :rules="rules" ref="formRef">
<el-form-item label="知识点名称" prop="name">
<el-input v-model="formModel.name" placeholder="请输入知识点名称" />
</el-form-item>
<el-form-item label="知识点简介" prop="desc">
<el-input
v-model="formModel.desc"
type="textarea"
placeholder="请输入知识点简介"
/>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="onSubmit">确认</el-button>
</div>
</template>
</el-dialog>
</template>

@ -0,0 +1,36 @@
<script setup>
import {} from 'vue'
//
defineProps({
title: {
required: true,
type: String,
},
})
</script>
<template>
<el-card class="page-container">
<template #header>
<div class="header">
<span>{{ title }}</span>
<div class="extra">
<!-- 具名插槽定制额外按钮 -->
<slot name="extra"></slot>
</div>
</div>
</template>
<!-- 插槽定制内容使用slot占位 -->
<slot></slot>
</el-card>
</template>
<style lang="scss" scoped>
.page-container {
min-height: 100%;
box-sizing: border-box;
.header {
display: flex;
justify-content: space-between;
align-items: center;
}
}
</style>

@ -0,0 +1,184 @@
<script setup>
import { ElMessage } from 'element-plus'
import { requiredNumber } from 'element-plus/es/components/table-v2/src/common.mjs'
import { ref } from 'vue'
import { editCourseApi } from '../../../api/user/crouse'
import { addCourseApi } from '../../../api/user/crouse'
const formModel = ref({
id: '',
name: '',
category: '',
nature: '',
code: '',
credit: '',
classhours: '',
assessmenttype: '',
assessmentway: '',
teachermethod: '',
teacherway: '',
description: '',
})
const rules = {
name: [
{
required: true,
message: '请输入课程名称',
trigger: 'blur',
},
],
category: [
{
required: true,
message: '请选择课程类别',
trigger: 'change',
},
],
nature: [
{
required: true,
message: '请选择课程性质',
trigger: 'change',
},
],
code: [
{
required: true,
message: '请输入课程编码',
trigger: 'blur',
},
],
credit: [
{
required: true,
message: '请输入课程学分',
trigger: 'blur',
},
{
validator: requiredNumber,
message: '请输入数字',
trigger: 'blur',
},
],
classhours: [
{
required: true,
message: '请输入课程学时',
trigger: 'blur',
},
{
validator: requiredNumber,
message: '请输入数字',
trigger: 'blur',
},
],
assessmenttype: [
{
required: true,
message: '请选择考核类型',
trigger: 'change',
},
],
assessmentway: [
{
required: true,
message: '请选择考核方式',
trigger: 'change',
},
],
}
const formRef = ref()
const visibleDrawer = ref(false)
const open = async (item) => {
formModel.value = { ...item }
visibleDrawer.value = true
}
// open,open
defineExpose({ open })
//
const emit = defineEmits(['success'])
const onSubmit = async () => {
await formRef.value.validate()
const isEdit = formModel.value.id
if (isEdit) {
await editCourseApi(formModel.value)
ElMessage.success('编辑成功')
} else {
await addCourseApi(formModel.value)
ElMessage.success('添加成功')
}
visibleDrawer.value = false
emit('success')
}
</script>
<template>
<el-drawer
v-model="visibleDrawer"
direction="rtl"
:title="formModel.id ? '编辑课程' : '添加课程'"
>
<el-form :model="formModel" :rules="rules" ref="formRef">
<el-form-item label="课程名称" prop="name">
<el-input style="width: 200px" v-model="formModel.name"></el-input>
</el-form-item>
<el-form-item label="课程类别" prop="category">
<el-radio-group v-model="formModel.category">
<el-radio value="major">专业教育</el-radio>
<el-radio value="common">通识教育</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="课程性质" prop="nature">
<el-radio-group v-model="formModel.nature">
<el-radio value="required">必修</el-radio>
<el-radio value="elective">选修</el-radio>
<el-radio value="optional">任修</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="课程编码" prop="code">
<el-input style="width: 200px" v-model="formModel.code"></el-input>
</el-form-item>
<el-form-item label="课程学分" prop="credit">
<el-input style="width: 200px" v-model="formModel.credit"></el-input>
</el-form-item>
<el-form-item label="课程学时" prop="classhours">
<el-input
style="width: 200px"
v-model="formModel.classhours"
></el-input>
</el-form-item>
<el-form-item label="考核类型" prop="assessmenttype">
<el-radio-group v-model="formModel.assessmenttype">
<el-radio value="exam">考试</el-radio>
<el-radio value="assessment">考察</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="考核方式" prop="assessmentway">
<el-radio-group v-model="formModel.assessmentway">
<el-radio value="open">开卷</el-radio>
<el-radio value="close">闭卷</el-radio>
<el-radio value="other">其他</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="教学方法" prop="teachermethod">
<el-input type="textarea" v-model="formModel.teachermethod" />
</el-form-item>
<el-form-item label="教学方式" prop="teacherway">
<el-input type="textarea" v-model="formModel.teacherway" />
</el-form-item>
<el-form-item label="课程简介" prop="description">
<el-input type="textarea" v-model="formModel.description" />
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="visibleDrawer = false" size="large">取消</el-button>
<el-button type="primary" size="large" @click="onSubmit()">
确认
</el-button>
</span>
</template>
</el-drawer>
</template>
<style scoped></style>

@ -1,14 +1,9 @@
<template> <template>
<div> <div>课程章节</div>
课程章节
</div>
</template> </template>
<script lang='ts' setup> <script lang="ts" setup>
import { } from 'vue' import {} from 'vue'
</script> </script>
<style lang='scss' scoped> <style lang="scss" scoped></style>
</style>

@ -1,14 +1,114 @@
<template> <script setup>
<div> import { ref } from 'vue'
知识点 import { Delete, Edit } from '@element-plus/icons-vue'
</div> import PageContainer from './components/PageContainer.vue'
</template> import KnowledgeEdit from './components/KnowledgeEdit.vue'
import { ElMessage, ElMessageBox } from 'element-plus'
// const loading = ref(false)
const knowledgeList = [
{
id: '1',
name: '数学',
desc: '数学简介',
},
{
id: '1',
name: '物理',
desc: '物理简介',
},
<script lang='ts' setup> {
import { } from 'vue' id: '1',
name: '语文',
desc: '语文简介',
},
{
id: '1',
name: '英语',
desc: '英语简介',
},
]
// ref
const dialog = ref()
const onEditKnowledge = (row) => {
dialog.value.open(row)
}
const onAddKnowledge = () => {
dialog.value.open({})
}
const onDelKnowledge = async () => {
// console.log('1')
await ElMessageBox.confirm('你确认删除该知识点信息吗?', '温馨提示', {
type: 'warning',
confirmButtonText: '确认',
cancelButtonText: '取消',
})
ElMessage({ type: 'success', message: '删除成功' })
}
const params = ref({
pagenum: 1,
pagesize: 2,
})
</script> </script>
<style lang='scss' scoped> <template>
<page-container title="知识点分类">
<!-- v-slot可以替换为# -->
<template #extra>
<el-button type="primary" @click="onAddKnowledge()">新增</el-button>
<el-button type="primary">导出Excel</el-button>
</template>
<el-table
:data="knowledgeList"
style="width: 100%; height: 100%"
:header-cell-style="{ textAlign: 'center' }"
>
<el-table-column align="center" type="index" label="编号" width="100" />
<el-table-column align="center" label="名称" prop="name" />
<el-table-column align="center" label="简介" prop="desc" />
<el-table-column align="center" label="操作">
<!-- 作用域插槽 -->
<!-- rowknowledgeList的每一项 &index下标 -->
<template #default="{ row }">
<el-button
:icon="Edit"
circle
plain
type="primary"
@click="onEditKnowledge(row)"
></el-button>
<el-button
:icon="Delete"
circle
plain
type="danger"
@click="onDelKnowledge(row)"
></el-button>
</template>
</el-table-column>
<template #empty>
<el-empty description="暂无数据"></el-empty>
</template>
</el-table>
<el-pagination
v-model:current-page="params.pagenum"
v-model:page-size="params.pagesize"
:page-sizes="[2, 3, 5, 10]"
:small="small"
:background="true"
layout=" jumper,total, sizes, prev, pager, next"
:total="knowledgeList.length"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
style="margin-top: 20px; justify-content: flex-end"
/>
<knowledge-edit ref="dialog"></knowledge-edit>
</page-container>
</template>
<style lang="scss" scoped>
.el-table {
overflow: hidden;
}
</style> </style>

Loading…
Cancel
Save