@ -0,0 +1,15 @@ |
|||||||
|
root = true |
||||||
|
|
||||||
|
[*] |
||||||
|
charset = utf-8 |
||||||
|
end_of_line = lf |
||||||
|
indent_size = 2 |
||||||
|
indent_style = space |
||||||
|
insert_final_newline = true |
||||||
|
max_line_length = 120 |
||||||
|
tab_width = 2 |
||||||
|
trim_trailing_whitespace = true |
||||||
|
|
||||||
|
[*.md] |
||||||
|
trim_trailing_whitespace = false |
||||||
|
|
@ -0,0 +1,2 @@ |
|||||||
|
#TARO_APP_API=http://192.168.1.19:9898 |
||||||
|
TARO_APP_API=https://yjx.dev.yaojiankang.top |
@ -0,0 +1,7 @@ |
|||||||
|
dist/ |
||||||
|
deploy_versions/ |
||||||
|
.temp/ |
||||||
|
.rn_temp/ |
||||||
|
node_modules/ |
||||||
|
.DS_Store |
||||||
|
.swc |
@ -0,0 +1,8 @@ |
|||||||
|
# 默认忽略的文件 |
||||||
|
/shelf/ |
||||||
|
/workspace.xml |
||||||
|
# 基于编辑器的 HTTP 客户端请求 |
||||||
|
/httpRequests/ |
||||||
|
# Datasource local storage ignored files |
||||||
|
/dataSources/ |
||||||
|
/dataSources.local.xml |
@ -0,0 +1,7 @@ |
|||||||
|
<component name="InspectionProjectProfileManager"> |
||||||
|
<profile version="1.0"> |
||||||
|
<option name="myName" value="Project Default" /> |
||||||
|
<inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" /> |
||||||
|
<inspection_tool class="Stylelint" enabled="true" level="ERROR" enabled_by_default="true" /> |
||||||
|
</profile> |
||||||
|
</component> |
@ -0,0 +1,8 @@ |
|||||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||||
|
<project version="4"> |
||||||
|
<component name="ProjectModuleManager"> |
||||||
|
<modules> |
||||||
|
<module fileurl="file://$PROJECT_DIR$/.idea/video.iml" filepath="$PROJECT_DIR$/.idea/video.iml" /> |
||||||
|
</modules> |
||||||
|
</component> |
||||||
|
</project> |
@ -0,0 +1,6 @@ |
|||||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||||
|
<project version="4"> |
||||||
|
<component name="VcsDirectoryMappings"> |
||||||
|
<mapping directory="" vcs="Git" /> |
||||||
|
</component> |
||||||
|
</project> |
@ -0,0 +1,9 @@ |
|||||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||||
|
<module type="JAVA_MODULE" version="4"> |
||||||
|
<component name="NewModuleRootManager" inherit-compiler-output="true"> |
||||||
|
<exclude-output /> |
||||||
|
<content url="file://$MODULE_DIR$" /> |
||||||
|
<orderEntry type="inheritedJdk" /> |
||||||
|
<orderEntry type="sourceFolder" forTests="false" /> |
||||||
|
</component> |
||||||
|
</module> |
@ -0,0 +1,11 @@ |
|||||||
|
// babel-preset-taro 更多选项和默认值:
|
||||||
|
// https://github.com/NervJS/taro/blob/next/packages/babel-preset-taro/README.md
|
||||||
|
module.exports = { |
||||||
|
presets: [ |
||||||
|
['taro', { |
||||||
|
framework: 'react', |
||||||
|
ts: true |
||||||
|
}] |
||||||
|
], |
||||||
|
plugins: [] |
||||||
|
} |
@ -0,0 +1,9 @@ |
|||||||
|
module.exports = { |
||||||
|
env: { |
||||||
|
NODE_ENV: '"development"' |
||||||
|
}, |
||||||
|
defineConstants: { |
||||||
|
}, |
||||||
|
mini: {}, |
||||||
|
h5: {} |
||||||
|
} |
@ -0,0 +1,82 @@ |
|||||||
|
const path = require('path') |
||||||
|
const config = { |
||||||
|
projectName: 'video', |
||||||
|
date: '2023-6-29', |
||||||
|
designWidth: 750, |
||||||
|
deviceRatio: { |
||||||
|
640: 2.34 / 2, |
||||||
|
750: 1, |
||||||
|
828: 1.81 / 2, |
||||||
|
375: 2 / 1 |
||||||
|
}, |
||||||
|
sourceRoot: 'src', |
||||||
|
outputRoot: 'dist', |
||||||
|
plugins: [], |
||||||
|
defineConstants: {}, |
||||||
|
copy: { |
||||||
|
patterns: [], |
||||||
|
options: {} |
||||||
|
}, |
||||||
|
framework: 'react', |
||||||
|
compiler: 'webpack5', |
||||||
|
cache: { |
||||||
|
enable: false // Webpack 持久化缓存配置,建议开启。默认配置请参考:https://docs.taro.zone/docs/config-detail#cache
|
||||||
|
}, |
||||||
|
sass: {}, |
||||||
|
alias: { |
||||||
|
"@": path.resolve(__dirname, '..', 'src') |
||||||
|
}, |
||||||
|
mini: { |
||||||
|
postcss: { |
||||||
|
pxtransform: { |
||||||
|
enable: true, |
||||||
|
config: { |
||||||
|
selectorBlackList: ['nut-'] |
||||||
|
} |
||||||
|
}, |
||||||
|
url: { |
||||||
|
enable: true, |
||||||
|
config: { |
||||||
|
limit: 1024 // 设定转换尺寸上限
|
||||||
|
} |
||||||
|
}, |
||||||
|
cssModules: { |
||||||
|
enable: true, // 默认为 false,如需使用 css modules 功能,则设为 true
|
||||||
|
config: { |
||||||
|
namingPattern: 'module', // 转换模式,取值为 global/module
|
||||||
|
generateScopedName: '[name]__[local]___[hash:base64:5]' |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
}, |
||||||
|
h5: { |
||||||
|
publicPath: '/', |
||||||
|
staticDirectory: 'static', |
||||||
|
postcss: { |
||||||
|
pxtransform: { |
||||||
|
enable: true, |
||||||
|
config: { |
||||||
|
selectorBlackList: ['nut-'] |
||||||
|
} |
||||||
|
}, |
||||||
|
autoprefixer: { |
||||||
|
enable: true, |
||||||
|
config: {} |
||||||
|
}, |
||||||
|
cssModules: { |
||||||
|
enable: true, // 默认为 false,如需使用 css modules 功能,则设为 true
|
||||||
|
config: { |
||||||
|
namingPattern: 'module', // 转换模式,取值为 global/module
|
||||||
|
generateScopedName: '[name]__[local]___[hash:base64:5]' |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
module.exports = function (merge) { |
||||||
|
if (process.env.NODE_ENV === 'development') { |
||||||
|
return merge({}, config, require('./dev')) |
||||||
|
} |
||||||
|
return merge({}, config, require('./prod')) |
||||||
|
} |
@ -0,0 +1,37 @@ |
|||||||
|
module.exports = { |
||||||
|
env: { |
||||||
|
NODE_ENV: '"production"' |
||||||
|
}, |
||||||
|
defineConstants: { |
||||||
|
}, |
||||||
|
mini: {}, |
||||||
|
h5: { |
||||||
|
/** |
||||||
|
* WebpackChain 插件配置 |
||||||
|
* @docs https://github.com/neutrinojs/webpack-chain
|
||||||
|
*/ |
||||||
|
// webpackChain (chain) {
|
||||||
|
// /**
|
||||||
|
// * 如果 h5 端编译后体积过大,可以使用 webpack-bundle-analyzer 插件对打包体积进行分析。
|
||||||
|
// * @docs https://github.com/webpack-contrib/webpack-bundle-analyzer
|
||||||
|
// */
|
||||||
|
// chain.plugin('analyzer')
|
||||||
|
// .use(require('webpack-bundle-analyzer').BundleAnalyzerPlugin, [])
|
||||||
|
|
||||||
|
// /**
|
||||||
|
// * 如果 h5 端首屏加载时间过长,可以使用 prerender-spa-plugin 插件预加载首页。
|
||||||
|
// * @docs https://github.com/chrisvfritz/prerender-spa-plugin
|
||||||
|
// */
|
||||||
|
// const path = require('path')
|
||||||
|
// const Prerender = require('prerender-spa-plugin')
|
||||||
|
// const staticDir = path.join(__dirname, '..', 'dist')
|
||||||
|
// chain
|
||||||
|
// .plugin('prerender')
|
||||||
|
// .use(new Prerender({
|
||||||
|
// staticDir,
|
||||||
|
// routes: [ '/pages/index/index' ],
|
||||||
|
// postProcess: (context) => ({ ...context, outputPath: path.join(staticDir, 'index.html') })
|
||||||
|
// }))
|
||||||
|
// }
|
||||||
|
} |
||||||
|
} |
@ -0,0 +1,89 @@ |
|||||||
|
{ |
||||||
|
"name": "video", |
||||||
|
"version": "1.0.0", |
||||||
|
"private": true, |
||||||
|
"description": "", |
||||||
|
"templateInfo": { |
||||||
|
"name": "react-NutUI", |
||||||
|
"typescript": true, |
||||||
|
"css": "sass" |
||||||
|
}, |
||||||
|
"scripts": { |
||||||
|
"build:weapp": "taro build --type weapp", |
||||||
|
"build:swan": "taro build --type swan", |
||||||
|
"build:alipay": "taro build --type alipay", |
||||||
|
"build:tt": "taro build --type tt", |
||||||
|
"build:h5": "taro build --type h5", |
||||||
|
"build:rn": "taro build --type rn", |
||||||
|
"build:qq": "taro build --type qq", |
||||||
|
"build:jd": "taro build --type jd", |
||||||
|
"build:quickapp": "taro build --type quickapp", |
||||||
|
"dev:weapp": "npm run build:weapp -- --watch", |
||||||
|
"dev:swan": "npm run build:swan -- --watch", |
||||||
|
"dev:alipay": "npm run build:alipay -- --watch", |
||||||
|
"dev:tt": "npm run build:tt -- --watch", |
||||||
|
"dev:h5": "npm run build:h5 -- --watch", |
||||||
|
"dev:rn": "npm run build:rn -- --watch", |
||||||
|
"dev:qq": "npm run build:qq -- --watch", |
||||||
|
"dev:jd": "npm run build:jd -- --watch", |
||||||
|
"dev:quickapp": "npm run build:quickapp -- --watch" |
||||||
|
}, |
||||||
|
"browserslist": [ |
||||||
|
"last 3 versions", |
||||||
|
"Android >= 4.1", |
||||||
|
"ios >= 8" |
||||||
|
], |
||||||
|
"author": "", |
||||||
|
"license": "MIT", |
||||||
|
"dependencies": { |
||||||
|
"@babel/runtime": "^7.7.7", |
||||||
|
"@tarojs/components": "3.6.8", |
||||||
|
"@tarojs/helper": "3.6.8", |
||||||
|
"@tarojs/plugin-framework-react": "3.6.8", |
||||||
|
"@tarojs/plugin-platform-alipay": "3.6.8", |
||||||
|
"@tarojs/plugin-platform-h5": "3.6.8", |
||||||
|
"@tarojs/plugin-platform-jd": "3.6.8", |
||||||
|
"@tarojs/plugin-platform-qq": "3.6.8", |
||||||
|
"@tarojs/plugin-platform-swan": "3.6.8", |
||||||
|
"@tarojs/plugin-platform-tt": "3.6.8", |
||||||
|
"@tarojs/plugin-platform-weapp": "3.6.8", |
||||||
|
"@tarojs/react": "3.6.8", |
||||||
|
"@tarojs/runtime": "3.6.8", |
||||||
|
"@tarojs/shared": "3.6.8", |
||||||
|
"@tarojs/taro": "3.6.8", |
||||||
|
"react": "^18.0.0", |
||||||
|
"react-dom": "^18.0.0", |
||||||
|
"react-refresh": "^0.11.0", |
||||||
|
"unstated-next": "^1.1.0" |
||||||
|
}, |
||||||
|
"devDependencies": { |
||||||
|
"@babel/core": "^7.8.0", |
||||||
|
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.5", |
||||||
|
"@tarojs/cli": "3.6.8", |
||||||
|
"@tarojs/taro-loader": "3.6.8", |
||||||
|
"@tarojs/webpack5-runner": "3.6.8", |
||||||
|
"@types/node": "^18.15.11", |
||||||
|
"@types/react": "^18.0.0", |
||||||
|
"@types/react-dom": "^18.0.0", |
||||||
|
"@types/react-router-dom": "^5.1.7", |
||||||
|
"@types/react-syntax-highlighter": "^13.5.2", |
||||||
|
"@types/react-test-renderer": "^18.0.0", |
||||||
|
"@types/react-transition-group": "^4.4.4", |
||||||
|
"@types/webpack-env": "^1.13.6", |
||||||
|
"@typescript-eslint/eslint-plugin": "^5.20.0", |
||||||
|
"@typescript-eslint/parser": "^5.20.0", |
||||||
|
"babel-plugin-import": "^1.13.3", |
||||||
|
"babel-preset-taro": "3.6.8", |
||||||
|
"eslint": "^8.12.0", |
||||||
|
"eslint-config-taro": "3.6.8", |
||||||
|
"eslint-plugin-import": "^2.12.0", |
||||||
|
"eslint-plugin-react": "^7.8.2", |
||||||
|
"eslint-plugin-react-hooks": "^4.2.0", |
||||||
|
"postcss": "^8.4.18", |
||||||
|
"style-loader": "1.3.0", |
||||||
|
"stylelint": "^14.4.0", |
||||||
|
"ts-node": "^10.9.1", |
||||||
|
"typescript": "^4.1.0", |
||||||
|
"webpack": "^5.78.0" |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,15 @@ |
|||||||
|
{ |
||||||
|
"miniprogramRoot": "./dist", |
||||||
|
"projectname": "video", |
||||||
|
"description": "", |
||||||
|
"appid": "wx703940a70f0f1be7", |
||||||
|
"setting": { |
||||||
|
"urlCheck": true, |
||||||
|
"es6": false, |
||||||
|
"enhance": false, |
||||||
|
"compileHotReLoad": false, |
||||||
|
"postcss": false, |
||||||
|
"minified": false |
||||||
|
}, |
||||||
|
"compileType": "miniprogram" |
||||||
|
} |
@ -0,0 +1,13 @@ |
|||||||
|
{ |
||||||
|
"miniprogramRoot": "Progress/", |
||||||
|
"projectname": "video", |
||||||
|
"description": "", |
||||||
|
"appid": "wx703940a70f0f1be7", |
||||||
|
"setting": { |
||||||
|
"urlCheck": true, |
||||||
|
"es6": false, |
||||||
|
"postcss": false, |
||||||
|
"minified": false |
||||||
|
}, |
||||||
|
"compileType": "miniprogram" |
||||||
|
} |
@ -0,0 +1,62 @@ |
|||||||
|
import {request} from "@/api/request"; |
||||||
|
|
||||||
|
|
||||||
|
export interface RecordData { |
||||||
|
key: string |
||||||
|
value: number |
||||||
|
} |
||||||
|
|
||||||
|
export interface CourseDepData { |
||||||
|
chapters: Chapters[] |
||||||
|
course: Curriculum |
||||||
|
hours: Hours |
||||||
|
/** 是否必修 */ |
||||||
|
is_required: boolean |
||||||
|
learn_hour_records: LearnHourRecords |
||||||
|
learn_record: LearnRecord | null |
||||||
|
} |
||||||
|
|
||||||
|
export interface HourPlayData { |
||||||
|
/** 时间 */ |
||||||
|
duration: number |
||||||
|
/** 格式 */ |
||||||
|
extension: string |
||||||
|
/** 地址 */ |
||||||
|
url: string |
||||||
|
} |
||||||
|
|
||||||
|
export interface Course { |
||||||
|
courses: Curriculum[] |
||||||
|
learn_course_records: unknown |
||||||
|
user_course_hour_count: unknown |
||||||
|
stats: CueStats |
||||||
|
} |
||||||
|
|
||||||
|
export const curriculum = { |
||||||
|
use() { |
||||||
|
return request<Manage[]>('/api/v1/user/all', 'GET') |
||||||
|
}, |
||||||
|
/** 学习记录 */ |
||||||
|
record(id: number) { |
||||||
|
return request<RecordData[]>(`/api/v1/user/record/${id}`, "GET") |
||||||
|
}, |
||||||
|
/** 查看课程课时数据 */ |
||||||
|
courseDep(id: number, depId: number | null) { |
||||||
|
return request<CourseDepData>(`/api/v1/course/${id}${depId ? `/dep/${depId}` : ''}`, "GET") |
||||||
|
}, |
||||||
|
hourPlay(courseId: number, id: number) { |
||||||
|
return request<HourPlayData>(`/api/v1/course/${courseId}/hour/${id}/play`, "GET") |
||||||
|
}, |
||||||
|
/** 删除课程 */ |
||||||
|
delCur(dep_id: number, course_id: number) { |
||||||
|
return request(`/api/v1/department/assign/${dep_id}?course_id=${course_id}`, "DELETE") |
||||||
|
}, |
||||||
|
/** 部门课程进度 */ |
||||||
|
course() { |
||||||
|
return request<Course>(`/api/v1/user/courses`, "GET") |
||||||
|
}, |
||||||
|
/** 课程结束 */ |
||||||
|
curEnd(courseId: number, id: number, duration: number) { |
||||||
|
return request(`/api/v1/course/${courseId}/hour/${id}/record`, "POST", {duration}) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,2 @@ |
|||||||
|
export * from './user' |
||||||
|
export * from './curriculum' |
@ -0,0 +1,123 @@ |
|||||||
|
import {request} from "@/api/request"; |
||||||
|
|
||||||
|
export interface Student { |
||||||
|
company_id:number |
||||||
|
avatar: string |
||||||
|
dep_ids: number[] |
||||||
|
email: string |
||||||
|
id_card: string |
||||||
|
name: string |
||||||
|
password: string |
||||||
|
phone_number: string |
||||||
|
} |
||||||
|
|
||||||
|
interface UserInfoData { |
||||||
|
dep_ids: number[] |
||||||
|
user: Student |
||||||
|
} |
||||||
|
|
||||||
|
interface setRoleTypeData { |
||||||
|
/** 当前超级管理员ID */ |
||||||
|
auth_id: number |
||||||
|
/** 管理员权限,0 为员工,1为管理员,2为超级管理员 */ |
||||||
|
role_type: number |
||||||
|
} |
||||||
|
|
||||||
|
type DepList = Record<number, Manage[]> |
||||||
|
|
||||||
|
export interface AddDepProps { |
||||||
|
id?: number | null |
||||||
|
name: string |
||||||
|
parent_id: number |
||||||
|
company_id: number |
||||||
|
sort: number |
||||||
|
} |
||||||
|
|
||||||
|
interface DepCurData { |
||||||
|
data: Curriculum[] |
||||||
|
total: number |
||||||
|
} |
||||||
|
|
||||||
|
export interface depCurProps { |
||||||
|
page: number |
||||||
|
size: number |
||||||
|
id: number |
||||||
|
} |
||||||
|
|
||||||
|
interface AddCurProps { |
||||||
|
course_id: number[] |
||||||
|
dep_id: number[] |
||||||
|
is_required: 1 | 0 |
||||||
|
} |
||||||
|
|
||||||
|
export interface CurLearningRecord { |
||||||
|
course: Curriculum |
||||||
|
data: User[] |
||||||
|
/** 部门 */ |
||||||
|
departments: Record<number, string> |
||||||
|
/** 第一次记录 */ |
||||||
|
user_course_hour_user_first_at: Record<string, unknown> |
||||||
|
/** 学习记录 */ |
||||||
|
user_course_records: Record<string, LearnRecord> |
||||||
|
/** 学员部门 */ |
||||||
|
user_dep_ids: Record<number, number[]> |
||||||
|
total: number |
||||||
|
} |
||||||
|
|
||||||
|
export const ManageApi = { |
||||||
|
/** 添加学员 */ |
||||||
|
addUser(data: Student) { |
||||||
|
return request('/api/v1/user/create', "POST", data) |
||||||
|
}, |
||||||
|
userInfo(id: number) { |
||||||
|
return request<UserInfoData>(`/api/v1/user/${id}`, "GET") |
||||||
|
}, |
||||||
|
putUser(id: number, data: Student) { |
||||||
|
return request(`/api/v1/user/front/${id}`, "PUT", data) |
||||||
|
}, |
||||||
|
del(id: number) { |
||||||
|
return request(`/api/v1/user/${id}`, "DELETE") |
||||||
|
}, |
||||||
|
/** 修改学员类型 */ |
||||||
|
setRoleType(id: number, data: setRoleTypeData) { |
||||||
|
return request(`/api/v1/user/${id}`, "PUT", data) |
||||||
|
}, |
||||||
|
/** 部门 */ |
||||||
|
depList() { |
||||||
|
return request<DepList>('/api/v1/department/index', "GET") |
||||||
|
}, |
||||||
|
addDep(data: AddDepProps) { |
||||||
|
return request('/api/v1/department/save', "POST", data) |
||||||
|
}, |
||||||
|
putDep(data: AddDepProps) { |
||||||
|
return request(`/api/v1/department/${data.id}`, "PUT", data) |
||||||
|
}, |
||||||
|
delDep(id: number) { |
||||||
|
return request(`/api/v1/department/${id}`, "DELETE") |
||||||
|
}, |
||||||
|
/** 部门课程 */ |
||||||
|
depCur(data: depCurProps) { |
||||||
|
return request<DepCurData>(`/api/v1/department/${data.id}/courses?page=${data.page}&size=${data.size}`, "GET") |
||||||
|
}, |
||||||
|
/** 未添加课程 */ |
||||||
|
optionalCur(dep_id: number, category_id: number) { |
||||||
|
return request<Curriculum[]>(`/api/v1/department/${dep_id}/optional?category_id=${category_id}`, "GET") |
||||||
|
}, |
||||||
|
addCur(data: AddCurProps) { |
||||||
|
return request('/api/v1/course/user', "POST", data) |
||||||
|
}, |
||||||
|
buyAll() { |
||||||
|
return request<Curriculum[]>('/api/v1/course/buy/all', "GET") |
||||||
|
}, |
||||||
|
buy(data_list: number[]) { |
||||||
|
return request(`/api/v1/course/buy?data_list=${data_list}`, "POST") |
||||||
|
}, |
||||||
|
/** 课程绑定部门 */ |
||||||
|
bingDep(dep_id: number) { |
||||||
|
return request<Curriculum & { department: Department[] }>(`/api/v1/course/all/bind/${dep_id}`, "GET") |
||||||
|
}, |
||||||
|
/** 课程学员学习记录 */ |
||||||
|
curLearningRecord(cur_id: number | string,data:{page:number,size:number}) { |
||||||
|
return request<CurLearningRecord>(`/api/v1/course/${cur_id}/user/index`, "GET", data) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,43 @@ |
|||||||
|
import {request} from "@/api/request"; |
||||||
|
|
||||||
|
export interface Category { |
||||||
|
id: number |
||||||
|
name: string |
||||||
|
parent_chain: string |
||||||
|
parent_id: number |
||||||
|
sort: number |
||||||
|
} |
||||||
|
|
||||||
|
interface CategoryList { |
||||||
|
categories: Record<number, Category[]> |
||||||
|
} |
||||||
|
|
||||||
|
export interface Courses { |
||||||
|
/** 已完成 */ |
||||||
|
is_finished: Curriculum[] |
||||||
|
/** 未完成 */ |
||||||
|
is_not_finished: Curriculum[] |
||||||
|
/** 选秀 */ |
||||||
|
is_not_required: Curriculum[] |
||||||
|
/** 必修 */ |
||||||
|
is_required: Curriculum[] |
||||||
|
} |
||||||
|
|
||||||
|
export type CoursesKey = keyof Courses |
||||||
|
|
||||||
|
export type Cur = Category & { |
||||||
|
courses: Courses |
||||||
|
name: string |
||||||
|
resourceCategory?: Cur[] |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
export const publicApi = { |
||||||
|
category() { |
||||||
|
return request<CategoryList>('/api/v1/category/all', "GET") |
||||||
|
}, |
||||||
|
/** 课程 */ |
||||||
|
curs() { |
||||||
|
return request<Cur[]>('/api/v1/category/index', "GET") |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,105 @@ |
|||||||
|
import Taro from "@tarojs/taro"; |
||||||
|
|
||||||
|
interface Method { |
||||||
|
/** HTTP 请求 OPTIONS */ |
||||||
|
OPTIONS |
||||||
|
/** HTTP 请求 GET */ |
||||||
|
GET |
||||||
|
/** HTTP 请求 HEAD */ |
||||||
|
HEAD |
||||||
|
/** HTTP 请求 POST */ |
||||||
|
POST |
||||||
|
/** HTTP 请求 PUT */ |
||||||
|
PUT |
||||||
|
/** HTTP 请求 PATCH */ |
||||||
|
PATCH |
||||||
|
/** HTTP 请求 DELETE */ |
||||||
|
DELETE |
||||||
|
/** HTTP 请求 TRACE */ |
||||||
|
TRACE |
||||||
|
/** HTTP 请求 CONNECT */ |
||||||
|
CONNECT |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
/** 请求不成功各种状态的错误 */ |
||||||
|
export const ERROR_STATUS: Record<number | string, string> = { |
||||||
|
'400': '400: 语法错误~', |
||||||
|
'401': '401: 未授权~', |
||||||
|
'403': '403: 拒绝访问~', |
||||||
|
'404': '404: 资源不存在~', |
||||||
|
'405': '405: 未允许~', |
||||||
|
'429': '请求过于频繁', |
||||||
|
'408': '408: 请求超时~', |
||||||
|
'500': '500: 服务器错误', |
||||||
|
'501': '501: 无请求功能~', |
||||||
|
'502': '502: 错误网关~', |
||||||
|
'503': '503: 服务不可用~', |
||||||
|
'504': '504: 网关超时~', |
||||||
|
'505': '505: http版本错误~', |
||||||
|
'DEFAULT': '请求错误~', |
||||||
|
'request:fail timeout': '请求超时~', |
||||||
|
'NETWORK_ERROR': '网络不可用~', |
||||||
|
'INVALID_DATA': '服务器响应异常~', |
||||||
|
'OVERSTEP': '请求越界~' |
||||||
|
} |
||||||
|
|
||||||
|
export function request<T = unknown>( |
||||||
|
url: string, |
||||||
|
method: keyof Method, |
||||||
|
data?: Record<string, any> |
||||||
|
): Promise<T> { |
||||||
|
const option: Taro.request.Option<T> = { |
||||||
|
url: process.env.TARO_APP_API + url, |
||||||
|
method: method, |
||||||
|
dataType: 'json', |
||||||
|
timeout: 30000, |
||||||
|
header: { |
||||||
|
'Content-Type': 'application/json;charset=UTF-8', |
||||||
|
} |
||||||
|
} |
||||||
|
const token = JSON.parse(Taro.getStorageSync('profile') || '{}')?.token |
||||||
|
if (token) { |
||||||
|
option.header ??= {} |
||||||
|
option.header['Authorization'] = `Bearer ${token}` |
||||||
|
} |
||||||
|
if (method === 'GET' && data) { |
||||||
|
let parameter = '' |
||||||
|
Object.entries(data).forEach(([key, value], index) => { |
||||||
|
parameter += (index === 0 ? '?' : '&') + key + '=' + JSON.stringify(value) |
||||||
|
}) |
||||||
|
option.url += parameter |
||||||
|
} |
||||||
|
data && (option.data = data) |
||||||
|
return new Promise<T>((resolve, reject) => { |
||||||
|
Taro.request<T>({ |
||||||
|
...option, |
||||||
|
success(res) { |
||||||
|
try { |
||||||
|
const data = res.data as any |
||||||
|
if (data?.code === 0 && res.statusCode === 200) { |
||||||
|
resolve(data.data || []) |
||||||
|
} else if (res.statusCode === 401) { |
||||||
|
Taro.showModal({ |
||||||
|
title: "登录过期,需重新登陆", |
||||||
|
showCancel: false, |
||||||
|
success({confirm}) { |
||||||
|
confirm && Taro.reLaunch({url: '/pages/login/login'}) |
||||||
|
} |
||||||
|
}) |
||||||
|
} else { |
||||||
|
Taro.showToast({title: data.msg || ERROR_STATUS[res.statusCode] || '请求错误~', icon: 'error'}) |
||||||
|
reject(null) |
||||||
|
} |
||||||
|
} catch (e) { |
||||||
|
reject(null) |
||||||
|
} |
||||||
|
}, |
||||||
|
fail(err) { |
||||||
|
const errMsg = err.errMsg |
||||||
|
Taro.showToast({title: ERROR_STATUS[errMsg] || ERROR_STATUS['DEFAULT'], icon: 'error'}) |
||||||
|
reject(null) |
||||||
|
} |
||||||
|
}) |
||||||
|
}) |
||||||
|
} |
@ -0,0 +1,53 @@ |
|||||||
|
import {request} from "./request"; |
||||||
|
|
||||||
|
interface Login { |
||||||
|
access_token: string |
||||||
|
code?: { |
||||||
|
image: string |
||||||
|
} |
||||||
|
company: Company |
||||||
|
token: string |
||||||
|
user: User |
||||||
|
catch_key: string |
||||||
|
} |
||||||
|
|
||||||
|
interface CheckoutBody { |
||||||
|
catch_key: string |
||||||
|
code: string |
||||||
|
phone_number: string |
||||||
|
} |
||||||
|
|
||||||
|
interface CheckoutData { |
||||||
|
token: string |
||||||
|
phone_number: string |
||||||
|
user: User |
||||||
|
company: Company |
||||||
|
} |
||||||
|
|
||||||
|
interface DepListData { |
||||||
|
departments: Department[] |
||||||
|
user: User |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
export const userApi = { |
||||||
|
login(code: string) { |
||||||
|
return request<Login>('/api/v1/auth/login/wechat', 'POST', {code}) |
||||||
|
}, |
||||||
|
checkout(data: CheckoutBody) { |
||||||
|
return request<CheckoutData>('/api/v1/auth/login/checkout', 'POST', data) |
||||||
|
}, |
||||||
|
unbind(id: number) { |
||||||
|
return request(`/api/v1/auth/login/unbind/${id}`, "PUT") |
||||||
|
}, |
||||||
|
putName(id: number, name: string) { |
||||||
|
return request<User>(`/api/v1/auth/login/${id}`, "PUT", {name}) |
||||||
|
}, |
||||||
|
/** 所属部门 */ |
||||||
|
depList() { |
||||||
|
return request<DepListData>(`/api/v1/user/detail`, "GET") |
||||||
|
}, |
||||||
|
code(catch_key: string) { |
||||||
|
return request(`/api/v1/auth/login/code`, "GET", {open_id:catch_key}) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,52 @@ |
|||||||
|
export default defineAppConfig({ |
||||||
|
pages: [ |
||||||
|
'pages/index/index', |
||||||
|
'pages/login/login', |
||||||
|
'pages/my/my' |
||||||
|
], |
||||||
|
window: { |
||||||
|
backgroundTextStyle: 'light', |
||||||
|
navigationBarBackgroundColor: '#fff', |
||||||
|
navigationBarTitleText: '课程', |
||||||
|
navigationBarTextStyle: 'black' |
||||||
|
}, |
||||||
|
tabBar: { |
||||||
|
list: [ |
||||||
|
{text: '课题', pagePath: 'pages/index/index'}, |
||||||
|
{text: "我的", pagePath: 'pages/my/my'} |
||||||
|
] |
||||||
|
}, |
||||||
|
preloadRule: { |
||||||
|
'pages/index/index': { |
||||||
|
network: 'all', |
||||||
|
packages: ['pages/business'] |
||||||
|
}, |
||||||
|
'pages/my/my': { |
||||||
|
network: 'all', |
||||||
|
packages: ['pages/manage'] |
||||||
|
} |
||||||
|
}, |
||||||
|
subpackages: [ |
||||||
|
{ |
||||||
|
root: 'pages/business', |
||||||
|
pages: [ |
||||||
|
'course/course', |
||||||
|
'userInfo/userInfo', |
||||||
|
'videoInfo/videoInfo' |
||||||
|
] |
||||||
|
}, |
||||||
|
{ |
||||||
|
root: 'pages/manage', |
||||||
|
pages: [ |
||||||
|
'depAdmin/depAdmin', |
||||||
|
'studentAdmin/studentAdmin', |
||||||
|
'college/college', |
||||||
|
'curriculum/curriculum', |
||||||
|
'addStudent/addStudent', |
||||||
|
'depCur/depCur', |
||||||
|
'addCur/addCur', |
||||||
|
'studentRecord/studentRecord', |
||||||
|
] |
||||||
|
} |
||||||
|
] |
||||||
|
}) |
@ -0,0 +1,330 @@ |
|||||||
|
@import "static/css/module"; |
||||||
|
|
||||||
|
.flex {display: flex !important;flex-direction:row} |
||||||
|
.flex-row{ flex-direction:row!important} |
||||||
|
.flex-column{ flex-direction:column!important} |
||||||
|
.flex-row-reverse{ flex-direction:row-reverse!important} |
||||||
|
.flex-column-reverse{ flex-direction:column-reverse!important} |
||||||
|
.flex-wrap{ flex-wrap:wrap} |
||||||
|
.flex-nowrap{ flex-wrap:nowrap} |
||||||
|
.justify-start{justify-content:flex-start} |
||||||
|
.justify-end{justify-content:flex-end} |
||||||
|
.justify-around{justify-content:space-around} |
||||||
|
.justify-between{justify-content:space-between} |
||||||
|
.justify-center{justify-content:center} |
||||||
|
|
||||||
|
.flex-wrap{flex-wrap:wrap} |
||||||
|
|
||||||
|
.align-center{ align-items: center} |
||||||
|
.align-stretch{ align-items: stretch} |
||||||
|
.align-start{ align-items: flex-start} |
||||||
|
.align-end{ align-items: flex-end} |
||||||
|
|
||||||
|
.content-start {align-content: flex-start} |
||||||
|
.content-end {align-content: flex-end} |
||||||
|
.content-center {align-content: center} |
||||||
|
.content-between {align-content: space-between} |
||||||
|
.content-around {align-content: space-around} |
||||||
|
.content-stretch {align-content: stretch} |
||||||
|
|
||||||
|
.flex-1{flex: 1} |
||||||
|
.flex-2{flex: 2} |
||||||
|
.flex-3{flex: 3} |
||||||
|
.flex-4{flex: 4} |
||||||
|
.flex-5{flex: 5} |
||||||
|
.flex-shrink{flex-shrink: 0} |
||||||
|
|
||||||
|
.w-1 {width: 10%;min-width: 75rpx} |
||||||
|
.w-2 {width: 20%;min-width: 150rpx} |
||||||
|
.w-3 {width: 30%;min-width: 225rpx} |
||||||
|
.w-4 {width: 40%;min-width: 300rpx} |
||||||
|
.w-5 {width: 50%;min-width: 375rpx} |
||||||
|
.w-6 {width: 60%;min-width: 450rpx} |
||||||
|
.w-7 {width: 70%;min-width: 525rpx} |
||||||
|
.w-8 {width: 80%;min-width: 600rpx} |
||||||
|
.w-9 {width: 90%;min-width: 675rpx} |
||||||
|
.w-10 {width: 100%;min-width: 750rpx} |
||||||
|
|
||||||
|
.h-1 {height: 10vh} |
||||||
|
.h-2 {height: 20vh} |
||||||
|
.h-3 {height: 30vh} |
||||||
|
.h-4 {height: 40vh} |
||||||
|
.h-5 {height: 50vh} |
||||||
|
.h-6 {height: 60vh} |
||||||
|
.h-7 {height: 70vh} |
||||||
|
.h-8 {height: 80vh} |
||||||
|
.h-9 {height: 90vh} |
||||||
|
.h-10 {height: 100vh} |
||||||
|
|
||||||
|
|
||||||
|
.m-0 {margin: 0} |
||||||
|
.m-auto{margin: auto} |
||||||
|
.m-1 {margin: 10rpx} |
||||||
|
.m-2 {margin: 20rpx} |
||||||
|
.m-3 {margin: 30rpx} |
||||||
|
.m-4 {margin: 40rpx} |
||||||
|
.m-5 {margin: 50rpx} |
||||||
|
.mt-0 {margin-top: 0} |
||||||
|
|
||||||
|
.mt-1 {margin-top: 10rpx} |
||||||
|
.mt-2 {margin-top: 20rpx} |
||||||
|
.mt-3 {margin-top: 30rpx} |
||||||
|
.mt-4 {margin-top: 40rpx} |
||||||
|
.mt-5 {margin-top: 50rpx} |
||||||
|
.mt-6 {margin-top: 60rpx} |
||||||
|
.mt-7 {margin-top: 70rpx} |
||||||
|
.mt-8 {margin-top: 80rpx} |
||||||
|
.mt-9 {margin-top: 80rpx} |
||||||
|
.mt-10 {margin-top: 100rpx} |
||||||
|
.mt-12 {margin-top: 120rpx} |
||||||
|
|
||||||
|
|
||||||
|
.mb-0 {margin-bottom: 0} |
||||||
|
.mb-auto {margin-bottom: auto} |
||||||
|
.mb-1 {margin-bottom: 10rpx} |
||||||
|
.mb-1_5 {margin-bottom: 15rpx} |
||||||
|
.mb-2 {margin-bottom: 20rpx} |
||||||
|
.mb-3 {margin-bottom: 30rpx} |
||||||
|
.mb-4 {margin-bottom: 40rpx} |
||||||
|
.mb-5 {margin-bottom: 50rpx} |
||||||
|
.mb-7 {margin-bottom: 70rpx} |
||||||
|
.mb-9 {margin-bottom: 90rpx} |
||||||
|
.mb-11 {margin-bottom: 110rpx} |
||||||
|
.mb-12 {margin-bottom: 120rpx} |
||||||
|
.mb-13 {margin-bottom: 130rpx} |
||||||
|
.mb-14 {margin-bottom: 140rpx} |
||||||
|
.mb-15 {margin-bottom: 150rpx} |
||||||
|
.ml-0 {margin-left: 0} |
||||||
|
|
||||||
|
.ml-auto {margin-left: auto} |
||||||
|
.ml-0_6 {margin-left: 6rpx} |
||||||
|
.ml-1 {margin-left: 10rpx} |
||||||
|
.ml-2 {margin-left: 20rpx} |
||||||
|
.ml-3 {margin-left: 30rpx} |
||||||
|
.ml-4 {margin-left: 40rpx} |
||||||
|
.ml-5 {margin-left: 50rpx} |
||||||
|
.ml-8 {margin-left: 80rpx} |
||||||
|
.ml-9 {margin-left: 90rpx} |
||||||
|
.ml-12 {margin-left: 120rpx} |
||||||
|
|
||||||
|
.mr-0 {margin-right: 0} |
||||||
|
.mr-auto {margin-right: auto} |
||||||
|
.mr-0_6 {margin-right: 6rpx} |
||||||
|
.mr-1 {margin-right: 10rpx} |
||||||
|
.mr-1_5 {margin-right: 15rpx} |
||||||
|
.mr-2 {margin-right: 20rpx} |
||||||
|
.mr-3 {margin-right: 30rpx} |
||||||
|
.mr-4 {margin-right: 40rpx} |
||||||
|
.mr-5 {margin-right: 50rpx} |
||||||
|
.mr-6 {margin-right: 60rpx} |
||||||
|
.mr-8 {margin-right: 80rpx} |
||||||
|
.mr-9 {margin-right: 90rpx} |
||||||
|
.mr-11 {margin-right:110rpx} |
||||||
|
.my-0 {margin-top: 0;margin-bottom: 0} |
||||||
|
.my-auto {margin-top: auto;margin-bottom: auto} |
||||||
|
|
||||||
|
.my-1 {margin-top: 10rpx; margin-bottom: 10rpx} |
||||||
|
.my-2 {margin-top: 20rpx; margin-bottom: 20rpx} |
||||||
|
.my-3 {margin-top: 30rpx; margin-bottom: 30rpx} |
||||||
|
.my-4 {margin-top: 40rpx; margin-bottom: 40rpx} |
||||||
|
.my-5 {margin-top: 50rpx; margin-bottom: 50rpx} |
||||||
|
|
||||||
|
.mx-0 { margin-left: 0; margin-right: 0;} |
||||||
|
.mx-auto { margin-left: auto; margin-right: auto;} |
||||||
|
.mx-1 { margin-left: 10rpx; margin-right: 10rpx;} |
||||||
|
.mx-2 { margin-left: 20rpx; margin-right: 20rpx;} |
||||||
|
.mx-3 { margin-left: 30rpx; margin-right: 30rpx;} |
||||||
|
.mx-4 { margin-left: 40rpx; margin-right: 40rpx;} |
||||||
|
.mx-5 { margin-left: 50rpx; margin-right: 50rpx;} |
||||||
|
|
||||||
|
|
||||||
|
.p-0 { padding: 0; } |
||||||
|
.p { padding: 5rpx; } |
||||||
|
.p-1 { padding: 10rpx; } |
||||||
|
.p-2 { padding: 20rpx; } |
||||||
|
.p-3 { padding: 30rpx; } |
||||||
|
.p-4 { padding: 40rpx; } |
||||||
|
.p-5 { padding: 50rpx; } |
||||||
|
|
||||||
|
.pt-0 { padding-top: 0; } |
||||||
|
.pt { padding-top: 5rpx; } |
||||||
|
.pt-1 { padding-top: 10rpx; } |
||||||
|
.pt-2 { padding-top: 20rpx; } |
||||||
|
.pt-3 { padding-top: 30rpx; } |
||||||
|
.pt-4 { padding-top: 40rpx; } |
||||||
|
.pt-5 { padding-top: 50rpx; } |
||||||
|
.pt-6 { padding-top: 60rpx; } |
||||||
|
.pt-7 { padding-top: 70rpx; } |
||||||
|
.pt-8 { padding-top: 80rpx; } |
||||||
|
.pt-9 { padding-top: 90rpx; } |
||||||
|
.pt-10{ padding-top: 100rpx;} |
||||||
|
.pt-11{ padding-top: 110rpx;} |
||||||
|
|
||||||
|
.pb-0 { padding-bottom: 0; } |
||||||
|
.pb-1 { padding-bottom: 10rpx; } |
||||||
|
.pb { padding-bottom: 5rpx; } |
||||||
|
.pb-2 { padding-bottom: 20rpx; } |
||||||
|
.pb-3 { padding-bottom: 30rpx; } |
||||||
|
.pb-4 { padding-bottom: 40rpx; } |
||||||
|
.pb-5 { padding-bottom: 50rpx; } |
||||||
|
|
||||||
|
.pl-0 { padding-left: 0; } |
||||||
|
.pl { padding-left: 5rpx; } |
||||||
|
.pl-1 { padding-left: 10rpx; } |
||||||
|
.pl-2 { padding-left: 20rpx; } |
||||||
|
.pl-3 { padding-left: 30rpx; } |
||||||
|
.pl-3_5 { padding-left: 35rpx; } |
||||||
|
.pl-4 { padding-left: 40rpx; } |
||||||
|
.pl-5 { padding-left: 50rpx; } |
||||||
|
.pl-6 { padding-left: 60rpx; } |
||||||
|
.pl-7 { padding-left: 70rpx; } |
||||||
|
|
||||||
|
.pr-0 { padding-right: 0; } |
||||||
|
.pr { padding-right: 5rpx; } |
||||||
|
.pr-1 { padding-right: 10rpx; } |
||||||
|
.pr-2 { padding-right: 20rpx; } |
||||||
|
.pr-3 { padding-right: 30rpx; } |
||||||
|
.pr-4 { padding-right: 40rpx; } |
||||||
|
.pr-5 { padding-right: 50rpx; } |
||||||
|
|
||||||
|
.py-0 { padding-top: 0; padding-bottom: 0; } |
||||||
|
.py { padding-top: 5rpx; padding-bottom: 5rpx; } |
||||||
|
.py-1 { padding-top: 10rpx; padding-bottom: 10rpx; } |
||||||
|
.py-1_5 { padding-top: 15rpx; padding-bottom: 15rpx; } |
||||||
|
.py-2 { padding-top: 20rpx; padding-bottom: 20rpx; } |
||||||
|
.py-3 { padding-top: 30rpx; padding-bottom: 30rpx; } |
||||||
|
.py-4 { padding-top: 40rpx; padding-bottom: 40rpx; } |
||||||
|
.py-5 { padding-top: 50rpx; padding-bottom: 50rpx; } |
||||||
|
.py-6 { padding-top: 50rpx; padding-bottom: 60rpx; } |
||||||
|
.py-7 { padding-top: 50rpx; padding-bottom: 70rpx; } |
||||||
|
.py-8 { padding-top: 80rpx; padding-bottom: 80rpx; } |
||||||
|
.py-9 { padding-top: 90rpx; padding-bottom: 90rpx; } |
||||||
|
|
||||||
|
.px-0 { padding-left: 0; padding-right: 0; } |
||||||
|
.px-1 { padding-left: 10rpx; padding-right: 10rpx;} |
||||||
|
.px { padding-left: 5rpx; padding-right: 5rpx;} |
||||||
|
.px-2 { padding-left: 20rpx; padding-right: 20rpx;} |
||||||
|
.px-3 { padding-left: 30rpx; padding-right: 30rpx;} |
||||||
|
.px-4 { padding-left: 40rpx; padding-right: 40rpx;} |
||||||
|
.px-5 { padding-left: 50rpx; padding-right: 50rpx;} |
||||||
|
.px-7 { padding-left: 70rpx; padding-right: 70rpx;} |
||||||
|
.px-8 { padding-left: 70rpx; padding-right: 80rpx;} |
||||||
|
.px-9 { padding-left: 90rpx; padding-right: 90rpx;} |
||||||
|
|
||||||
|
|
||||||
|
.font-12{font-size: 12rpx;line-height: 1;} |
||||||
|
.font-16{font-size: 16rpx;line-height: 1;} |
||||||
|
.font-20{font-size:20rpx;line-height: 1;} |
||||||
|
.font-24{font-size:24rpx;line-height: 1;} |
||||||
|
.font-26{font-size:26rpx;line-height: 1;} |
||||||
|
.font-28{font-size:28rpx;line-height: 1;} |
||||||
|
.font-30{font-size:30rpx;line-height: 1;} |
||||||
|
.font-32{font-size:32rpx;line-height: 1;} |
||||||
|
.font-34{font-size:34rpx;line-height: 1;} |
||||||
|
.font-36{font-size:36rpx;line-height: 1;} |
||||||
|
.font-40{font-size: 40rpx;line-height: 1;} |
||||||
|
.font-45{font-size: 45rpx;line-height: 1;} |
||||||
|
.font-50{font-size: 50rpx;line-height: 1;} |
||||||
|
.font-52{font-size: 52rpx;line-height: 1;} |
||||||
|
.font-60{font-size: 60rpx;line-height: 1;} |
||||||
|
.font-68{font-size: 68rpx;line-height: 1;} |
||||||
|
.font-weight{font-weight: bold} |
||||||
|
|
||||||
|
.text-indent{text-indent:2;} |
||||||
|
.text-through{text-decoration:line-through;} |
||||||
|
.text-left { text-align: left;} |
||||||
|
.text-right { text-align: right;} |
||||||
|
.text-center { text-align: center;} |
||||||
|
.text-justify{ text-align: justify;} |
||||||
|
.letspac3{ letter-spacing: 3px;} |
||||||
|
.letspac5{ letter-spacing: 5px;} |
||||||
|
|
||||||
|
/* 背景颜色 */ |
||||||
|
.bg-f2{background-color:#F2F2F2;} |
||||||
|
.bg-danger { background-color: red;} |
||||||
|
.bg-dark { background-color: #343a40;} |
||||||
|
.bg-hover-dark { background-color: #1d2124;} |
||||||
|
.bg-white { background-color: #ffffff;} |
||||||
|
.bg-orange{ background-color: orange;} |
||||||
|
.bg-gray{ background-color: gray;} |
||||||
|
.bg-transparent { background-color: transparent;} |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* 文字颜色 */ |
||||||
|
.text-white {color: #ffffff } |
||||||
|
.text-primary {color: #007bff;} |
||||||
|
.text-hover-primary { color: #0056b3;} |
||||||
|
.text-secondary {color: #6c757d;} |
||||||
|
.text-hover-secondary { color: #494f54;} |
||||||
|
.text-success {color: #28a745;} |
||||||
|
.text-hover-success{color: #19692c;} |
||||||
|
.text-info { color: #17a2b8;} |
||||||
|
.text-hover-info {color: #0f6674;} |
||||||
|
.text-warning {color: #FF9E5F;} |
||||||
|
.text-danger { color: #dc3545;} |
||||||
|
.text-hover-danger { color: #a71d2a;} |
||||||
|
.text-light { color: #f8f9fa;} |
||||||
|
.text-hover-light { color: #cbd3da;} |
||||||
|
.text-dark { color: #343a40;} |
||||||
|
.text-hover-dark{ color: #121416;} |
||||||
|
.text-body { color: #212529;} |
||||||
|
.text-muted { color: #6c757d;} |
||||||
|
|
||||||
|
|
||||||
|
/* 圆角 */ |
||||||
|
.rounded { border-radius: 8rpx;} |
||||||
|
.rounded-lg { border-radius: 14rpx;} |
||||||
|
.rounded-10 { border-radius: 10rpx;} |
||||||
|
.rounded-12 { border-radius: 12rpx;} |
||||||
|
.rounded-15 { border-radius: 15rpx;} |
||||||
|
.rounded-20 { border-radius: 20rpx;} |
||||||
|
.rounded-30 { border-radius: 30rpx;} |
||||||
|
.rounded-40 { border-radius: 40rpx;} |
||||||
|
.rounded-50 { border-radius: 50rpx;} |
||||||
|
.rounded-botm-30{border-bottom-left-radius: 30rpx;border-bottom-right-radius: 30rpx;} |
||||||
|
.rounded-top-30 {border-top-left-radius: 30rpx;border-top-right-radius: 30rpx;} |
||||||
|
.rounded-top { |
||||||
|
border-top-left-radius: 8rpx; |
||||||
|
border-top-right-radius: 8rpx; |
||||||
|
} |
||||||
|
.rounded-top-lg { |
||||||
|
border-top-left-radius: 14rpx; |
||||||
|
border-top-right-radius: 14rpx; |
||||||
|
} |
||||||
|
.rounded-right { |
||||||
|
border-top-right-radius: 8rpx; |
||||||
|
border-bottom-right-radius: 8rpx; |
||||||
|
} |
||||||
|
.rounded-bottom { |
||||||
|
border-bottom-right-radius: 8rpx; |
||||||
|
border-bottom-left-radius: 8rpx; |
||||||
|
} |
||||||
|
.rounded-bottom-lg { |
||||||
|
border-bottom-right-radius: 14rpx; |
||||||
|
border-bottom-left-radius: 14rpx; |
||||||
|
} |
||||||
|
.rounded-bottom-lg40 { |
||||||
|
border-bottom-right-radius: 40rpx; |
||||||
|
border-bottom-left-radius: 40rpx; |
||||||
|
} |
||||||
|
.rounded-left-50{ |
||||||
|
border-top-left-radius: 50rpx; |
||||||
|
border-bottom-left-radius: 50rpx; |
||||||
|
} |
||||||
|
.rounded-bottom-right-50{ |
||||||
|
border-bottom-right-radius: 50rpx; |
||||||
|
} |
||||||
|
.rounded-top-right-30{ |
||||||
|
border-top-right-radius: 30rpx; |
||||||
|
} |
||||||
|
.rounded-left { |
||||||
|
border-top-left-radius: 8rpx; |
||||||
|
border-bottom-left-radius: 8rpx; |
||||||
|
} |
||||||
|
.rounded-100 { border-radius: 100rpx;} |
||||||
|
.rounded-0 { border-radius: 0;} |
||||||
|
|
||||||
|
.border-none{border: none} |
||||||
|
|
||||||
|
|
@ -0,0 +1,72 @@ |
|||||||
|
import {useEffect} from 'react' |
||||||
|
import Taro, {useDidShow, useDidHide} from '@tarojs/taro' |
||||||
|
import './app.scss' |
||||||
|
import {CustomWrapper} from "@tarojs/components"; |
||||||
|
|
||||||
|
function updateApp() { |
||||||
|
if (Taro.canIUse('getUpdateManager')) { |
||||||
|
const updateManager = Taro.getUpdateManager() |
||||||
|
updateManager.onCheckForUpdate((res) => { |
||||||
|
console.log('新版本', res.hasUpdate) |
||||||
|
}) |
||||||
|
|
||||||
|
|
||||||
|
updateManager.onUpdateReady(() => { |
||||||
|
Taro.showModal({ |
||||||
|
title: '更新提示', |
||||||
|
content: '新版本已经准备好,是否重启应用?', |
||||||
|
success(res) { |
||||||
|
if (res.confirm) { |
||||||
|
updateManager.applyUpdate() |
||||||
|
} |
||||||
|
} |
||||||
|
}) |
||||||
|
}) |
||||||
|
updateManager.onUpdateFailed(() => { |
||||||
|
// 新版本下载失败
|
||||||
|
Taro.showToast({title: '新版本下载失败'}) |
||||||
|
}) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
function App(props) { |
||||||
|
// 可以使用所有的 React Hooks
|
||||||
|
useEffect(() => { |
||||||
|
updateApp() |
||||||
|
const token = JSON.parse(Taro.getStorageSync('profile') || '{}')?.token |
||||||
|
if (!token) { |
||||||
|
Taro.switchTab({url: '/pages/login/login'}) |
||||||
|
} |
||||||
|
}, []) |
||||||
|
|
||||||
|
|
||||||
|
Taro.getSystemInfo({ |
||||||
|
success(res) { |
||||||
|
Taro.getApp().globalData = { |
||||||
|
statusBarHeight: res.statusBarHeight, |
||||||
|
screenWidth: res.screenWidth, |
||||||
|
screenHeight: res.screenHeight, |
||||||
|
safeArea: res.safeArea, |
||||||
|
} |
||||||
|
} |
||||||
|
}) |
||||||
|
|
||||||
|
|
||||||
|
// 对应 onShow
|
||||||
|
useDidShow(() => { |
||||||
|
}) |
||||||
|
|
||||||
|
// 对应 onHide
|
||||||
|
useDidHide(() => { |
||||||
|
}) |
||||||
|
|
||||||
|
|
||||||
|
return ( |
||||||
|
<CustomWrapper> |
||||||
|
{props.children} |
||||||
|
</CustomWrapper> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
export default App |
@ -0,0 +1,25 @@ |
|||||||
|
.collapse { |
||||||
|
display: flex; |
||||||
|
justify-content: space-between; |
||||||
|
align-items: center; |
||||||
|
font-weight: bold; |
||||||
|
line-height: 84rpx; |
||||||
|
background: #F5F8F7; |
||||||
|
border-radius: 24rpx; |
||||||
|
padding: 0 24rpx; |
||||||
|
margin-top: 20rpx; |
||||||
|
} |
||||||
|
|
||||||
|
.children { |
||||||
|
overflow: hidden; |
||||||
|
max-height: 0; |
||||||
|
transition: max-height 300ms ease; |
||||||
|
} |
||||||
|
|
||||||
|
.open { |
||||||
|
max-height: 1000px !important; |
||||||
|
} |
||||||
|
|
||||||
|
.close { |
||||||
|
max-height: 0 !important; |
||||||
|
} |
@ -0,0 +1,29 @@ |
|||||||
|
import styles from './collapse.module.scss' |
||||||
|
import {FC, useState} from "react"; |
||||||
|
import {View} from "@tarojs/components"; |
||||||
|
import Icon from "@/components/icon"; |
||||||
|
|
||||||
|
interface Props { |
||||||
|
title: string |
||||||
|
children?: JSX.Element | string |
||||||
|
onChange?: () => void |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
const Collapse: FC<Props> = (props: Props) => { |
||||||
|
const [show, setShow] = useState(false) |
||||||
|
|
||||||
|
return ( |
||||||
|
<> |
||||||
|
<View className={styles.collapse} onClick={() => setShow(!show)}> |
||||||
|
<View>{props.title}</View> |
||||||
|
<Icon name={show ? 'chevron-up' : 'chevron-down'}/> |
||||||
|
</View> |
||||||
|
<View className={`${styles.children} ${show ? styles.open : styles.close}`}> |
||||||
|
{props?.children} |
||||||
|
</View> |
||||||
|
</> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
export default Collapse |
@ -0,0 +1,228 @@ |
|||||||
|
import {CSSProperties, FC} from "react"; |
||||||
|
import {Text} from "@tarojs/components"; |
||||||
|
import {ITouchEvent} from "@tarojs/components/types/common"; |
||||||
|
import './icon.scss' |
||||||
|
|
||||||
|
export type IconName = |
||||||
|
| 'add' |
||||||
|
| 'add-circle' |
||||||
|
| 'subtract' |
||||||
|
| 'subtract-circle' |
||||||
|
| 'align-center' |
||||||
|
| 'align-left' |
||||||
|
| 'align-right' |
||||||
|
| 'arrow-down' |
||||||
|
| 'arrow-left' |
||||||
|
| 'arrow-right' |
||||||
|
| 'arrow-up' |
||||||
|
| 'bell' |
||||||
|
| 'blocked' |
||||||
|
| 'bookmark' |
||||||
|
| 'bullet-list' |
||||||
|
| 'calendar' |
||||||
|
| 'camera' |
||||||
|
| 'check-circle' |
||||||
|
| 'chevron-down' |
||||||
|
| 'chevron-left' |
||||||
|
| 'chevron-right' |
||||||
|
| 'chevron-up' |
||||||
|
| 'clock' |
||||||
|
| 'close-circle' |
||||||
|
| 'close' |
||||||
|
| 'credit-card' |
||||||
|
| 'download-cloud' |
||||||
|
| 'download' |
||||||
|
| 'edit' |
||||||
|
| 'equalizer' |
||||||
|
| 'external-link' |
||||||
|
| 'eye' |
||||||
|
| 'file-audio' |
||||||
|
| 'file-code' |
||||||
|
| 'file-generic' |
||||||
|
| 'file-jpg' |
||||||
|
| 'file-new' |
||||||
|
| 'file-png' |
||||||
|
| 'file-svg' |
||||||
|
| 'file-video' |
||||||
|
| 'filter' |
||||||
|
| 'folder' |
||||||
|
| 'font-color' |
||||||
|
| 'heart' |
||||||
|
| 'help' |
||||||
|
| 'home' |
||||||
|
| 'image' |
||||||
|
| 'iphone-x' |
||||||
|
| 'iphone' |
||||||
|
| 'lightning-bolt' |
||||||
|
| 'link' |
||||||
|
| 'list' |
||||||
|
| 'lock' |
||||||
|
| 'mail' |
||||||
|
| 'map-pin' |
||||||
|
| 'menu' |
||||||
|
| 'message' |
||||||
|
| 'money' |
||||||
|
| 'next' |
||||||
|
| 'numbered-list' |
||||||
|
| 'pause' |
||||||
|
| 'phone' |
||||||
|
| 'play' |
||||||
|
| 'playlist' |
||||||
|
| 'prev' |
||||||
|
| 'reload' |
||||||
|
| 'repeat-play' |
||||||
|
| 'search' |
||||||
|
| 'settings' |
||||||
|
| 'share-2' |
||||||
|
| 'share' |
||||||
|
| 'shopping-bag-2' |
||||||
|
| 'shopping-bag' |
||||||
|
| 'shopping-cart' |
||||||
|
| 'shuffle-play' |
||||||
|
| 'sketch' |
||||||
|
| 'sound' |
||||||
|
| 'star' |
||||||
|
| 'stop' |
||||||
|
| 'streaming' |
||||||
|
| 'tag' |
||||||
|
| 'tags' |
||||||
|
| 'text-italic' |
||||||
|
| 'text-strikethrough' |
||||||
|
| 'text-underline' |
||||||
|
| 'trash' |
||||||
|
| 'upload' |
||||||
|
| 'user' |
||||||
|
| 'video' |
||||||
|
| 'volume-minus' |
||||||
|
| 'volume-off' |
||||||
|
| 'volume-plus' |
||||||
|
| 'analytics' |
||||||
|
| 'star-2' |
||||||
|
| 'check' |
||||||
|
| 'heart-2' |
||||||
|
| 'loading' |
||||||
|
| 'loading-2' |
||||||
|
| 'loading-3' |
||||||
|
| 'alert-circle' |
||||||
|
|
||||||
|
export const icons: IconName[] = [ |
||||||
|
'add', |
||||||
|
'add-circle', |
||||||
|
'subtract', |
||||||
|
'subtract-circle', |
||||||
|
'align-center', |
||||||
|
'align-left', |
||||||
|
'align-right', |
||||||
|
'arrow-down', |
||||||
|
'arrow-left', |
||||||
|
'arrow-right', |
||||||
|
'arrow-up', |
||||||
|
'bell', |
||||||
|
'blocked', |
||||||
|
'bookmark', |
||||||
|
'bullet-list', |
||||||
|
'calendar', |
||||||
|
'camera', |
||||||
|
'check-circle', |
||||||
|
'chevron-down', |
||||||
|
'chevron-left', |
||||||
|
'chevron-right', |
||||||
|
'chevron-up', |
||||||
|
'clock', |
||||||
|
'close-circle', |
||||||
|
'close', |
||||||
|
'credit-card', |
||||||
|
'download-cloud', |
||||||
|
'download', |
||||||
|
'edit', |
||||||
|
'equalizer', |
||||||
|
'external-link', |
||||||
|
'eye', |
||||||
|
'file-audio', |
||||||
|
'file-code', |
||||||
|
'file-generic', |
||||||
|
'file-jpg', |
||||||
|
'file-new', |
||||||
|
'file-png', |
||||||
|
'file-svg', |
||||||
|
'file-video', |
||||||
|
'filter', |
||||||
|
'folder', |
||||||
|
'font-color', |
||||||
|
'heart', |
||||||
|
'help', |
||||||
|
'home', |
||||||
|
'image', |
||||||
|
'iphone-x', |
||||||
|
'iphone', |
||||||
|
'lightning-bolt', |
||||||
|
'link', |
||||||
|
'list', |
||||||
|
'lock', |
||||||
|
'mail', |
||||||
|
'map-pin', |
||||||
|
'menu', |
||||||
|
'message', |
||||||
|
'money', |
||||||
|
'next', |
||||||
|
'numbered-list', |
||||||
|
'pause', |
||||||
|
'phone', |
||||||
|
'play', |
||||||
|
'playlist', |
||||||
|
'prev', |
||||||
|
'reload', |
||||||
|
'repeat-play', |
||||||
|
'search', |
||||||
|
'settings', |
||||||
|
'share-2', |
||||||
|
'share', |
||||||
|
'shopping-bag-2', |
||||||
|
'shopping-bag', |
||||||
|
'shopping-cart', |
||||||
|
'shuffle-play', |
||||||
|
'sketch', |
||||||
|
'sound', |
||||||
|
'star', |
||||||
|
'stop', |
||||||
|
'streaming', |
||||||
|
'tag', |
||||||
|
'tags', |
||||||
|
'text-italic', |
||||||
|
'text-strikethrough', |
||||||
|
'text-underline', |
||||||
|
'trash', |
||||||
|
'upload', |
||||||
|
'user', |
||||||
|
'video', |
||||||
|
'volume-minus', |
||||||
|
'volume-off', |
||||||
|
'volume-plus', |
||||||
|
'analytics', |
||||||
|
'star-2', |
||||||
|
'check', |
||||||
|
'heart-2', |
||||||
|
'loading', |
||||||
|
'loading-2', |
||||||
|
'loading-3', |
||||||
|
'alert-circle', |
||||||
|
]; |
||||||
|
|
||||||
|
export interface IconProps { |
||||||
|
name: IconName |
||||||
|
size?: string | number |
||||||
|
color?: string |
||||||
|
onClick?: (event: ITouchEvent) => void |
||||||
|
} |
||||||
|
|
||||||
|
const Icon: FC<IconProps> = (props) => { |
||||||
|
const size = typeof props.size === 'string' ? props.size : `${props.size ?? 16}px` |
||||||
|
const color = props.color ?? 'currentColor' |
||||||
|
const fontStyle: CSSProperties = {fontSize: size, color} |
||||||
|
|
||||||
|
return ( |
||||||
|
<Text className={`icon icon-${props.name}`} style={fontStyle} onClick={props.onClick} /> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
export default Icon |
@ -0,0 +1,34 @@ |
|||||||
|
import {FC, useEffect, useState} from "react"; |
||||||
|
import {Input, Text, View} from "@tarojs/components"; |
||||||
|
import Icon, {icons, IconName} from './index' |
||||||
|
|
||||||
|
const IconList: FC = () => { |
||||||
|
const [list, setList] = useState<IconName[]>(icons) |
||||||
|
const [keyword, setKeyword] = useState("") |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
setList(() => { |
||||||
|
if (!keyword) return [...icons] |
||||||
|
return icons.filter(name => name.includes(keyword)); |
||||||
|
}); |
||||||
|
}, [keyword]) |
||||||
|
|
||||||
|
return ( |
||||||
|
<View> |
||||||
|
<View> |
||||||
|
<Text>关键字:</Text> |
||||||
|
<Input style={{border: '1px solid #ddd', marginBottom: "12px", padding: '8px'}} onInput={(e) => setKeyword(e.detail.value)}/> |
||||||
|
</View> |
||||||
|
<View style={{display:"flex", flexWrap:'wrap'}}> |
||||||
|
{list.map((name) => ( |
||||||
|
<View style={{display: "flex", flexDirection: 'column', alignItems: "center", justifyContent: 'center', width: '100px', height: '100px', border: '1px solid #ddd'}}> |
||||||
|
<Icon name={name} size={26}/> |
||||||
|
<Text>{name}</Text> |
||||||
|
</View> |
||||||
|
))} |
||||||
|
</View> |
||||||
|
</View> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
export default IconList |
@ -0,0 +1,36 @@ |
|||||||
|
.loading { |
||||||
|
position: relative; |
||||||
|
} |
||||||
|
|
||||||
|
.background, |
||||||
|
.foreground { |
||||||
|
position: absolute; |
||||||
|
top: 0; |
||||||
|
left: 0; |
||||||
|
width: 100%; |
||||||
|
height: 100%; |
||||||
|
box-sizing: border-box; |
||||||
|
border-radius: 50%; |
||||||
|
border: 2px solid currentColor; |
||||||
|
} |
||||||
|
|
||||||
|
.background { |
||||||
|
z-index: 1; |
||||||
|
opacity: 0.3; |
||||||
|
} |
||||||
|
|
||||||
|
.foreground { |
||||||
|
z-index: 2; |
||||||
|
border-color: currentColor transparent transparent transparent; |
||||||
|
//animation: loading 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite; |
||||||
|
animation: loading .5s linear infinite; |
||||||
|
} |
||||||
|
|
||||||
|
@keyframes loading { |
||||||
|
from { |
||||||
|
transform: rotate(0deg); |
||||||
|
} |
||||||
|
to { |
||||||
|
transform: rotate(360deg); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,27 @@ |
|||||||
|
import {CSSProperties, FC} from "react"; |
||||||
|
import {View} from "@tarojs/components"; |
||||||
|
import styles from './index.module.scss' |
||||||
|
|
||||||
|
interface LoadingProps { |
||||||
|
size?: string | number |
||||||
|
color?: string |
||||||
|
} |
||||||
|
|
||||||
|
const Loading: FC<LoadingProps> = (props) => { |
||||||
|
const size = typeof props.size === 'string' ? props.size : `${props.size ?? 16}px` |
||||||
|
const color = props.color ?? 'currentColor' |
||||||
|
const sizeStyle: CSSProperties = { |
||||||
|
width: size, |
||||||
|
height: size, |
||||||
|
color |
||||||
|
} |
||||||
|
|
||||||
|
return ( |
||||||
|
<View className={styles.loading} style={sizeStyle}> |
||||||
|
<View className={styles.background} /> |
||||||
|
<View className={styles.foreground} /> |
||||||
|
</View> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
export default Loading |
@ -0,0 +1,65 @@ |
|||||||
|
import {FC, ReactNode, useEffect, useState} from "react"; |
||||||
|
import {View, Image, Text, PageContainer} from "@tarojs/components"; |
||||||
|
import Icon from "@/components/icon"; |
||||||
|
|
||||||
|
|
||||||
|
interface Props { |
||||||
|
height?: number | string |
||||||
|
title: string |
||||||
|
content?: string |
||||||
|
chevron?: boolean |
||||||
|
image?: string |
||||||
|
isProp?: boolean |
||||||
|
children?: ReactNode |
||||||
|
show?: boolean |
||||||
|
onClick?: () => void |
||||||
|
no_border?: boolean |
||||||
|
} |
||||||
|
|
||||||
|
const PopPut: FC<Props> = ({title, chevron, content, image, isProp, children, show, ...opt}: Props) => { |
||||||
|
const [PageShow, setShow] = useState(false) |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
if (PageShow) { |
||||||
|
setShow(false) |
||||||
|
} |
||||||
|
}, [show]) |
||||||
|
|
||||||
|
function click() { |
||||||
|
setShow(true) |
||||||
|
opt.onClick?.() |
||||||
|
} |
||||||
|
|
||||||
|
const style = (): string => { |
||||||
|
let css = '' |
||||||
|
if (opt.height) { |
||||||
|
css += ` height:${opt.height}px;font-size:16px` |
||||||
|
} |
||||||
|
if (opt.no_border) { |
||||||
|
css += ` border:none` |
||||||
|
} |
||||||
|
return css |
||||||
|
} |
||||||
|
|
||||||
|
return ( |
||||||
|
<> |
||||||
|
<View className='card' onClick={click} style={style()}> |
||||||
|
<View>{title}</View> |
||||||
|
<View className='card-content'> |
||||||
|
<Text>{content}</Text> |
||||||
|
{!chevron && <Icon name='chevron-right'/>} |
||||||
|
{image && <Image src={image} mode='scaleToFill'/>} |
||||||
|
</View> |
||||||
|
</View> |
||||||
|
{ |
||||||
|
isProp |
||||||
|
&& <PageContainer show={PageShow} position='bottom' round onBeforeLeave={() => setShow(false)}> |
||||||
|
{children} |
||||||
|
</PageContainer> |
||||||
|
} |
||||||
|
|
||||||
|
</> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
export default PopPut |
@ -0,0 +1,55 @@ |
|||||||
|
View::-webkit-scrollbar { |
||||||
|
display: none !important; |
||||||
|
} |
||||||
|
|
||||||
|
.tabs { |
||||||
|
width: 100%; |
||||||
|
height: 100%; |
||||||
|
overflow: hidden; |
||||||
|
|
||||||
|
|
||||||
|
.tabs-scroll { |
||||||
|
white-space: nowrap; |
||||||
|
position: relative; |
||||||
|
display: -webkit-flex; |
||||||
|
display: flex; |
||||||
|
text-align: center; |
||||||
|
} |
||||||
|
|
||||||
|
.current { |
||||||
|
position: relative; |
||||||
|
font-weight: bold; |
||||||
|
|
||||||
|
&:after { |
||||||
|
position: absolute; |
||||||
|
left: 50%; |
||||||
|
bottom: 0; |
||||||
|
width: 32rpx; |
||||||
|
height: 6rpx; |
||||||
|
background: #45D4A8; |
||||||
|
border-radius: 12rpx; |
||||||
|
opacity: 1; |
||||||
|
content: ''; |
||||||
|
display: block; |
||||||
|
animation: spread 200ms ease-out; |
||||||
|
transform: translateX(-18rpx); |
||||||
|
transition: all 200ms; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
.tabs-item { |
||||||
|
padding: 25rpx; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@keyframes spread { |
||||||
|
from { |
||||||
|
width: 0%; |
||||||
|
transform: translateX(0); |
||||||
|
} |
||||||
|
|
||||||
|
to { |
||||||
|
width: 32rpx; |
||||||
|
transform: translateX(-18rpx); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,64 @@ |
|||||||
|
import {FC, useEffect, useState} from "react"; |
||||||
|
import './tabs.scss' |
||||||
|
import {ScrollView, View} from "@tarojs/components"; |
||||||
|
import Taro from "@tarojs/taro"; |
||||||
|
|
||||||
|
|
||||||
|
export interface TabList<T = unknown> { |
||||||
|
title: string |
||||||
|
value?: number | string | Record<string, T> | T |
||||||
|
|
||||||
|
[key: string]: any |
||||||
|
} |
||||||
|
|
||||||
|
export type OnChangOpt<T = unknown> = { |
||||||
|
index: number, |
||||||
|
tab?: { title: string, value?: number | string | Record<string, T> | T } |
||||||
|
} |
||||||
|
|
||||||
|
interface TabsProps { |
||||||
|
current?: number | string |
||||||
|
tabList: TabList[] |
||||||
|
onChange?: (data: OnChangOpt) => void |
||||||
|
} |
||||||
|
|
||||||
|
const Tabs: FC<TabsProps> = (opt: TabsProps) => { |
||||||
|
const {screenWidth} = Taro.getApp().globalData |
||||||
|
const [current, setCurrent] = useState<number | string>(opt.current || 0) |
||||||
|
const [left, setLeft] = useState(0) |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
setCurrent(opt.current || 0) |
||||||
|
}, [opt.current]) |
||||||
|
|
||||||
|
function onChange(event: any, index: number, tab: TabList) { |
||||||
|
const offsetLeft = event.target.offsetLeft |
||||||
|
setLeft(offsetLeft - screenWidth / 2) |
||||||
|
setCurrent(index) |
||||||
|
opt?.onChange?.({index, tab}) |
||||||
|
} |
||||||
|
|
||||||
|
function is_current(value: unknown, index: number): boolean { |
||||||
|
if (opt.current) { |
||||||
|
return String(value) === String(opt.current) |
||||||
|
} |
||||||
|
return index === current |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
return ( |
||||||
|
<View className='tabs'> |
||||||
|
<ScrollView scrollX scrollLeft={left} scrollWithAnimation showScrollbar={false}> |
||||||
|
<View className={'tabs-scroll ' + (opt.tabList.length < 5 ? 'justify-around' : '')}> |
||||||
|
{opt.tabList.map((d, index) => <View |
||||||
|
className={'tabs-item ' + (is_current(d.value, index) ? 'current' : null)} |
||||||
|
onClick={(event) => onChange(event, index, d)}> |
||||||
|
{d.title} |
||||||
|
</View>)} |
||||||
|
</View> |
||||||
|
</ScrollView> |
||||||
|
</View> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
export default Tabs |
@ -0,0 +1,23 @@ |
|||||||
|
interface Breakpoint { |
||||||
|
id: number |
||||||
|
/** 秒 */ |
||||||
|
time: number |
||||||
|
} |
||||||
|
|
||||||
|
export interface HVideoOptions { |
||||||
|
/** 视频时长s */ |
||||||
|
duration: number |
||||||
|
/** 是否预览 */ |
||||||
|
preview: boolean |
||||||
|
/** 视频播放地址 */ |
||||||
|
src: string |
||||||
|
/** 视频封面 */ |
||||||
|
poster?: string |
||||||
|
/** 视频断点 */ |
||||||
|
breakpoint: Breakpoint[] |
||||||
|
|
||||||
|
/** 进入断点 */ |
||||||
|
onBreakpoint: (id: number) => void |
||||||
|
/** 视频播放结束 */ |
||||||
|
onEnded: () => void |
||||||
|
} |
@ -0,0 +1,63 @@ |
|||||||
|
import {BaseEventOrig, Video, VideoProps} from "@tarojs/components"; |
||||||
|
import {HVideoOptions} from "@/components/video/type"; |
||||||
|
import Taro from "@tarojs/taro"; |
||||||
|
import {FC, useState} from "react"; |
||||||
|
import {Profile} from '@/store' |
||||||
|
|
||||||
|
const deviation: number = 0.5 |
||||||
|
|
||||||
|
const HVideo: FC<HVideoOptions> = (opt: HVideoOptions) => { |
||||||
|
const {user} = Profile.useContainer() |
||||||
|
const video = Taro.createVideoContext('video') |
||||||
|
const [currentTime, setCurrentTime] = useState(0) |
||||||
|
|
||||||
|
function onTimeUpdate(event: BaseEventOrig<VideoProps.onTimeUpdateEventDetail>) { |
||||||
|
if (opt.preview) return; |
||||||
|
if (user?.role_type !== 0) return; |
||||||
|
const time = event.detail.currentTime |
||||||
|
/** 前进回退 */ |
||||||
|
if (currentTime + deviation < time) { |
||||||
|
video.seek(currentTime) |
||||||
|
return |
||||||
|
} |
||||||
|
setCurrentTime(time) |
||||||
|
|
||||||
|
/** 判断是否进入断点 */ |
||||||
|
opt.breakpoint.forEach(d => { |
||||||
|
if (time < d.time + deviation && time > d.time - deviation) { |
||||||
|
opt.onBreakpoint(d.id) |
||||||
|
video.pause() |
||||||
|
video.seek(d.time - deviation) |
||||||
|
return |
||||||
|
} |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
function onEnded() { |
||||||
|
console.log(user) |
||||||
|
if (user?.role_type !== 0) return; |
||||||
|
if (currentTime + 1 > opt.duration) { |
||||||
|
opt.onEnded() |
||||||
|
} else { |
||||||
|
video.seek(currentTime) |
||||||
|
video.play() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return ( |
||||||
|
<> |
||||||
|
<Video |
||||||
|
id='video' |
||||||
|
style={'width:100%'} |
||||||
|
poster={opt.poster} |
||||||
|
src={opt.src} |
||||||
|
enableProgressGesture={false} |
||||||
|
direction={90} |
||||||
|
onTimeUpdate={onTimeUpdate} |
||||||
|
onEnded={onEnded} |
||||||
|
/> |
||||||
|
</> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
export default HVideo |
@ -0,0 +1,68 @@ |
|||||||
|
.videoBox { |
||||||
|
padding: 10px; |
||||||
|
width: 50%; |
||||||
|
box-sizing: border-box; |
||||||
|
margin-bottom: 10px; |
||||||
|
} |
||||||
|
|
||||||
|
.video { |
||||||
|
width: 100%; |
||||||
|
background: #fff; |
||||||
|
border-radius: 10rpx; |
||||||
|
overflow: hidden; |
||||||
|
position: relative; |
||||||
|
|
||||||
|
.content { |
||||||
|
position: absolute; |
||||||
|
color: #fff; |
||||||
|
left: 0; |
||||||
|
top: 172rpx; |
||||||
|
width: 100%; |
||||||
|
line-height: 48rpx; |
||||||
|
font-size: 24rpx; |
||||||
|
text-overflow: ellipsis; |
||||||
|
overflow: hidden; |
||||||
|
white-space: nowrap; |
||||||
|
background: rgba(#000, .5); |
||||||
|
text-align: center; |
||||||
|
} |
||||||
|
|
||||||
|
.box { |
||||||
|
padding: 20rpx; |
||||||
|
|
||||||
|
.title{ |
||||||
|
width: 100%; |
||||||
|
//word-break: break-word; |
||||||
|
//white-space: pre-line; |
||||||
|
font-size: 28rpx; |
||||||
|
|
||||||
|
word-break: break-all; |
||||||
|
overflow: hidden; |
||||||
|
display: -webkit-box; |
||||||
|
-webkit-line-clamp: 2; |
||||||
|
-webkit-box-orient: vertical; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
.marker { |
||||||
|
position: absolute; |
||||||
|
background: rgba(#000, .5); |
||||||
|
color: #fff; |
||||||
|
padding: 0 10px; |
||||||
|
border-radius: 0 0 0 10px; |
||||||
|
top: 0; |
||||||
|
right: 0; |
||||||
|
} |
||||||
|
|
||||||
|
Image { |
||||||
|
width: 100%; |
||||||
|
height: 220rpx; |
||||||
|
display: block; |
||||||
|
} |
||||||
|
|
||||||
|
.videoButton{ |
||||||
|
margin-top: 20rpx; |
||||||
|
color: #909795; |
||||||
|
font-size: 22rpx; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,46 @@ |
|||||||
|
import {Image, View} from "@tarojs/components"; |
||||||
|
import {FC} from "react"; |
||||||
|
import './videoCover.scss' |
||||||
|
import Taro from "@tarojs/taro"; |
||||||
|
|
||||||
|
interface VideoCoverProps { |
||||||
|
thumb: string |
||||||
|
title: string | JSX.Element |
||||||
|
/** 右上角标签 */ |
||||||
|
marker?: string | JSX.Element |
||||||
|
content?: string | JSX.Element |
||||||
|
id: number |
||||||
|
/** 课程id */ |
||||||
|
depId: number | null |
||||||
|
/** 时间 */ |
||||||
|
time?: string |
||||||
|
/** 学习进度 */ |
||||||
|
schedule?: string |
||||||
|
} |
||||||
|
|
||||||
|
const VideoCover: FC<VideoCoverProps> = (opt: VideoCoverProps) => { |
||||||
|
function jump() { |
||||||
|
Taro.navigateTo({url: `/pages/business/videoInfo/videoInfo?id=${opt.id}&depId=${opt.depId || ''}`}) |
||||||
|
} |
||||||
|
|
||||||
|
return ( |
||||||
|
<View className='videoBox'> |
||||||
|
<View className='video' > |
||||||
|
<View onClick={jump}> |
||||||
|
<Image src={opt.thumb} mode='scaleToFill'/> |
||||||
|
{opt.content && <View className='content'>{opt.content}</View>} |
||||||
|
{opt.marker && <View className='marker'>{opt.marker}</View>} |
||||||
|
</View> |
||||||
|
<View className='box'> |
||||||
|
<View className='title'>{opt.title}</View> |
||||||
|
<View className='flex justify-between videoButton'> |
||||||
|
<View>{opt?.time}</View> |
||||||
|
<View>{opt?.schedule}</View> |
||||||
|
</View> |
||||||
|
</View> |
||||||
|
</View> |
||||||
|
</View> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
export default VideoCover |
@ -0,0 +1,17 @@ |
|||||||
|
<!DOCTYPE html> |
||||||
|
<html> |
||||||
|
<head> |
||||||
|
<meta content="text/html; charset=utf-8" http-equiv="Content-Type"> |
||||||
|
<meta content="width=device-width,initial-scale=1,user-scalable=no" name="viewport"> |
||||||
|
<meta name="apple-mobile-web-app-capable" content="yes"> |
||||||
|
<meta name="apple-touch-fullscreen" content="yes"> |
||||||
|
<meta name="format-detection" content="telephone=no,address=no"> |
||||||
|
<meta name="apple-mobile-web-app-status-bar-style" content="white"> |
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" > |
||||||
|
<title>video</title> |
||||||
|
<script><%= htmlWebpackPlugin.options.script %></script> |
||||||
|
</head> |
||||||
|
<body> |
||||||
|
<div id="app"></div> |
||||||
|
</body> |
||||||
|
</html> |
@ -0,0 +1,3 @@ |
|||||||
|
export default definePageConfig({ |
||||||
|
navigationBarTitleText: '课程' |
||||||
|
}) |
@ -0,0 +1,75 @@ |
|||||||
|
import {CustomWrapper, PageContainer, ScrollView} from "@tarojs/components"; |
||||||
|
import {useEffect, useState} from "react"; |
||||||
|
import HVideo from "@/components/video/video"; |
||||||
|
import {getCurrentInstance} from "@tarojs/runtime"; |
||||||
|
import {curriculum, HourPlayData} from "@/api"; |
||||||
|
import {Profile} from '@/store' |
||||||
|
import Taro from "@tarojs/taro"; |
||||||
|
|
||||||
|
const Course = () => { |
||||||
|
const {courseId, id, preview} = getCurrentInstance()?.router?.params as { |
||||||
|
courseId: number, |
||||||
|
id: number, |
||||||
|
preview: string | null |
||||||
|
} |
||||||
|
const [breakpoint, setBreakpoint] = useState<any[]>([]) |
||||||
|
const [show, setShow] = useState(false) |
||||||
|
const [data, setData] = useState<HourPlayData | null>(null) |
||||||
|
|
||||||
|
async function onEnded() { |
||||||
|
try { |
||||||
|
await curriculum.curEnd(courseId, id, data?.duration || 0) |
||||||
|
Taro.showModal({title: "学习完成"}) |
||||||
|
} catch (e) { |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function onBreakpoint(id: number) { |
||||||
|
setBreakpoint(breakpoint.filter(d => d.id != id)) |
||||||
|
setShow(true) |
||||||
|
} |
||||||
|
|
||||||
|
async function getData() { |
||||||
|
const res = await curriculum.hourPlay(courseId, id) |
||||||
|
if (res) { |
||||||
|
setData(res) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
getData() |
||||||
|
}, []) |
||||||
|
|
||||||
|
return ( |
||||||
|
<CustomWrapper> |
||||||
|
<Profile.Provider> |
||||||
|
{data && <HVideo |
||||||
|
duration={data?.duration} |
||||||
|
preview={!!preview} |
||||||
|
src={data?.url || ''} |
||||||
|
onEnded={onEnded} |
||||||
|
breakpoint={breakpoint} |
||||||
|
onBreakpoint={onBreakpoint} |
||||||
|
/>} |
||||||
|
|
||||||
|
|
||||||
|
<view> |
||||||
|
<PageContainer |
||||||
|
show={show} |
||||||
|
position='bottom' |
||||||
|
round |
||||||
|
> |
||||||
|
<ScrollView |
||||||
|
style='height:70vh' |
||||||
|
scrollY |
||||||
|
scrollTop={0} |
||||||
|
scrollWithAnimation |
||||||
|
> |
||||||
|
</ScrollView> |
||||||
|
</PageContainer> |
||||||
|
</view> |
||||||
|
</Profile.Provider> |
||||||
|
</CustomWrapper> |
||||||
|
) |
||||||
|
} |
||||||
|
export default Course |
@ -0,0 +1,3 @@ |
|||||||
|
export default definePageConfig({ |
||||||
|
navigationBarTitleText: '个人中心' |
||||||
|
}) |
@ -0,0 +1,22 @@ |
|||||||
|
.box { |
||||||
|
width: 690rpx; |
||||||
|
height: 412rpx; |
||||||
|
background: #FFF; |
||||||
|
border-radius: 20rpx; |
||||||
|
margin: auto; |
||||||
|
overflow: hidden; |
||||||
|
margin-top: 20px; |
||||||
|
padding: 10px 0; |
||||||
|
} |
||||||
|
|
||||||
|
.button{ |
||||||
|
width: 690rpx; |
||||||
|
line-height: 76rpx; |
||||||
|
background: #45D4A8; |
||||||
|
border-radius: 40rpx; |
||||||
|
color: #fff; |
||||||
|
position: fixed; |
||||||
|
bottom:100px; |
||||||
|
left: 30rpx; |
||||||
|
font-size: 32rpx; |
||||||
|
} |
@ -0,0 +1,80 @@ |
|||||||
|
import {FC, useState} from "react"; |
||||||
|
import {Profile} from '@/store' |
||||||
|
import avatar from "@/static/img/avatar.png" |
||||||
|
import PopPut from "@/components/popPut/popPut"; |
||||||
|
import {Button, CustomWrapper, Input, View} from "@tarojs/components"; |
||||||
|
import Taro from "@tarojs/taro"; |
||||||
|
import {userApi} from "@/api"; |
||||||
|
import styles from './userInfo.module.scss' |
||||||
|
|
||||||
|
|
||||||
|
const List = () => { |
||||||
|
const {empty, user, setUser} = Profile.useContainer() |
||||||
|
const [show, setShow] = useState(false) |
||||||
|
const [name, setName] = useState<string>(user?.name || '') |
||||||
|
|
||||||
|
function unbind() { |
||||||
|
Taro.showModal({ |
||||||
|
title: '解绑微信', |
||||||
|
async success({confirm}) { |
||||||
|
if (confirm) { |
||||||
|
const res = await userApi.unbind(user?.id!) |
||||||
|
if (res) { |
||||||
|
empty() |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
async function putName() { |
||||||
|
if (!name) { |
||||||
|
Taro.showToast({title: "名称不能为空", icon: 'error'}) |
||||||
|
return |
||||||
|
} |
||||||
|
const res = await userApi.putName(user?.id!, name) |
||||||
|
if (res) { |
||||||
|
setUser(res) |
||||||
|
setShow(!show) |
||||||
|
Taro.showToast({title: '修改成功'}) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
return ( |
||||||
|
<> |
||||||
|
<View className={styles.box}> |
||||||
|
<PopPut title='头像' image={avatar} chevron no_border/> |
||||||
|
<PopPut title='手机号' content={user?.phone_number} chevron no_border/> |
||||||
|
<PopPut title='昵称' content={user?.name} isProp show={show} no_border> |
||||||
|
<View className='h-6 pt-4 px-3'> |
||||||
|
<View className='text-center font-weight'>修改昵称{name}</View> |
||||||
|
<Input className='input' |
||||||
|
placeholder='请输入昵称' |
||||||
|
onInput={(event) => setName(event.detail.value)} |
||||||
|
value={name} |
||||||
|
/> |
||||||
|
<View className='text-muted mt-2 font-24'>限制4-20个字符,可由中英文、数字、“_”、“-”组成</View> |
||||||
|
<Button className={styles.button} onClick={putName}>确定</Button> |
||||||
|
</View> |
||||||
|
</PopPut> |
||||||
|
|
||||||
|
<PopPut title='解绑微信' onClick={unbind} no_border/> |
||||||
|
</View> |
||||||
|
<Button className={styles.button} onClick={empty}>退出登录</Button> |
||||||
|
</> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
const userInfo: FC = () => { |
||||||
|
return ( |
||||||
|
<CustomWrapper> |
||||||
|
<Profile.Provider> |
||||||
|
<List/> |
||||||
|
</Profile.Provider> |
||||||
|
</CustomWrapper> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
export default userInfo |
@ -0,0 +1,75 @@ |
|||||||
|
import {FC, useState} from "react"; |
||||||
|
import {View} from "@tarojs/components"; |
||||||
|
import {ManageApi} from "@/api/manage"; |
||||||
|
import {Profile} from '@/store' |
||||||
|
import PopPut from "@/components/popPut/popPut"; |
||||||
|
import Taro from "@tarojs/taro"; |
||||||
|
|
||||||
|
|
||||||
|
interface Props { |
||||||
|
cur_id: number |
||||||
|
name?: string |
||||||
|
} |
||||||
|
|
||||||
|
const Dep: FC<Props> = ({cur_id}: Props) => { |
||||||
|
const [bindDep, setBindDep] = useState<Department[]>([]) |
||||||
|
|
||||||
|
async function getBind() { |
||||||
|
try { |
||||||
|
const res = await ManageApi.bingDep(cur_id) |
||||||
|
setBindDep(res.department) |
||||||
|
} catch (e) { |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function jump(item: Department) { |
||||||
|
Taro.showModal({ |
||||||
|
title: "是否查看" + item.name, |
||||||
|
success({confirm}) { |
||||||
|
confirm && Taro.navigateTo({url: `/pages/manage/depCur/depCur?id=${item.id}`}) |
||||||
|
} |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
Taro.useDidShow(() => { |
||||||
|
getBind() |
||||||
|
}) |
||||||
|
|
||||||
|
return (<> |
||||||
|
{bindDep && <View className='header'> |
||||||
|
<View className='font-weight font-26'>课程已分配部门</View> |
||||||
|
{bindDep && bindDep.map(d => ( |
||||||
|
<PopPut title={d.name} height={40} content={'查看'} onClick={() => jump(d)}/> |
||||||
|
))} |
||||||
|
</View> |
||||||
|
} |
||||||
|
</>) |
||||||
|
} |
||||||
|
|
||||||
|
const StudentRecord: FC<Props> = ({cur_id, name}: Props) => { |
||||||
|
function jump() { |
||||||
|
Taro.navigateTo({url: `/pages/manage/studentRecord/studentRecord?cur_id=${cur_id}&name=${name}`}) |
||||||
|
} |
||||||
|
|
||||||
|
return ( |
||||||
|
<View className='header'> |
||||||
|
<PopPut title={'学员学习记录'} height={40} content={'查看'} onClick={jump}/> |
||||||
|
</View> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
const ContainDeps: FC<Props> = ({cur_id,name}: Props) => { |
||||||
|
const {user} = Profile.useContainer() |
||||||
|
|
||||||
|
return ( |
||||||
|
<> |
||||||
|
{user?.role_type !== 0 ? |
||||||
|
<View> |
||||||
|
<Dep cur_id={cur_id}/> |
||||||
|
<StudentRecord cur_id={cur_id} name={name}/> |
||||||
|
</View> |
||||||
|
: null} |
||||||
|
</> |
||||||
|
) |
||||||
|
} |
||||||
|
export default ContainDeps |
@ -0,0 +1,91 @@ |
|||||||
|
import {FC, useEffect, useState} from "react"; |
||||||
|
import Tabs, {OnChangOpt, TabList} from "@/components/tabs/tabs"; |
||||||
|
import {View} from "@tarojs/components"; |
||||||
|
import {Profile} from '@/store' |
||||||
|
import {CourseDepData} from "@/api"; |
||||||
|
import Collapse from "@/components/collapse/collapse"; |
||||||
|
import Hours from "@/pages/business/videoInfo/components/hours"; |
||||||
|
import Taro from "@tarojs/taro"; |
||||||
|
|
||||||
|
interface Props { |
||||||
|
data: CourseDepData | null |
||||||
|
} |
||||||
|
|
||||||
|
const Catalogue: FC<Props> = ({data}: Props) => { |
||||||
|
const {user} = Profile.useContainer() |
||||||
|
const [current, setCurrent] = useState(1) |
||||||
|
const [tabList, setTabList] = useState<TabList[]>([ |
||||||
|
{title: '介绍', value: 0}, |
||||||
|
{title: '目录', value: 1}, |
||||||
|
{title: '评价', value: 2}, |
||||||
|
]) |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
if (user?.role_type && user?.role_type > 0) { |
||||||
|
setTabList([...tabList, {title: '学员管理', value: 3}]) |
||||||
|
} |
||||||
|
}, []) |
||||||
|
|
||||||
|
function tabChange({tab}: OnChangOpt) { |
||||||
|
setCurrent(tab?.value as number) |
||||||
|
} |
||||||
|
|
||||||
|
function getHors(chapter_id: number): Hour[] | null { |
||||||
|
for (const d of Object.values(data?.hours || {})) { |
||||||
|
if (d[0].chapter_id === chapter_id) { |
||||||
|
return d |
||||||
|
} |
||||||
|
} |
||||||
|
return null |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
function complete(id: number): boolean { |
||||||
|
return !!data?.learn_hour_records[id]?.is_finished |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
function jump(id) { |
||||||
|
Taro.navigateTo({url: `/pages/business/course/course?courseId=${data?.course.id}&id=${id}`}) |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
return ( |
||||||
|
<View className='catalogue'> |
||||||
|
<Tabs tabList={tabList} onChange={tabChange} current={current}/> |
||||||
|
<View className='py-2'> |
||||||
|
{current === 0 && <View className='short_desc'>{data?.course.short_desc}</View>} |
||||||
|
{current === 1 && <View> |
||||||
|
<View className='font-weight'>课程目录</View> |
||||||
|
{data?.chapters.length ? Object.values(data?.chapters || {}).map((d, index) => <View> |
||||||
|
<Collapse title={`${index + 1}.${d.name}`}> |
||||||
|
<> |
||||||
|
{getHors(d.id)?.map((hor, index) => <Hours |
||||||
|
id={hor.id} |
||||||
|
index={index} |
||||||
|
title={hor.title} |
||||||
|
duration={hor.duration} |
||||||
|
complete={complete} |
||||||
|
click={jump} |
||||||
|
/> |
||||||
|
)} |
||||||
|
</> |
||||||
|
</Collapse> |
||||||
|
</View>) |
||||||
|
: data?.hours?.[0].map((hor, index) => <Hours |
||||||
|
id={hor.id} |
||||||
|
index={index} |
||||||
|
title={hor.title} |
||||||
|
duration={hor.duration} |
||||||
|
complete={complete} |
||||||
|
click={jump} |
||||||
|
/> |
||||||
|
)} |
||||||
|
</View>} |
||||||
|
{current === 2 && <View className='text-center'>评论暂未开放</View>} |
||||||
|
</View> |
||||||
|
</View> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
export default Catalogue |
@ -0,0 +1,33 @@ |
|||||||
|
import {FC} from "react"; |
||||||
|
import '../videoInfo.scss' |
||||||
|
import {Image, View} from "@tarojs/components"; |
||||||
|
import playOk from "@/static/img/play-ok.png"; |
||||||
|
import play from "@/static/img/play.png"; |
||||||
|
import {formatMinute} from "@/utils/time"; |
||||||
|
|
||||||
|
interface Props { |
||||||
|
id: number |
||||||
|
index: number |
||||||
|
title: string |
||||||
|
duration: number |
||||||
|
click: (id: number) => void |
||||||
|
complete: (id: number) => boolean |
||||||
|
} |
||||||
|
|
||||||
|
const Hours: FC<Props> = (opt: Props) => { |
||||||
|
return ( |
||||||
|
<> |
||||||
|
<View className={'hor' + ` ${opt.complete(opt.id) ? 'complete' : null}`} |
||||||
|
onClick={() => opt.click(opt.id)} |
||||||
|
> |
||||||
|
<Image src={opt.complete(opt.id) ? playOk : play} mode='aspectFit'/> |
||||||
|
<View> |
||||||
|
<View>{opt.index + 1}.{opt.title}</View> |
||||||
|
<View className='font-26'>时长{formatMinute(opt.duration)}</View> |
||||||
|
</View> |
||||||
|
</View> |
||||||
|
</> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
export default Hours |
@ -0,0 +1,3 @@ |
|||||||
|
export default definePageConfig({ |
||||||
|
navigationBarTitleText: '课程' |
||||||
|
}) |
@ -0,0 +1,50 @@ |
|||||||
|
.content { |
||||||
|
.image { |
||||||
|
width: 100%; |
||||||
|
display: block; |
||||||
|
} |
||||||
|
|
||||||
|
.header { |
||||||
|
margin-bottom: 10px; |
||||||
|
border-radius: 0 0 40rpx 40rpx; |
||||||
|
padding: 30rpx 30rpx; |
||||||
|
background: #fff; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
.catalogue { |
||||||
|
background: #fff; |
||||||
|
border-radius: 40rpx; |
||||||
|
padding: 24rpx; |
||||||
|
margin-top: 20rpx; |
||||||
|
|
||||||
|
.short_desc { |
||||||
|
color: #606563; |
||||||
|
line-height: 1.75; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
.hor { |
||||||
|
padding: 20px 0; |
||||||
|
display: flex; |
||||||
|
|
||||||
|
Image { |
||||||
|
width: 40rpx; |
||||||
|
height: 40rpx; |
||||||
|
margin-top: 6px; |
||||||
|
} |
||||||
|
|
||||||
|
& > View { |
||||||
|
flex: 1; |
||||||
|
margin-left: 20px; |
||||||
|
border-bottom: 1px solid #ddd; |
||||||
|
|
||||||
|
View { |
||||||
|
margin-bottom: 20px; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
.complete { |
||||||
|
color: #45D4A8; |
||||||
|
} |
@ -0,0 +1,49 @@ |
|||||||
|
import {Image, Text, View} from "@tarojs/components"; |
||||||
|
import {FC, useState} from "react"; |
||||||
|
import {getCurrentInstance} from "@tarojs/runtime"; |
||||||
|
import {CourseDepData, curriculum} from "@/api"; |
||||||
|
import './videoInfo.scss' |
||||||
|
import Taro from "@tarojs/taro"; |
||||||
|
import {Profile} from '@/store' |
||||||
|
import Catalogue from "@/pages/business/videoInfo/components/catalogue"; |
||||||
|
|
||||||
|
const VideoInfo: FC = () => { |
||||||
|
const {id, depId} = getCurrentInstance()?.router?.params as { id: number, depId: number | null } |
||||||
|
const [data, setData] = useState<CourseDepData | null>(null) |
||||||
|
|
||||||
|
async function getData() { |
||||||
|
const res = await curriculum.courseDep(id, depId) |
||||||
|
if (res) { |
||||||
|
setData(res) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
Taro.useDidShow(getData) |
||||||
|
|
||||||
|
|
||||||
|
return ( |
||||||
|
<Profile.Provider> |
||||||
|
<View className='content'> |
||||||
|
<Image src={data?.course.thumb || ''} className='image' mode='scaleToFill'/> |
||||||
|
|
||||||
|
{/*<ContainDeps cur_id={id} name={data?.course.title}/>*/} |
||||||
|
|
||||||
|
<View className='header'> |
||||||
|
<View className='flex justify-between text-muted'> |
||||||
|
<Text className='font-34 text-warning'>{data?.is_required ? '必修' : '选秀'}</Text> |
||||||
|
<Text>{data?.course.class_hour}课时</Text> |
||||||
|
</View> |
||||||
|
<View className='font-weight font-40 my-4'>{data?.course.title}</View> |
||||||
|
<View className='text-muted font-26'> |
||||||
|
{/*<Text className='mr-3'>时长:32:10</Text>*/} |
||||||
|
<Text>学习进度{data?.learn_record?.finished_count || 0}/{data?.learn_record?.hour_count || Object.keys(data?.hours || {}).length}</Text> |
||||||
|
</View> |
||||||
|
</View> |
||||||
|
|
||||||
|
<Catalogue data={data}/> |
||||||
|
</View> |
||||||
|
</Profile.Provider> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
export default VideoInfo |
@ -0,0 +1,15 @@ |
|||||||
|
import {FC} from "react"; |
||||||
|
import {Text, View} from "@tarojs/components"; |
||||||
|
import styles from "@/pages/index/index.module.scss"; |
||||||
|
import Icon from "@/components/icon"; |
||||||
|
|
||||||
|
export const Search: FC = () => { |
||||||
|
return ( |
||||||
|
<View className={styles.search}> |
||||||
|
<View> |
||||||
|
<Icon name='search' size={18}/> |
||||||
|
<Text>搜索课程</Text> |
||||||
|
</View> |
||||||
|
</View> |
||||||
|
) |
||||||
|
} |
@ -0,0 +1,52 @@ |
|||||||
|
import {FC, useState} from "react"; |
||||||
|
import Taro from "@tarojs/taro"; |
||||||
|
import {View} from "@tarojs/components"; |
||||||
|
import {CoursesKey, Cur, publicApi} from "@/api/public"; |
||||||
|
import VideoCover from "@/components/videoCover/videoCover"; |
||||||
|
import styles from '../index.module.scss' |
||||||
|
|
||||||
|
interface Props { |
||||||
|
categoryId: CoursesKey |
||||||
|
} |
||||||
|
|
||||||
|
export const VideoList: FC<Props> = ({categoryId}: Props) => { |
||||||
|
const [data, setDta] = useState<Cur[] | null>(null) |
||||||
|
|
||||||
|
async function getData() { |
||||||
|
const res = await publicApi.curs() |
||||||
|
setDta(res) |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
Taro.useDidShow(() => { |
||||||
|
getData() |
||||||
|
}) |
||||||
|
|
||||||
|
function rateOfLearning(id: number, class_hour: number): JSX.Element { |
||||||
|
console.log(id) |
||||||
|
return (<View>{`共${class_hour}节/已学${0}节`}</View>) |
||||||
|
} |
||||||
|
|
||||||
|
return ( |
||||||
|
<> |
||||||
|
{data?.map(d => ( |
||||||
|
<>{ |
||||||
|
d.courses?.[categoryId].length ? <View> |
||||||
|
<View className='font-weight'>{d.name}</View> |
||||||
|
<View className={'py-2 flex justify-between flex-wrap ' + styles.videoListBox}> |
||||||
|
{d.courses[categoryId].map(d => ( |
||||||
|
<VideoCover |
||||||
|
thumb={d.thumb} |
||||||
|
title={d.title} |
||||||
|
id={d.id} |
||||||
|
depId={d.id} |
||||||
|
content={rateOfLearning(d.id, d.class_hour)} |
||||||
|
/> |
||||||
|
))} |
||||||
|
</View> |
||||||
|
</View> : null |
||||||
|
}</> |
||||||
|
))} |
||||||
|
</> |
||||||
|
) |
||||||
|
} |
@ -0,0 +1,3 @@ |
|||||||
|
export default definePageConfig({ |
||||||
|
navigationStyle: 'custom' |
||||||
|
}) |
@ -0,0 +1,43 @@ |
|||||||
|
.content { |
||||||
|
position: relative; |
||||||
|
min-height: 100vh; |
||||||
|
padding: 0 20px; |
||||||
|
|
||||||
|
&:after { |
||||||
|
min-height: 100vh; |
||||||
|
position: absolute; |
||||||
|
top: 0; |
||||||
|
left: -10%; |
||||||
|
width: 120%; |
||||||
|
content: ''; |
||||||
|
display: block; |
||||||
|
background: linear-gradient(40deg, #fff 50rpx, #caf0e2, #92ecc5) no-repeat; |
||||||
|
min-height: 100vh; |
||||||
|
background-size: 100% 600rpx; |
||||||
|
filter: blur(50px); |
||||||
|
z-index: -1; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
.search { |
||||||
|
View { |
||||||
|
width: 710rpx; |
||||||
|
margin: 34rpx 0 0; |
||||||
|
background: #fff; |
||||||
|
border-radius: 100px; |
||||||
|
line-height: 68rpx; |
||||||
|
color: #bbb; |
||||||
|
font-size: 28rpx; |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
justify-content: center; |
||||||
|
|
||||||
|
Text { |
||||||
|
padding-right: 20px; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
.videoListBox { |
||||||
|
border-radius: 20px; |
||||||
|
} |
@ -0,0 +1,40 @@ |
|||||||
|
import {FC, useState} from "react"; |
||||||
|
import {View} from "@tarojs/components"; |
||||||
|
import Taro from "@tarojs/taro"; |
||||||
|
import styles from './index.module.scss' |
||||||
|
import Tabs, {OnChangOpt, TabList} from "@/components/tabs/tabs"; |
||||||
|
import {Profile} from '@/store' |
||||||
|
import {Search} from "@/pages/index/components/search"; |
||||||
|
import {VideoList} from "@/pages/index/components/videoList"; |
||||||
|
import {CoursesKey} from "@/api/public"; |
||||||
|
|
||||||
|
|
||||||
|
const Index: FC = () => { |
||||||
|
const category: TabList[] = [ |
||||||
|
{title: "必修", value: 'is_required'}, |
||||||
|
{title: "选修", value: 'is_not_required'}, |
||||||
|
{title: "已完成", value: 'is_finished'}, |
||||||
|
{title: "未完成", value: 'is_not_finished'}, |
||||||
|
] |
||||||
|
const [categoryId, setCategoryId] = useState<CoursesKey>('is_required') |
||||||
|
|
||||||
|
function tabChange(data: OnChangOpt) { |
||||||
|
setCategoryId(data.tab?.value as CoursesKey) |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
const globalData = Taro.getApp().globalData |
||||||
|
return ( |
||||||
|
<Profile.Provider> |
||||||
|
<View className={styles.content} style={`paddingTop:${globalData.statusBarHeight}px`}> |
||||||
|
<View className='text-center font-weight font-34 mt-3'>医学道</View> |
||||||
|
<Search/> |
||||||
|
<Tabs tabList={category} onChange={tabChange} current={categoryId}/> |
||||||
|
<VideoList categoryId={categoryId}/> |
||||||
|
<View className='text-center text-muted mt-3'>- 暂无更多 -</View> |
||||||
|
</View> |
||||||
|
</Profile.Provider> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
export default Index |
@ -0,0 +1,3 @@ |
|||||||
|
export default definePageConfig({ |
||||||
|
navigationStyle: 'custom', |
||||||
|
}) |
@ -0,0 +1,66 @@ |
|||||||
|
.container { |
||||||
|
position: relative; |
||||||
|
} |
||||||
|
|
||||||
|
.navbar, |
||||||
|
.brand { |
||||||
|
display: flex; |
||||||
|
flex-direction: column; |
||||||
|
justify-content: center; |
||||||
|
align-items: center; |
||||||
|
} |
||||||
|
|
||||||
|
.navbar { |
||||||
|
position: relative; |
||||||
|
line-height: 1; |
||||||
|
font-size: 28px; |
||||||
|
} |
||||||
|
|
||||||
|
.brand { |
||||||
|
width: 140px; |
||||||
|
height: 140px; |
||||||
|
background: #fff; |
||||||
|
border-radius: 20px; |
||||||
|
margin: 250px auto 145px; |
||||||
|
|
||||||
|
image { |
||||||
|
width: 100px; |
||||||
|
height: 100px; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
.loginTips { |
||||||
|
margin: 24px; |
||||||
|
text-align: center; |
||||||
|
} |
||||||
|
|
||||||
|
.submit { |
||||||
|
display: flex; |
||||||
|
justify-content: center; |
||||||
|
align-items: center; |
||||||
|
gap: 12px; |
||||||
|
background: red; |
||||||
|
color: #fff; |
||||||
|
border-radius: 20px; |
||||||
|
margin: 0 56px; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
.errorTips { |
||||||
|
position: absolute; |
||||||
|
top: 100%; |
||||||
|
left: 24px; |
||||||
|
right: 24px; |
||||||
|
background: red; |
||||||
|
color: white; |
||||||
|
padding: 24px; |
||||||
|
border-radius: 20px; |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
gap: 12px; |
||||||
|
} |
||||||
|
|
||||||
|
.bing { |
||||||
|
height: 50vh; |
||||||
|
padding: 50px 30px 0; |
||||||
|
} |
@ -0,0 +1,157 @@ |
|||||||
|
import {FC, useEffect, useRef, useState} from "react"; |
||||||
|
import {Profile} from "@/store"; |
||||||
|
import {Button, CustomWrapper, Form, Image, Input, PageContainer, Text, View} from "@tarojs/components"; |
||||||
|
import Taro from "@tarojs/taro"; |
||||||
|
import styles from './login.module.scss' |
||||||
|
import Loading from "@/components/loading"; |
||||||
|
import Icon from "@/components/icon"; |
||||||
|
import {userApi} from "@/api"; |
||||||
|
import {regexTel} from "@/utils/regu"; |
||||||
|
|
||||||
|
interface BingProps { |
||||||
|
code: string |
||||||
|
catch_key: string |
||||||
|
} |
||||||
|
|
||||||
|
const Bing: FC<BingProps> = ({code, catch_key}: BingProps) => { |
||||||
|
const [useCode, setUseCode] = useState<string>(code) |
||||||
|
const form = useRef<HTMLFormElement | null>(null) |
||||||
|
const [loading, setLoading] = useState(false) |
||||||
|
const {setUser, setToken, setCompany} = Profile.useContainer() |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
form.current?.reset?.() |
||||||
|
setUseCode(code) |
||||||
|
}, [code]) |
||||||
|
|
||||||
|
async function refreshCode() { |
||||||
|
try { |
||||||
|
await userApi.code(catch_key) |
||||||
|
|
||||||
|
} catch (e) { |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
async function Submit(data) { |
||||||
|
Taro.showLoading() |
||||||
|
setLoading(true) |
||||||
|
const value = data.target.value |
||||||
|
if (!regexTel.exec(value.phone_number)) { |
||||||
|
Taro.showToast({title: '手机号错误', icon: 'error'}) |
||||||
|
setLoading(false) |
||||||
|
return |
||||||
|
} |
||||||
|
try { |
||||||
|
const res = await userApi.checkout({...value, catch_key}) |
||||||
|
if (res) { |
||||||
|
setCompany(res.company) |
||||||
|
setUser(res.user) |
||||||
|
setToken(res.token) |
||||||
|
Taro.switchTab({url: '/pages/index/index'}) |
||||||
|
} |
||||||
|
} catch (e) { |
||||||
|
} |
||||||
|
Taro.hideLoading() |
||||||
|
setLoading(false) |
||||||
|
} |
||||||
|
|
||||||
|
return ( |
||||||
|
<View className='h-5 pt-6 px-3'> |
||||||
|
<Form className='form' onSubmit={Submit} ref={form}> |
||||||
|
<View className='item'> |
||||||
|
<View className='label'>手机号</View> |
||||||
|
<Input name='phone_number' placeholder={'请输入手机号'}/> |
||||||
|
</View> |
||||||
|
|
||||||
|
<View className='item'> |
||||||
|
<View className='label'>验证码</View> |
||||||
|
<View className='flex align-center'> |
||||||
|
<Input name='code' className='flex-1' placeholder={'请输入验证码'}/> |
||||||
|
<Image className='w-2 ml-1' style='height:28px' src={useCode} onClick={refreshCode}/> |
||||||
|
</View> |
||||||
|
</View> |
||||||
|
|
||||||
|
<Button className={styles.submit} style='margin:30px 0' formType='submit' disabled={loading}>提交</Button> |
||||||
|
</Form> |
||||||
|
</View> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
const Login = () => { |
||||||
|
const {statusBarHeight = 0} = Taro.getSystemInfoSync() |
||||||
|
const bbc = Taro.getMenuButtonBoundingClientRect(); |
||||||
|
const navHeight = bbc.bottom + (bbc.top - statusBarHeight) - statusBarHeight |
||||||
|
|
||||||
|
|
||||||
|
const [isLoading, setLoading] = useState(false) |
||||||
|
const [error, setError] = useState<string | null>(null) |
||||||
|
const [validateCode, setCode] = useState<string | null>(null) |
||||||
|
const [catch_key, setCatch_key] = useState<string | null>(null) |
||||||
|
const {setUser, setToken, setCompany} = Profile.useContainer() |
||||||
|
|
||||||
|
|
||||||
|
function login() { |
||||||
|
if (isLoading) return; |
||||||
|
setLoading(true) |
||||||
|
Taro.login({ |
||||||
|
success: async (res) => { |
||||||
|
try { |
||||||
|
const {code, catch_key, user, token, company} = await userApi.login(res.code) |
||||||
|
if (!code) { |
||||||
|
setUser(user) |
||||||
|
setToken(token) |
||||||
|
setCompany(company) |
||||||
|
Taro.switchTab({url: '/pages/index/index'}) |
||||||
|
return |
||||||
|
} |
||||||
|
setCatch_key(catch_key) |
||||||
|
setCode(code.image) |
||||||
|
} catch (e) { |
||||||
|
} |
||||||
|
setLoading(false) |
||||||
|
}, |
||||||
|
fail: (res) => { |
||||||
|
setError(res.errMsg) |
||||||
|
setLoading(false) |
||||||
|
}, |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
return ( |
||||||
|
<View className={styles.container}> |
||||||
|
<PageContainer show={!!validateCode} position='bottom' round onBeforeLeave={() => setCode(null)}> |
||||||
|
{validateCode && <Bing code={validateCode!} catch_key={catch_key!}/>} |
||||||
|
</PageContainer> |
||||||
|
<View className={styles.navbar} style={`height:${navHeight}px;margin-top:${statusBarHeight}px`}> |
||||||
|
<Text>微信授权登录</Text> |
||||||
|
{error ? <View className={styles.errorTips}> |
||||||
|
<View style={{flex: 1}}>{error}</View> |
||||||
|
<View> |
||||||
|
<Icon name={'close'} onClick={() => setError(null)}/> |
||||||
|
</View> |
||||||
|
</View> : null} |
||||||
|
</View> |
||||||
|
<View className={styles.brand}> |
||||||
|
<Image mode={'scaleToFill'} src="https://admin.playedu.xyz/favicon.ico"/> |
||||||
|
</View> |
||||||
|
<View className={styles.loginTips}> |
||||||
|
<Text>请完成微信授权以继续使用!</Text> |
||||||
|
</View> |
||||||
|
<Button className={styles.submit} onClick={login} disabled={isLoading}> |
||||||
|
{isLoading ? <Loading/> : null} |
||||||
|
<Text>微信授权登录</Text> |
||||||
|
</Button> |
||||||
|
</View> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
const Index: FC = () => { |
||||||
|
return ( |
||||||
|
<CustomWrapper> |
||||||
|
<Profile.Provider> |
||||||
|
<Login/> |
||||||
|
</Profile.Provider> |
||||||
|
</CustomWrapper> |
||||||
|
) |
||||||
|
} |
||||||
|
export default Index |
@ -0,0 +1,3 @@ |
|||||||
|
export default definePageConfig({ |
||||||
|
navigationBarTitleText: '添加课程', |
||||||
|
}) |
@ -0,0 +1,137 @@ |
|||||||
|
import {CustomWrapper, View} from "@tarojs/components"; |
||||||
|
import {getCurrentInstance} from "@tarojs/runtime"; |
||||||
|
import {ManageApi} from "@/api/manage"; |
||||||
|
import React, {FC, useEffect, useState} from "react"; |
||||||
|
import Tabs, {TabList} from "@/components/tabs/tabs"; |
||||||
|
import {Category, publicApi} from "@/api/public"; |
||||||
|
import Taro from "@tarojs/taro"; |
||||||
|
import VideoCover from "@/components/videoCover/videoCover"; |
||||||
|
|
||||||
|
interface AddProps { |
||||||
|
cur_id: number, |
||||||
|
name: string, |
||||||
|
index: number |
||||||
|
} |
||||||
|
|
||||||
|
const AddCur = () => { |
||||||
|
const {id} = getCurrentInstance()?.router?.params as { id: string } |
||||||
|
const [category, setCategory] = useState<TabList[]>([]) |
||||||
|
const [categoryId, setCategoryId] = useState<number>(0) |
||||||
|
const [dataMap, setDataMap] = useState<Map<number, Curriculum[]>>(new Map()) |
||||||
|
|
||||||
|
|
||||||
|
async function getCategory() { |
||||||
|
try { |
||||||
|
const data: (TabList)[] = [] |
||||||
|
const {categories} = await publicApi.category() |
||||||
|
Object.values(categories).map(d => { |
||||||
|
data.push(...d.map<TabList<Category>>(c => ({title: c.name, value: c.id}))) |
||||||
|
}) |
||||||
|
setCategory(data) |
||||||
|
setCategoryId(data[0].value as number) |
||||||
|
} catch (e) { |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
async function getData() { |
||||||
|
if (categoryId !== null) { |
||||||
|
const res = await ManageApi.optionalCur(Number(id), categoryId) |
||||||
|
const map = new Map(dataMap) |
||||||
|
const data = map.get(categoryId) |
||||||
|
if (!data) { |
||||||
|
map.delete(categoryId) |
||||||
|
map.set(categoryId, res) |
||||||
|
setDataMap(map) |
||||||
|
} else { |
||||||
|
res.forEach(d => { |
||||||
|
const index = data.findIndex(c => c.id === d.id) |
||||||
|
if (index === -1) { |
||||||
|
data.push(d) |
||||||
|
} else { |
||||||
|
data.splice(index, 1, d) |
||||||
|
} |
||||||
|
}) |
||||||
|
map.delete(categoryId) |
||||||
|
map.set(categoryId, data) |
||||||
|
setDataMap(map) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
const Add: FC<AddProps> = ({cur_id, name, index}: AddProps) => { |
||||||
|
function addCur() { |
||||||
|
function required() { |
||||||
|
Taro.showModal({ |
||||||
|
title: '课程是否为必修', |
||||||
|
cancelText: '选修', |
||||||
|
confirmText: '必修', |
||||||
|
async success({confirm}) { |
||||||
|
try { |
||||||
|
const is_required = confirm ? 1 : 0 |
||||||
|
Taro.showLoading() |
||||||
|
await ManageApi.addCur({course_id: [cur_id], dep_id: [Number(id)], is_required}) |
||||||
|
const map = new Map(dataMap) |
||||||
|
const data = map.get(categoryId!) || [] |
||||||
|
if (data) { |
||||||
|
data.splice(index, 1) |
||||||
|
} |
||||||
|
map.delete(categoryId!) |
||||||
|
map.set(categoryId!, data) |
||||||
|
setDataMap(map) |
||||||
|
Taro.showToast({title: "添加成功"}) |
||||||
|
} catch (e) { |
||||||
|
} |
||||||
|
Taro.hideLoading() |
||||||
|
} |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
Taro.showModal({ |
||||||
|
title: '确定添加' + name, |
||||||
|
success({confirm}) { |
||||||
|
confirm && required() |
||||||
|
} |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
return ( |
||||||
|
<View className='text-center mt-1' onClick={addCur}>添加课程</View> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
getCategory().then() |
||||||
|
}, []) |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
getData().then() |
||||||
|
}, [categoryId]) |
||||||
|
return ( |
||||||
|
<CustomWrapper> |
||||||
|
<View className='bg-white'> |
||||||
|
<Tabs tabList={category} onChange={(data) => setCategoryId(data.tab?.value as number)} current={categoryId}/> |
||||||
|
</View> |
||||||
|
{ |
||||||
|
(categoryId && dataMap.get(categoryId)?.length) ? |
||||||
|
<View className='bg-white mt-2 py-2 flex flex-wrap'> |
||||||
|
{dataMap.get(categoryId)?.map((d, index) => ( |
||||||
|
<VideoCover |
||||||
|
key={d.id} |
||||||
|
thumb={d.thumb} |
||||||
|
title={<Add cur_id={d.id} name={d.title} index={index}/>} |
||||||
|
id={d.id} |
||||||
|
depId={Number(id)} |
||||||
|
content={d.title} |
||||||
|
/> |
||||||
|
))} |
||||||
|
</View> |
||||||
|
: null |
||||||
|
} |
||||||
|
|
||||||
|
<View className='text-center mt-3'>- 暂无更多 -</View> |
||||||
|
</CustomWrapper> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
export default AddCur |
@ -0,0 +1,3 @@ |
|||||||
|
export default definePageConfig({ |
||||||
|
navigationBarTitleText: '请填写学员信息', |
||||||
|
}) |
@ -0,0 +1,19 @@ |
|||||||
|
.dep { |
||||||
|
background: #ddd; |
||||||
|
padding: 5px 15px; |
||||||
|
border-radius: 3px; |
||||||
|
} |
||||||
|
|
||||||
|
.depSelected { |
||||||
|
background: #9ee0a3; |
||||||
|
color: #3f6942; |
||||||
|
} |
||||||
|
|
||||||
|
.add { |
||||||
|
border-radius: 10px; |
||||||
|
background: linear-gradient(to right, #8284f7, #5a93f9); |
||||||
|
color: #fff; |
||||||
|
position: fixed; |
||||||
|
width: 710rpx; |
||||||
|
bottom: 20px; |
||||||
|
} |
@ -0,0 +1,170 @@ |
|||||||
|
import {Button, CustomWrapper, Form, Input, PageContainer, View} from "@tarojs/components"; |
||||||
|
import {FC, useEffect, useState} from "react"; |
||||||
|
import {ManageApi, Student} from "@/api/manage"; |
||||||
|
import Icon from "@/components/icon"; |
||||||
|
import Taro from "@tarojs/taro"; |
||||||
|
import {curriculum} from "@/api"; |
||||||
|
import './addStudent.scss' |
||||||
|
import {getCurrentInstance} from "@tarojs/runtime"; |
||||||
|
import {Profile} from '@/store' |
||||||
|
|
||||||
|
interface Department { |
||||||
|
id: number |
||||||
|
title: string |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
const AddStudent = () => { |
||||||
|
const [userInfo, setUerInfo] = useState<Student | null>(null) |
||||||
|
const [department, setDepartment] = useState<Department[]>([]) |
||||||
|
const [show, setShow] = useState(false) |
||||||
|
const [depIds, setDepIds] = useState<number[]>([]) |
||||||
|
const params = getCurrentInstance()?.router?.params as { id?: number } |
||||||
|
const [disable, setDisable] = useState(false) |
||||||
|
const {company} = Profile.useContainer() |
||||||
|
|
||||||
|
|
||||||
|
async function getDepartment() { |
||||||
|
Taro.showLoading() |
||||||
|
const res = await curriculum.use() |
||||||
|
if (res) { |
||||||
|
setDepartment(res.map(d => ({title: d.name, id: d.id}))) |
||||||
|
} |
||||||
|
Taro.hideLoading() |
||||||
|
} |
||||||
|
|
||||||
|
async function submit(event) { |
||||||
|
const value: Student = event.detail.value |
||||||
|
for (const [key, value1] of Object.entries(value)) { |
||||||
|
if (!value1 && !['id_card', 'password'].includes(key)) { |
||||||
|
Taro.showToast({title: "请填写完整", icon: 'error'}) |
||||||
|
return |
||||||
|
} |
||||||
|
} |
||||||
|
Taro.showLoading() |
||||||
|
setDisable(true) |
||||||
|
try { |
||||||
|
if (params.id) { |
||||||
|
await ManageApi.putUser(params.id, {...value, dep_ids: depIds, company_id: company?.id || 0}) |
||||||
|
} else { |
||||||
|
await ManageApi.addUser({...value, dep_ids: depIds, company_id: company?.id || 0}) |
||||||
|
} |
||||||
|
Taro.hideLoading() |
||||||
|
Taro.showToast({title: "添加成功", icon: 'success'}) |
||||||
|
setTimeout(() => { |
||||||
|
Taro.navigateBack() |
||||||
|
}, 500) |
||||||
|
} catch (e) { |
||||||
|
console.log(e) |
||||||
|
} |
||||||
|
Taro.hideLoading() |
||||||
|
setDisable(true) |
||||||
|
} |
||||||
|
|
||||||
|
function changeDepIds(item: Department) { |
||||||
|
const ids = JSON.parse(JSON.stringify(depIds)) |
||||||
|
const index = depIds.indexOf(item.id) |
||||||
|
if (index === -1) { |
||||||
|
ids.push(item.id) |
||||||
|
} else { |
||||||
|
ids.splice(index, 1) |
||||||
|
} |
||||||
|
setDepIds(ids) |
||||||
|
} |
||||||
|
|
||||||
|
function formatDep() { |
||||||
|
const selected = department.filter(d => depIds.includes(d.id)).map(d => d.title) |
||||||
|
const top4 = selected.splice(0, 3).join('、') |
||||||
|
return top4 + (selected.length ? "+" + selected.length : '') |
||||||
|
} |
||||||
|
|
||||||
|
async function getUserInfo() { |
||||||
|
const res = await ManageApi.userInfo(params.id!) |
||||||
|
setUerInfo(res.user) |
||||||
|
setDepIds(res.dep_ids) |
||||||
|
} |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
getDepartment() |
||||||
|
if (params.id) { |
||||||
|
getUserInfo() |
||||||
|
} |
||||||
|
}, [params.id]) |
||||||
|
|
||||||
|
return ( |
||||||
|
<View className='bg-white px-2'> |
||||||
|
<Form className='form' onSubmit={submit}> |
||||||
|
<View className='item'> |
||||||
|
<View>姓名</View> |
||||||
|
<Input placeholder='请输入学员姓名' name='name' value={userInfo?.name} |
||||||
|
onInput={(event) => setUerInfo({...userInfo, name: event.detail.value} as Student)}/> |
||||||
|
</View> |
||||||
|
|
||||||
|
|
||||||
|
<View className='item'> |
||||||
|
<View>手机号</View> |
||||||
|
<Input placeholder='请输入手机号' name='phone_number' value={userInfo?.phone_number} |
||||||
|
onInput={(event) => setUerInfo({...userInfo, phone_number: event.detail.value} as Student)}/> |
||||||
|
</View> |
||||||
|
|
||||||
|
<View className='item'> |
||||||
|
<View>登录邮箱</View> |
||||||
|
<Input placeholder='请输入登录邮箱' name='email' value={userInfo?.email} |
||||||
|
onInput={(event) => setUerInfo({...userInfo, email: event.detail.value} as Student)}/> |
||||||
|
</View> |
||||||
|
|
||||||
|
<View className='item'> |
||||||
|
<View>登录密码</View> |
||||||
|
<Input password placeholder='请输入登录密码' name='password' value={userInfo?.password} |
||||||
|
onInput={(event) => setUerInfo({...userInfo, password: event.detail.value} as Student)}/> |
||||||
|
</View> |
||||||
|
|
||||||
|
<View className='item'> |
||||||
|
<View>所属部门</View> |
||||||
|
<View className='flex align-center' onClick={() => setShow(true)}> |
||||||
|
<View> |
||||||
|
{ |
||||||
|
depIds.length ? formatDep() : '请选择' |
||||||
|
} |
||||||
|
</View> |
||||||
|
<Icon name='chevron-right'/> |
||||||
|
</View> |
||||||
|
</View> |
||||||
|
|
||||||
|
<View className='item'> |
||||||
|
<View>身份证号</View> |
||||||
|
<Input password placeholder='请输入身份证号' name='id_card' value={userInfo?.id_card} |
||||||
|
onInput={(event) => setUerInfo({...userInfo, id_card: event.detail.value} as Student)}/> |
||||||
|
</View> |
||||||
|
|
||||||
|
<Button className='add' formType='submit' disabled={disable}>保存</Button> |
||||||
|
</Form> |
||||||
|
|
||||||
|
<PageContainer show={show} round> |
||||||
|
<View className='px-2 pt-1' style='text-align:right' onClick={() => setShow(false)}>确定</View> |
||||||
|
<View className='h-4 p-2 flex flex-wrap align-start'> |
||||||
|
{department?.map(item => { |
||||||
|
return ( |
||||||
|
<View className={'mx-2 dep ' + (depIds.includes(item.id) ? ' depSelected' : '')} |
||||||
|
onClick={() => changeDepIds(item)}> |
||||||
|
{item.title} |
||||||
|
</View> |
||||||
|
) |
||||||
|
})} |
||||||
|
</View> |
||||||
|
</PageContainer> |
||||||
|
</View> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
const AddPage: FC = () => { |
||||||
|
return ( |
||||||
|
<CustomWrapper> |
||||||
|
<Profile.Provider> |
||||||
|
<AddStudent/> |
||||||
|
</Profile.Provider> |
||||||
|
</CustomWrapper> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
export default AddPage |
@ -0,0 +1,3 @@ |
|||||||
|
export default definePageConfig({ |
||||||
|
navigationBarTitleText: '学员', |
||||||
|
}) |
@ -0,0 +1,11 @@ |
|||||||
|
.college { |
||||||
|
background: #ffffff; |
||||||
|
width: 100%; |
||||||
|
box-sizing: border-box; |
||||||
|
padding: 20px; |
||||||
|
border-bottom: 1px solid #ddd; |
||||||
|
|
||||||
|
View { |
||||||
|
padding-bottom: 10px; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,39 @@ |
|||||||
|
import {getCurrentInstance} from "@tarojs/runtime"; |
||||||
|
import {curriculum, RecordData} from "@/api"; |
||||||
|
import {useEffect, useState} from "react"; |
||||||
|
import {View, Progress, CustomWrapper} from "@tarojs/components"; |
||||||
|
import './college.scss' |
||||||
|
import Taro from "@tarojs/taro"; |
||||||
|
|
||||||
|
const College = () => { |
||||||
|
const {id, name} = getCurrentInstance()?.router?.params as any |
||||||
|
const [data, setData] = useState<RecordData[]>([]) |
||||||
|
|
||||||
|
const getData = () => { |
||||||
|
curriculum.record(id!).then(res => { |
||||||
|
setData(res) |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
Taro.setNavigationBarTitle({title:name}) |
||||||
|
getData() |
||||||
|
}, []) |
||||||
|
|
||||||
|
return ( |
||||||
|
<CustomWrapper> |
||||||
|
{ |
||||||
|
data.map(d => { |
||||||
|
return ( |
||||||
|
<View className='college'> |
||||||
|
<View>{d.key}</View> |
||||||
|
<Progress percent={d.value} activeColor='red' active showInfo/> |
||||||
|
</View> |
||||||
|
) |
||||||
|
}) |
||||||
|
} |
||||||
|
<View className='text-center py-1'>暂无更多数据</View> |
||||||
|
</CustomWrapper> |
||||||
|
) |
||||||
|
} |
||||||
|
export default College |
@ -0,0 +1,3 @@ |
|||||||
|
export default definePageConfig({ |
||||||
|
navigationBarTitleText: '课程管理', |
||||||
|
}) |
@ -0,0 +1,10 @@ |
|||||||
|
import {FC} from "react"; |
||||||
|
import {View} from "@tarojs/components"; |
||||||
|
|
||||||
|
const CurAdmin: FC = () => { |
||||||
|
return ( |
||||||
|
<View>sd</View> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
export default CurAdmin |
@ -0,0 +1,3 @@ |
|||||||
|
export default definePageConfig({ |
||||||
|
navigationBarTitleText: '所有课程', |
||||||
|
}) |
@ -0,0 +1,11 @@ |
|||||||
|
.curBuy { |
||||||
|
background: #c94f4f; |
||||||
|
color: #fff; |
||||||
|
margin: auto; |
||||||
|
width: 250px; |
||||||
|
line-height: 70px; |
||||||
|
border-radius: 70px; |
||||||
|
text-align: center; |
||||||
|
font-size: 30rpx; |
||||||
|
font-weight: bold; |
||||||
|
} |
@ -0,0 +1,66 @@ |
|||||||
|
import {CustomWrapper, View} from "@tarojs/components"; |
||||||
|
import {FC, useEffect, useState} from "react"; |
||||||
|
import VideoCover from "@/components/videoCover/videoCover"; |
||||||
|
import styles from './curriculum.module.scss' |
||||||
|
import {ManageApi} from "@/api/manage"; |
||||||
|
import Taro from "@tarojs/taro"; |
||||||
|
|
||||||
|
|
||||||
|
const Curriculum = () => { |
||||||
|
const [data, setDta] = useState<Curriculum[]>([]) |
||||||
|
|
||||||
|
async function getData() { |
||||||
|
try { |
||||||
|
const res = await ManageApi.buyAll() |
||||||
|
setDta(res) |
||||||
|
} catch (e) { |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
const Add: FC<{ id: number }> = ({id}: { id: number }) => { |
||||||
|
function buy() { |
||||||
|
Taro.showModal({ |
||||||
|
title: '是否购买', |
||||||
|
async success({confirm}) { |
||||||
|
if (confirm) { |
||||||
|
try { |
||||||
|
await ManageApi.buy([id]) |
||||||
|
Taro.showToast({title: "购买成功"}) |
||||||
|
await getData() |
||||||
|
} catch (e) { |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
return ( |
||||||
|
<View className={styles.curBuy} onClick={buy}>购买</View> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
getData() |
||||||
|
}, []) |
||||||
|
|
||||||
|
return ( |
||||||
|
<CustomWrapper> |
||||||
|
{data.length ? |
||||||
|
<View className='bg-white flex flex-wrap'> |
||||||
|
{data.map(d => (<VideoCover |
||||||
|
marker='限时免费' |
||||||
|
thumb={d.thumb} |
||||||
|
title={<Add id={d.id}/>} |
||||||
|
id={d.id} |
||||||
|
depId={null} |
||||||
|
content={d.title}/> |
||||||
|
))} |
||||||
|
</View> |
||||||
|
: null |
||||||
|
} |
||||||
|
<View className='text-center mt-3'>- 暂无更多数据 -</View> |
||||||
|
</CustomWrapper> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
export default Curriculum |
@ -0,0 +1,3 @@ |
|||||||
|
export default definePageConfig({ |
||||||
|
navigationBarTitleText: '部门管理', |
||||||
|
}) |
@ -0,0 +1,158 @@ |
|||||||
|
import {FC, useEffect, useState} from "react"; |
||||||
|
import {AddDepProps, ManageApi} from "@/api/manage"; |
||||||
|
import '../studentAdmin/student.scss' |
||||||
|
import {Button, Text, View, PageContainer, Input, Form, CustomWrapper} from "@tarojs/components"; |
||||||
|
import Taro from "@tarojs/taro"; |
||||||
|
import {Profile} from '@/store' |
||||||
|
|
||||||
|
interface ChangeDataProps { |
||||||
|
putCompany: Manage | null |
||||||
|
getDeps: () => Promise<void> |
||||||
|
} |
||||||
|
|
||||||
|
const ChangeData: FC<ChangeDataProps> = ({putCompany, getDeps}: ChangeDataProps) => { |
||||||
|
const {company} = Profile.useContainer() |
||||||
|
const [name, setName] = useState<string>('') |
||||||
|
const [sort, setSort] = useState<number>(putCompany?.sort || 0) |
||||||
|
const [disable, setDisable] = useState(false) |
||||||
|
const company_id = putCompany?.company_id || company?.id || 0 |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
if (putCompany) { |
||||||
|
setName(putCompany.name) |
||||||
|
} |
||||||
|
}, []) |
||||||
|
|
||||||
|
async function submit() { |
||||||
|
if (!name) { |
||||||
|
Taro.showToast({title: "请认真填写", icon: "error"}) |
||||||
|
return |
||||||
|
} |
||||||
|
setDisable(true) |
||||||
|
Taro.showLoading() |
||||||
|
try { |
||||||
|
const data: AddDepProps = { |
||||||
|
id: putCompany?.id || null, |
||||||
|
name, |
||||||
|
parent_id: putCompany?.parent_id || 0, |
||||||
|
company_id: company_id, |
||||||
|
sort: sort |
||||||
|
} |
||||||
|
if (putCompany) { |
||||||
|
await ManageApi.putDep(data) |
||||||
|
} else { |
||||||
|
await ManageApi.addDep(data) |
||||||
|
} |
||||||
|
setTimeout(() => Taro.showToast({title: '操作成功'})) |
||||||
|
await getDeps() |
||||||
|
} catch (e) { |
||||||
|
} |
||||||
|
Taro.hideLoading() |
||||||
|
setDisable(false) |
||||||
|
} |
||||||
|
|
||||||
|
return ( |
||||||
|
<View className='p-2'> |
||||||
|
<View className='mt-2 text-center font-weight font-34'>添加部门</View> |
||||||
|
<Form className='form mt-3'> |
||||||
|
<View className='item'> |
||||||
|
<View>名称:</View> |
||||||
|
<Input placeholder='请输入部门名称' value={name} onInput={(event) => setName(event.detail.value)}/> |
||||||
|
</View> |
||||||
|
<View className='item'> |
||||||
|
<View>排序:</View> |
||||||
|
<Input |
||||||
|
type="number" |
||||||
|
placeholder='请输入部门名称' |
||||||
|
value={String(sort)} |
||||||
|
onInput={(event) => setSort(Number(event.detail.value))}/> |
||||||
|
</View> |
||||||
|
|
||||||
|
<Button className='mt-3' formType='submit' onClick={submit} disabled={disable}>保存</Button> |
||||||
|
</Form> |
||||||
|
</View> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
const DepAdmin: FC = () => { |
||||||
|
const [data, setData] = useState<Manage[]>([]) |
||||||
|
const [show, setShow] = useState(false) |
||||||
|
const [putCompany, setPutCompany] = useState<Manage | null>(null) |
||||||
|
|
||||||
|
async function getData() { |
||||||
|
show && setShow(false) |
||||||
|
const res = await ManageApi.depList() |
||||||
|
if (res) { |
||||||
|
const formatData: Manage[] = [] |
||||||
|
Object.values(res)?.forEach(d => { |
||||||
|
formatData.push(...d) |
||||||
|
}) |
||||||
|
setData(formatData) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function showPop(company: Manage | null) { |
||||||
|
setPutCompany(company) |
||||||
|
setShow(true) |
||||||
|
} |
||||||
|
|
||||||
|
function del(name: string, id: number) { |
||||||
|
Taro.showModal({ |
||||||
|
title: name + '删除后将不可恢复', |
||||||
|
async success({confirm}) { |
||||||
|
if (confirm) { |
||||||
|
try { |
||||||
|
Taro.showLoading() |
||||||
|
await ManageApi.delDep(id) |
||||||
|
Taro.hideLoading() |
||||||
|
Taro.showToast({title: "删除成功"}) |
||||||
|
await getData() |
||||||
|
} catch (e) { |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
function jumpCur(id: number) { |
||||||
|
Taro.navigateTo({url: `/pages/manage/depCur/depCur?id=${id}`}) |
||||||
|
} |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
getData() |
||||||
|
}, []) |
||||||
|
|
||||||
|
return ( |
||||||
|
<CustomWrapper> |
||||||
|
<Profile.Provider> |
||||||
|
{data.map(d => ( |
||||||
|
<View className='bg-white user' key={d.id}> |
||||||
|
<View className='flex mt-3 header p-2 justify-between'> |
||||||
|
<View className='flex'> |
||||||
|
<Text>{d.id}:</Text> |
||||||
|
<View className='font-weight'>{d.name}</View> |
||||||
|
</View> |
||||||
|
</View> |
||||||
|
<View className='flex justify-between p-2 operation'> |
||||||
|
<View onClick={() => showPop(d)}>修改</View> |
||||||
|
<View onClick={() => jumpCur(d.id)}>课程</View> |
||||||
|
<View className='text-danger' onClick={() => del(d.name, d.id)}>删除</View> |
||||||
|
</View> |
||||||
|
</View> |
||||||
|
))} |
||||||
|
|
||||||
|
<View className='mt-3 text-center'>- 暂无更多数据 -</View> |
||||||
|
|
||||||
|
<Button className='add' onClick={() => showPop(null)}>新建部门</Button> |
||||||
|
|
||||||
|
<PageContainer show={show} round onAfterLeave={() => setShow(false)}> |
||||||
|
<View className='h-4'> |
||||||
|
{show && <ChangeData getDeps={getData} putCompany={putCompany}/>} |
||||||
|
</View> |
||||||
|
</PageContainer> |
||||||
|
</Profile.Provider> |
||||||
|
</CustomWrapper> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
export default DepAdmin |
@ -0,0 +1,4 @@ |
|||||||
|
export default definePageConfig({ |
||||||
|
navigationBarTitleText: '部门课程', |
||||||
|
onReachBottomDistance: 30 |
||||||
|
}) |
@ -0,0 +1,103 @@ |
|||||||
|
import {getCurrentInstance} from "@tarojs/runtime"; |
||||||
|
import Taro, {useReachBottom} from "@tarojs/taro"; |
||||||
|
import {ManageApi} from "@/api/manage"; |
||||||
|
import React, {FC, useState} from "react"; |
||||||
|
import {Button, CustomWrapper, View} from "@tarojs/components"; |
||||||
|
import VideoCover from "@/components/videoCover/videoCover"; |
||||||
|
import {curriculum} from "@/api"; |
||||||
|
|
||||||
|
interface DelOpt { |
||||||
|
id: number |
||||||
|
findDel: (id: number) => Promise<void> |
||||||
|
} |
||||||
|
|
||||||
|
const Del: FC<DelOpt> = ({id, findDel}: DelOpt) => { |
||||||
|
function del() { |
||||||
|
Taro.showModal({ |
||||||
|
title: "是否删除课程", |
||||||
|
success({confirm}) { |
||||||
|
confirm && findDel(id) |
||||||
|
} |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
return ( |
||||||
|
<View className='text-center p-2' onClick={del}>删除</View> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
let page = 1 |
||||||
|
const DepCur: FC = () => { |
||||||
|
const {id} = getCurrentInstance()?.router?.params as { id: number } |
||||||
|
const [total, setTotal] = useState<number>(0) |
||||||
|
const [data, setData] = useState<Curriculum[]>([]) |
||||||
|
|
||||||
|
const getData = async (init?: boolean) => { |
||||||
|
try { |
||||||
|
Taro.showLoading({title: "课程查询中"}) |
||||||
|
if (init) { |
||||||
|
page += 1 |
||||||
|
} else { |
||||||
|
page = 1 |
||||||
|
} |
||||||
|
const res = await ManageApi.depCur({id, size: 10, page: page}) |
||||||
|
setTotal(res.total) |
||||||
|
const oldData: Curriculum[] = JSON.parse(JSON.stringify(data)) |
||||||
|
res.data.forEach(d => { |
||||||
|
const index = oldData.findIndex(c => c.id === d.id) |
||||||
|
if (index === -1) { |
||||||
|
oldData.push(d) |
||||||
|
} else { |
||||||
|
oldData.splice(index, 1, d) |
||||||
|
} |
||||||
|
}) |
||||||
|
setData(oldData) |
||||||
|
} catch (e) { |
||||||
|
} |
||||||
|
Taro.hideLoading() |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
async function findDel(cur_id: number) { |
||||||
|
try { |
||||||
|
await curriculum.delCur(id, cur_id) |
||||||
|
const index = data.findIndex(d => d.id === cur_id) |
||||||
|
if (index > -1) { |
||||||
|
const oldData: Curriculum[] = JSON.parse(JSON.stringify(data)) |
||||||
|
oldData.splice(index, 1) |
||||||
|
setData(oldData) |
||||||
|
} |
||||||
|
Taro.showToast({title: '删除成功'}) |
||||||
|
} catch (e) { |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function jumpAddCur() { |
||||||
|
Taro.navigateTo({url: "/pages/manage/addCur/addCur?id="+id}) |
||||||
|
} |
||||||
|
|
||||||
|
Taro.useDidShow(() => getData()) |
||||||
|
useReachBottom(() => { |
||||||
|
if (data.length < total) { |
||||||
|
getData(true) |
||||||
|
} |
||||||
|
}) |
||||||
|
|
||||||
|
return ( |
||||||
|
<CustomWrapper> |
||||||
|
<View className='flex p-1 flex-wrap'> |
||||||
|
{data.map(d => <VideoCover |
||||||
|
key={d.id} |
||||||
|
thumb={d.thumb} |
||||||
|
title={<Del id={d.id} findDel={findDel}/>} |
||||||
|
id={d.id} |
||||||
|
depId={id} |
||||||
|
content={d.title} |
||||||
|
/>)} |
||||||
|
</View> |
||||||
|
<Button className='add' onClick={jumpAddCur}>添加课程</Button> |
||||||
|
<View className='text-center p-3'>- 暂无更多数据 -</View> |
||||||
|
</CustomWrapper> |
||||||
|
) |
||||||
|
} |
||||||
|
export default DepCur |
@ -0,0 +1,48 @@ |
|||||||
|
.user { |
||||||
|
.header { |
||||||
|
border-bottom: 1px solid #ddd; |
||||||
|
|
||||||
|
.lock { |
||||||
|
padding: 4px 20px; |
||||||
|
border-radius: 5px; |
||||||
|
color: #fff; |
||||||
|
margin-right: 20px; |
||||||
|
} |
||||||
|
|
||||||
|
Text { |
||||||
|
color: #6e6e6e; |
||||||
|
} |
||||||
|
|
||||||
|
.del { |
||||||
|
color: red; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
.info { |
||||||
|
Image { |
||||||
|
width: 150px; |
||||||
|
height: 150px; |
||||||
|
background: #ddd; |
||||||
|
border-radius: 50%; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
.add { |
||||||
|
margin: 20px; |
||||||
|
border-radius: 10px; |
||||||
|
background: linear-gradient(to right, #8284f7, #5a93f9); |
||||||
|
color: #fff; |
||||||
|
position: fixed; |
||||||
|
width: 710rpx; |
||||||
|
bottom: 20px; |
||||||
|
} |
||||||
|
|
||||||
|
.operation { |
||||||
|
border-top: 1px solid #ddd; |
||||||
|
|
||||||
|
View { |
||||||
|
width: 50%; |
||||||
|
text-align: center; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,3 @@ |
|||||||
|
export default definePageConfig({ |
||||||
|
navigationBarTitleText: '学员管理', |
||||||
|
}) |
@ -0,0 +1,140 @@ |
|||||||
|
import {Button, CustomWrapper, Image, Text, View} from "@tarojs/components"; |
||||||
|
import {curriculum} from "@/api"; |
||||||
|
import {FC, useState} from "react"; |
||||||
|
import Taro, {useDidShow} from "@tarojs/taro"; |
||||||
|
import Tabs, {OnChangOpt, TabList} from "@/components/tabs/tabs"; |
||||||
|
import './student.scss' |
||||||
|
import {Profile} from '@/store' |
||||||
|
import {ManageApi} from "@/api/manage"; |
||||||
|
|
||||||
|
interface RoleTypeProps { |
||||||
|
id: number |
||||||
|
role_type: number |
||||||
|
getData: () => Promise<void> |
||||||
|
} |
||||||
|
|
||||||
|
const RoleType: FC<RoleTypeProps> = ({id, role_type, getData}: RoleTypeProps) => { |
||||||
|
const {user} = Profile.useContainer() |
||||||
|
|
||||||
|
function setRoleType() { |
||||||
|
if (role_type === 2) { |
||||||
|
Taro.showModal({title: "禁止修改超级管理员"}) |
||||||
|
return |
||||||
|
} |
||||||
|
const type = role_type === 0 ? 1 : 0 |
||||||
|
Taro.showModal({ |
||||||
|
title: "设置为" + ['学员', '管理员'][type], |
||||||
|
async success({confirm}) { |
||||||
|
if (confirm) { |
||||||
|
try { |
||||||
|
Taro.showLoading() |
||||||
|
await ManageApi.setRoleType(id, {auth_id: user?.id!, role_type: type}) |
||||||
|
Taro.hideLoading() |
||||||
|
Taro.showToast({title: "设置成功"}) |
||||||
|
await getData() |
||||||
|
} catch (e) { |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
return (user?.role_type === 2 ? |
||||||
|
<View onClick={setRoleType}>{['设置管理员', '设置学员', '超级管理员'][role_type]}</View> |
||||||
|
: null) |
||||||
|
} |
||||||
|
|
||||||
|
const studentAdmin = () => { |
||||||
|
const [list, setList] = useState<TabList[]>([]) |
||||||
|
const [user, setUser] = useState<ManageUsers[]>([]) |
||||||
|
|
||||||
|
async function getData() { |
||||||
|
Taro.showLoading() |
||||||
|
const res = await curriculum.use() |
||||||
|
if (res) { |
||||||
|
setList(res.map(d => ({title: d.name, value: d}))) |
||||||
|
setUser(res[0].users) |
||||||
|
} |
||||||
|
Taro.hideLoading() |
||||||
|
} |
||||||
|
|
||||||
|
useDidShow(getData) |
||||||
|
|
||||||
|
|
||||||
|
function listClick(data: OnChangOpt) { |
||||||
|
setUser((data.tab?.value as Manage).users) |
||||||
|
} |
||||||
|
|
||||||
|
function jumCollege(id: number, name: string) { |
||||||
|
Taro.navigateTo({url: `/pages/manage/college/college?id=${id}&name=${name}`}) |
||||||
|
} |
||||||
|
|
||||||
|
function changeStudent(id?: number) { |
||||||
|
Taro.navigateTo({url: "/pages/manage/addStudent/addStudent" + (id ? `?id=${id}` : '')}) |
||||||
|
} |
||||||
|
|
||||||
|
function del(id: number) { |
||||||
|
Taro.showModal({ |
||||||
|
title: '是否确认删除', |
||||||
|
async success({confirm}) { |
||||||
|
if (confirm) { |
||||||
|
await ManageApi.del(id) |
||||||
|
Taro.showToast({title: '删除成功'}) |
||||||
|
await getData() |
||||||
|
} |
||||||
|
} |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
return ( |
||||||
|
<CustomWrapper> |
||||||
|
<Profile.Provider> |
||||||
|
<View className='bg-white mb-3'> |
||||||
|
<Tabs tabList={list} onChange={listClick}/> |
||||||
|
</View> |
||||||
|
{user.length ? user.map((d) => ( |
||||||
|
<View className='bg-white user'> |
||||||
|
<View className='flex mt-3 header p-2 justify-between'> |
||||||
|
<View className='flex'> |
||||||
|
<View className='lock' |
||||||
|
style={`background:${['#73c057', '#c94f4f'][d.is_lock]}`}>{['正常', '警用'][d.is_lock]}</View> |
||||||
|
<Text>学员编号 {d.id}</Text> |
||||||
|
</View> |
||||||
|
<View className='del' onClick={() => del(d.id)}>删除</View> |
||||||
|
</View> |
||||||
|
<View className='p-2 flex info justify-between'> |
||||||
|
<View> |
||||||
|
<View className='font-weight my-3'>{d.name}</View> |
||||||
|
<View className='flex mb-3'> |
||||||
|
<View style='width:60px' className='text-muted'>类型</View> |
||||||
|
<View>{['学员', '管理员', '超级管理员'][d.role_type]}</View> |
||||||
|
</View> |
||||||
|
<View className='flex mb-3'> |
||||||
|
<View style='width:60px' className='text-muted'>手机号</View> |
||||||
|
<View>{d.phone_number}</View> |
||||||
|
</View> |
||||||
|
<View className='flex mb-3'> |
||||||
|
<View style='width:60px' className='text-muted'>邮箱</View> |
||||||
|
<View>{d.email}</View> |
||||||
|
</View> |
||||||
|
</View> |
||||||
|
<Image src={d.avatar} mode='widthFix'/> |
||||||
|
</View> |
||||||
|
<View className='flex justify-between p-2 operation'> |
||||||
|
<View onClick={() => changeStudent(d.id)}>修改</View> |
||||||
|
<View onClick={() => jumCollege(d.id, d.name)}>学习记录</View> |
||||||
|
<RoleType id={d.id} role_type={d.role_type} getData={getData}/> |
||||||
|
</View> |
||||||
|
</View> |
||||||
|
)) |
||||||
|
: <View className='text-center'>暂无数据</View>} |
||||||
|
|
||||||
|
<View className='py-8'/> |
||||||
|
|
||||||
|
<Button className='add' onClick={() => changeStudent()}>新建学员</Button> |
||||||
|
</Profile.Provider> |
||||||
|
</CustomWrapper> |
||||||
|
) |
||||||
|
} |
||||||
|
export default studentAdmin |
@ -0,0 +1,4 @@ |
|||||||
|
export default definePageConfig({ |
||||||
|
navigationBarTitleText: '学员学习记录', |
||||||
|
onReachBottomDistance:30 |
||||||
|
}) |
@ -0,0 +1,102 @@ |
|||||||
|
import {CustomWrapper, Image, Text, View} from "@tarojs/components"; |
||||||
|
import {FC, useEffect, useState} from "react"; |
||||||
|
import {getCurrentInstance} from "@tarojs/runtime"; |
||||||
|
import Taro, {useReachBottom} from "@tarojs/taro"; |
||||||
|
import {CurLearningRecord, ManageApi} from "@/api/manage"; |
||||||
|
import '@/pages/manage/studentAdmin/student.scss' |
||||||
|
|
||||||
|
const StudentRecord: FC = () => { |
||||||
|
const [page, setPage] = useState(1) |
||||||
|
const {cur_id, name} = getCurrentInstance()?.router?.params as { cur_id: string, name: string } |
||||||
|
const [data, setData] = useState<CurLearningRecord | null>(null) |
||||||
|
const [total, setTotal] = useState(0) |
||||||
|
|
||||||
|
async function getData() { |
||||||
|
try { |
||||||
|
const res = await ManageApi.curLearningRecord(cur_id, {page, size: 10}) |
||||||
|
if (!data) { |
||||||
|
setData(res) |
||||||
|
} else { |
||||||
|
const oldData: CurLearningRecord = JSON.parse(JSON.stringify(data)) |
||||||
|
oldData.data.push(...res.data) |
||||||
|
oldData.departments = res.departments |
||||||
|
Object.entries(res.user_dep_ids).forEach(([key, value]) => { |
||||||
|
oldData.user_dep_ids[key] = value |
||||||
|
}) |
||||||
|
Object.entries(res.user_course_records).forEach(([key, value]) => { |
||||||
|
oldData.user_course_records[key] = value |
||||||
|
}) |
||||||
|
Object.entries(res.user_course_hour_user_first_at).forEach(([key, value]) => { |
||||||
|
oldData.user_course_hour_user_first_at[key] = value |
||||||
|
}) |
||||||
|
setData(oldData) |
||||||
|
} |
||||||
|
setTotal(res.total) |
||||||
|
} catch (e) { |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function getDep(user_id: number): string { |
||||||
|
const cur_ids = data?.user_dep_ids[user_id] |
||||||
|
if (cur_ids) { |
||||||
|
return cur_ids.map(d => data?.departments[d]).join('、') |
||||||
|
} |
||||||
|
return '' |
||||||
|
} |
||||||
|
|
||||||
|
useReachBottom(() => { |
||||||
|
if (data && data.data.length < total) { |
||||||
|
setPage(page + 1) |
||||||
|
} |
||||||
|
}) |
||||||
|
|
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
getData() |
||||||
|
}, [page]) |
||||||
|
Taro.setNavigationBarTitle({title: name}) |
||||||
|
|
||||||
|
return ( |
||||||
|
<CustomWrapper> |
||||||
|
{data?.data.map(d => ( |
||||||
|
<View className='bg-white user'> |
||||||
|
<View className='flex mt-3 header p-2 justify-between'> |
||||||
|
<View className='flex'> |
||||||
|
<Text>学员编号 {d.id}</Text> |
||||||
|
</View> |
||||||
|
</View> |
||||||
|
<View className='p-2 flex info justify-between'> |
||||||
|
<View> |
||||||
|
<View className='font-weight my-3'>{d.name}</View> |
||||||
|
<View className='flex mb-3'> |
||||||
|
<View style='width:80px' className='text-muted'>类型</View> |
||||||
|
<View>{['学员', '管理员', '超级管理员'][d.role_type]}</View> |
||||||
|
</View> |
||||||
|
<View className='flex mb-3'> |
||||||
|
<View style='width:80px' className='text-muted'>手机号</View> |
||||||
|
<View>{d.phone_number}</View> |
||||||
|
</View> |
||||||
|
<View className='flex mb-3'> |
||||||
|
<View style='width:80px' className='text-muted'>邮箱</View> |
||||||
|
<View>{d.email}</View> |
||||||
|
</View> |
||||||
|
<View className='flex mb-3'> |
||||||
|
<View style='width:80px' className='text-muted'>部门</View> |
||||||
|
<View>{getDep(d.id)}</View> |
||||||
|
</View> |
||||||
|
<View className='flex mb-3'> |
||||||
|
<View style='width:80px' className='text-muted'>学习进度</View> |
||||||
|
<View>{data?.user_course_records[d.id]?.finished_count || 0}/{data?.course.class_hour}</View> |
||||||
|
</View> |
||||||
|
</View> |
||||||
|
<Image src={d.avatar} mode='widthFix'/> |
||||||
|
</View> |
||||||
|
<View className='flex justify-between p-2 operation'> |
||||||
|
</View> |
||||||
|
</View> |
||||||
|
))} |
||||||
|
</CustomWrapper> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
export default StudentRecord |
@ -0,0 +1,24 @@ |
|||||||
|
import {Profile} from "@/store"; |
||||||
|
import {Image, Text, View} from "@tarojs/components"; |
||||||
|
import styles from "@/pages/my/my.module.scss"; |
||||||
|
import avatar from "@/static/img/avatar.png" |
||||||
|
|
||||||
|
const Header = () => { |
||||||
|
const {user} = Profile.useContainer() |
||||||
|
|
||||||
|
return ( |
||||||
|
<View className={styles.header}> |
||||||
|
<View className='flex'> |
||||||
|
<Image src={avatar}/> |
||||||
|
<View className='flex-1'> |
||||||
|
<View className='font-32 font-weight'>{user?.name}</View> |
||||||
|
<View className='login font-24 mt-2 text-secondary flex justify-between content-start'> |
||||||
|
<Text>手机号:{user?.phone_number}</Text> |
||||||
|
</View> |
||||||
|
</View> |
||||||
|
</View> |
||||||
|
</View> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
export default Header |
@ -0,0 +1,60 @@ |
|||||||
|
import {useEffect, useState} from "react"; |
||||||
|
import {Image, View} from "@tarojs/components"; |
||||||
|
import Taro from "@tarojs/taro"; |
||||||
|
import {Profile} from '@/store/profile' |
||||||
|
import styles from '../../my.module.scss' |
||||||
|
import dep from '@/static/img/dep.png' |
||||||
|
import cur from '@/static/img/cur.png' |
||||||
|
import student from '@/static/img/student.png' |
||||||
|
import buy from '@/static/img/buy.png' |
||||||
|
|
||||||
|
interface List { |
||||||
|
title: string; |
||||||
|
src: string; |
||||||
|
router: string; |
||||||
|
} |
||||||
|
|
||||||
|
const Service = () => { |
||||||
|
const [list, setList] = useState<List[]>([ |
||||||
|
{title: '设置', src: dep, router: '/pages/business/userInfo/userInfo'} |
||||||
|
]) |
||||||
|
|
||||||
|
const {user} = Profile.useContainer() |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
const oldList: List[] = JSON.parse(JSON.stringify(list)) |
||||||
|
if ([1, 2].includes(user?.role_type || 0)) { |
||||||
|
oldList.unshift(...[ |
||||||
|
{title: '部门管理', src: dep, router: '/pages/manage/depAdmin/depAdmin'}, |
||||||
|
{title: '学员管理', src: student, router: '/pages/manage/studentAdmin/studentAdmin'}, |
||||||
|
{title: '课程管理', src: cur, router: ''}, |
||||||
|
{title: '课程购买', src: buy, router: '/pages/manage/curriculum/curriculum'}, |
||||||
|
]) |
||||||
|
setList(oldList) |
||||||
|
} |
||||||
|
}, []) |
||||||
|
|
||||||
|
function jump(url: string) { |
||||||
|
Taro.navigateTo({url}) |
||||||
|
} |
||||||
|
|
||||||
|
return ( |
||||||
|
<View className={'mt-3 ' + styles.tool}> |
||||||
|
<View className='font-weight font-32'>工具服务</View> |
||||||
|
<View className={'mt-4 ' + styles.service}> |
||||||
|
{ |
||||||
|
list.map(d => { |
||||||
|
return ( |
||||||
|
<View onClick={() => jump(d.router)}> |
||||||
|
<Image src={d.src} mode='aspectFit' className={styles.serviceImage}/> |
||||||
|
<View>{d.title}</View> |
||||||
|
</View> |
||||||
|
) |
||||||
|
}) |
||||||
|
} |
||||||
|
</View> |
||||||
|
</View> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
export default Service |
@ -0,0 +1,66 @@ |
|||||||
|
import {Image, View} from "@tarojs/components"; |
||||||
|
import styles from '../../my.module.scss' |
||||||
|
import curriculum1 from '@/static/img/curriculum1.png' |
||||||
|
import curriculum2 from '@/static/img/curriculum2.png' |
||||||
|
import Taro from "@tarojs/taro"; |
||||||
|
import {curriculum} from "@/api"; |
||||||
|
import {useState} from "react"; |
||||||
|
import {formatMinute} from "@/utils/time"; |
||||||
|
import time1 from "@/static/img/time1.png"; |
||||||
|
import time2 from "@/static/img/time2.png"; |
||||||
|
import over from '@/static/img/over.png' |
||||||
|
import incomplete from '@/static/img/incomplete.png' |
||||||
|
|
||||||
|
interface List { |
||||||
|
title: string |
||||||
|
time: string | number |
||||||
|
src: string |
||||||
|
} |
||||||
|
|
||||||
|
const Time = () => { |
||||||
|
const [list, setList] = useState<List[]>([ |
||||||
|
{title: '今日时长', time: '00:00', src: time1}, |
||||||
|
{title: '累计时长', time: '00:00', src: time2}, |
||||||
|
{title: '必修课', time: '0/0', src: curriculum1}, |
||||||
|
{title: '选修课', time: '0/0', src: curriculum2}, |
||||||
|
{title: '已完成', time: '0', src: over}, |
||||||
|
{title: '未完成', time: '0', src: incomplete}, |
||||||
|
]) |
||||||
|
|
||||||
|
|
||||||
|
Taro.useDidShow(async () => { |
||||||
|
try { |
||||||
|
const {stats} = await curriculum.course() |
||||||
|
const oldList: List[] = JSON.parse(JSON.stringify(list)) |
||||||
|
oldList[0].time = formatMinute(stats.today_learn_duration) |
||||||
|
oldList[1].time = formatMinute(stats.learn_duration) |
||||||
|
oldList[2].time = stats.required_course_count |
||||||
|
oldList[3].time = stats.nun_required_course_count |
||||||
|
oldList[4].time = stats.required_finished_course_count + stats.nun_required_finished_course_count |
||||||
|
oldList[5].time = stats.total_course_count - (stats.required_finished_course_count + stats.nun_required_finished_course_count) |
||||||
|
setList(oldList) |
||||||
|
} catch (e) { |
||||||
|
} |
||||||
|
}) |
||||||
|
|
||||||
|
|
||||||
|
return ( |
||||||
|
<View className='flex mt-3 justify-between flex-wrap'> |
||||||
|
{ |
||||||
|
list.map(d => { |
||||||
|
return ( |
||||||
|
<View className={'flex justify-between ' + styles.timeBox} key={d.title}> |
||||||
|
<View> |
||||||
|
<View className='font-weight'>{d.title}</View> |
||||||
|
<View className='text-muted'>{d.time}</View> |
||||||
|
</View> |
||||||
|
<Image src={d.src} mode='aspectFit' className={styles.timeImag}/> |
||||||
|
</View> |
||||||
|
) |
||||||
|
}) |
||||||
|
} |
||||||
|
</View> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
export default Time |
@ -0,0 +1,3 @@ |
|||||||
|
export default definePageConfig({ |
||||||
|
navigationStyle: 'custom' |
||||||
|
}) |
@ -0,0 +1,87 @@ |
|||||||
|
page { |
||||||
|
background: #F2F8F6 !important; |
||||||
|
} |
||||||
|
|
||||||
|
.content { |
||||||
|
background: linear-gradient(180deg, #45D4A8 0%, rgba(69, 212, 168, 0) 100%) no-repeat; |
||||||
|
background-size: 100% 458rpx; |
||||||
|
position: relative; |
||||||
|
|
||||||
|
&:after { |
||||||
|
content: ''; |
||||||
|
display: block; |
||||||
|
position: absolute; |
||||||
|
top: 106rpx; |
||||||
|
right: -100rpx; |
||||||
|
width: 290rpx; |
||||||
|
height: 290rpx; |
||||||
|
background: #FFFF; |
||||||
|
opacity: 0.2; |
||||||
|
border-radius: 290rpx; |
||||||
|
} |
||||||
|
|
||||||
|
&:before { |
||||||
|
content: ''; |
||||||
|
display: block; |
||||||
|
position: absolute; |
||||||
|
top: -80rpx; |
||||||
|
left: -80rpx; |
||||||
|
width: 230rpx; |
||||||
|
height: 230rpx; |
||||||
|
background: #FFFF; |
||||||
|
opacity: 0.2; |
||||||
|
border-radius: 230rpx; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
.header { |
||||||
|
|
||||||
|
padding: 130px 20px 0; |
||||||
|
|
||||||
|
Image { |
||||||
|
width: 100px; |
||||||
|
height: 100px; |
||||||
|
margin-right: 30px; |
||||||
|
margin-top: -10px; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
.ribbon { |
||||||
|
padding: 20px; |
||||||
|
} |
||||||
|
|
||||||
|
.timeBox { |
||||||
|
width: 40%; |
||||||
|
padding: 20px; |
||||||
|
border-radius: 20px; |
||||||
|
line-height: 1.7; |
||||||
|
margin-bottom: 20px; |
||||||
|
background: #fff; |
||||||
|
} |
||||||
|
|
||||||
|
.timeImag { |
||||||
|
width: 80px; |
||||||
|
height: 80px; |
||||||
|
margin-left: 20px; |
||||||
|
} |
||||||
|
|
||||||
|
.service { |
||||||
|
display: grid; |
||||||
|
margin-top: 48rpx; |
||||||
|
grid-template-columns:1fr 1fr 1fr 1fr; |
||||||
|
grid-auto-rows: 100px 100px 100px 100px; |
||||||
|
grid-gap: 60px; |
||||||
|
font-size: 28px; |
||||||
|
text-align: center; |
||||||
|
} |
||||||
|
|
||||||
|
.serviceImage { |
||||||
|
width: 48rpx; |
||||||
|
height: 48rpx; |
||||||
|
} |
||||||
|
|
||||||
|
.tool { |
||||||
|
background: #fff; |
||||||
|
border-radius: 20px; |
||||||
|
padding:30rpx 20px; |
||||||
|
} |
@ -0,0 +1,29 @@ |
|||||||
|
import {CustomWrapper, View} from "@tarojs/components"; |
||||||
|
import Taro from "@tarojs/taro"; |
||||||
|
import styles from './my.module.scss' |
||||||
|
import {Profile} from '@/store' |
||||||
|
import Header from "./components/header/header"; |
||||||
|
import {FC} from "react"; |
||||||
|
import Time from "@/pages/my/components/header/time"; |
||||||
|
import Service from "@/pages/my/components/header/service"; |
||||||
|
|
||||||
|
const My: FC = () => { |
||||||
|
const globalData = Taro.getApp().globalData |
||||||
|
|
||||||
|
return ( |
||||||
|
<CustomWrapper> |
||||||
|
<Profile.Provider> |
||||||
|
<View className={styles.content} style={`paddingTop:${globalData.statusBarHeight}px`}> |
||||||
|
<Header/> |
||||||
|
|
||||||
|
<View className={styles.ribbon}> |
||||||
|
<Time/> |
||||||
|
<Service/> |
||||||
|
</View> |
||||||
|
</View> |
||||||
|
</Profile.Provider> |
||||||
|
</CustomWrapper> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
export default My |
@ -0,0 +1,53 @@ |
|||||||
|
page { |
||||||
|
background-color: #efeff7; |
||||||
|
font-family: PingFang SC-Bold, PingFang SC; |
||||||
|
} |
||||||
|
|
||||||
|
.input { |
||||||
|
height: 24px; |
||||||
|
padding: 11px 15px; |
||||||
|
border-bottom: 1px solid #ddd; |
||||||
|
} |
||||||
|
|
||||||
|
.form { |
||||||
|
|
||||||
|
.item { |
||||||
|
margin-bottom: 10px; |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
justify-content: space-between; |
||||||
|
border-bottom: 1px solid #ddd; |
||||||
|
height: 80px; |
||||||
|
} |
||||||
|
|
||||||
|
Input { |
||||||
|
flex: 1; |
||||||
|
text-align: right; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
.card { |
||||||
|
height: 100px; |
||||||
|
background: #fff; |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
justify-content: space-between; |
||||||
|
border-bottom: 1px solid #ddd; |
||||||
|
padding: 0 20rpx; |
||||||
|
font-size: 35rpx; |
||||||
|
|
||||||
|
|
||||||
|
&-content { |
||||||
|
font-size: 27rpx; |
||||||
|
color: #8c8c8c; |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
justify-content: center; |
||||||
|
} |
||||||
|
|
||||||
|
Image { |
||||||
|
width: 80px; |
||||||
|
height: 80px; |
||||||
|
} |
||||||
|
} |
After Width: | Height: | Size: 6.6 KiB |
After Width: | Height: | Size: 993 B |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 3.3 KiB |
After Width: | Height: | Size: 5.4 KiB |
After Width: | Height: | Size: 574 B |
After Width: | Height: | Size: 4.4 KiB |
After Width: | Height: | Size: 4.3 KiB |
After Width: | Height: | Size: 3.5 KiB |
After Width: | Height: | Size: 3.5 KiB |
After Width: | Height: | Size: 2.7 KiB |
After Width: | Height: | Size: 4.3 KiB |