Compare commits

...

133 Commits
main ... v2

Author SHA1 Message Date
一杯沧海 bae5ece6d7 自定义ui视频组件调试 11 months ago
一杯沧海 0c7d7bc52b tabbar页面增加分享,自定义ui视频组件封装 1 year ago
一杯沧海 1df2db15b0 tabbar页面增加分享,自定义ui视频组件封装 1 year ago
一杯沧海 9178e7ba47 选择部门时如果没有部门则指引跳转添加部门,选择公司名展示成了联系人名字的bug修复 1 year ago
一杯沧海 7666062ea0 添加部门新跳转页面,调整添加学员手机号长度限制 1 year ago
一杯沧海 3844bbc0ca 文章列表返回时的刷新 1 year ago
一杯沧海 b33c7170ea 文章列表返回时的刷新 1 year ago
一杯沧海 2a29617017 登录模块调整,验证模块调整 1 year ago
king 84fbd98fa9 文章全局事件 1 year ago
king c2b9a4812f 修改课程管理 1 year ago
king acd6b352b5 2.修改tab的样式 1 year ago
king a43a0fabc2 2.修改tab的backMode模式样式 1 year ago
king 9d9506c66e 2.修改tab的backMode模式样式 1 year ago
king ee5741a752 1.封装暂无更多组件 1 year ago
king 5e6375cd4f 新增推荐文章页面 1 year ago
king 9f17e4c886 修改视频样式 1 year ago
king aeb6e1bbc5 修改ScrollView样式和隐藏滚动条 1 year ago
king 3a777d0b57 修改学习记录柱状图请求的loading动画 1 year ago
king cb88f49e85 增加视频组件内容 1 year ago
king aa4e006b00 1.修改弹窗居中动画 1 year ago
king 358c3ee95c 视频播放量 1 year ago
king 50bc02a178 文章浏览量加1 1 year ago
king d52902def3 区分文章品牌和疾病header和样式 1 year ago
king bc0682351e 统一文章样式 1 year ago
king 380b7be4fa 修改个人中心学习记录 1 year ago
king fd06be84d5 课程登录后刷新 1 year ago
king 22e012e711 浏览量本地增加 1 year ago
king a004bceaf9 疾病缓存 1 year ago
king a538461a9b 修改课程样式 1 year ago
king c1e6f8a5bc 1.修改文章没有内容时不能添加收藏 1 year ago
xing 56de47bf2c 调整跳转和图片默认高度 1 year ago
king 93064963a7 文章修改目录显示 1 year ago
king cecc425f17 我的学习记录 1 year ago
king a318591244 Merge remote-tracking branch 'origin/master' 1 year ago
king 278a408137 图片默认图 1 year ago
一杯沧海 887b7a4bb6 收藏列表调整 1 year ago
king 4cc69d9c2f 统一课程和播放 1 year ago
一杯沧海 e4acf12d87 Merge branch 'master' of https://git.yaojiankang.top/xing/video 1 year ago
king 86bc1a4cc0 统一文章收藏 1 year ago
一杯沧海 5e00de014c 收藏列表 1 year ago
king ae52d73588 品牌收藏 1 year ago
king 353e1d51e0 解决搜索代码冲突 1 year ago
king d47392954a Merge remote-tracking branch 'origin/master' 1 year ago
king e97f59d7f8 收藏样式 我的权限页面 1 year ago
一杯沧海 29006f34f3 Merge branch 'master' of https://git.yaojiankang.top/xing/video 1 year ago
一杯沧海 c332fdca11 搜索页面bug&个人中心学习记录bug&防抖 1 year ago
king c37e360cb6 统一暂无更多数据字段样式 和 添加 1 year ago
king 5137d3297f 搜索导航条动画和文章header头部 1 year ago
king 805b47435f 1取消课程部门分配必修选修功能 1 year ago
king 3e4a30cf1f 修改课程布局和搜索 1 year ago
king 68d99f86b1 修改文章显示 1 year ago
king adc5d17a5d 添加审核 1 year ago
king fa81566f32 修改疾病分类数据为空展示和页面高度计算 1 year ago
king 389827ac79 修改我的页面布局样式 1 year ago
king 85da032f41 1.修改首页 top样式和自动轮播 1 year ago
xing 4d2b83b950 1.添加文章标题和创建时间 1 year ago
king ae57654a46 品牌 1 year ago
king 8f104e2a54 品牌详情 1 year ago
king a70dcc2de1 修改学员信息页面 1 year ago
king 13c6a2d7b9 学习进度 1 year ago
king 9f5e6af772 修改首页搜索,top3,功能模块,课程推荐样式 1 year ago
king 9fb280b866 未登录的时候文章内容不超过半屏不显示登录按钮 1 year ago
king 485bc0815b 新增学习记录加载动画 1 year ago
king 70e61e187e 新增图片默认加载类型 和 我的页面header动画 1 year ago
xing a43fc7498c 品牌无数据显示 1 year ago
king dfe30478ea 修改我的header 和 学习记录 1 year ago
king 451828b70c 学习页面技术高度 1 year ago
king 9d8b36f300 块级tabs & 调整学习记录样式 1 year ago
king ce42bfd06f 新增个人折线图 1 year ago
king 54e581f2ae 1.修改个人中心手机号和新增上传头像 1 year ago
xing 19e01cf623 Merge remote-tracking branch 'origin/master' 1 year ago
king 017d05d456 hupeh: 调整页面 1 year ago
king c99493e9de 调整学习页面 1 year ago
king e437e131cd Merge remote-tracking branch 'origin/master' 1 year ago
king c0038a7e93 首页 1 year ago
一杯沧海 5af5eb80da 我的页面调整,搜索页面输入框范围调大 1 year ago
king 6b052df04c Merge remote-tracking branch 'origin/master' 1 year ago
king 178c227b1a 修改课程未登录查看 1 year ago
一杯沧海 e629874abe 搜索页面列表兼容多种类 1 year ago
一杯沧海 3c8e8f626c Merge branch 'master' of https://git.yaojiankang.top/xing/video 1 year ago
一杯沧海 eea8f99fe7 搜索页面样式调整 1 year ago
king 4605f01aeb Merge remote-tracking branch 'origin/master' 1 year ago
king 38afb77146 tabbaer 图片 1 year ago
一杯沧海 806614f5d6 重构登录视图&&重构搜索页面及列表 1 year ago
king eb218db694 Merge remote-tracking branch 'origin/master' 1 year ago
king f8703c0754 课程详情预览 1 year ago
一杯沧海 8cbad8268b Merge branch 'master' of https://git.yaojiankang.top/xing/video 1 year ago
一杯沧海 02c1a92a37 增加搜索页面&&搜索列表页面 1 year ago
king be402c557b Merge remote-tracking branch 'origin/master' 1 year ago
king cfd404dda1 自定义状态栏 1 year ago
一杯沧海 3e5190d0bd Merge branch 'master' of https://git.yaojiankang.top/xing/video 1 year ago
一杯沧海 1cd8fba6a3 登录视图高度调整 1 year ago
king fe67088214 Merge remote-tracking branch 'origin/master' 1 year ago
king 7075cd45ac 修改首页样式 & 见面会微信小程序二维码 1 year ago
一杯沧海 3fe6226304 我的页面切换公司功能 1 year ago
king f1bef606a9 修改登录过期 1 year ago
king a51502d559 修改tabbar 1 year ago
king 2fdf0d3085 适配轮播图高度 1 year ago
king 8ae17e1bfc Merge remote-tracking branch 'origin/master' 1 year ago
king d742560fb2 自定义首页头部 1 year ago
一杯沧海 aa94eb9a27 Merge branch 'master' of https://git.yaojiankang.top/xing/video 1 year ago
一杯沧海 1775c39136 未登录页面展示登录视图,个人中心展示公司,并可以切换 1 year ago
king bcbf3b8e41 替换微信加载状态 && 替换网络图片加载公共组件 1 year ago
king 20564f42fb 修改页面级加载 和 图片加载 1 year ago
一杯沧海 1eb468a699 疾病列表调整 1 year ago
king 8e465b9d03 优化收益加载速度 1 year ago
king 76b08de3c1 优化首页加载速度 1 year ago
king b3fd259076 视频弹窗答题 1 year ago
king 8cb14727c0 Merge remote-tracking branch 'origin/master' 1 year ago
一杯沧海 26fbcc475a 首页图片组件封装 1 year ago
xing 3794aca0a7 Merge remote-tracking branch 'origin/master' 1 year ago
king 0e46e2d76b 学习记录 1 year ago
king 7e03c67400 学习滑动 1 year ago
king 4fd03d0b8d 首页和全屏视频 1 year ago
king cccb057722 疾病知识 1 year ago
king c2ee704cf9 顶部固定 1 year ago
一杯沧海 2efdeb0bd2 文章详情样式再次调整 1 year ago
一杯沧海 5380bc35a0 样式调整 1 year ago
king 7ee05c66d0 样式 1 year ago
一杯沧海 ea75fdc4c9 疾病文章列表 1 year ago
king c961892d9b 分类 1 year ago
一杯沧海 7844106068 品牌列表品牌详情调整 1 year ago
king 2029037d7a 我的学习记录 1 year ago
king 7b0c91dfcd 专业知识滑动列表 && 下拉加载 1 year ago
一杯沧海 c2cea8feef 疾病文章详情 1 year ago
king 50460bd891 专业知识列表 1 year ago
king a6fed2ec3a 专业知识 1 year ago
king ad75871248 品牌 1 year ago
king ed2c5aedb0 修改预览分包管理文件管理 1 year ago
一杯沧海 39aa8c808c 品牌详情&文章详情 1 year ago
king 736f9387b6 分类 1 year ago
一杯沧海 14a6cfac38 品牌列表 1 year ago
一杯沧海 6caf55b914 品牌模块 1 year ago
  1. 5
      .env
  2. 1
      config/index.js
  3. 44335
      package-lock.json
  4. 1
      package.json
  5. 14
      pnpm-lock.yaml
  6. 4
      project.config.json
  7. 34
      src/api/brand.ts
  8. 3
      src/api/curriculum.ts
  9. 43
      src/api/home.ts
  10. 18
      src/api/illness.ts
  11. 1
      src/api/manage.ts
  12. 10
      src/api/meeting.ts
  13. 30
      src/api/public.ts
  14. 22
      src/api/request.ts
  15. 11
      src/api/search.ts
  16. 24
      src/api/user.ts
  17. 18
      src/app.config.ts
  18. 87
      src/app.scss
  19. 20
      src/app.tsx
  20. 128
      src/components/IconFont/icon.css
  21. 88
      src/components/IconFont/index.tsx
  22. 40
      src/components/articlesBox/articlesBox.module.scss
  23. 68
      src/components/articlesBox/articlesBox.tsx
  24. 73
      src/components/collect/collect.module.scss
  25. 78
      src/components/collect/collect.tsx
  26. 4
      src/components/custom-page-container/custom-page-container.module.scss
  27. 10
      src/components/custom-page-container/custom-page-container.tsx
  28. 7
      src/components/empty/empty.module.scss
  29. 18
      src/components/empty/empty.tsx
  30. 7
      src/components/icon/index.tsx
  31. 108
      src/components/image/image.tsx
  32. 16
      src/components/learningRecord/learningRecord.module.scss
  33. 99
      src/components/learningRecord/learningRecord.tsx
  34. 52
      src/components/lineChart/lineChart.module.scss
  35. 46
      src/components/lineChart/lineChart.tsx
  36. 38
      src/components/login-view/index.module.scss
  37. 28
      src/components/login-view/index.tsx
  38. 47
      src/components/loginView/index.module.scss
  39. 102
      src/components/loginView/index.tsx
  40. 34
      src/components/navigationBar/navigationBar.module.scss
  41. 60
      src/components/navigationBar/navigationBar.tsx
  42. 18
      src/components/pageScript/pageScript.tsx
  43. 8
      src/components/popPut/popPut.tsx
  44. 44
      src/components/spinner/index.tsx
  45. 21
      src/components/spinner/style.scss
  46. 50
      src/components/tabs/tabs.scss
  47. 45
      src/components/tabs/tabs.tsx
  48. 2
      src/components/textCollapse/collapse.module.scss
  49. 76
      src/components/textCollapse/collapse.tsx
  50. 13
      src/components/topic/single.module.scss
  51. 46
      src/components/topic/single.tsx
  52. 7
      src/components/video/video.tsx
  53. 13
      src/components/videoCover/videoCover.scss
  54. 7
      src/components/videoCover/videoCover.tsx
  55. 27
      src/components/videoList/videoList.module.scss
  56. 70
      src/components/videoList/videoList.tsx
  57. 4
      src/components/videoPro/index.module.scss
  58. 200
      src/components/videoPro/index.tsx
  59. 28
      src/hooks/articlesEvent.ts
  60. 25
      src/hooks/eventsIndex.ts
  61. 39
      src/hooks/pubsub.ts
  62. 25
      src/hooks/storageDep.ts
  63. 28
      src/hooks/videoEvent.ts
  64. 44
      src/pages/business/courType/courType.tsx
  65. 6
      src/pages/business/curHistory/curHistory.tsx
  66. 5
      src/pages/business/history/history.module.scss
  67. 47
      src/pages/business/history/history.tsx
  68. 64
      src/pages/business/userInfo/userInfo.tsx
  69. 65
      src/pages/business/videoInfo/components/catalogue.tsx
  70. 15
      src/pages/business/videoInfo/components/course.tsx
  71. 27
      src/pages/business/videoInfo/components/hours.tsx
  72. 23
      src/pages/business/videoInfo/videoInfo.scss
  73. 65
      src/pages/business/videoInfo/videoInfo.tsx
  74. 2
      src/pages/check/check.config.ts
  75. 4
      src/pages/check/check.module.scss
  76. 26
      src/pages/check/check.tsx
  77. 62
      src/pages/home/components/adware.tsx
  78. 81
      src/pages/home/components/curRecommended.tsx
  79. 26
      src/pages/home/components/feature.tsx
  80. 118
      src/pages/home/components/feature_recommended.tsx
  81. 25
      src/pages/home/components/search.tsx
  82. 8
      src/pages/home/home.config.ts
  83. 91
      src/pages/home/home.module.scss
  84. 98
      src/pages/home/home.tsx
  85. 109
      src/pages/index/components/videoList.tsx
  86. 6
      src/pages/index/index.config.ts
  87. 31
      src/pages/index/index.module.scss
  88. 83
      src/pages/index/index.tsx
  89. 8
      src/pages/login/login.module.scss
  90. 88
      src/pages/login/login.tsx
  91. 5
      src/pages/manage/addStudent/addStudent.scss
  92. 6
      src/pages/manage/addStudent/addStudent.tsx
  93. 111
      src/pages/manage/courseAdmin/components/search.tsx
  94. 76
      src/pages/manage/courseAdmin/courseAdmin.module.scss
  95. 66
      src/pages/manage/courseAdmin/courseAdmin.tsx
  96. 12
      src/pages/manage/depAdmin/depAdmin.scss
  97. 37
      src/pages/manage/depAdmin/depAdmin.tsx
  98. 1
      src/pages/manage/department/addDepartment.config.ts
  99. 17
      src/pages/manage/department/addDepartment.scss
  100. 68
      src/pages/manage/department/addDepartment.tsx
  101. Some files were not shown because too many files have changed in this diff Show More

@ -1,5 +1,6 @@
#TARO_APP_API=https://yjx.dev.yaojiankang.top #TARO_APP_API=https://yjx.dev.yaojiankang.top
TARO_APP_API=https://mooc.yaojiankang.top #TARO_APP_API=https://mooc.yaojiankang.top
#TARO_APP_API=https://xingui.yaojiankang.top
#TARO_APP_API=https://shopfix.yaojiankang.top #TARO_APP_API=https://shopfix.yaojiankang.top
#TARO_APP_API=https://playedu.yaojiankang.top TARO_APP_API=https://playedu.yaojiankang.top
TARO_APP_LGOIN=true TARO_APP_LGOIN=true

@ -27,6 +27,7 @@ const config = {
"@": path.resolve(__dirname, '..', 'src') "@": path.resolve(__dirname, '..', 'src')
}, },
mini: { mini: {
debugReact: true,
postcss: { postcss: {
pxtransform: { pxtransform: {
enable: true, enable: true,

44335
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -55,6 +55,7 @@
"@tarojs/taro": "3.6.8", "@tarojs/taro": "3.6.8",
"dayjs": "^1.11.9", "dayjs": "^1.11.9",
"marked": "^7.0.4", "marked": "^7.0.4",
"pnpm": "^8.9.0",
"react": "^18.0.0", "react": "^18.0.0",
"react-dom": "^18.0.0", "react-dom": "^18.0.0",
"react-refresh": "^0.11.0", "react-refresh": "^0.11.0",

@ -1,4 +1,4 @@
lockfileVersion: '6.1' lockfileVersion: '6.0'
settings: settings:
autoInstallPeers: true autoInstallPeers: true
@ -56,6 +56,9 @@ dependencies:
marked: marked:
specifier: ^7.0.4 specifier: ^7.0.4
version: 7.0.4 version: 7.0.4
pnpm:
specifier: ^8.9.0
version: 8.9.0
react: react:
specifier: ^18.0.0 specifier: ^18.0.0
version: 18.0.0 version: 18.0.0
@ -716,6 +719,7 @@ packages:
/@babel/plugin-proposal-numeric-separator@7.18.6(@babel/core@7.22.5): /@babel/plugin-proposal-numeric-separator@7.18.6(@babel/core@7.22.5):
resolution: {integrity: sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q==} resolution: {integrity: sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q==}
engines: {node: '>=6.9.0'} engines: {node: '>=6.9.0'}
deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-numeric-separator instead.
peerDependencies: peerDependencies:
'@babel/core': ^7.0.0-0 '@babel/core': ^7.0.0-0
dependencies: dependencies:
@ -727,6 +731,7 @@ packages:
/@babel/plugin-proposal-numeric-separator@7.18.6(@babel/core@7.8.0): /@babel/plugin-proposal-numeric-separator@7.18.6(@babel/core@7.8.0):
resolution: {integrity: sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q==} resolution: {integrity: sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q==}
engines: {node: '>=6.9.0'} engines: {node: '>=6.9.0'}
deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-numeric-separator instead.
peerDependencies: peerDependencies:
'@babel/core': ^7.0.0-0 '@babel/core': ^7.0.0-0
dependencies: dependencies:
@ -11623,6 +11628,12 @@ packages:
semver-compare: 1.0.0 semver-compare: 1.0.0
dev: true dev: true
/pnpm@8.9.0:
resolution: {integrity: sha512-74hZk44fBTe5/PAwkEQxE5Lzs4s0QXbmzU/e4hsiVSSwrCobCK4q4t3Vs/9LjKSW1neOlQ8+fJ9VW4EyWYJEHA==}
engines: {node: '>=16.14'}
hasBin: true
dev: false
/portfinder@1.0.32: /portfinder@1.0.32:
resolution: {integrity: sha512-on2ZJVVDXRADWE6jnQaX0ioEylzgBpQk8r55NE4wjXW1ZxO+BgDlY6DXwj20i0V8eB4SenDQ00WEaxfiIQPcxg==} resolution: {integrity: sha512-on2ZJVVDXRADWE6jnQaX0ioEylzgBpQk8r55NE4wjXW1ZxO+BgDlY6DXwj20i0V8eB4SenDQ00WEaxfiIQPcxg==}
engines: {node: '>= 0.12.0'} engines: {node: '>= 0.12.0'}
@ -12192,6 +12203,7 @@ packages:
/prr@1.0.1: /prr@1.0.1:
resolution: {integrity: sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==} resolution: {integrity: sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==}
requiresBuild: true
dev: true dev: true
optional: true optional: true

@ -1,8 +1,8 @@
{ {
"miniprogramRoot": "./dist", "miniprogramRoot": "./dist",
"projectname": "video", "projectname": "信桂",
"description": "", "description": "",
"appid": "wx703940a70f0f1be7", "appid": "wx7ef5cf2d16bdaea2",
"setting": { "setting": {
"urlCheck": true, "urlCheck": true,
"es6": false, "es6": false,

@ -1,42 +1,58 @@
import {request} from "@/api/request"; import {request} from "@/api/request";
import {Illness} from "@/api/illness";
export type BrandRecord = { export type BrandRecord = {
logo: string; logo: string;
name: string; name: string;
id: number id: number
introductory_video: string introductory_video: string
brand_album: string[] brand_album: string
graphic_introduction: string graphic_introduction: string
disabled: number disabled: number
introductory_video_resource:any introductory_video_resource: any
article_count: number
created_at: string
page_view: number
collect: boolean
last_publish_time: string
} }
export type ArticleRecord = { export type ArticleRecord = {
id: number;
title: string title: string
page_view: number page_view: number
created_at: string created_at: string
content: string content: string
brands: BrandRecord[]
illness: Illness[]
collect: boolean
cover: string
owner_type: 1 | 2 // 疾病
} }
export const brandApi = { export const brandApi = {
/** 品牌列表 */ /** 品牌列表 */
list(page: number , page_size: number) { list(page: number, page_size: number) {
return request<{ return request<{
list: BrandRecord[], list: BrandRecord[],
total: number total: number
}>(`/home/v1/brand/list?page=${page}&page_size=${page_size}` , "GET") }>(`/home/v1/brand/list?page=${page}&page_size=${page_size}`, "GET")
}, },
/** 品牌详情 */ /** 品牌详情 */
info(id: number) { info(id: number) {
return request<BrandRecord>(`/home/v1/brand/${id}`, "GET") return request<BrandRecord>(`/home/v1/brand/${id}`, "GET")
}, },
/** 文章列表 */ /** 文章列表 */
articleList(owner_id: number,page:number) { articleList(owner_id: number, page: number) {
return request<{ return request<{
list: ArticleRecord[], list: Articles[],
total: number total: number
}>(`/home/v1/article/list?owner_id=${owner_id}&page=${page}&page_size=10` , "GET") }>(`/home/v1/article/list?owner_id=${owner_id}&page=${page}&page_size=10`, "GET")
}, },
articleInfo(id: number ) { articleInfo(id: number) {
return request<ArticleRecord>(`/home/v1/article/${id}` , "GET") return request<ArticleRecord>(`/home/v1/article/${id}`, "GET")
}, },
/** 品牌 & 健康详情 */
videoInfo(id: number | string) {
return request<VideList>(`/home/v1/health/${id}`, "GET")
}
} }

@ -32,6 +32,7 @@ export interface HourPlayData {
} }
export interface Course { export interface Course {
audit_mode: boolean
/** 完成 */ /** 完成 */
finished_count: number; finished_count: number;
/** 未完成 */ /** 未完成 */
@ -81,7 +82,7 @@ export const curriculum = {
}, },
/** 查看课程课时数据 */ /** 查看课程课时数据 */
courseDep(id: number, depId: number | null) { courseDep(id: number, depId: number | null) {
return request<CourseDepData>(`/api/v1/course/${id}${depId ? `/dep/${depId}` : ''}`, "GET") return request<CourseDepData>(`/api/v1/course/dep`, "GET", {id, depId})
}, },
/** 播放 */ /** 播放 */
hourPlay(courseId: number, id: number) { hourPlay(courseId: number, id: number) {

@ -31,50 +31,40 @@ export interface AdwareType {
export interface HomeData { export interface HomeData {
adverts: AdwareType[] adverts: AdwareType[]
skill: Kill[] skill: VideList[]
health: Health[] health: VideList[]
brand: { brand: {
list: Brand[] list: Brand[]
} }
illness: { illness: {
list: Illness[] list: Illness[]
} }
articles: Articles[]
courses: {data:any,total:number}
}
interface Course {
articles: any[]
audit_mode: boolean
course: { data: Curriculum[], total: number }
} }
export const HomeApi = { export const HomeApi = {
advert(only_flag: string) {
return request<AdwareType[]>("/home/v1/advert/unique?only_flag=" + only_flag, "GET")
},
course(page: number, page_size: number) { course(page: number, page_size: number) {
return request<{ data: Curriculum[], total: number }>('/home/v1/course/course', "GET", {page, page_size}) return request<Course>('/home/v1/course/course', "GET", {page, page_size})
},
/** 健康管理 */
healthTop(count: number) {
return request<Health[]>('/home/v1/health/top', "GET", {count})
}, },
health(page: number, page_size: number) { health(page: number, page_size: number) {
return request<{ data: Health[], total: number }>('/home/v1/health/index', "GET", {page, page_size}) return request<{ data: VideList[], total: number }>('/home/v1/health/index', "GET", {page, page_size})
},
/** 增加播放量 */
healthSetPlay(id) {
return request(`/home/v1/health/set_play/${id}`, "PUT")
}, },
/** 品牌 */ /** 品牌 */
brand(page: number, page_size: number) { brand(page: number, page_size: number) {
return request<{ list: Brand[], total: number }>('/home/v1/brand/list', "GET", {page, page_size}) return request<{ list: Brand[], total: number }>('/home/v1/brand/list', "GET", {page, page_size})
}, },
/** 技能 */
skillTop(count: number) {
return request<Kill[]>('/home/v1/skill/top', "GET", {count})
},
skillCategory() { skillCategory() {
return request<Category[]>('/home/v1/skill/category', "GET") return request<Category[]>('/home/v1/skill/category', "GET")
}, },
skillList(categoryId: number, page: number, page_size: number) { skillList(categoryId: number, page: number, page_size: number) {
return request<{ data: Kill[], total: number }>('/home/v1/skill/index', "GET", {categoryId, page, page_size}) return request<{ data: VideList[], total: number }>('/home/v1/skill/index', "GET", {categoryId, page, page_size})
},
skillSetPlay(id: number) {
return request(`/home/v1/skill/set_play/${id}`, "PUT")
}, },
/** 疾病知识 */ /** 疾病知识 */
illness(page: number, page_size: number) { illness(page: number, page_size: number) {
@ -85,5 +75,12 @@ export const HomeApi = {
}, },
home() { home() {
return request<HomeData>('/home/v1/home/home_page', "GET", {count: 3}) return request<HomeData>('/home/v1/home/home_page', "GET", {count: 3})
},
/** 文章播放量 + 1 */
articleViews(id: string | number) {
return request(`/home/v1/article/views/${id}`, "POST")
},
voideView(id: number | string) {
return request(`/home/v1/health/set_play/${id}`, "PUT")
} }
} }

@ -1,5 +1,21 @@
import {request} from "@/api/request"; import {request} from "@/api/request";
export interface Illness {
name?: any;
illness: {
name: string;
description: string;
resource: any;
album: string[]
created_at: string
}
list: {
list: any[],
total: number
}
}
export const illnessApi = { export const illnessApi = {
/** 疾病列表 */ /** 疾病列表 */
@ -7,6 +23,6 @@ export const illnessApi = {
return request<{ list: any[], total: number }>(`/home/v1/illness/list`, "GET", {page, page_size, id}) return request<{ list: any[], total: number }>(`/home/v1/illness/list`, "GET", {page, page_size, id})
}, },
articleInfo(owner_id: number, page: number, page_size: number) { articleInfo(owner_id: number, page: number, page_size: number) {
return request<{ list: any[], total: number }>(`/home/v1/article/illness_list`, "GET", {page, page_size, owner_id}) return request<Illness>(`/home/v1/article/illness_list`, "GET", {page, page_size, owner_id})
}, },
} }

@ -46,7 +46,6 @@ export interface depCurProps {
interface AddCurProps { interface AddCurProps {
course_id: number[] course_id: number[]
dep_id: number[] dep_id: number[]
is_required: (1 | 0)[]
} }

@ -27,21 +27,29 @@ interface MeetingInfo {
} }
export const meetingAPi = { export const meetingAPi = {
setMeetings(data: Meeting) { addMeetings(data: Meeting) {
return request<{ id: number }>('/api/v1/meetings/meeting', "POST", data) return request<{ id: number }>('/api/v1/meetings/meeting', "POST", data)
}, },
setMeetings(data: Meeting) {
return request<{ id: number }>(`/api/v1/meetings/meeting/${data.id}`, "PUT", data)
},
putMeeting(id: number, data: Partial<Meeting>) { putMeeting(id: number, data: Partial<Meeting>) {
return request(`/api/v1/meetings/meeting/${id}`, "PUT", data) return request(`/api/v1/meetings/meeting/${id}`, "PUT", data)
}, },
setMeeting(id: string) { setMeeting(id: string) {
return request<Meeting>(`/api/v1/meetings/meeting/${id}`, "GET") return request<Meeting>(`/api/v1/meetings/meeting/${id}`, "GET")
}, },
unstopsList() {
return request<Meeting[]>(`/api/v1/meetings/unstops`, "GET")
},
setList(page: number, page_size: number) { setList(page: number, page_size: number) {
return request<{ return request<{
data: Meeting[], data: Meeting[],
total: number total: number
}>(`/api/v1/meetings/meeting?page=${page}&page_size=${page_size}`, "GET") }>(`/api/v1/meetings/meeting?page=${page}&page_size=${page_size}`, "GET")
}, },
exists() { exists() {
return request<Meeting>('/api/v1/meetings/exists', "GET") return request<Meeting>('/api/v1/meetings/exists', "GET")
}, },

@ -9,15 +9,19 @@ export interface Category {
resource_category?: Category[] resource_category?: Category[]
} }
interface CategoryList { export interface CoursesMode {
categories: Record<number, Category[]> articles: any[]
audit_mode: boolean
course: Courses
}
export interface NewCoursesMode {
articles: any[]
audit_mode: boolean
platform_courses: Curriculum[]
company_courses: Curriculum[]
} }
export interface Courses { export interface Courses {
/** 已完成 */
is_finished: Curriculum[]
/** 未完成 */
is_not_finished: Curriculum[]
/** 选秀 */ /** 选秀 */
is_not_required: Curriculum[] is_not_required: Curriculum[]
/** 必修 */ /** 必修 */
@ -30,12 +34,14 @@ export type CoursesKey = keyof Omit<Courses, 'total_course_duration'>
export const publicApi = { export const publicApi = {
/** 分类 */
category() {
return request<CategoryList>('/api/v1/category/all', "GET")
},
/** 课程 */ /** 课程 */
course(data: { page: number, pageSize: number }) { course(data: { page: number, pageSize: number }) {
return request<Courses>('/api/v1/category/course/index', "GET", data) return request<CoursesMode>('/api/v1/category/course/index', "GET", data)
} },
articlesPush(page: number, page_size: number) {
return request<{ list: Articles[], total: number }>('/home/v1/article/list', "GET", {page, page_size})
},
newCourse(data: { page: number, page_size: number }) {
return request<NewCoursesMode>('/home/v1/home/courses', "GET", data)
},
} }

@ -44,6 +44,8 @@ export const ERROR_STATUS: Record<number | string, string> = {
'OVERSTEP': '请求越界~' 'OVERSTEP': '请求越界~'
} }
let notLoging = false
export function request<T = unknown>( export function request<T = unknown>(
url: string, url: string,
method: keyof Method, method: keyof Method,
@ -82,15 +84,19 @@ export function request<T = unknown>(
const data = res?.data as any const data = res?.data as any
if (data?.code === 0 && res?.statusCode === 200) { if (data?.code === 0 && res?.statusCode === 200) {
resolve(data?.data) resolve(data?.data)
notLoging = false
} else if (res.statusCode === 401) { } else if (res.statusCode === 401) {
// Taro.showModal({ if (notLoging || !token) return
// title: "登录过期,需重新登陆", notLoging = true
// showCancel: false, Taro.showModal({
// success() { title: "登录过期",
Taro.clearStorageSync() confirmText: "登录",
Taro.redirectTo({url: '/pages/login/login'}) showCancel: false,
// } success() {
// }) Taro.clearStorage()
Taro.navigateTo({url: '/pages/login/login'})
}
})
} else { } else {
reject(null) reject(null)
Taro.showToast({ Taro.showToast({

@ -0,0 +1,11 @@
import {request} from "@/api/request";
export const SearchApi = {
/** 品牌列表 */
list(page: number , page_size: number, name: string) {
return request<{
data: any[],
total: number
}>(`/home/v1/search/home?page=${page}&page_size=${page_size}&keywords=${name}&sort_type=1&sort=0` , "GET")
},
}

@ -75,19 +75,35 @@ export const userApi = {
hourCourse(course_id: string, unique_ident: number) { hourCourse(course_id: string, unique_ident: number) {
return request<HourCourse>(`/api/v1/course/${course_id}/info/${unique_ident}`, "GET") return request<HourCourse>(`/api/v1/course/${course_id}/info/${unique_ident}`, "GET")
}, },
meetingSave(data: any) {
return request<LoginData>(`/api/v1/auth/login/meeting/save`, "POST", data)
},
info(user_id: string) { info(user_id: string) {
return request<User>(`/api/v1/statistics/${user_id}`, "GET") return request<User>(`/api/v1/statistics/${user_id}`, "GET")
}, },
/**获取指定学员指定时间学习记录 */ /**获取指定学员指定时间学习记录 */
statistics(user_id: string, data: StatisticsParam) { statistics(user_id: string | number, data: StatisticsParam) {
return request<{ return request<{
data: Record<number, number> data: Record<number, number>
}>(`/api/v1/statistics/statistics/${user_id}?start_time=${data.start_time}&end_time=${data.end_time}`, "GET") }>(`/api/v1/statistics/statistics/${user_id}?start_time=${data.start_time}&end_time=${data.end_time}`, "GET")
}, },
getCode(phone_number: number) { getCode(phone_number: number) {
return request('/api/v1/sms/send?phone_number=' + phone_number, "GET") return request('/api/v1/sms/send?phone_number=' + phone_number, "GET")
},
/** 公司列表 */
companyList() {
return request<unknown[]>(`/api/v1/company/mine_list`, "GET")
},
companyReplace(id: number) {
return request(`/api/v1/company/replace/${id}`, "PATCH")
},
// /** 上传头像 */
// putAvatar(file: string) {
// return request('/api/v1/user/avatar', "PUT", {file})
// }
/** 收藏列表 */
collectList(owner_type: number, page: number, page_size: number) {
return request<{ list: any[], total: number }>('/api/v1/collect/list', "GET", {owner_type, page, page_size})
},
/** 收藏 */
create(data: Create) {
return request("/api/v1/collect/create", "POST", data)
} }
} }

@ -6,15 +6,16 @@ export default defineAppConfig({
'pages/login/login', 'pages/login/login',
'pages/check/check', 'pages/check/check',
'pages/my/my', 'pages/my/my',
'pages/search/index'
], ],
window: { window: {
backgroundTextStyle: 'light', backgroundTextStyle: 'light',
navigationBarBackgroundColor: '#fff', navigationBarBackgroundColor: '#fff',
navigationBarTitleText: '课程', navigationBarTitleText: '信桂',
navigationBarTextStyle: 'black' navigationBarTextStyle: 'black'
}, },
tabBar: { tabBar: {
color: '#909795', color: '#606563',
selectedColor: '#45D4A8', selectedColor: '#45D4A8',
list: [ list: [
{ {
@ -29,6 +30,12 @@ export default defineAppConfig({
iconPath: "static/tabs/study.png", iconPath: "static/tabs/study.png",
selectedIconPath: "static/tabs/study-selected.png", selectedIconPath: "static/tabs/study-selected.png",
}, },
{
text: '搜索',
pagePath: 'pages/search/index',
iconPath: "static/img/graySearch.png",
selectedIconPath: "static/img/cyanSearch.png",
},
{ {
text: "我的", text: "我的",
pagePath: 'pages/my/my', pagePath: 'pages/my/my',
@ -71,11 +78,14 @@ export default defineAppConfig({
'student/student', 'student/student',
'curriculum/curriculum', 'curriculum/curriculum',
'addStudent/addStudent', 'addStudent/addStudent',
'department/addDepartment',
'depCur/depCur', 'depCur/depCur',
'addCur/addCur', 'addCur/addCur',
'spotMeeting/spotMeeting', 'spotMeeting/spotMeeting',
'selectDep/selectDep', 'selectDep/selectDep',
'meetings/meetings', 'meetings/meetings',
'meetings/list',
'meetings/form/form',
'userInfo/userInfo', 'userInfo/userInfo',
'courseAdmin/courseAdmin', 'courseAdmin/courseAdmin',
] ]
@ -91,8 +101,10 @@ export default defineAppConfig({
'videoFull/videoFull', // 资源id 视频全屏 'videoFull/videoFull', // 资源id 视频全屏
'illness/sort/sort', 'illness/sort/sort',
'illness/list/list', 'illness/list/list',
'illness/article/article',
'webView/webView', 'webView/webView',
'search/search/index',
'collect/collect', // 收藏列表
'jumpArticles/jumpArticles', // 推荐文章
] ]
}, },
], ],

@ -1,5 +1,7 @@
@import "static/css/module"; @import "static/css/module";
@import 'taro-ui/dist/style/index.scss'; //@import 'taro-ui/dist/style/index.scss';
.inlineblock { display: inline-block }
.flex {display: flex !important;flex-direction:row} .flex {display: flex !important;flex-direction:row}
.flex-row{ flex-direction:row!important} .flex-row{ flex-direction:row!important}
@ -13,12 +15,14 @@
.justify-around{justify-content:space-around} .justify-around{justify-content:space-around}
.justify-between{justify-content:space-between} .justify-between{justify-content:space-between}
.justify-center{justify-content:center} .justify-center{justify-content:center}
.justify-stretch{justify-content:stretch}
.flex-wrap{flex-wrap:wrap} .flex-wrap{flex-wrap:wrap}
.align-center{ align-items: center} .align-center{ align-items: center}
.align-stretch{ align-items: stretch} .align-stretch{ align-items: stretch}
.align-start{ align-items: flex-start} .align-start{ align-items: flex-start}
.align-baseline{align-items: baseline}
.align-end{ align-items: flex-end} .align-end{ align-items: flex-end}
.content-start {align-content: flex-start} .content-start {align-content: flex-start}
@ -35,6 +39,10 @@
.flex-5{flex: 5} .flex-5{flex: 5}
.flex-shrink{flex-shrink: 0} .flex-shrink{flex-shrink: 0}
.gap20rpx{gap: 20rpx}
.gap30rpx{gap: 30rpx}
.gap60rpx{gap: 60rpx}
.w-1 {width: 10%;min-width: 75rpx} .w-1 {width: 10%;min-width: 75rpx}
.w-2 {width: 20%;min-width: 150rpx} .w-2 {width: 20%;min-width: 150rpx}
.w-3 {width: 30%;min-width: 225rpx} .w-3 {width: 30%;min-width: 225rpx}
@ -57,7 +65,6 @@
.h-9 {height: 90vh} .h-9 {height: 90vh}
.h-10 {height: 100vh} .h-10 {height: 100vh}
.m-0 {margin: 0} .m-0 {margin: 0}
.m-auto{margin: auto} .m-auto{margin: auto}
.m-1 {margin: 10rpx} .m-1 {margin: 10rpx}
@ -85,6 +92,7 @@
.mb-1 {margin-bottom: 10rpx} .mb-1 {margin-bottom: 10rpx}
.mb-1_5 {margin-bottom: 15rpx} .mb-1_5 {margin-bottom: 15rpx}
.mb-2 {margin-bottom: 20rpx} .mb-2 {margin-bottom: 20rpx}
.mb-2_4 {margin-bottom: 24rpx}
.mb-3 {margin-bottom: 30rpx} .mb-3 {margin-bottom: 30rpx}
.mb-4 {margin-bottom: 40rpx} .mb-4 {margin-bottom: 40rpx}
.mb-5 {margin-bottom: 50rpx} .mb-5 {margin-bottom: 50rpx}
@ -231,6 +239,8 @@
.font-68{font-size: 68rpx;line-height: 1;} .font-68{font-size: 68rpx;line-height: 1;}
.font-weight{font-weight: bold} .font-weight{font-weight: bold}
.bold {font-weight: bold}
.text-indent{text-indent:2;} .text-indent{text-indent:2;}
.text-through{text-decoration:line-through;} .text-through{text-decoration:line-through;}
.text-left { text-align: left;} .text-left { text-align: left;}
@ -265,14 +275,16 @@
.text-hover-danger { color: #a71d2a;} .text-hover-danger { color: #a71d2a;}
.text-light { color: #f8f9fa;} .text-light { color: #f8f9fa;}
.text-hover-light { color: #cbd3da;} .text-hover-light { color: #cbd3da;}
.text-dark { color: #343a40;} .text-dark { color: #323635;}
.text-hover-dark{ color: #121416;} .text-hover-dark{ color: #121416;}
.text-body { color: #212529;} .text-body { color: #212529;}
.text-muted { color: #909795;} .text-muted { color: #909795;}
.text-black {color: #000 }
.lh-28 {line-height: 28rpx} .lh-28 {line-height: 28rpx}
.lh-40 {line-height: 40rpx} .lh-40 {line-height: 40rpx}
.lh1_2 {line-height: 1.2}
.lh1_5 {line-height: 1.5}
/* 圆角 */ /* 圆角 */
.rounded { border-radius: 8rpx;} .rounded { border-radius: 8rpx;}
@ -329,6 +341,10 @@
.border-none{border: none} .border-none{border: none}
.clip {
overflow: hidden;
}
.text-row1 { .text-row1 {
display: -webkit-box; display: -webkit-box;
text-overflow: ellipsis; text-overflow: ellipsis;
@ -337,4 +353,67 @@
-webkit-line-clamp:1; -webkit-line-clamp:1;
} }
.text-row3 {
display: -webkit-box;
text-overflow: ellipsis;
overflow: hidden;
-webkit-box-orient:vertical;
-webkit-line-clamp:3;
}
.hr-solid,
.hr-dashed {
box-sizing: border-box;
height: 1px;
transform: scaleY(1);
border-bottom-color: #000;
border-bottom-width: 1px;
transform-origin: bottom center;
opacity: 0.05;
}
.hr-solid {
border-bottom-style: solid;
}
.hr-dashed {
border-bottom-style: dashed;
}
.relative {position: relative}
.absolute {position: absolute}
.absolute-impt{position: absolute !important;}
.fixed {position: fixed}
.sticky {position: sticky}
.top {top: 0}
.right{right: 0}
.bottom{bottom: 0}
.left{left: 0}
.divided::after {
position: absolute;
left: 0;
right: 0;
bottom: 0;
height: 1px;
transform: scaleY(0.5);
transform-origin: bottom center;
content: "";
background: #000;
opacity: 0.05;
}
.z1{
z-index: 1;
}
.z2{
z-index: 2;
}
.z3{
z-index: 3;
}
.word-break{
word-break: break-all;
}

@ -32,19 +32,26 @@ function updateApp() {
function App(props) { function App(props) {
Taro.useLaunch(() => { Taro.useLaunch(() => {
updateApp() updateApp()
storageDep.remove() storageDep.remove()
}) })
Taro.getSystemInfo({ Taro.getSystemInfo({
success(res) { success({statusBarHeight = 0, screenWidth, screenHeight, windowHeight, safeArea}) {
const isIos = Taro.getSystemInfoSync().platform === 'ios';
const {top, height} = Taro.getMenuButtonBoundingClientRect()
const textBarHeight = (top - statusBarHeight) * 2 + height
Taro.getApp().globalData = { Taro.getApp().globalData = {
statusBarHeight: res.statusBarHeight || 0, statusBarHeight,
screenWidth: res.screenWidth, screenWidth,
screenHeight: res.screenHeight, screenHeight,
safeArea: res.safeArea, windowHeight,
safeArea,
isIos,
textBarHeight,
pageHeight: screenHeight - statusBarHeight - textBarHeight - (screenHeight - (safeArea?.bottom || 0)),
menu: Taro.getMenuButtonBoundingClientRect(),
} }
} }
}) })
@ -58,7 +65,6 @@ function App(props) {
useDidHide(() => { useDidHide(() => {
}) })
return ( return (
<CustomWrapper> <CustomWrapper>
<Profile.Provider> <Profile.Provider>

File diff suppressed because one or more lines are too long

@ -0,0 +1,88 @@
import { CSSProperties, FC } from "react"
import { Text, ITouchEvent } from "@tarojs/components"
import './icon.css'
export type IconName =
| 'arrow-clockwise'
| 'arrow-counterclockwise'
| 'backspace'
| 'bar-chart'
| 'bar-chart-line'
| 'caret-down'
| 'caret-up'
| 'caret-left'
| 'caret-right'
| 'check'
| 'check-all'
| 'chevron-double-down'
| 'chevron-double-up'
| 'chevron-double-left'
| 'chevron-double-right'
| 'chevron-down'
| 'chevron-up'
| 'chevron-left'
| 'chevron-right'
| 'circle'
| 'exclamation'
| 'gear'
| 'gender-female'
| 'gender-male'
| 'heart'
| 'info'
| 'list'
| 'lock'
| 'unlock'
| 'play-btn'
| 'play'
| 'question'
| 'shield'
| 'square'
| 'star'
| 'three-dots-vertical'
| 'three-dots'
| 'trash'
| 'x'
export type IconShape =
| 'circle'
| 'square'
| 'diamond'
| 'octagon'
| 'triangle'
export interface IconProps {
name: IconName
shape?: IconShape
color?: string
size?: string | number
fill?: boolean
className?: string
style?: CSSProperties
onClick?: (event: ITouchEvent) => void
}
const XgIcon: FC<IconProps> = (props) => {
const name = [
props.name,
props.shape,
props.fill ? 'fill' : undefined
].filter(Boolean).join('-')
const color = props.color ?? 'currentColor'
const size = typeof props.size === 'string' ? props.size : `${props.size ?? 16}px`
const fontStyle: CSSProperties = {
...(props.style ?? {}),
fontSize: size,
color,
}
return (
<Text
className={`xg-icon xg-icon-${name} ${props.className}`}
style={fontStyle}
onClick={props.onClick}
/>
)
}
export default XgIcon

@ -0,0 +1,40 @@
.artcles {
padding: 30rpx 0;
border-bottom: 1px solid #F5F8F7;
}
.outside {
display: flex;
justify-content: space-between;
.title {
font-size: 28rpx;
font-family: PingFang SC-Bold, PingFang SC;
font-weight: bold;
color: #323635;
}
.intro {
font-size: 24rpx;
font-family: PingFang SC-Medium, PingFang SC;
font-weight: 500;
color: #909795;
margin-top: 10rpx;
}
}
.img {
overflow: hidden;
border-radius: 10rpx;
}
.pageView {
font-size: 22rpx;
font-family: PingFang SC-Medium, PingFang SC;
font-weight: 500;
color: #909795;
margin-top: 20rpx;
}

@ -0,0 +1,68 @@
import {FC, useEffect, useState} from "react";
import {Text, View} from "@tarojs/components";
import Taro from "@tarojs/taro";
import styles from './articlesBox.module.scss'
import Img from "@/components/image/image";
import {beforeTime} from "@/utils/time";
import articlesEvent from "@/hooks/articlesEvent";
interface Props {
data: Articles
}
const ArticlesBox: FC<Props> = (props) => {
const [data, setData] = useState(props.data)
useEffect(() => {
setData(props.data)
}, [props.data])
useEffect(() => {
articlesEvent.recordsOn(data.id, ({view}) => {
setData({
...data,
page_view: view
})
})
}, [])
Taro.useUnload(() => {
articlesEvent.off(data.id)
})
Taro.useDidShow(()=>{
articlesEvent.recordsOn(data.id, ({view}) => {
setData({
...data,
page_view: view
})
})
})
const toArticlePage = () => {
Taro.navigateTo({
url: `/pages/preview/brand/article/article?id=${data.id}`
})
}
return (
<View className={styles.artcles} onClick={toArticlePage}>
<View className={styles.outside}>
<View className='flex-1 mr-2'>
<View className={`${styles.title} text-ellipsis-1`}>{data.title}</View>
<View className={`${styles.intro} text-ellipsis-2`}>{data.intro}</View>
<View className={styles.pageView}>
<Text>{beforeTime(data.created_at)}</Text>·
<Text>{data.page_view || 0}</Text>
</View>
</View>
<Img src={data.cover || ''}
errorType={data.owner_type === 1 ? 'brand' : 'health'}
width={148}
height={116}
className={styles.img}/>
</View>
</View>
)
}
export default ArticlesBox

@ -0,0 +1,73 @@
.collect {
width: 130rpx;
display: flex;
align-items: center;
color: #909795;
font-size: 24rpx;
padding: 0 10rpx;
}
.collectImage {
position: relative;
width: 32rpx;
height: 32rpx;
margin-right: 10rpx;
//Image {
// width: 100%;
// height: 100%;
//}
}
.zoom {
//top: 0;
//left: 0;
position: absolute;
content: '';
width: 32rpx;
height: 32rpx;
border-radius: 50%;
border: 1px solid #FF9E5F;
padding: 0 !important;
margin: 0 !important;
animation: Zooms 300ms ease forwards;
opacity: 1;
transform-origin: -100%, -100% 0;
}
.pulse {
animation: Pulse 300ms;
}
@keyframes Zooms {
0% {
transform: scale(1);
}
30% {
transform: scale(1.5);
left: -5%;
top: -5%;
opacity: 1;
}
100% {
transform: scale(1.5);
left: -5%;
top: -5%;
opacity: 0;
}
}
@keyframes Pulse {
0% {
transform: scale(.2);
}
60% {
transform: scale(1.2);
}
80% {
transform: scale(.8);
}
100% {
transform: scale(1);
}
}

@ -0,0 +1,78 @@
import {CSSProperties, FC, useCallback, useEffect, useState} from "react";
// import star from '@/static/img/star.png'
// import starLine from '@/static/img/starLine.png'
import {View} from "@tarojs/components";
import styles from './collect.module.scss'
import {Profile} from "@/store"
import Taro from "@tarojs/taro";
import {userApi} from "@/api";
import IconFont from "@/components/IconFont";
interface Props {
onClick?: () => void
select?: boolean
stylesImage?: CSSProperties
styles?: CSSProperties
owner_id: number
owner_type: CreateOwnerType
textHidden?: boolean
onUpdate?: (v: boolean) => void
}
const versions = new Map()
const collect = async (scope: string, params: Create, setSelect: (v: boolean) => void, value: boolean) => {
const localVersion = (versions.get(scope) || 0) + 1
versions.set(scope, localVersion)
await userApi.create(params)
if (localVersion === versions.get(scope)) {
setSelect(value)
}
}
/** 收藏 */
const Collect: FC<Props> = (props) => {
const {token} = Profile.useContainer()
const [loading, setLoading] = useState(false)
const [select, setSelect] = useState(props.select)
useEffect(() => {
setSelect(props.select)
}, [props])
const change = useCallback(async () => {
if (!token) {
Taro.navigateTo({url: '/pages/login/login'})
return
}
props.onClick?.()
setSelect(!select)
setLoading(true)
collect(`${props.owner_id}#${props.owner_type}`, {
owner_id: props.owner_id,
owner_type: props.owner_type,
}, (v) => {
setSelect(v)
props.onUpdate?.(v)
}, !select)
setTimeout(() => {
setLoading(false)
}, 300)
}, [loading, token, select])
return (
<View className={styles.collect} onClick={change} style={props.styles}>
<View className={styles.collectImage} style={props.stylesImage}>
{/*<View className={`${loading && select && styles.zoom}`}/>*/}
<View className={`${loading ? styles.pulse : ''}`}>
<IconFont name='star' fill={!!(select && token)} color={(select && token) ? '#FF9E5F' : undefined} />
</View>
{/*<Image src={(select && token) ? star : starLine} className={`${loading && styles.pulse}`}/>*/}
</View>
{/*{!props.textHidden && (select ? '已收藏' : '收藏')}*/}
{!props.textHidden && '收藏'}
</View>
)
}
export default Collect

@ -84,11 +84,11 @@
@keyframes childrenCenter { @keyframes childrenCenter {
from { from {
transform: scale(0); transform: scale(0) translateY(calc(-50% - 100px));
padding: 0; padding: 0;
} }
to { to {
transform: scale(1); transform: scale(1) translateY(calc(-50% - 100px));
padding: 0 30px; padding: 0 30px;
} }
} }

@ -49,7 +49,9 @@ export const PageContainerInner: FC<PageContainerProps> = (props) => {
case 'center': case 'center':
style.width = '90%' style.width = '90%'
style.borderRadius = '10px' style.borderRadius = '10px'
style.padding = '0' style.padding = '0 !important'
style.top = '50%'
// style.transform = 'translateY(-100px) !important'
break break
} }
@ -89,14 +91,14 @@ export const PageContainerInner: FC<PageContainerProps> = (props) => {
const CustomPageContainer: FC<PageContainerProps> = (props) => { const CustomPageContainer: FC<PageContainerProps> = (props) => {
if (props.position === 'center') { if (props.position === 'center') {
return <PageContainerInner {...props} /> return <PageContainerInner {...props} onAfterLeave={props.onClickOverlay}/>
} }
if (process.env.TARO_ENV !== 'h5') { if (process.env.TARO_ENV !== 'h5') {
return (<PageContainer {...props} round> return (<PageContainer {...props} round onAfterLeave={props.onClickOverlay}>
<View className={styles.content} style={{position: 'relative'}}>{props.children}</View> <View className={styles.content} style={{position: 'relative'}}>{props.children}</View>
</PageContainer>) </PageContainer>)
} }
return (<PageContainerInner {...props} />); return (<PageContainerInner {...props} onAfterLeave={props.onClickOverlay}/>);
} }
export default CustomPageContainer export default CustomPageContainer

@ -3,6 +3,9 @@
text-align: center; text-align: center;
color: #6c757d; color: #6c757d;
position: relative; position: relative;
display: flex;
flex-direction: column;
align-items: center;
} }
.image { .image {
@ -14,9 +17,5 @@
.name { .name {
font-size: 30rpx; font-size: 30rpx;
position: absolute;
left: 0;
right: 0;
bottom: 50rpx;
margin: auto; margin: auto;
} }

@ -1,17 +1,29 @@
import {FC} from "react"; import {FC} from "react";
import {Image, View} from "@tarojs/components"; import {Button, Image, View} from "@tarojs/components";
import emptyImg from '@/static/img/empty.png' import emptyImg from '@/static/img/empty.png'
import styles from './empty.module.scss' import styles from './empty.module.scss'
import MyButton from "@/components/button/MyButton";
import Taro from "@tarojs/taro";
interface Props { interface Props {
name: string name: string
showBack?: boolean
onRefresh?:() => void
} }
const Empty: FC<Props> = ({name}) => { const Empty: FC<Props> = ({name, showBack, onRefresh}) => {
const hasBack = showBack && Taro.getCurrentPages().length > 1
const hasRefresh = onRefresh != null
return ( return (
<View className={styles.empty}> <View className={styles.empty}>
<Image src={emptyImg} mode='widthFix' className={styles.image}/> <Image src={emptyImg} mode='widthFix' className={styles.image} fadeIn lazyLoad/>
<View className={styles.name}>{name}</View> <View className={styles.name}>{name}</View>
{(hasBack || hasRefresh) && (
<View className='flex justify-center align-center gap60rpx mt-3'>
{hasBack && <Button type={'default'} size={'mini'} style={{padding:'0 20px'}} onClick={() => Taro.navigateBack()}></Button>}
{hasRefresh && <MyButton size={'mini'} style={{padding:'0 20px'}} onClick={onRefresh} type="primary"></MyButton>}
</View>
)}
</View> </View>
) )
} }

@ -213,12 +213,17 @@ export interface IconProps {
size?: string | number size?: string | number
color?: string color?: string
onClick?: (event: ITouchEvent) => void onClick?: (event: ITouchEvent) => void
bold?: boolean
} }
const Icon: FC<IconProps> = (props) => { const Icon: FC<IconProps> = (props) => {
const size = typeof props.size === 'string' ? props.size : `${props.size ?? 16}px` const size = typeof props.size === 'string' ? props.size : `${props.size ?? 16}px`
const color = props.color ?? 'currentColor' const color = props.color ?? 'currentColor'
const fontStyle: CSSProperties = {fontSize: size, color} const fontStyle: CSSProperties = {
fontSize: size,
color,
fontWeight: props.bold ? 'bold' : undefined,
}
return ( return (
<Text className={`icon icon-${props.name}`} style={fontStyle} onClick={props.onClick} /> <Text className={`icon icon-${props.name}`} style={fontStyle} onClick={props.onClick} />

@ -1,28 +1,72 @@
import {FC, useEffect, useState} from "react"; import {FC, useEffect, useState} from "react";
import {Image, ImageProps, View} from "@tarojs/components"; import {Image, ImageProps, View} from "@tarojs/components";
import {AtActivityIndicator} from "taro-ui";
import shard from '@/static/img/shard.png' import shard from '@/static/img/shard.png'
import Taro from "@tarojs/taro";
import avatar from '@/static/img/avatar.png'
import healthShard from '@/static/img/healthShard.png'
import professionShard from '@/static/img/professionShard.png'
import brandSecond from '@/static/img/brandSecond.png'
import courseShard from '@/static/img/courseShard.png'
interface Props extends ImageProps { interface Props extends ImageProps {
width: number width?: number | string
height: number height?: number | string
fallback?: string fallback?: string
errorType?: ImgErrType
loadingImage?: string
errorImage?: string
fit?: boolean // 当网络图片加载完成后高度自动
} }
const Img: FC<Props> = ({src, mode = 'aspectFill', width, height, fallback = shard}) => { const Img: FC<Props> = ({src, mode = 'aspectFill', width, fallback = shard, ...props}) => {
const [isError, setIsError] = useState(false) const [isError, setIsError] = useState(false)
const [loading, setLoading] = useState(true) const [loading, setLoading] = useState(true)
const [errorUrl, setErrorUrl] = useState(fallback)
const [height, setHeight] = useState(props.height)
const imgAnimation = Taro.createAnimation({duration: 0}).opacity(0).step()
const [animationData, setAnimationData] = useState<TaroGeneral.IAnyObject>(imgAnimation.export())
useEffect(() => { useEffect(() => {
if (!src) { if (!isError && props.fit) {
setIsError(true) Taro.getImageInfo({
setLoading(false) src,
} else { success() {
setIsError(false) setHeight(undefined)
setLoading(false) }
})
} }
setIsError(!src)
setLoading(!!src)
}, [src]) }, [src])
useEffect(() => {
if (props.errorImage) {
setErrorUrl(props.errorImage)
return
}
switch (props.errorType) {
case undefined:
case "acquiesce":
setErrorUrl(fallback)
break
case "avatar":
setErrorUrl(avatar)
break
case 'health':
setErrorUrl(healthShard)
break
case 'profession':
setErrorUrl(professionShard)
break
case 'brand':
setErrorUrl(brandSecond)
break
case 'course':
setErrorUrl(courseShard)
break
}
}, [props.errorType])
// 图片加载失败 // 图片加载失败
function onErrorHandler() { function onErrorHandler() {
setLoading(false) setLoading(false)
@ -32,26 +76,44 @@ const Img: FC<Props> = ({src, mode = 'aspectFill', width, height, fallback = sha
function onLoadHandler() { function onLoadHandler() {
setLoading(false) setLoading(false)
setIsError(false) setIsError(false)
imgAnimation.opacity(1).step({duration: 200})
setAnimationData(imgAnimation.export())
} }
return ( return (
<View style={{width: `${width}rpx`, height: `${height}rpx`, backgroundColor: 'eee'}}> <View className={props?.className}
style={{
overflow: 'hidden',
width: width ? typeof width === 'string' ? width : `${width}rpx` : "100%",
height: height ? typeof height === 'string' ? height : `${height}rpx` : "100%",
backgroundColor: (isError || !loading) ? 'transparent' : '#F8F8F8',
}}>
{!isError && {!isError &&
<Image <View animation={animationData} style={{height: '100%', width: '100%'}}>
src={src} <Image
mode={mode} {...props}
style={{width: `${width}rpx`, height: `${height}rpx`}} src={src}
onError={onErrorHandler} mode={mode}
onLoad={onLoadHandler}> lazyLoad
</Image> fadeIn
} defaultSource={errorUrl}
{ style={{
loading && width: width ? `${width}rpx` : "100%",
<AtActivityIndicator mode={"center"} content='加载中...'/> height: height ? `${height}rpx` : "100%",
verticalAlign: 'middle'
}}
onError={onErrorHandler}
onLoad={onLoadHandler}/>
</View>
} }
{ {
isError && !loading && isError && !loading &&
<Image mode={'aspectFill'} src={fallback} style={{width: `${width}rpx`, height: `${height}rpx`}}></Image> <Image
mode='aspectFill'
src={errorUrl}
lazyLoad
fadeIn
style={{width: "100%", height: '100%'}}/>
} }
</View> </View>
) )

@ -0,0 +1,16 @@
.box {
background: #fff;
border-radius: 20rpx;
display: flex;
align-items: center;
box-sizing: border-box;
line-height: 1.75;
}
.total {
font-size: 40rpx;
font-family: PingFang SC-Heavy, PingFang SC;
font-weight: 800;
color: #323635;
margin-top: 48rpx;
}

@ -0,0 +1,99 @@
import {Text, View} from "@tarojs/components";
import Tabs, {OnChangOpt, TabList} from "@/components/tabs/tabs";
import {everyDay, formatTime, getMonday, getSunday, monthEnd, monthFirst} from "@/utils/time";
import LineChart from "@/components/lineChart/lineChart";
import {CSSProperties, FC, useEffect, useState} from "react";
import {StatisticsParam, userApi} from "@/api";
import styles from './learningRecord.module.scss'
import Spin from "@/components/spinner";
import {Profile} from "@/store";
import Taro from "@tarojs/taro";
import debounce from "@/utils/debounce";
const tabList: TabList<any>[] = [
{
title: '月', value: {
start_time: monthFirst(),
end_time: monthEnd()
}
},
{
title: '周', value: {
start_time: getMonday(),
end_time: getSunday()
}
},
{
title: '日',
value: {
start_time: new Date().setHours(0, 0, 0, 0),
end_time: new Date().setHours(24, 0, 0, 0)
}
},
]
interface Props {
userId?: string | number
style?: CSSProperties
className?: string
}
/**
* 线
*/
const LearningRecord: FC<Props> = ({userId, style, className}) => {
const [lineData, setLineData] = useState<any[]>([])
const [loading, setLoading] = useState(false)
const {token} = Profile.useContainer()
async function getStatistics(data: StatisticsParam) {
try {
if (!userId) return;
setLineData([])
const res = await userApi.statistics(userId, data)
const everyDayValue = everyDay(data.start_time, Number(data.end_time), res.data)
setLineData(everyDayValue)
} catch (e) {
setLineData([])
}
}
// @ts-ignore
function tabChange({tab}: OnChangOpt<StatisticsParam>) {
if (!token) {
Taro.navigateTo({url: '/pages/login/login'})
return
}
setLoading(true)
debounce(() => {
try {
getStatistics(tab?.value! as StatisticsParam).then()
} catch (e) {
}
setLoading(false)
})
}
useEffect(() => {
userId && setTimeout(() => {
getStatistics(tabList[0].value)
}, 500)
}, [userId])
return (<View className={[styles.box, className].filter(Boolean).join(' ')} style={{display: 'block', ...style}}>
<Tabs tabList={tabList} onChange={tabChange} backMode/>
<View style={{position: "relative"}}>
{loading && <Spin enable={loading} block/>}
<View className={styles.total}>
<Text style={{margin: '0 10px', color: '#00D6AC'}}>
{formatTime(lineData.reduce((pre, cur) => pre + cur.value, 0) || 0)}
</Text>
</View>
<LineChart data={lineData}/>
</View>
</View>)
}
export default LearningRecord

@ -3,8 +3,18 @@
align-items: flex-end; align-items: flex-end;
justify-content: left; justify-content: left;
flex-wrap: nowrap; flex-wrap: nowrap;
height: 420px; height: 380px;
position: relative; position: relative;
padding-bottom: 10rpx;
}
.overlay {
width: 100%;
height: 10rpx;
background: #fff;
position: absolute;
bottom: 0;
left: 0
} }
.empty { .empty {
@ -13,12 +23,31 @@
bottom: 0; bottom: 0;
left: 0; left: 0;
right: 0; right: 0;
border-radius: 10rpx;
color: #00D6AC; color: #00D6AC;
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
background: rgba(#fff, .9); background: rgba(#fff, .9);
view {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
display: flex;
justify-content: center;
align-items: center;
}
}
.records {
font-size: 28rpx;
font-family: PingFang SC-Medium, PingFang SC;
font-weight: 500;
color: #323635;
margin-top: 10rpx;
margin-bottom: 42rpx;
} }
.columnBox { .columnBox {
@ -31,10 +60,27 @@
width: 30rpx; width: 30rpx;
background: linear-gradient(180deg, #03D9B3 0%, #05BF88 100%); background: linear-gradient(180deg, #03D9B3 0%, #05BF88 100%);
border-radius: 30rpx; border-radius: 30rpx;
margin-bottom: 20rpx; margin-bottom: 6rpx;
overflow: hidden; overflow: hidden;
animation: rise 300ms ease-in-out forwards; animation: rise 300ms ease-in-out forwards;
max-height: 0; max-height: 0;
margin-top: 6rpx;
}
.value {
whiteSpace: nowrap;
font-size: 20rpx;
font-family: PingFang SC-Bold, PingFang SC;
font-weight: bold;
color: #606563;
line-height: 20rpx;
}
.time {
font-size: 20rpx;
font-family: PingFang SC-Bold, PingFang SC;
font-weight: bold;
color: #323635;
} }
.line { .line {

@ -1,7 +1,8 @@
import {ScrollView, View} from "@tarojs/components"; import {Image, ScrollView, Text, View} from "@tarojs/components";
import {FC, useEffect, useState} from "react"; import {FC, useEffect, useState} from "react";
import style from './lineChart.module.scss' import style from './lineChart.module.scss'
import {formatMinute} from "@/utils/time"; import {formatDateTime, formatTime} from "@/utils/time";
import emptyLineChart from '@/static/img/emptyLineChart.png'
export interface lineData { export interface lineData {
time: string time: string
@ -12,10 +13,11 @@ interface Props {
data: lineData[] data: lineData[]
} }
const height = 180 const height = 150
const LineChart: FC<Props> = ({data}) => { const LineChart: FC<Props> = ({data}) => {
const [maxHeight, setMaxHeight] = useState<lineData>({time: '', value: 0}) const [maxHeight, setMaxHeight] = useState<lineData>({time: '', value: 0})
const [lineChartList, setLineChartList] = useState(data) const [lineChartList, setLineChartList] = useState(data)
console.log(maxHeight,lineChartList,'maxHeight')
useEffect(() => { useEffect(() => {
setLineChartList(data) setLineChartList(data)
@ -24,28 +26,42 @@ const LineChart: FC<Props> = ({data}) => {
return cur return cur
} }
return pre return pre
}, {time: '', value: 0})) }, {time: formatDateTime(new Date(), 'MM/dd'), value: 0}))
}, [data]) }, [data])
return ( return (
<> <View style={{width: '100%', position: 'relative'}}>
<ScrollView scrollX={!!maxHeight.value}> <View className={style.records}>
<View style={{marginBottom: '30px'}}>{maxHeight.time}{formatMinute(maxHeight.value)}</View> {/*<Text>{maxHeight.time}</Text>*/}
<Text>{maxHeight.time.split('/').join('月')}</Text>
{maxHeight.value > 0 ? `日最努力` : `期间没有学习记录`}{formatTime(maxHeight.value, 0)}
</View>
<ScrollView scrollX={!!maxHeight.value} enhanced showScrollbar={false} type='list'>
<View className={style.lineChart}> <View className={style.lineChart}>
{!maxHeight.value && <View className={style.empty}></View>}
{ {
!!maxHeight.value && lineChartList.map(d => <View key={d.time}> !maxHeight.value && <View className={style.empty}>
<View className={style.columnBox} style={{width: "100px"}}> <View></View>
<View className={style.line} style={{height: height - 10 - (d.value / maxHeight.value * height) + "px"}}></View> <Image src={emptyLineChart} mode='widthFix' style={{width: '100%'}}/>
{d.value > 0 && <View>{formatMinute(d.value)}</View>} </View>
<View className={style.column} style={{height: d.value / maxHeight.value * height + "px"}}> </View> }
<View>{d.time}</View> {
!!maxHeight.value
&& lineChartList.map(d => <View key={d.time}>
<View className={style.columnBox} style={{width: "80px"}}>
<View className={style.line} style={{height: height - 10 - (d.value / maxHeight.value * height) + "px"}}/>
{
d.value > 0 && <View className={style.value}>{formatTime(d.value, 0)}</View>
}
<View className={style.column} style={{height: d.value / maxHeight.value * height + "px"}}/>
<View className={style.time}>{d.time}</View>
</View> </View>
</View>) </View>)
} }
</View> </View>
</ScrollView> </ScrollView>
</> <View className={style.overlay}/>
</View>
) )
} }

@ -0,0 +1,38 @@
.fixedBox {
position: relative;
width: 100vw;
height: 100vw;
background: linear-gradient(to bottom, rgba(255, 255, 255, 0), rgba(255, 255, 255, 1), rgba(255, 255, 255, 1));
//border: 1px solid #f40;
&-inner {
position: absolute;
width: 100vw;
bottom: 24rpx;
display: flex;
flex-direction: column;
align-items: center;
&-icon {
image {
width: 32rpx;
height: 32rpx;
}
}
&-box {
margin-top: 24rpx;
width: 680rpx;
left: 35rpx;
height: 76rpx;
background-color: #45D4A8;
color: #fff;
line-height: 76rpx;
text-align: center;
font-size: 32rpx;
font-weight: 500;
border-radius: 8rpx 8rpx 8rpx 8rpx;
}
}
}

@ -0,0 +1,28 @@
import styles from "./index.module.scss";
import { Text, View} from "@tarojs/components";
import Taro from "@tarojs/taro";
import {CSSProperties, FC} from "react";
import IconFont from "@/components/IconFont";
type Props = {
className?: string
style?: CSSProperties
}
const LoginView3: FC<Props> = ({className,style}) => {
return (
<View className={`${styles.fixedBox} ${className}`} style={style}>
<View className={styles['fixedBox-inner']}>
<View className={styles['fixedBox-inner-icon']}>
<IconFont color='#909795' name='chevron-double-down'/>
</View>
<View className={styles['fixedBox-inner-box']}
onClick={() => Taro.navigateTo({url: '/pages/login/login'})}>
<Text>1</Text>
</View>
</View>
</View>
)
}
export default LoginView3

@ -0,0 +1,47 @@
.content {
position: relative;
width: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
image {
width: 320rpx;
height: 208rpx;
}
.title {
font-size: 28rpx;
font-weight: bold;
color: #323635;
}
.label {
font-size: 24rpx;
font-weight: 500;
color: #909795;
line-height: 24rpx;
margin: 20rpx 0 ;
}
.button {
padding: 0 60rpx;
height: 76rpx;
background: #45D4A8;
border-radius: 38rpx 38rpx 38rpx 38rpx;
color: #fff;
line-height: 76rpx;
text-align: center;
font-size: 32rpx;
font-weight: 500;
}
.nextLabel {
font-size: 24rpx;
font-weight: 500;
color: #909795;
line-height: 24rpx;
margin-top: 15rpx;
}
}

@ -0,0 +1,102 @@
import {CSSProperties, FC, useState} from "react";
import {View, Image} from "@tarojs/components";
import styles from './index.module.scss'
import NoLogin from '@/static/img/noLogin.png'
import {Profile} from "@/store";
import Taro from "@tarojs/taro";
import {userApi} from "@/api";
interface Props {
tips?: string
height?: number
paddingTop?: number
style?: CSSProperties
offImage?: boolean
onSuccess?: VoidFunction
}
export const LoginView: FC<Props> = (props) => {
const [isLoading, setLoading] = useState(false)
const {setUser, setToken, setCompany} = Profile.useContainer()
const sizeStyle: CSSProperties = {
height: `${props.height || 1000}rpx`,
paddingTop: `${props.paddingTop || 0}rpx`,
...props.style,
}
function login() {
if (isLoading) return;
Taro.showLoading({title: '微信授权中...'})
setLoading(true)
Taro.login({
success: async (res) => {
try {
const {catch_key, user, token, company} = await userApi.login(res.code)
Taro.hideLoading()
if (token) {
Taro.showToast({title: '授权成功', duration: 1500, icon: 'success', mask: true})
setTimeout(() => {
setUser(user)
setToken(token)
setCompany(company)
setLoading(false)
if (props.onSuccess) {
props.onSuccess()
} else {
Taro.switchTab({url: '/pages/home/home'})
}
}, 1500)
} else {
Taro.setStorageSync('openid', catch_key)
Taro.reLaunch({url: '/pages/check/check'})
}
} catch (e) {
Taro.hideLoading()
}
setLoading(false)
}
})
}
return (
<View className={styles.content} style={sizeStyle}>
{
!props.offImage && <>
<Image src={NoLogin}/>
<View className={styles.title}></View>
</>
}
<View className={styles.label}></View>
<View onClick={login} className={styles.button}></View>
</View>
)
}
const LoginView2: FC<Props> = (props) => {
const sizeStyle: CSSProperties = {
height: `${props.height || 1000}rpx`,
paddingTop: `${props.paddingTop || 0}rpx`,
...props.style,
}
return (
<View className={styles.content} style={sizeStyle}>
{
!props.offImage && <>
<Image src={NoLogin}/>
{/*<View className={styles.title}>暂未登录</View>*/}
</>
}
<View onClick={() => {
Taro.navigateTo({
url:'/pages/login/login'
})
}} className={styles.button}></View>
</View>
)
}
export default LoginView2

@ -0,0 +1,34 @@
.navigation {
position: sticky;
top: 0;
left: 0;
width: 100%;
z-index: 100;
overflow: hidden;
background: #fff;
}
.leftNode {
position: absolute;
display: flex;
left: 20rpx;
bottom: 0;
align-items: center;
z-index: 1;
}
.text {
position: absolute;
left: 20rpx;
right: 0;
margin: auto;
display: flex;
justify-content: center;
align-items: center;
font-size: 28rpx;
}
.arrow {
width: 32px;
height: 32px;
}

@ -0,0 +1,60 @@
import {Image, View} from "@tarojs/components";
import React, {CSSProperties, FC, ReactNode, useMemo} from "react";
import styles from './navigationBar.module.scss'
import Taro from "@tarojs/taro";
import leftArrow from '@/static/img/leftArrow.png'
interface Props {
// 文本
text?: string | ReactNode
children?: ReactNode | ReactNode[]
// 左边节点
leftNode?: string | ReactNode | ReactNode[]
// 字体颜色
color?: string
// 背景颜色
backgroundColor?: string
// 取消返回按钮
cancelBack?: boolean
// 跟随页面滚动
inherit?: boolean
className?: string
style?: CSSProperties
}
const NavigationBar: FC<Props> = (props) => {
const globalData = Taro.getApp().globalData
const navigationBarStyle = useMemo((): React.CSSProperties => ({
background: props.backgroundColor,
position: props.inherit ? 'inherit' : "sticky",
paddingTop: globalData.statusBarHeight + 'px',
height: globalData.textBarHeight + globalData.statusBarHeight + 'px',
boxSizing: 'border-box',
...props.style
}), [props])
const navigationTextStyle = useMemo((): React.CSSProperties => ({
color: props.color,
height: globalData.textBarHeight + 'px',
}), [props])
return (
<View className={`${props.className} ${styles.navigation}`} style={navigationBarStyle}>
<View style={navigationTextStyle} className={styles.leftNode}>
{
!props.cancelBack && <View className="flex flex-column justify-center align-center" style={{
width: globalData.textBarHeight + 'px',
height: globalData.textBarHeight + 'px',
}} onClick={() => Taro.navigateBack()}>
<Image src={leftArrow} mode='aspectFill' className={styles.arrow}/>
</View>
}
{props.leftNode}
</View>
<View style={navigationTextStyle} className={styles.text}>{props.children || props.text}</View>
</View>
)
}
export default NavigationBar

@ -0,0 +1,18 @@
import {CSSProperties, FC} from "react";
import {View} from "@tarojs/components";
interface Props {
text?: string
styles?: CSSProperties
className?: string
}
const PageScript: FC<Props> = (props) => {
return <View
className={'text-center font-24 text-muted py-3 ' + props.className}
style={props.styles}>
{props.text || "暂无更多"}
</View>
}
export default PageScript

@ -1,7 +1,8 @@
import {FC, ReactNode, useEffect, useState} from "react"; import {FC, ReactNode, useEffect, useState} from "react";
import {View, Image} from "@tarojs/components"; import {View} from "@tarojs/components";
import Icon from "@/components/icon"; import Icon from "@/components/icon";
import CustomPageContainer from "@/components/custom-page-container/custom-page-container"; import CustomPageContainer from "@/components/custom-page-container/custom-page-container";
import Img from "@/components/image/image";
interface Props { interface Props {
height?: number | string height?: number | string
@ -15,6 +16,7 @@ interface Props {
onClick?: () => void onClick?: () => void
no_border?: boolean no_border?: boolean
leftImage?: string leftImage?: string
errorType?: "acquiesce" | 'avatar'
} }
const PopPut: FC<Props> = ({title, chevron, content, image, isProp, children, show, ...opt}: Props) => { const PopPut: FC<Props> = ({title, chevron, content, image, isProp, children, show, ...opt}: Props) => {
@ -46,13 +48,13 @@ const PopPut: FC<Props> = ({title, chevron, content, image, isProp, children, sh
<> <>
<View className='card' onClick={click} style={style()}> <View className='card' onClick={click} style={style()}>
<View className='flex align-center'> <View className='flex align-center'>
{opt.leftImage != null && <Image src={opt.leftImage} className='mr-3 image' mode='aspectFit'/>} {opt.leftImage != null && <Img errorType={opt.errorType} width={68} height={68} src={opt.leftImage} className='mr-3' mode='aspectFit'/>}
<View>{title}</View> <View>{title}</View>
</View> </View>
<View className='card-content'> <View className='card-content'>
<View>{content}</View> <View>{content}</View>
{image && <Img src={image} mode='scaleToFill' className='image' width={68} height={68}/>}
{!chevron && <Icon name='chevron-right'/>} {!chevron && <Icon name='chevron-right'/>}
{image && <Image src={image} mode='scaleToFill' className='image'/>}
</View> </View>
</View> </View>
{ {

@ -1,6 +1,6 @@
import Taro from "@tarojs/taro"; import Taro from "@tarojs/taro";
import { View, Image } from '@tarojs/components' import {View, Image} from '@tarojs/components'
import { Component, ReactNode } from "react"; import {Component, ReactNode} from "react";
import indicator from './loading.svg' import indicator from './loading.svg'
import './style.scss' import './style.scss'
@ -12,8 +12,9 @@ type Status =
| 'completed' | 'completed'
interface Props { interface Props {
enable?: boolean enable?: boolean // 控制显现
overlay?: boolean overlay?: boolean // 页面覆盖
block?: boolean // 块级
} }
interface State { interface State {
@ -30,8 +31,8 @@ type Controller = {
} }
function createController(setState: StateSetter): Controller { function createController(setState: StateSetter): Controller {
const background = Taro.createAnimation({ duration: 600 }) const background = Taro.createAnimation({duration: 600})
const rotation = Taro.createAnimation({ duration: 600 }) const rotation = Taro.createAnimation({duration: 600})
let rotateTimer: ReturnType<typeof setTimeout> | undefined let rotateTimer: ReturnType<typeof setTimeout> | undefined
let status: Status | undefined let status: Status | undefined
@ -49,8 +50,8 @@ function createController(setState: StateSetter): Controller {
} }
// 清空动画 // 清空动画
background.export() background.step().export()
rotation.export() rotation.step().export()
// 通知 UI 刷新 // 通知 UI 刷新
if (notify) { if (notify) {
@ -66,7 +67,7 @@ function createController(setState: StateSetter): Controller {
// 旋转动画定时器 // 旋转动画定时器
const rotate = () => { const rotate = () => {
rotation.opacity(opacity).rotate(360).step({ duration: 600 }) rotation.opacity(opacity).rotate(360).step({duration: 600})
notifyListener() notifyListener()
rotateTimer = setTimeout(rotate, 600) rotateTimer = setTimeout(rotate, 600)
} }
@ -80,23 +81,20 @@ function createController(setState: StateSetter): Controller {
const onFinish = (opacity: number, nextStatus: Status) => { const onFinish = (opacity: number, nextStatus: Status) => {
const lockStatus = status const lockStatus = status
setTimeout(() => { if (lockStatus === status) {
if (lockStatus === status) { background.backgroundColor(`rgba(255,255,255,${opacity})`).step({duration: 0})
background.backgroundColor(`rgba(255,255,255,${opacity})`).step({ duration: 0 }) if (nextStatus === 'dismissed') {
if (nextStatus === 'dismissed') { clearAnimation()
clearAnimation()
}
status = nextStatus
notifyListener()
} }
}, 600) status = nextStatus
notifyListener()
}
} }
const setStatus = (newStatus: Status, opacity: number) => { const setStatus = (newStatus: Status, opacity: number) => {
if (status !== newStatus) { if (status !== newStatus) {
status = newStatus status = newStatus
setAnimation(opacity) setAnimation(opacity)
if (status === 'reverse') { if (status === 'reverse') {
onFinish(0, 'dismissed') onFinish(0, 'dismissed')
} else if (status === 'forward') { } else if (status === 'forward') {
@ -141,7 +139,6 @@ export default class Spin extends Component<Props, State> {
} }
componentDidMount(): void { componentDidMount(): void {
console.log(this.props.enable)
this.controller.setTick(this.props.enable) this.controller.setTick(this.props.enable)
} }
@ -150,7 +147,7 @@ export default class Spin extends Component<Props, State> {
} }
componentWillUnmount(): void { componentWillUnmount(): void {
this.controller.clear() this.controller.clear()
} }
shouldComponentUpdate(nextProps: Readonly<Props>, nextState: Readonly<State>): boolean { shouldComponentUpdate(nextProps: Readonly<Props>, nextState: Readonly<State>): boolean {
@ -161,9 +158,10 @@ export default class Spin extends Component<Props, State> {
render(): ReactNode { render(): ReactNode {
return ( return (
<View className={`spinner-wrapper ${this.state.status} ${this.props.overlay ? 'is-fixed' : ''}`}> <View
className={`spinner-wrapper ${this.state.status} ${this.props.overlay && 'is-fixed'} ${this.props.block && 'is-block'}`}>
<View className={`spinner ${this.state.status}`}> <View className={`spinner ${this.state.status}`}>
<Image className="spinner-icon" src={indicator} /> <Image className="spinner-icon" src={indicator}/>
</View> </View>
</View> </View>
) )

@ -1,8 +1,24 @@
.spinner-wrapper { .spinner-wrapper {
background-color: rgba( #fff, 1.0);
transition: background-color 1200ms ease-out; transition: background-color 1200ms ease-out;
&.is-block {
width: auto;
position: absolute;
z-index: 99999;
background-color: rgba(#fff, 1.0);
width: 100%;
height: 100%;
top: 0;
left: 0;
right: 0;
bottom: 0;
//left: 50%;
//top: 50%;
//transform: translate(-50%, -50%);
}
&.is-fixed { &.is-fixed {
background-color: rgba(#fff, 1.0);
z-index: 99999; z-index: 99999;
position: fixed; position: fixed;
width: 100%; width: 100%;
@ -15,7 +31,7 @@
&.reverse, &.reverse,
&.dismissed { &.dismissed {
background-color: rgba( #fff, 0.0); background-color: rgba(#fff, 0.0);
} }
&.dismissed { &.dismissed {
@ -36,6 +52,7 @@
flex-direction: column; flex-direction: column;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
width: 100%;
} }
.spinner { .spinner {

@ -1,5 +1,21 @@
View::-webkit-scrollbar { .tabsBack {
display: none !important; height: 76rpx;
background: #F5F8F7;
border-radius: 24rpx;
.tabs-item {
padding: 8rpx !important;
}
.current {
background: #FFF;
border-radius: 25rpx !important;
background-clip: content-box;
&:after {
display: none !important;
}
}
} }
.tabs { .tabs {
@ -15,22 +31,31 @@ View::-webkit-scrollbar {
display: -webkit-flex; display: -webkit-flex;
display: flex; display: flex;
text-align: center; text-align: center;
align-items: center;
} }
.tabs-item { .tabs-item {
padding: 20rpx; padding: 16rpx 25rpx;
font-size: 36rpx; font-size: 30rpx;
font-family: PingFang SC-Medium, PingFang SC; font-family: PingFang SC-Medium, PingFang SC;
font-weight: 500; font-weight: 500;
color: #606563; color: #606563;
box-sizing: border-box;
width: 100%;
border-radius: 90rpx;
transition: background-color 300ms;
&:first-child{
padding: 16rpx 16rpx 16rpx 0;
}
&:last-child{
padding: 16rpx 0 16rpx 16rpx;
}
} }
.current { .sliding{
position: relative; position: relative;
font-size: 36rpx;
font-family: PingFang SC-Bold, PingFang SC;
font-weight: bold;
color: #323635;
&:after { &:after {
position: absolute; position: absolute;
@ -48,6 +73,13 @@ View::-webkit-scrollbar {
transition: all 200ms; transition: all 200ms;
} }
} }
.current {
font-size: 36rpx;
font-family: PingFang SC-Bold, PingFang SC;
font-weight: bold;
color: #323635;
}
} }
@keyframes spread { @keyframes spread {

@ -1,4 +1,4 @@
import {FC, useEffect, useState} from "react"; import {CSSProperties, FC, useEffect, useState} from "react";
import './tabs.scss' import './tabs.scss'
import {ScrollView, View} from "@tarojs/components"; import {ScrollView, View} from "@tarojs/components";
import Taro from "@tarojs/taro"; import Taro from "@tarojs/taro";
@ -19,10 +19,15 @@ export type OnChangOpt<T = unknown> = {
interface TabsProps { interface TabsProps {
current?: number | string current?: number | string
tabList: TabList[] tabList: TabList[]
height?: number | string
onChange?: (data: OnChangOpt) => void onChange?: (data: OnChangOpt) => void
backMode?: boolean // 块级模式
style?: CSSProperties
hiddenSliding?: boolean // 取消底部滑块
scrollable?: boolean
} }
const Tabs: FC<TabsProps> = (opt: TabsProps) => { const Tabs: FC<TabsProps> = (opt) => {
const {screenWidth} = Taro.getApp().globalData const {screenWidth} = Taro.getApp().globalData
const [current, setCurrent] = useState<number | string>(opt.current || 0) const [current, setCurrent] = useState<number | string>(opt.current || 0)
const [left, setLeft] = useState(0) const [left, setLeft] = useState(0)
@ -45,19 +50,33 @@ const Tabs: FC<TabsProps> = (opt: TabsProps) => {
return index === current return index === current
} }
let children = (
<View className={'tabs-scroll ' + (opt.tabList.length < 5 ? 'justify-around' : '')}>
{opt.tabList.map((d, index) => <View
key={index}
className={'tabs-item ' + (is_current(d.value, index) && 'current ') + (!opt.hiddenSliding && 'sliding')}
onClick={(event) => onChange(event, index, d)}>
{d.title}
</View>)}
</View>
)
if (opt.scrollable != false) {
children = (
<ScrollView scrollX scrollLeft={left} scrollWithAnimation showScrollbar={false} style={{height: opt.height}}>
{children}
</ScrollView>
)
} else {
children = (
<View style={{height: opt.height}}>
{children}
</View>
)
}
return ( return (
<View className='tabs'> <View className={`tabs ${opt.backMode && 'tabsBack'}`} style={{height: opt.height, ...opt.style}}>
<ScrollView scrollX scrollLeft={left} scrollWithAnimation showScrollbar={false}> {children}
<View className={'tabs-scroll ' + (opt.tabList.length < 5 ? 'justify-around' : '')}>
{opt.tabList.map((d, index) => <View
key={index}
className={'tabs-item ' + (is_current(d.value, index) ? 'current' : null)}
onClick={(event) => onChange(event, index, d)}>
{d.title}
</View>)}
</View>
</ScrollView>
</View> </View>
) )
} }

@ -1,4 +1,4 @@
.expansion{ .expansion {
display: inline-block; display: inline-block;
padding: 0 10rpx; padding: 0 10rpx;
color: #00d6ac; color: #00d6ac;

@ -8,44 +8,43 @@ interface Props {
text: string text: string
} }
const LineEllipsis:FC<Props> = ({text}:Props) => { const LineEllipsis: FC<Props> = ({text}: Props) => {
const [disabled, setDisabled] = useState(false) const [disabled, setDisabled] = useState(false)
const [isExpansioned, setIsExpansioned] = useState(false) const [isExpansioned, setIsExpansioned] = useState(false)
const [overflow, setOverflow] = useState(false) const [overflow, setOverflow] = useState(false)
useEffect(()=>{ useEffect(() => {
init() init()
},[text]) }, [text])
function init () { function init() {
Taro.nextTick(() => { Taro.nextTick(() => {
const query = Taro.createSelectorQuery() const query = Taro.createSelectorQuery()
query.select('#Text').boundingClientRect() query.select('#Text').boundingClientRect()
query.exec((res) => { query.exec((res) => {
console.log({res})
const height = res[0].height const height = res[0].height
if(height <= 30) { if (height <= 105) {
setDisabled(true) setDisabled(true)
}else{ } else {
setOverflow(true) setOverflow(true)
} }
}) })
}) })
} }
function handleExpend (e?: ITouchEvent) { function handleExpend(e?: ITouchEvent) {
e && e.stopPropagation(); e && e.stopPropagation();
setOverflow(false) setOverflow(false)
setIsExpansioned(true) setIsExpansioned(true)
} }
function handleHide (e?: ITouchEvent) { function handleHide(e?: ITouchEvent) {
e && e.stopPropagation(); e && e.stopPropagation();
setOverflow(true) setOverflow(true)
setIsExpansioned(false) setIsExpansioned(false)
}; }
function toggle () { function toggle() {
if (disabled) return; if (disabled) return;
if (isExpansioned) { if (isExpansioned) {
handleHide(); handleHide();
@ -54,37 +53,36 @@ const LineEllipsis:FC<Props> = ({text}:Props) => {
} }
} }
return ( return (
<View <View
id="wrap" id="wrap"
style={{ style={{
position: 'relative', position: 'relative',
color: '#999795', color: '#999795',
fontSize: '25rpx', fontSize: '25rpx',
overflow: overflow ? 'hidden' : 'unset', overflow: overflow ? 'hidden' : 'unset',
height: overflow ? '35rpx' : 'unset', height: overflow ? '105rpx' : 'unset',
lineHeight: overflow ? '35rpx' : '35rpx', lineHeight: overflow ? '35rpx' : '35rpx',
marginTop:'24rpx', marginTop: '24rpx',
}}> }}>
<View id='Text' onClick={toggle}> <View id='Text' onClick={toggle}>
{text} {text}
{!overflow && isExpansioned && ( {!overflow && isExpansioned && (
<Text className={styles.expansion} onClick={handleHide}> <Text className={styles.expansion} onClick={handleHide}>
</Text>
)}
</View>
{overflow && (
<Text
className={styles.expansion}
onClick={handleExpend}
style={{position: 'absolute', top: '0', right: '0', background: '#fff'}}>
...
</Text> </Text>
)} )}
</View> </View>
); {overflow && (
// } <Text
style={{position: "absolute", top: "70rpx", right: "0", background: "#fff"}}
className={styles.expansion}
onClick={handleExpend}>
. . .
</Text>
)}
</View>
)
} }
export default LineEllipsis; export default LineEllipsis;

@ -1,4 +1,5 @@
.single-cover {
.singleCover {
position: absolute; position: absolute;
bottom: 0; bottom: 0;
top: 0; top: 0;
@ -28,10 +29,12 @@
} }
.correct{ .correct {
background: #bdf6c1 !important;
color: #fff !important;
} }
.mistake{ .mistake {
background: #e7abab !important;
color: #fff !important;
} }

@ -12,8 +12,6 @@ interface Props {
type AnswerType = "true" | 'false' type AnswerType = "true" | 'false'
export const Single: FC<Props> = (props) => { export const Single: FC<Props> = (props) => {
if (!props.topic) return (<></>)
let timer: NodeJS.Timeout let timer: NodeJS.Timeout
const [lastState, setLastState] = useState<0 | 1>(0) // 0为竖屏,1为横屏 const [lastState, setLastState] = useState<0 | 1>(0) // 0为竖屏,1为横屏
const [result, setResult] = useState<AnswerType | undefined>(undefined) const [result, setResult] = useState<AnswerType | undefined>(undefined)
@ -58,18 +56,19 @@ export const Single: FC<Props> = (props) => {
}) })
useEffect(() => { useEffect(() => {
// timer = setTimeout(() => { timer = setTimeout(() => {
// props.examination(false) props.examination(false)
// }, 4000) }, 4000)
}, [props.topic]) }, [props.topic])
const style: React.CSSProperties = useMemo(() => ({ const style: React.CSSProperties = useMemo(() => ({
transform: lastState === 0 && props.full ? "rotate(-90deg)" : 'none' transform: lastState === 1 && props.full ? "rotate(90deg)" : 'none'
}), [lastState]) }), [lastState, props.full])
function examination(result: AnswerType) { function examination(answer: AnswerType) {
if (result) return;
clearTimeout(timer) clearTimeout(timer)
setResult(result) setResult(answer)
setTimeout(() => { setTimeout(() => {
props.examination(props.topic?.right_value === result) props.examination(props.topic?.right_value === result)
@ -78,24 +77,29 @@ export const Single: FC<Props> = (props) => {
} }
function judgment(answer: AnswerType): string { function judgment(answer: AnswerType): string {
if (props.topic?.right_value === answer && result === answer) { if (result !== answer) return ''
if (props.topic?.right_answer === answer && result === answer) {
return styles.correct return styles.correct
} }
return styles.mistake return styles.mistake
} }
return ( return (
<View className={styles.singleCover} style={style}> <>
<View>{props.topic.question}</View> {
<View className={judgment("true")} props.topic && <View className={styles.singleCover} style={style}>
onClick={() => examination("true")}> <View>{props.topic.question}</View>
{props.topic.right_value} <View className={judgment("true")}
</View> onClick={() => examination("true")}>
<View className={judgment("false")} {props.topic.right_value}
onClick={() => examination("false")}> </View>
{props.topic.error_value} <View className={judgment("false")}
</View> onClick={() => examination("false")}>
</View> {props.topic.error_value}
</View>
</View>
}
</>
) )
} }

@ -49,13 +49,6 @@ const HVideo: FC<HVideoOptions> = (opt: HVideoOptions) => {
}) })
} }
// opt.setTime((time?: number) => {
// if (typeof time === 'number') {
// video?.seek(time)
// }
// video?.play()
// })
function onEnded() { function onEnded() {
if (currentTime + 1 > opt.duration) { if (currentTime + 1 > opt.duration) {
opt.onEnded() opt.onEnded()

@ -33,13 +33,18 @@
} }
.marker { .marker {
font-size: 24rpx;
font-family: PingFang SC-Medium, PingFang SC;
font-weight: 500;
color: #FFFFFF;
height: 34rpx;
padding: 0 8rpx;
position: absolute; position: absolute;
background: rgba(#000, .5); background: rgba(#000, .5);
color: #fff; color: #fff;
padding: 0 10px; border-radius: 0 0 8px 0;
border-radius: 0 0 0 10px;
top: 0; top: 0;
right: 0; left: 0;
} }
Image { Image {
@ -69,7 +74,7 @@
} }
.videoButton { .videoButton {
margin-top: 10rpx; margin-top: 20rpx;
color: #909795; color: #909795;
font-size: 22rpx; font-size: 22rpx;
} }

@ -1,7 +1,8 @@
import {Image, View} from "@tarojs/components"; import {View} from "@tarojs/components";
import {FC} from "react"; import {FC} from "react";
import './videoCover.scss' import './videoCover.scss'
import Taro from "@tarojs/taro"; import Taro from "@tarojs/taro";
import Img from "@/components/image/image";
interface VideoCoverProps { interface VideoCoverProps {
thumb: string thumb: string
@ -28,9 +29,9 @@ const VideoCover: FC<VideoCoverProps> = (opt: VideoCoverProps) => {
<View className='videoBox'> <View className='videoBox'>
<View className='video' onClick={jump}> <View className='video' onClick={jump}>
<View className='upper'> <View className='upper'>
<Image src={opt.thumb} mode='widthFix'/> <Img height={180} src={opt.thumb} mode='widthFix' errorType='course'/>
{opt.content && <View className='content'>{opt.content}</View>}
{opt.marker && <View className='marker'>{opt.marker}</View>} {opt.marker && <View className='marker'>{opt.marker}</View>}
{opt.content && <View className='content'>{opt.content}</View>}
</View> </View>
<View className='box'> <View className='box'>
<View className='title'>{opt.title}</View> <View className='title'>{opt.title}</View>

@ -0,0 +1,27 @@
.container {
width: 100%;
padding: 20rpx;
box-sizing: border-box;
columns: 2;
column-gap: 20rpx;
}
.health {
break-inside: avoid;
background: #fff;
border-radius: 10px;
overflow: hidden;
margin-bottom: 20rpx;
position: relative;
}
.play {
position: absolute;
min-height: 70rpx !important;
z-index: 9999;
width: 40rpx !important;
height: 40rpx !important;
top: 20rpx;
right: 20rpx;
background: transparent !important;
}

@ -0,0 +1,70 @@
import {FC, useEffect, useState} from "react";
import styles from "@/pages/preview/health/health.module.scss";
import {Image, Text, View} from "@tarojs/components";
import Img from "@/components/image/image";
import Taro from "@tarojs/taro";
import videoEvent from "@/hooks/videoEvent";
import palyWhite from '@/static/img/palyWhite.png'
import starWhite from '@/static/img/starWhite.png'
import {formatMinute} from "@/utils/time";
interface Props {
data: VideList
errorType?: ImgErrType
}
const VideoList: FC<Props> = (props) => {
const [data, setData] = useState(props.data)
useEffect(() => {
setData(props.data)
}, [props.data])
useEffect(() => {
videoEvent.videoOn(data.id, ({view}) => {
setData({
...data,
video_view: view
})
})
}, [])
Taro.useUnload(() => {
videoEvent.off(data.id)
})
function jump() {
Taro.preload(data)
Taro.navigateTo({url: `/pages/preview/videoFull/videoFull?id=${data.id}`})
}
return (
<View className={styles.health}>
<View key={data.id} onClick={jump} className="bg-white">
<Img src={data.url_path} errorType={props.errorType} height={346}/>
<View className='p-2 relative'>
<View className='text-ellipsis-1 font-28 text-dark'>{data.title}</View>
<View className='text-ellipsis-2 mt-2 font-24 text-secondary'>{data.introduction}</View>
<View className={styles.info}>
<View className='flex'>
<View className='flex align-center mr-3'>
<Image src={palyWhite}/>
<Text>{(data.video_view || 0)}</Text>
</View>
<View className='flex align-center'>
<Image src={starWhite}/>
<Text>{(data.collect_quantity || 0)}</Text>
</View>
</View>
<View>{formatMinute(data.duration || 0)}</View>
</View>
</View>
</View>
</View>)
}
export default VideoList

@ -0,0 +1,4 @@
.container{
width:750rpx;
height:600rpx;
}

@ -0,0 +1,200 @@
import React, {FC, useEffect, useRef, useState, useImperativeHandle} from "react";
import {Image, Text, Video, View} from "@tarojs/components";
import "./index.module.scss"
import Icon from "@/components/icon";
import {AtSlider} from "taro-ui";
import Taro from "@tarojs/taro";
import full from "@/static/img/fullscreen.png"
import unfull from "@/static/img/exitFullscreen.png"
type Props = {
src: string
onRef: any
}
const VideoPro:FC<Props> = ({src,onRef}) => {
const globalData = Taro.getApp().globalData
//用useImperativeHandle暴露一些外部ref能访问的属性
useImperativeHandle(onRef, () => {
return {
func: pause,
}
})
// 视频ui控制需要的响应式数据
const videoContext = useRef<any>()
const [isPlay, setIsPlay] = useState(false)
const [duration,setDuration] = useState(0) // 视频长度 单位秒
const updateState = useRef(false)
const [sliderValue,setSlidervalue] = useState(0)
const [process_duration, set_process_duration] = useState('00:00')
const [total_duration, set_total_duration] = useState('00:00')
const [showMenu,setShowMenu] = useState(true)
const [isFull,setIsFull] = useState(false)
const time = useRef<any>()
useEffect(() => {
console.log('组件加载')
videoContext.current = Taro.createVideoContext('video')
updateState.current = true
}, []);
function onTouchStart(){
if(!showMenu) {
setShowMenu(true)
}
}
function onTouchEnd(){
if(time.current) {
clearTimeout(time.current)
time.current = null
}
time.current = setTimeout(() => {
setShowMenu(false)
},5000)
}
function bindTimeupdateFun(e) {
if (updateState.current) {
let sliderValue = e.detail.currentTime / e.detail.duration * 100;
setSlidervalue(sliderValue)
setDuration(e.detail.duration)
set_total_duration(formatSeconds(e.detail.duration))
set_process_duration(formatSeconds(e.detail.currentTime))
}
}
function addZero(i){
i = typeof i === 'string' ? Number(i) : i;
return i < 10 ? "0" + i : "" + i;
}
function formatSeconds(value){
if (value == undefined) {
value = 0;
}
let second = parseInt(value);
let min = 0;
let secondStr = ''
let minStr = ''
let result = ''
if (second > 60) {
min = parseInt(String(second / 60));
second = parseInt(String(second % 60));
if (min > 60) {
// hour = parseInt(String(min / 60));
min = parseInt(String(min % 60));
}
}
if (min > 0) {
minStr = addZero(parseInt(String(min)));
result = minStr + ":";
}else{
result = "00:";
}
if(second > 0){
secondStr = addZero(parseInt(String(second)));
result = result + secondStr;
}else{
result = result + '00';
}
return result;
}
function bindEnded(){
setIsPlay(false)
if(!showMenu){
setShowMenu(true)
}
}
function onFullScreenChange(e){
console.log(e.detail.fullScreen)
if(e.detail.fullScreen){
setIsFull(true)
}else{
setIsFull(false)
}
}
function play(e){
e.stopPropagation();
videoContext.current.play()
setIsPlay(true)
}
function pause(){
setIsPlay(false)
videoContext.current.pause()
}
function sliderOnChange(e){
if (duration) {
// 视频跳转到指定位置
videoContext.current.seek(e / 100 * duration);
updateState.current = true
setSlidervalue(e)
}
}
function sliderOnChanging(){
updateState.current = false
}
return (
<View className="container">
<Video
style={{width: '750rpx', height: '600rpx'}}
id='video'
// src={brandInfo?.introductory_video_resource?.url}
src={src}
controls={false}
showPlayBtn={false}
showCenterPlayBtn={false}
showProgress={false}
showFullscreenBtn={false}
enableProgressGesture={false}
onTimeUpdate={bindTimeupdateFun}
onEnded={bindEnded}
onFullScreenChange={onFullScreenChange}
>
<View onTouchStart={onTouchStart} onTouchEnd={onTouchEnd} style={{width:isFull?`${globalData.screenHeight}px`:'750rpx',height:isFull?'750rpx':'600rpx',display:'flex',flexDirection:'column',boxSizing:'border-box',paddingLeft:isFull?`${globalData.statusBarHeight}px`:'0',paddingRight:isFull?`${globalData.statusBarHeight}px`:'0'}}>
<View onClick={pause} className="justify-center align-center flex pt-5" style={{flex:'1',boxSizing:'border-box'}}>
{ !isPlay && showMenu &&
<View className="flex justify-center align-center rounded-50 pl-1" style={{width:'50px',height:'50px',backgroundColor:'rgba(0,0,0,0.5)',boxSizing:'border-box'}}>
<Icon onClick={play} name="play" color="#fff" size="35px"></Icon>
</View>
}
</View>
<View className="flex align-center px-3" style={{height:'40px',boxSizing:'border-box'}}>
{ duration && showMenu &&
<>
{
isPlay ?
<Icon name="pause" color="#fff" size="23px" onClick={() => {videoContext.current.pause();setIsPlay(false)}} /> :
<Icon name="play" color="#fff" size="23px" onClick={() => {videoContext.current.play();setIsPlay(true)}} />
}
<Text className="text-white pl-1 font-26">{process_duration}</Text>
<View style={{flex:'1'}}>
<AtSlider onChange={sliderOnChange} onChanging={sliderOnChanging} step={1} value={sliderValue} activeColor='#fff' backgroundColor='#BDBDBD' blockColor='#fff' blockSize={10}></AtSlider>
</View>
<Text onClick={() => {
isFull?videoContext.current.exitFullScreen():videoContext.current.requestFullScreen({direction:90})
}} className="text-white font-26 pr-1">{total_duration}</Text>
{
isFull? <Image style={{width:'25px',height:'25px'}} onClick={() => {videoContext.current.exitFullScreen()}} src={unfull} />
: <Image style={{width:'23px',height:'23px'}} src={full} onClick={() => {videoContext.current.requestFullScreen({direction:90})}} />
}
</>
}
</View>
</View>
</Video>
</View>
)
}
export default VideoPro

@ -0,0 +1,28 @@
import Taro from "@tarojs/taro";
const KEY = 'ARTICLES_EVENTS'
interface Data {
id: number
view: number
}
function recordsAdd(data: Data) {
Taro.eventCenter.trigger(KEY + data.id, data)
}
function recordsOn(id: number, fn: (data: Data) => void) {
Taro.eventCenter.on(KEY + id, fn)
}
function off(id: number) {
Taro.eventCenter.off(KEY + id)
}
const ArticlesEvent = {
recordsAdd,
recordsOn,
off
}
export default ArticlesEvent

@ -1,25 +0,0 @@
import Taro from "@tarojs/taro";
/**
*
* -
*/
const KEY = 'REFRESH_INDEX'
interface RefreshIndex {
id: number
}
function on(fn: (arg: RefreshIndex) => void) {
Taro.eventCenter.on(KEY, fn)
}
function trigger(data: RefreshIndex) {
Taro.eventCenter.trigger(KEY, data)
}
export default {
on,
trigger
}

@ -0,0 +1,39 @@
import Taro from "@tarojs/taro";
type Topic = string | symbol
type Subscriber = (...args: any[]) => void
export default function usePubsub() {
const events = new Map<Topic, Subscriber[]>()
const registers = new Map<Topic, Subscriber>()
const pub = (topic: Topic, data: any) => {
Taro.eventCenter.trigger(topic, data)
};
const sub = (topic: Topic, callback: Subscriber) => {
const cbs = events.get(topic) ?? []
events.set(topic, cbs.concat(callback))
if (!registers.has(topic)) {
console.log({topic})
const listener = (...args: any[]): void => {
events.get(topic)?.forEach((cb) => cb(...args))
}
registers.set(topic, listener)
Taro.eventCenter.on(topic, listener)
}
};
Taro.useUnload(() => {
registers.forEach((listener, topic) => {
Taro.eventCenter.off(topic, listener)
})
registers.clear()
events.clear()
})
return {
pub,
sub
}
}

@ -1,14 +1,12 @@
import Taro from "@tarojs/taro"; import Taro from "@tarojs/taro";
const KET = 'SELECT_DEP' const KET = 'SELECT_DEP'
const KET_REQUIRED = 'SELECT_REQUIRED'
/** 部门 */ /** 部门 */
function set(data: number[]) { function set(data: number[]) {
Taro.setStorageSync(KET, data) Taro.setStorageSync(KET, data)
} }
function get(): number[] { function get(): number[] {
const deps = Taro.getStorageSync(KET) const deps = Taro.getStorageSync(KET)
if (deps && deps.length) { if (deps && deps.length) {
@ -23,28 +21,7 @@ function removeDeps() {
Taro.removeStorageSync(KET) Taro.removeStorageSync(KET)
} }
/** 必修选修 */
function removeRequired() {
Taro.removeStorageSync(KET_REQUIRED)
}
function setRequired(data: number[]) {
Taro.setStorageSync(KET_REQUIRED, data)
}
/** getRequired 比 set后调用 */
function getRequired(): (0 | 1)[] {
const deps = Taro.getStorageSync(KET_REQUIRED)
if (deps && deps.length) {
removeRequired()
return deps
}
return []
}
function remove() { function remove() {
removeRequired()
removeDeps() removeDeps()
} }
@ -52,6 +29,4 @@ export default {
set, set,
get, get,
remove, remove,
setRequired,
getRequired
} }

@ -0,0 +1,28 @@
import Taro from "@tarojs/taro";
const KEY = 'VIDEO_EVENTS'
interface Data {
id: number
view: number
}
function videoAdd(data: Data) {
Taro.eventCenter.trigger(KEY + data.id, data)
}
function videoOn(id: number, fn: (data: Data) => void) {
Taro.eventCenter.on(KEY + id, fn)
}
function off(id: number) {
Taro.eventCenter.off(KEY + id)
}
const VideoEvent = {
videoAdd,
videoOn,
off
}
export default VideoEvent

@ -5,7 +5,7 @@ import {courseApi} from "@/api";
import VideoCover from "@/components/videoCover/videoCover"; import VideoCover from "@/components/videoCover/videoCover";
import {formatMinute} from "@/utils/time"; import {formatMinute} from "@/utils/time";
import Empty from "@/components/empty/empty"; import Empty from "@/components/empty/empty";
import eventsIndex from "@/hooks/eventsIndex"; import PageScript from "@/components/pageScript/pageScript";
const CourType: FC = () => { const CourType: FC = () => {
const params = useRouter().params const params = useRouter().params
@ -46,16 +46,6 @@ const CourType: FC = () => {
} }
} }
eventsIndex.on(({id}) => {
if (id == null && params.type === "4") return;
for (const [index, notFinished] of data.entries()) {
if (notFinished.id === id) {
data.splice(index, 1)
return
}
}
})
useEffect(() => { useEffect(() => {
getData() getData()
@ -68,17 +58,27 @@ const CourType: FC = () => {
}) })
return ( return (
<View className='py-2 flex justify-between flex-wrap '> <>
{data.length > 0 ? data.map(c => {
<VideoCover data.length > 0 ?
thumb={c.thumb} <>
title={c.title} <View className='py-2 flex justify-between flex-wrap'>
id={c.id} {
depId={c.id} data.map(c => <VideoCover
key={c.id} thumb={c.thumb}
time={formatMinute(c.course_duration)} title={c.title}
/>) : <Empty name='暂无数据'/>} id={c.id}
</View> depId={c.id}
key={c.id}
time={formatMinute(c.course_duration)}
/>)
}
</View>
<PageScript/>
</>
: <Empty name='暂无数据'/>
}
</>
) )
} }

@ -1,4 +1,4 @@
import {Image, Progress, View} from "@tarojs/components"; import {Image, Progress, ScrollView, View} from "@tarojs/components";
import Taro from "@tarojs/taro"; import Taro from "@tarojs/taro";
import styles from './curHistory.module.scss' import styles from './curHistory.module.scss'
import {useState} from "react"; import {useState} from "react";
@ -6,6 +6,7 @@ import {userApi} from "@/api";
import {formatDateTime, formatMinute} from "@/utils/time"; import {formatDateTime, formatMinute} from "@/utils/time";
import CustomPageContainer from "@/components/custom-page-container/custom-page-container"; import CustomPageContainer from "@/components/custom-page-container/custom-page-container";
import Empty from "@/components/empty/empty"; import Empty from "@/components/empty/empty";
import PageScript from "@/components/pageScript/pageScript";
const CurHistory = () => { const CurHistory = () => {
const [show, setShow] = useState(false) const [show, setShow] = useState(false)
@ -106,6 +107,7 @@ const CurHistory = () => {
/> />
</View>) </View>)
} }
<PageScript/>
</View> </View>
: <Empty name='暂无学习记录'/> : <Empty name='暂无学习记录'/>
} }
@ -116,6 +118,7 @@ const CurHistory = () => {
round round
position='bottom' position='bottom'
onClickOverlay={() => setShow(false)}> onClickOverlay={() => setShow(false)}>
<ScrollView showScrollbar={false} scrollY={true} style={{maxHeight:'40vh',paddingBottom:'40px'}}>
<View className={styles.hourRecord}> <View className={styles.hourRecord}>
{hours?.length ? {hours?.length ?
hours?.map(d => <View key={d.id}> hours?.map(d => <View key={d.id}>
@ -136,6 +139,7 @@ const CurHistory = () => {
</View>) </View>)
: <Empty name='暂无学习记录'/>} : <Empty name='暂无学习记录'/>}
</View> </View>
</ScrollView>
</CustomPageContainer> </CustomPageContainer>
</View> </View>
) )

@ -6,11 +6,6 @@
background: #fff; background: #fff;
} }
.image {
width: 100%;
height: 100%;
display: block;
}
.thumb { .thumb {
background: #ddd; background: #ddd;

@ -1,10 +1,12 @@
import {Image, Text, View} from "@tarojs/components"; import {Text, View} from "@tarojs/components";
import styles from './history.module.scss' import styles from './history.module.scss'
import Taro from "@tarojs/taro"; import Taro from "@tarojs/taro";
import {useEffect, useState} from "react"; import React, {useEffect, useState} from "react";
import {formatMinute} from "@/utils/time"; import {formatMinute} from "@/utils/time";
import Empty from "@/components/empty/empty"; import Empty from "@/components/empty/empty";
import {userApi} from "@/api"; import {userApi} from "@/api";
import Img from "@/components/image/image";
import PageScript from "@/components/pageScript/pageScript";
const History = () => { const History = () => {
const [data, setData] = useState<HourHistory[]>([]) const [data, setData] = useState<HourHistory[]>([])
@ -25,24 +27,29 @@ const History = () => {
}, []) }, [])
return ( return (
<View> <View className='mt-3'>
<View className='mt-3'> {
{data.length ? data.map((d, index) => data.length ? <> {
<View key={index} className={styles.category} onClick={() => jump(d.userCourseRecord.course_id)}> data.map((d, index) =>
<View className={styles.thumb}> <View key={index} className={styles.category} onClick={() => jump(d.userCourseRecord.course_id)}>
<Image src={d.thumb} className={styles.image}/> <View className={styles.thumb}>
<View <Img src={d.thumb} className={styles.image} width={300} height={188}/>
className={styles.count}>{d.userCourseRecord.hour_count}/{d.userCourseRecord.finished_count}</View> <View
</View> className={styles.count}>{d.userCourseRecord.hour_count}/{d.userCourseRecord.finished_count}</View>
<View className={styles.text}> </View>
<View>{d.title}</View> <View className={styles.text}>
<View className={styles.describe}> <View>{d.title}</View>
<Text className='mr-4'>{formatMinute(durations[d.id])}</Text> <View className={styles.describe}>
<Text>{(d.userCourseRecord.finished_count / d.userCourseRecord.hour_count * 100).toFixed(0)}%</Text> <Text className='mr-4'>{formatMinute(durations[d.id])}</Text>
</View> <Text>{(d.userCourseRecord.finished_count / d.userCourseRecord.hour_count * 100).toFixed(0)}%</Text>
</View> </View>
</View>) : <Empty name='无观看记录'/>} </View>
</View> </View>)
}
<PageScript/>
</>
: <Empty name='无观看记录'/>
}
</View> </View>
) )
} }

@ -1,6 +1,5 @@
import {useState} from "react"; import {useState} from "react";
import {Profile} from '@/store' import {Profile} from '@/store'
import avatar from "@/static/img/avatar.png"
import PopPut from "@/components/popPut/popPut"; import PopPut from "@/components/popPut/popPut";
import {Input, View} from "@tarojs/components"; import {Input, View} from "@tarojs/components";
import Taro from "@tarojs/taro"; import Taro from "@tarojs/taro";
@ -10,7 +9,7 @@ import MyButton from "@/components/button/MyButton";
const List = () => { const List = () => {
const {empty, user, setUser} = Profile.useContainer() const {empty, user, setUser, token} = Profile.useContainer()
const [show, setShow] = useState(false) const [show, setShow] = useState(false)
const [name, setName] = useState<string>(user?.name || '') const [name, setName] = useState<string>(user?.name || '')
@ -45,17 +44,72 @@ const List = () => {
Taro.showModal({ Taro.showModal({
title: '是否确定退出登录', title: '是否确定退出登录',
success({confirm}) { success({confirm}) {
confirm && empty() confirm && empty(true, true)
} }
}) })
} }
function putAvatar(filePath: string) {
Taro.showLoading({title: "正在上传图片"})
Taro.uploadFile({
url: process.env.TARO_APP_API + '/api/v1/user/avatar',
filePath,
name: 'file',
header: {
'Authorization': `Bearer ${token}`
},
success(res) {
Taro.hideLoading()
if (res.statusCode === 200) {
const resp:{msg:string,code:number,data:User} =JSON.parse(res.data)
if(resp.code === 0){
const data: User = resp.data
setUser(data)
Taro.showToast({title: "图片上传成功"})
}else{
Taro.showToast({title: resp.msg, icon: 'none',mask: true})
}
} else {
Taro.showToast({title: "图片上传失败", icon: 'error'})
}
},
fail() {
Taro.showToast({title: "图片上传失败", icon: 'error'})
}
})
}
function selectAvatar() {
Taro.chooseImage({
count: 1,
sizeType: ['original', 'compressed'],
sourceType: ['album', 'camera'],
success: async (res) => {
if (!res.tempFiles.length) return;
const file = res.tempFiles[0]
if (file.size > 40960000) {
Taro.compressImage({
src: file.path,
quality: 50,
compressedWidth: 50,
compressHeight: 50,
success(res) {
putAvatar(res.tempFilePath)
}
})
} else {
putAvatar(file.path)
}
}
})
}
return ( return (
<> <>
<View className={styles.box}> <View className={styles.box}>
<PopPut title='头像' image={avatar} chevron no_border/> <PopPut title='头像' errorType={'avatar'} image={user?.avatar} no_border onClick={selectAvatar}/>
<PopPut title='手机号' content={user?.phone_number} chevron no_border/> <PopPut title='手机号' content={user?.phone_number?.replace(user?.phone_number.slice(3, 7), "****")} chevron
no_border/>
<PopPut title='昵称' content={user?.name} isProp show={show} no_border> <PopPut title='昵称' content={user?.name} isProp show={show} no_border>
<View className='p-2'> <View className='p-2'>
<View className='text-center font-weigh mb-3'></View> <View className='text-center font-weigh mb-3'></View>

@ -9,13 +9,20 @@ import MyButton from "@/components/button/MyButton";
import videoEvents from "@/hooks/videoEvents"; import videoEvents from "@/hooks/videoEvents";
import curRecord from '@/static/img/curRecord.png' import curRecord from '@/static/img/curRecord.png'
import hourRecord from "@/static/img/hourRecord.png" import hourRecord from "@/static/img/hourRecord.png"
import {PageContainerInner} from "@/components/custom-page-container/custom-page-container"; import CustomPageContainer from "@/components/custom-page-container/custom-page-container";
import {Profile} from "@/store";
import LoginView from "@/components/login-view";
import Collect from "@/components/collect/collect";
// import omit from '@/static/img/omit.png'
import IconFont from "@/components/IconFont";
interface Props { interface Props {
data: CourseDepData | null data: CourseDepData | null
setHors: (is_complete: boolean, id: number) => void setHors: (is_complete: boolean, id: number) => void
id: number //课程 id: number //课程
playId: number | null playId: number | null
refresh: () => void
collectUpdate?: (v: boolean) => void
} }
const tabList = [ const tabList = [
@ -25,11 +32,11 @@ const tabList = [
] ]
const Catalogue: FC<Props> = ({data, setHors, id, playId}) => { const Catalogue: FC<Props> = ({data, setHors, id, playId, collectUpdate}) => {
const [current, setCurrent] = useState(1) const [current, setCurrent] = useState(1)
const [show, setShow] = useState(false) const [show, setShow] = useState(false)
const [playing, setPlaying] = useState(false) const [playing, setPlaying] = useState(false)
const {token} = Profile.useContainer()
videoEvents.onVideoState(({name}) => { videoEvents.onVideoState(({name}) => {
switch (name) { switch (name) {
@ -42,14 +49,11 @@ const Catalogue: FC<Props> = ({data, setHors, id, playId}) => {
} }
}) })
function onPause() {
videoEvents.setVideoState('pause')
}
function jumCurHistory() { function jumCurHistory() {
if (playId) { if (playId) {
Taro.navigateTo({url: `/pages/business/hourHistory/hourHistory?courseId=${id}&hourId=${playId}`}) Taro.navigateTo({
url: `/pages/business/hourHistory/hourHistory?courseId=${id}&hourId=${playId}`
})
} }
} }
@ -143,7 +147,6 @@ const Catalogue: FC<Props> = ({data, setHors, id, playId}) => {
} }
} }
return ( return (
<> <>
<View className='catalogue'> <View className='catalogue'>
@ -152,6 +155,9 @@ const Catalogue: FC<Props> = ({data, setHors, id, playId}) => {
{current === 0 && <View className='short_desc'>{data?.course.short_desc || data?.course.title}</View>} {current === 0 && <View className='short_desc'>{data?.course.short_desc || data?.course.title}</View>}
{current === 1 && <View> {current === 1 && <View>
<View className='my-2'></View> <View className='my-2'></View>
{
!token && <LoginView className="absolute-impt left top" />
}
{data?.chapters.length {data?.chapters.length
? Object.values(data?.chapters || {}).map((d, index) => <View key={d.id}> ? Object.values(data?.chapters || {}).map((d, index) => <View key={d.id}>
<Collapse title={`${index + 1}.${d.name}`}> <Collapse title={`${index + 1}.${d.name}`}>
@ -173,33 +179,48 @@ const Catalogue: FC<Props> = ({data, setHors, id, playId}) => {
</View> </View>
</View> </View>
<View className='Videobutton'> {
{ token && <View className='Videobutton'>
playing ? <MyButton style={{flex: '1'}} onClick={onPause}></MyButton> {
: <MyButton style={{flex: '1'}} onClick={learning}></MyButton> playing ? <MyButton className='flex-1'></MyButton>
} : <MyButton className='flex-1' onClick={learning}></MyButton>
<View className='px-3' onClick={() => setShow(true)}>...</View> }
</View> <Collect
owner_id={id}
owner_type={3}
select={data?.course.collect}
styles={{flexDirection: 'column', justifyContent: 'center'}}
stylesImage={{margin: '0 0 8rpx 0'}}
onUpdate={collectUpdate}
/>
<View className='px-2 text-center' onClick={() => setShow(true)}>
{/*<Image src={omit} style={{width: '32rpx', height: '32rpx'}} mode='widthFix'/>*/}
<IconFont name='three-dots' />
<View></View>
</View>
</View>
}
<PageContainerInner <CustomPageContainer
show={show} show={show}
position='bottom' position='bottom'
onClickOverlay={() => setShow(false)}> onClickOverlay={() => setShow(false)}
>
<View className='more'> <View className='more'>
<View></View> <View></View>
<View className='flex justify-around'> <View className='flex justify-around'>
<View onClick={() => Taro.navigateTo({url: '/pages/business/curHistory/curHistory?course_id=' + id})}> <View onClick={() => Taro.navigateTo({url: '/pages/business/curHistory/curHistory?course_id=' + id})}>
<Image src={curRecord} className='image'/> <Image src={curRecord} className='image'/>
<View className='mt-2'></View>
</View> </View>
<View onClick={jumCurHistory} className={playId ? undefined : 'filter-saturate'}> <View onClick={jumCurHistory} className={playId ? undefined : 'filter-saturate'}>
<Image src={hourRecord} className='image'/> <Image src={hourRecord} className='image'/>
<View className='mt-2'></View>
</View> </View>
</View> </View>
<MyButton onClick={() => setShow(false)} type='default' fillet></MyButton> <MyButton onClick={() => setShow(false)} type='default' fillet></MyButton>
</View> </View>
</PageContainerInner> </CustomPageContainer>
</> </>
); );
} }

@ -1,4 +1,4 @@
import {FC, useEffect, useState} from "react"; import {FC, useCallback, useEffect, useState} from "react";
import HVideo from "@/components/video/video"; import HVideo from "@/components/video/video";
import {curriculum, HourPlayData} from "@/api"; import {curriculum, HourPlayData} from "@/api";
import {Profile} from '@/store' import {Profile} from '@/store'
@ -13,11 +13,9 @@ interface Props {
curEnd: (test?: boolean) => void curEnd: (test?: boolean) => void
} }
const Course: FC<Props> = ({id, courseId, preview, curEnd}) => { const Course: FC<Props> = ({id, courseId, preview, curEnd}) => {
const [breakpoints, setBreakpoints] = useState<number[]>([]) // 断点 const [breakpoints, setBreakpoints] = useState<number[]>([]) // 断点
const [isFull, setIsFull] = useState(false) const [isFull, setIsFull] = useState(false)
const [examAll, setExamAll] = useState<Record<number, ShareSubject[]>>([]) // 题库 const [examAll, setExamAll] = useState<Record<number, ShareSubject[]>>([]) // 题库
const [data, setData] = useState<HourPlayData | null>(null) const [data, setData] = useState<HourPlayData | null>(null)
const [time, setTime] = useState<number>(0) // 进入断点的时间 const [time, setTime] = useState<number>(0) // 进入断点的时间
@ -67,7 +65,8 @@ const Course: FC<Props> = ({id, courseId, preview, curEnd}) => {
getData() getData()
}, [id]) }, [id])
function examination(result: boolean) { const examination = useCallback((result: boolean) => {
if (!time) return
const {id: question_id, question_type} = examAll?.[time]?.[0] const {id: question_id, question_type} = examAll?.[time]?.[0]
curriculum.answerRecord(id, { curriculum.answerRecord(id, {
is_pass: result, is_pass: result,
@ -77,8 +76,7 @@ const Course: FC<Props> = ({id, courseId, preview, curEnd}) => {
question_id question_id
}) })
setTime(0) setTime(0)
} }, [time])
return ( return (
<> <>
@ -88,9 +86,10 @@ const Course: FC<Props> = ({id, courseId, preview, curEnd}) => {
src={data?.url || ''} src={data?.url || ''}
onEnded={onEnded} onEnded={onEnded}
breakpoint={breakpoints} breakpoint={breakpoints}
onBreakpoint={(time) => setTime(time)}
fullChange={(fullScreen) => setIsFull(fullScreen)} fullChange={(fullScreen) => setIsFull(fullScreen)}
> onBreakpoint={(now) => {
time !== now && setTime(now)
}}>
<Single <Single
full={isFull} full={isFull}
examination={examination} examination={examination}

@ -1,12 +1,14 @@
import {FC} from "react"; import {FC} from "react";
import '../videoInfo.scss'
import {Image, Text, View} from "@tarojs/components"; import {Image, Text, View} from "@tarojs/components";
import playOk from "@/static/img/play-ok.png"; // import playOk from "@/static/img/play-ok.png";
import play from "@/static/img/play.png"; // import play from "@/static/img/play.png";
import {formatMinute} from "@/utils/time"; import {formatMinute} from "@/utils/time";
import Taro from "@tarojs/taro"; import Taro from "@tarojs/taro";
import {curriculum} from "@/api"; import {curriculum} from "@/api";
import lock from '@/static/img/lock.png' import lock from '@/static/img/lock.png'
import {Profile} from "@/store";
import IconFont from "@/components/IconFont";
import '../videoInfo.scss'
interface Props { interface Props {
playId: number | null playId: number | null
@ -23,6 +25,7 @@ async function jumTest(hour: Hour) {
} }
const Hours: FC<Props> = ({data, click, learn_hour_records, playId}) => { const Hours: FC<Props> = ({data, click, learn_hour_records, playId}) => {
const {token, empty} = Profile.useContainer()
const complete = (id: number): number | undefined => { const complete = (id: number): number | undefined => {
const find = learn_hour_records?.find(d => d.id === id) const find = learn_hour_records?.find(d => d.id === id)
if (find) { if (find) {
@ -32,7 +35,11 @@ const Hours: FC<Props> = ({data, click, learn_hour_records, playId}) => {
} }
} }
function onClick(id: number, is_complete: number | undefined, hour: Hour, upId?: number,) { function onClick(id: number, is_complete: number | undefined, hour: Hour, upId?: number) {
if (!token) {
empty()
return;
}
if (is_complete === 0) { if (is_complete === 0) {
Taro.showModal({ Taro.showModal({
title: '考卷未完成,是否前往', title: '考卷未完成,是否前往',
@ -65,10 +72,13 @@ const Hours: FC<Props> = ({data, click, learn_hour_records, playId}) => {
<> <>
{data?.map((d, index) => {data?.map((d, index) =>
<View key={d.id}> <View key={d.id}>
<View className={'hor' + ` ${complete(d.id) ? 'complete' : undefined}`} <View
key={index} className={'hor' + ` ${complete(d.id) ? 'complete' : undefined}`}
onClick={() => onClick(d.id, complete(d.id), d, data?.[index - 1]?.id)}> key={index}
<Image src={(complete(d.id) || playId === d.id) ? playOk : play} mode="scaleToFill" className='image'/> onClick={() => onClick(d.id, complete(d.id), d, data?.[index - 1]?.id)}
>
{/*<Image src={(complete(d.id) || playId === d.id) ? playOk : play} mode="scaleToFill" className='image'/>*/}
<IconFont name='play' shape='circle' size={18} color={(complete(d.id) || playId === d.id) ? undefined : '#e2e2e2'} fill />
<View className='title'> <View className='title'>
<View className='text'> <View className='text'>
<View style={{wordBreak: 'break-all'}}>{playId === d.id && <View style={{wordBreak: 'break-all'}}>{playId === d.id &&
@ -84,7 +94,6 @@ const Hours: FC<Props> = ({data, click, learn_hour_records, playId}) => {
</View> </View>
</View> </View>
</View>)} </View>)}
</> </>
) )
} }

@ -7,12 +7,6 @@
height: 500rpx; height: 500rpx;
} }
.image {
width: 100%;
height: 100%;
display: block;
}
.header { .header {
margin-bottom: 10px; margin-bottom: 10px;
border-radius: 0 0 40rpx 40rpx; border-radius: 0 0 40rpx 40rpx;
@ -25,19 +19,28 @@
.catalogue { .catalogue {
background: #fff; background: #fff;
border-radius: 40rpx; border-radius: 40rpx;
padding: 0 24rpx 24rpx; padding: 0;
margin-top: 30rpx; margin-top: 30rpx;
.short_desc { .short_desc {
padding: 0 24rpx 24rpx;
color: #606563; color: #606563;
line-height: 1.75; line-height: 1.75;
word-break: break-word; word-break: break-word;
} }
.hours {
padding: 0 24rpx 24rpx;
position: relative;
min-height: 100vw;
}
} }
.hor { .hor {
padding: 20rpx 0; padding: 20rpx 0;
display: flex; display: flex;
justify-content: flex-start;
align-items: baseline;
color: #323635; color: #323635;
.image { .image {
@ -46,7 +49,6 @@
margin-top: 8rpx; margin-top: 8rpx;
} }
.title { .title {
flex: 1; flex: 1;
width: 710rpx; width: 710rpx;
@ -67,7 +69,6 @@
margin-left: 10rpx; margin-left: 10rpx;
margin-top: 10rpx; margin-top: 10rpx;
} }
} }
} }
@ -76,6 +77,7 @@
} }
.Videobutton { .Videobutton {
align-items: center;
display: flex; display: flex;
background: #fff; background: #fff;
position: fixed; position: fixed;
@ -84,6 +86,8 @@
bottom: env(safe-area-inset-bottom); bottom: env(safe-area-inset-bottom);
box-sizing: border-box; box-sizing: border-box;
padding: 10px 20px; padding: 10px 20px;
color: #909795;
font-size: 24rpx;
} }
.more { .more {
@ -106,4 +110,3 @@
filter: saturate(0); filter: saturate(0);
} }

@ -1,14 +1,14 @@
import {Image, Text, View} from "@tarojs/components"; import {Text, View} from "@tarojs/components";
import {FC, useCallback, useEffect, useState} from "react"; import {FC, useCallback, useEffect, useState} from "react";
import {CourseDepData, curriculum} from "@/api"; import {CourseDepData, curriculum} from "@/api";
import './videoInfo.scss' import './videoInfo.scss'
import Catalogue from "./components/catalogue"; import Catalogue from "./components/catalogue";
import Course from "./components/course"; import Course from "./components/course";
import Taro from "@tarojs/taro"; import Taro from "@tarojs/taro";
import eventsIndex from "@/hooks/eventsIndex";
import {formatMinute} from "@/utils/time"; import {formatMinute} from "@/utils/time";
import videoEvents from "@/hooks/videoEvents"; import videoEvents from "@/hooks/videoEvents";
import unique_ident from "@/hooks/unique_ident"; import unique_ident from "@/hooks/unique_ident";
import Img from "@/components/image/image";
const VideoInfo: FC = () => { const VideoInfo: FC = () => {
const {id, depId} = Taro.getCurrentInstance()?.router?.params as any const {id, depId} = Taro.getCurrentInstance()?.router?.params as any
@ -18,15 +18,20 @@ const VideoInfo: FC = () => {
const [playing, setPlaying] = useState(false) // 学习中 const [playing, setPlaying] = useState(false) // 学习中
const getData = useCallback(async (playing: boolean) => { const getData = useCallback(async (playing: boolean) => {
const res = await curriculum.courseDep(id, depId) try {
if (res) { const res = await curriculum.courseDep(id, depId)
setData(res) res && setData(res)
} playId != null && currentVideo(res, playing) // 用于自动播放 判断当前课程是否完成
if (playId != null) { // 用于自动播放 判断当前课程是否完成 } catch (e) {
currentVideo(res, playing)
} }
}, [playing, playId]) }, [playing, playId])
function changeCollect(v: boolean){
let temp = data
temp!.course.collect = v
setData(temp)
}
const curEnd = (test?: boolean) => { const curEnd = (test?: boolean) => {
setPlaying(false) setPlaying(false)
if (!test) { // 没有考题才会请求 if (!test) { // 没有考题才会请求
@ -51,7 +56,6 @@ const VideoInfo: FC = () => {
const flats: Hour[] = Object.values(data?.hours || {}).flat(Infinity) as Hour[] const flats: Hour[] = Object.values(data?.hours || {}).flat(Infinity) as Hour[]
if (playId === flats?.[flats.length - 1]?.id && !preview) { if (playId === flats?.[flats.length - 1]?.id && !preview) {
Taro.showModal({title: '当前课程结束'}) Taro.showModal({title: '当前课程结束'})
eventsIndex.trigger({id: Number(id)})
return; return;
} }
for (const [index, flat] of flats.entries()) { for (const [index, flat] of flats.entries()) {
@ -73,9 +77,7 @@ const VideoInfo: FC = () => {
} }
}, [playId, data, preview]) }, [playId, data, preview])
/** /** 判断当前课程是否完成 */
*
*/
const currentVideo = useCallback((data: CourseDepData, playing: boolean) => { const currentVideo = useCallback((data: CourseDepData, playing: boolean) => {
const courseHourRecordsFinish = data?.learn_hour_records.find(d => d.id === playId)?.courseHourRecordsFinish const courseHourRecordsFinish = data?.learn_hour_records.find(d => d.id === playId)?.courseHourRecordsFinish
if (typeof courseHourRecordsFinish === 'number') { if (typeof courseHourRecordsFinish === 'number') {
@ -100,35 +102,30 @@ const VideoInfo: FC = () => {
data && getData(playing) data && getData(playing)
}) })
Taro.useUnload(() => { Taro.useUnload(() => {
videoEvents.videoOff() videoEvents.videoOff()
}) })
return ( return (
<> <View className='content'>
<View className='content'> <View className='content-video'>
<View className='content-video'> {playId
{ ? <Course id={playId} courseId={id} curEnd={curEnd} preview={preview}/>
playId : <Img width={750} height={500} src={data?.course.thumb || ''} errorType='health'/>}
? <Course id={playId} courseId={id} curEnd={curEnd} preview={preview}/> </View>
: <Image src={data?.course.thumb || ''} className='image' mode='aspectFill'/>
}
</View>
<View className='header'> <View className='header'>
<View className='flex justify-between text-muted'> <View className='flex justify-between text-muted'>
<Text className='font-34 text-warning'>{data?.is_required ? '必修' : '选修'}</Text> <Text className='font-34 text-warning'>{data?.is_required ? '必修' : '选修'}</Text>
<Text className='font-24'>{data?.course.class_hour}</Text> <Text className='font-24'>{data?.course.class_hour}</Text>
</View> </View>
<View className='font-weight font-36 my-2'>{data?.course.title}</View> <View className='font-weight font-36 my-2'>{data?.course.title}</View>
<View className='text-muted font-26 mt-3'> <View className='text-muted font-26 mt-3'>
<Text className='mr-2'>{formatMinute(data?.duration || 0)}</Text> <Text className='mr-2'>{formatMinute(data?.duration || 0)}</Text>
<Text>{((data?.learn_hour_records.length || 0) / (data?.course.class_hour || 1) * 100).toFixed(0)}%</Text> <Text>{((data?.learn_hour_records.length || 0) / (data?.course.class_hour || 1) * 100).toFixed(0)}%</Text>
</View>
</View> </View>
<Catalogue data={data} setHors={setHors} id={id} playId={playId}/>
</View> </View>
</> <Catalogue data={data} setHors={setHors} id={id} playId={playId} collectUpdate={changeCollect} refresh={() => getData(false)}/>
</View>
) )
} }

@ -1,3 +1,3 @@
export default definePageConfig({ export default definePageConfig({
navigationBarTitleText: '绑定手机号' navigationBarTitleText: '员工验证',
}) })

@ -5,7 +5,6 @@
bottom: 0; bottom: 0;
top: 0; top: 0;
background: #fff; background: #fff;
padding: 0 20rpx;
} }
.submit { .submit {
@ -13,7 +12,7 @@
justify-content: center; justify-content: center;
align-items: center; align-items: center;
gap: 12px; gap: 12px;
margin: 0 auto; margin: 50px 24px;
a { a {
color: #fff; color: #fff;
@ -24,6 +23,7 @@
display: flex; display: flex;
border-bottom: 1px solid #ededed; border-bottom: 1px solid #ededed;
padding: 30px 0; padding: 30px 0;
margin: 0 24px;
} }
.image { .image {

@ -1,5 +1,5 @@
import {FC, useCallback, useEffect, useRef, useState} from "react"; import {FC, useCallback, useEffect, useRef, useState} from "react";
import {Form, Image, Input, View} from "@tarojs/components"; import {Form, Image, Input, Text, View} from "@tarojs/components";
import {Profile} from "@/store"; import {Profile} from "@/store";
import {userApi} from "@/api"; import {userApi} from "@/api";
import Taro from "@tarojs/taro"; import Taro from "@tarojs/taro";
@ -8,6 +8,7 @@ import styles from './check.module.scss'
import code from '@/static/img/vCode.png' import code from '@/static/img/vCode.png'
import tel from '@/static/img/tel.png' import tel from '@/static/img/tel.png'
import MyButton from "@/components/button/MyButton"; import MyButton from "@/components/button/MyButton";
import IconFont from "@/components/IconFont";
const Bing: FC = () => { const Bing: FC = () => {
const form = useRef<HTMLFormElement | null>(null) const form = useRef<HTMLFormElement | null>(null)
@ -76,6 +77,23 @@ const Bing: FC = () => {
return ( return (
<View className={styles.page}> <View className={styles.page}>
<View className="p-3 mb-7 flex align-baseline font-30 lh1_5" style={{color:'#FF9E5F',backgroundColor:'#fff5ef'}}>
<IconFont className="mr-2" name={'exclamation'} shape={'triangle'} fill></IconFont>
<View className="flex-1">
<Text></Text>
<Text className="bold"></Text>
<Text></Text>
<Text onClick={()=>{
Taro.navigateTo({
url:`/pages/preview/webView/webView?url=${encodeURIComponent(`${process.env.TARO_APP_API}/QA/yuangongyanzheng.html`)}`
})
}} className="text-success mx-1" style={{textDecoration:'underline'}}></Text>
<Text>!</Text>
</View>
</View>
<Form onSubmit={Submit} ref={form}> <Form onSubmit={Submit} ref={form}>
<View className={styles.formItem}> <View className={styles.formItem}>
@ -85,7 +103,7 @@ const Bing: FC = () => {
<Input <Input
type='number' type='number'
name='phone_number' name='phone_number'
placeholder={'请输入手机号'} placeholder={'手机号'}
value={phone_number as unknown as string} value={phone_number as unknown as string}
onInput={(e) => setPhone_number(Number(e.detail.value))}/> onInput={(e) => setPhone_number(Number(e.detail.value))}/>
</View> </View>
@ -96,16 +114,16 @@ const Bing: FC = () => {
</View> </View>
<View className='flex align-center flex-1'> <View className='flex align-center flex-1'>
<Input type='number' name='code' className='flex-1' placeholder='请输入短信验证码'/> <Input type='number' name='code' className='flex-1' placeholder='短信验证码'/>
<View onClick={getCode}> <View onClick={getCode}>
{codeTime > 0 ? `${codeTime}` : '获取验证码'} {codeTime > 0 ? `${codeTime}` : '获取验证码'}
</View> </View>
</View> </View>
</View> </View>
<MyButton <MyButton
className={styles.submit} className={styles.submit}
style={{margin: '30px auto'}}
formType='submit' formType='submit'
disabled={loading} disabled={loading}
> >

@ -1,8 +1,9 @@
import {Image, Swiper, SwiperItem, View} from "@tarojs/components"; import {Swiper, SwiperItem, View} from "@tarojs/components";
import {FC, useEffect, useState} from "react"; import {FC, useEffect, useState} from "react";
import {AdwareType} from "@/api"; import {AdwareType} from "@/api";
import Taro from "@tarojs/taro"; import Taro from "@tarojs/taro";
import styles from '../home.module.scss' import styles from '../home.module.scss'
import Img from "@/components/image/image";
interface Props { interface Props {
data: any[] data: any[]
@ -14,7 +15,6 @@ const Adware: FC<Props> = ({data, only_flag, width}) => {
const [adverts, setAdverts] = useState<AdwareType[]>([]) const [adverts, setAdverts] = useState<AdwareType[]>([])
const [space, setSpace] = useState<any | null>(null) const [space, setSpace] = useState<any | null>(null)
useEffect(() => { useEffect(() => {
const res = data.find(d => d.only_flag === only_flag) const res = data.find(d => d.only_flag === only_flag)
setSpace(res) setSpace(res)
@ -30,36 +30,42 @@ const Adware: FC<Props> = ({data, only_flag, width}) => {
} }
return ( return (
<View> <>
{
adverts.length === 1 && <Image
src={adverts[0].image_url}
mode='scaleToFill'
lazyLoad
fadeIn
style={{width: width + "rpx", height: (space.height / space.width) * width + "rpx"}}
onClick={() => jumpAdware(adverts[0].image_path)}
className={styles.adware}/>
}
{ {
adverts.length > 1 && <Swiper adverts.length > 0 && <View className={styles.adware}>
indicatorDots {
autoplay adverts.length === 1 && <Img
circular src={adverts[0].image_url}
style={{width: width + "rpx", height: (space.height / space.width) * width + "rpx", overflow: 'hidden'}} mode='scaleToFill'
indicatorActiveColor='rgba(255,255,255,0.5)'
className={styles.adware}>
{adverts.map(d => <SwiperItem key={d.id}>
<Image
src={d.image_url}
lazyLoad lazyLoad
fadeIn fadeIn
onClick={() => jumpAdware(d.image_path)} onClick={() => jumpAdware(adverts[0].image_path)}
style={{width: width + "rpx", height: '100%'}}/> width={width}
</SwiperItem>)} errorType='profession'
</Swiper> height={(space.height / space.width) * width}/>
}
{
adverts.length > 1 && <Swiper
indicatorDots
autoplay
circular
style={{width: width + "rpx", height: (space.height / space.width) * width + "rpx", overflow: 'hidden'}}
indicatorActiveColor='rgba(255,255,255,0.5)'>
{adverts.map(d => <SwiperItem key={d.id}>
<Img
src={d.image_url}
lazyLoad
fadeIn
width={width}
errorType='profession'
height={(space.height / space.width) * width}
onClick={() => jumpAdware(d.image_path)}/>
</SwiperItem>)}
</Swiper>
}
</View>
} }
</View> </>
) )
} }

@ -1,20 +1,26 @@
import {FC, useEffect, useState} from "react"; import {FC, ReactNode, useEffect, useState} from "react";
import {Image, View} from "@tarojs/components"; import {Image, Text, View} from "@tarojs/components";
import {HomeApi} from "@/api"; import {HomeApi} from "@/api";
import {useReachBottom} from "@tarojs/taro"; import Taro, {useReachBottom} from "@tarojs/taro";
import styles from "../home.module.scss"; import styles from "../home.module.scss";
import VideoCover from "@/components/videoCover/videoCover"; import VideoCover from "@/components/videoCover/videoCover";
import courseTag from '@/static/img/courseTag.png' import courseTag from '@/static/img/courseTag.png'
import ArticlesBox from "@/components/articlesBox/articlesBox";
import PageScript from "@/components/pageScript/pageScript";
import IconFont from "@/components/IconFont";
const CurRecommended: FC = () => { const CurRecommended: FC = () => {
const [page, setPage] = useState(1) const [page, setPage] = useState(1)
const [data, setData] = useState<Curriculum[]>([]) const [data, setData] = useState<Curriculum[]>([])
const [total, setTotal] = useState(0) const [total, setTotal] = useState(0)
const [articles, setArticles] = useState<Articles[]>([])
async function getData() { async function getData() {
const res = await HomeApi.course(page, 4)
setTotal(res.total) const res = await HomeApi.home()
const newData = res.data.reduce((pre, cut) => { setArticles(res.articles)
setTotal(res.courses.total)
const newData = res.courses.data?.reduce((pre, cut) => {
const index = pre.findIndex(d => d.id === cut.id) const index = pre.findIndex(d => d.id === cut.id)
if (index === -1) { if (index === -1) {
pre.push(cut) pre.push(cut)
@ -23,7 +29,11 @@ const CurRecommended: FC = () => {
} }
return pre return pre
}, JSON.parse(JSON.stringify(data)) as Curriculum[]) }, JSON.parse(JSON.stringify(data)) as Curriculum[])
setData(newData) setData(newData ?? [])
}
function jumpArticles() {
Taro.navigateTo({url: '/pages/preview/jumpArticles/jumpArticles'})
} }
useEffect(() => { useEffect(() => {
@ -34,25 +44,48 @@ const CurRecommended: FC = () => {
data.length < total && setPage(page + 1) data.length < total && setPage(page + 1)
}) })
return ( let examines: ReactNode | undefined
<> if (articles.length > 0) {
{ examines = (
data.length > 0 && <View> <View className='bg-white rounded-20 clip px-3'>
<Image src={courseTag} mode='widthFix' className={styles.courseTag}/> <View className='mt-3 bold text-dark flex justify-between align-center mb-2'>
<View className={'pb-2 flex justify-between flex-wrap ' + styles.videoListBox}> <Text className='font-32'></Text>
{ <View className='font-24 text-muted flex align-center pl-2 py-2' onClick={jumpArticles}>
data.map(c => <VideoCover <Text></Text>
thumb={c.thumb} <IconFont name={'chevron-right'} size={14} />
title={c.title}
id={c.id}
depId={c.id}
key={c.id}
/>)
}
</View> </View>
</View> </View>
} {articles.map(d => <ArticlesBox key={d.id} data={d}/>)}
<View className='text-center text-muted pb-3 font-28'>- -</View> </View>
)
}
let videos: ReactNode | undefined
if (data.length > 0) {
videos = (
<View className='mt-3'>
<Image src={courseTag} mode='widthFix' className={styles.courseTag}/>
<View className={'pb-2 flex justify-between flex-wrap ' + styles.videoListBox}>
{
data.map(c => <VideoCover
thumb={c.thumb}
title={c.title}
id={c.id}
depId={c.id}
key={c.id}
marker={`${c.class_hour}`}
/>)
}
</View>
</View>
)
}
return (
<>
{examines}
{videos}
<PageScript/>
</> </>
) )
} }

@ -7,23 +7,23 @@ import article from '@/static/img/article.png'
import styles from '../home.module.scss' import styles from '../home.module.scss'
import Taro from "@tarojs/taro"; import Taro from "@tarojs/taro";
const Feature: FC = () => { const list = [
const list = [ {url: '/pages/preview/brand/list/list', image: article, text: '企业品牌'},
{url: '/pages/preview/brand/list/list', image: article, text: '品牌'}, {url: '/pages/preview/health/health', image: health, text: '健康常识'},
{url: '/pages/preview/health/health', image: health, text: '健康管理'}, {url: '/pages/preview/profession/profession', image: profession, text: '专业技能'},
{url: '/pages/preview/profession/profession', image: profession, text: '专业技能'}, {url: '/pages/preview/illness/sort/sort', image: illness, text: '病症百科'},
{url: '/pages/preview/illness/sort/sort', image: illness, text: '病症百科'}, ]
]
function jump(url) { function jump(url) {
Taro.navigateTo({url}) Taro.navigateTo({url})
} }
const Feature: FC = () => {
return ( return (
<View className='flex justify-around' style={{marginBottom: '25px'}}> <View className='flex justify-around' style={{marginBottom: '20px'}}>
{ {
list.map(d => <View className='text-center' onClick={() => jump(d.url)}> list.map(d => <View className='text-center' key={d.url} onClick={() => jump(d.url)}>
<Image src={d.image} style={{width: '48px', height: '48px'}}/> <Image src={d.image} style={{width: '48px', height: '48px', verticalAlign: 'middle'}}/>
<View className={styles.featureList}>{d.text}</View> <View className={styles.featureList}>{d.text}</View>
</View>) </View>)
} }

@ -2,7 +2,6 @@ import {FC, useEffect, useState} from "react";
import {Image, Swiper, SwiperItem, View} from "@tarojs/components"; import {Image, Swiper, SwiperItem, View} from "@tarojs/components";
import styles from '../home.module.scss' import styles from '../home.module.scss'
import Taro from "@tarojs/taro"; import Taro from "@tarojs/taro";
import {HomeApi} from "@/api";
import first from '@/static/img/first.png' import first from '@/static/img/first.png'
import second from '@/static/img/second.png' import second from '@/static/img/second.png'
import third from '@/static/img/third.png' import third from '@/static/img/third.png'
@ -26,12 +25,12 @@ interface Data {
url: string url: string
detailsUrl: string detailsUrl: string
data: DataContent[] data: DataContent[]
type?: 'health' | 'kill' errorType: ImgErrType
} }
interface Props { interface Props {
skill: Kill[] // 技能 skill: VideList[] // 技能
health: Health[] // 健康 health: VideList[] // 健康
brand: Brand[] // 品牌 brand: Brand[] // 品牌
illness: Illness[] // 疾病 illness: Illness[] // 疾病
} }
@ -42,27 +41,29 @@ const FeatureRecommended: FC<Props> = (props) => {
titleUrl: brandTop, titleUrl: brandTop,
url: '/pages/preview/brand/list/list', url: '/pages/preview/brand/list/list',
detailsUrl: '/pages/preview/brand/info/info', detailsUrl: '/pages/preview/brand/info/info',
data: [] data: [],
errorType: 'brand',
}, },
{ {
titleUrl: healthTop, titleUrl: healthTop,
url: '/pages/preview/health/health', url: '/pages/preview/health/health',
detailsUrl: '/pages/preview/videoFull/videoFull', detailsUrl: '/pages/preview/videoFull/videoFull',
data: [], data: [],
type: "health" errorType: 'health'
}, },
{ {
titleUrl: professionTop, titleUrl: professionTop,
url: '/pages/preview/profession/profession', url: '/pages/preview/profession/profession',
detailsUrl: '/pages/preview/videoFull/videoFull', detailsUrl: '/pages/preview/videoFull/videoFull',
data: [], data: [],
type: 'kill' errorType: 'profession'
}, },
{ {
titleUrl: illnessTop, titleUrl: illnessTop,
url: '/pages/preview/illness/sort/sort', url: '/pages/preview/illness/sort/sort',
detailsUrl: '/pages/preview/illness/article/article', detailsUrl: '/pages/preview/illness/list/list',
data: [] data: [],
errorType: 'health'
}, },
]) ])
@ -72,11 +73,12 @@ const FeatureRecommended: FC<Props> = (props) => {
return props.brand.map<DataContent>(d => ({ return props.brand.map<DataContent>(d => ({
id: d.id, id: d.id,
title: d.name, title: d.name,
imageUrl: d.brand_album, imageUrl: d.brand_album?.split(',')[0] ?? d.logo,
description: d.graphic_introduction, description: d.graphic_introduction,
path: `?id=${d.id}`, path: `?id=${d.id}`,
})) }))
} catch (e) {} } catch (e) {
}
return [] return []
} }
@ -88,7 +90,7 @@ const FeatureRecommended: FC<Props> = (props) => {
title: d.title, title: d.title,
imageUrl: d.url_path, imageUrl: d.url_path,
description: d.introduction, description: d.introduction,
path: `?url=${d.resource?.url}&poster=${d.url_path}&title=${d.title}` path: `?id=${d.id}`,
})) }))
} catch (e) { } catch (e) {
} }
@ -103,7 +105,7 @@ const FeatureRecommended: FC<Props> = (props) => {
imageUrl: d.url_path, imageUrl: d.url_path,
description: d.introduction, description: d.introduction,
title: d.title, title: d.title,
path: `?url=${d.resource?.url}&poster=${d.url_path}&title=${d.title}` path: `?id=${d.id}`,
})) }))
} catch (e) { } catch (e) {
} }
@ -115,8 +117,8 @@ const FeatureRecommended: FC<Props> = (props) => {
try { try {
return props.illness.map<DataContent>(d => ({ return props.illness.map<DataContent>(d => ({
id: d.id, id: d.id,
imageUrl: '', imageUrl: d.album?.split(',')[0] ?? '',
description: d.description?.replace(/[^\u4e00-\u9fa5]/gi,"") ?? '暂无描述', description: d.description || '暂无简介',
title: d.name, title: d.name,
path: `?id=${d.id}` path: `?id=${d.id}`
})) }))
@ -136,47 +138,61 @@ const FeatureRecommended: FC<Props> = (props) => {
}) })
}, [props]) }, [props])
// TODO 后续增加播放量使用公共接口 function jump(url: string, data: any) {
function jump(url: string, playId?: number, type?: 'health' | 'kill') { Taro.preload(data)
if (playId && type) {
if (type === 'health') {
HomeApi.healthSetPlay(playId)
} else if (type === 'kill') {
HomeApi.skillSetPlay(playId)
}
}
Taro.navigateTo({url}) Taro.navigateTo({url})
} }
return (
<View className={styles.feature}>
<Swiper nextMargin='30px' style={{height: '263px'}}> const keys: string[] = []
const items = data.filter(d => d.data.length === 3).map(d => {
keys.push(d.url)
return (
<>
<Image
mode='heightFix'
className={styles.featureTitle}
onClick={() => jump(d.url, d)} src={d.titleUrl}/>
{ {
data.map(d => <SwiperItem key={d.url}> d.data.length > 0 && d.data.map((c, index) => <View
<Image className='flex'
mode='heightFix' style={{marginBottom: '16rpx'}}
className={styles.featureTitle} key={c.id}
onClick={() => jump(d.url)} src={d.titleUrl}/> onClick={() => jump(d.detailsUrl + c.path, d)}>
{ <View style={{position: 'relative'}}>
d.data.map((c, index) => <View <View className={styles.featureImage}>
className='flex mb-3' <Img src={c.imageUrl} height={100} width={140} errorType={d.errorType}/>
key={c.id} </View>
onClick={() => jump(d.detailsUrl + c.path, c.id, d.type)}> <Image src={[first, second, third][index]} className={styles.ranking} mode='aspectFill'/>
<View style={{position:'relative'}}> </View>
<View className={styles.featureImage} > <View className={styles.featureText}>
<Img src={c.imageUrl} height={100} width={140}/> <View className={styles.featureTextTitle}>{c.title}</View>
</View> <View className={styles.featureTextDescription}>{c.description}</View>
<Image src={[first, second, third][index]} className={styles.ranking} mode='aspectFill'/> </View>
</View> </View>)
<View className={styles.featureText}>
<View className={styles.featureTextTitle}>{c.title}</View>
<View className={styles.featureTextDescription}>{c.description}</View>
</View>
</View>)
}
</SwiperItem>)
} }
</Swiper> </>
)
})
// 没有top3
if (keys.length === 0) {
return null
}
// 至少2个top3
if (keys.length > 1) {
return (
<View className={styles.feature}>
<Swiper disableTouchmove={'true'} nextMargin='30px' style={{height: '390rpx'}} >
{items.map((d, i) => (<SwiperItem key={keys[i]}>{d}</SwiperItem>))}
</Swiper>
</View>
)
}
// 只有一个top3
return (
<View className={styles.feature}>
{items[0]}
</View> </View>
) )
} }

@ -1,21 +1,20 @@
import {FC} from "react"; import {FC} from "react";
import {Input, View} from "@tarojs/components"; import {Image, View} from "@tarojs/components";
import styles from "../home.module.scss"; import styles from "../home.module.scss";
import Icon from "@/components/icon"; import Taro from "@tarojs/taro";
import search from "@/static/img/search.png"
interface Props {
onConfirm: (value: string) => void
}
export const Search: FC<Props> = (props) => { export const Search: FC = () => {
function jump() {
Taro.navigateTo({url: '/pages/preview/search/search/index'})
}
return ( return (
<View className={styles.search}> <View className={styles.search} onClick={jump}>
<Icon name='search' size={18} color='#808080'/> <Image src={search} mode='widthFix' style={{width: 16, height: 16, verticalAlign: 'middle',paddingRight:'10rpx'}}/>
<Input <View></View>
placeholder='搜索疾病名称'
confirmType='search'
onConfirm={(e) => props.onConfirm((e.target as any).value!)}
className='flex-1 pl-1'/>
</View> </View>
) )
} }

@ -1,7 +1,9 @@
export default definePageConfig({ export default definePageConfig({
navigationBarTitleText: '康一诺', navigationBarTitleText: '康一诺',
// navigationStyle: 'custom', navigationStyle: 'custom',
navigationBarBackgroundColor: '#92ecc5', navigationBarBackgroundColor: '#92ecc5',
navigationBarTextStyle: 'white', // navigationBarTextStyle: 'white',
onReachBottomDistance: 50 onReachBottomDistance: 50,
enableShareTimeline: true,
enableShareAppMessage: true
}) })

@ -1,3 +1,7 @@
page {
padding: 0 !important;
}
.tipsLogin { .tipsLogin {
padding: 20px; padding: 20px;
color: #fff; color: #fff;
@ -13,47 +17,67 @@
font-size: 28rpx; font-size: 28rpx;
font-family: PingFang SC-Medium, PingFang SC; font-family: PingFang SC-Medium, PingFang SC;
font-weight: 500; font-weight: 500;
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
}
.header {
width: 100%;
overflow: hidden;
margin-bottom: 20rpx;
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
} }
.headerDivider {
z-index: 99;
position: absolute;
left: 0;
right: 0;
bottom: 0;
height: 0;
transform: scaleY(0.5);
transform-origin: bottom center;
}
.content { .content {
position: relative; padding: 0 30rpx;
padding: 0 20px;
min-height: 90vh;
box-sizing: border-box; box-sizing: border-box;
width: 750rpx;
overflow: hidden;
&:after { &:after {
position: absolute; position: absolute;
top: 0; top: 0;
left: -10%; left: 0;
width: 120%; width: 100%;
height: 400rpx; height: 400rpx;
content: ''; content: '';
display: block; display: block;
background: linear-gradient(to bottom, #92ecc5, #f1f8f6) no-repeat; background: linear-gradient(to right, #DBF3F5, #B9ECD7, #C1EEDA) no-repeat;
z-index: -1; z-index: -1;
filter: blur(50px);
} }
} }
.search { .search {
width: 710rpx;
background: #fff; background: #fff;
border-radius: 100px; border-radius: 100px;
font-size: 28rpx; font-size: 28rpx;
display: flex; display: flex;
padding-left: 20rpx;
align-items: center; align-items: center;
box-sizing: border-box; box-sizing: border-box;
margin: 20rpx; justify-content: center;
height: 76rpx; height: 68rpx;
margin-bottom: 40rpx;
font-size: 28rpx;
font-family: PingFang SC-Medium, PingFang SC;
color: #909795;
line-height: 1;
} }
.adware { .adware {
width: 100%;
border-radius: 16rpx; border-radius: 16rpx;
overflow: hidden; overflow: hidden;
background: #eee;
margin-bottom: 40rpx; margin-bottom: 40rpx;
} }
@ -63,30 +87,44 @@
.featureList { .featureList {
font-size: 28rpx; font-size: 28rpx;
font-family: PingFang SC-Medium, PingFang SC; font-family: PingFang SC-Bold, PingFang SC;
font-weight: 500; font-weight: bold;
color: #323635; color: #323635;
margin-top: 10px; margin-top: 18rpx;
} }
.courseTag { .courseTag {
width: 162px; width: 162px;
margin: 0 auto 30rpx; height: 46rpx;
margin: 0 auto 10rpx;
display: block; display: block;
} }
.feature { .feature {
color: #323635; color: #323635;
background: #fff; background: #fff;
padding: 30rpx 0 30rpx 30rpx; padding: 20rpx 0 25rpx 25rpx;
margin-bottom: 50rpx; margin-bottom: 40rpx;
border-radius: 30rpx; border-radius: 20rpx;
position: relative;
overflow: hidden;
&:after {
content: '';
display: block;
position: absolute;
right: -1px;
top: 0;
bottom: 0;
width: 40rpx;
background: linear-gradient(90deg, rgba(255, 255, 255, 0) 0%, #FFFFFF 100%);
}
} }
.featureTitle { .featureTitle {
height: 42rpx; max-width: 212px;
padding-bottom: 30rpx; height: 50rpx;
} }
.featureText { .featureText {
@ -94,7 +132,7 @@
} }
.featureTextTitle { .featureTextTitle {
font-size: 30rpx; font-size: 28rpx;
font-family: PingFang SC-Medium, PingFang SC; font-family: PingFang SC-Medium, PingFang SC;
font-weight: 500; font-weight: 500;
color: #323635; color: #323635;
@ -102,7 +140,6 @@
white-space: nowrap; white-space: nowrap;
text-overflow: ellipsis; text-overflow: ellipsis;
-o-text-overflow: ellipsis; -o-text-overflow: ellipsis;
line-height: 2;
} }
.featureTextDescription { .featureTextDescription {
@ -114,12 +151,13 @@
white-space: nowrap; white-space: nowrap;
text-overflow: ellipsis; text-overflow: ellipsis;
-o-text-overflow: ellipsis; -o-text-overflow: ellipsis;
margin-top: 8rpx;
} }
.ranking { .ranking {
top:0; top: 0;
position: absolute; position: absolute;
left: 24rpx; left: 12rpx;
width: 30px; width: 30px;
height: 30px; height: 30px;
} }
@ -131,4 +169,5 @@
border-radius: 12rpx; border-radius: 12rpx;
overflow: hidden; overflow: hidden;
margin-right: 20rpx; margin-right: 20rpx;
vertical-align: middle;
} }

@ -1,5 +1,5 @@
import {FC, useEffect, useState} from "react"; import {FC, useState} from "react";
import {View} from "@tarojs/components"; import {Image, View, Text} from "@tarojs/components";
import styles from "./home.module.scss"; import styles from "./home.module.scss";
import Adware from "@/pages/home/components/adware"; import Adware from "@/pages/home/components/adware";
import Feature from "@/pages/home/components/feature"; import Feature from "@/pages/home/components/feature";
@ -9,42 +9,92 @@ import MyButton from "@/components/button/MyButton";
import {Profile} from "@/store"; import {Profile} from "@/store";
import Taro from "@tarojs/taro"; import Taro from "@tarojs/taro";
import {HomeApi, HomeData} from "@/api"; import {HomeApi, HomeData} from "@/api";
import logo from '@/static/img/logo.svg'
import Spin from "@/components/spinner";
import NavigationBar from "@/components/navigationBar/navigationBar";
// import {Search} from "./components/search"
const Home: FC = () => { const Home: FC = () => {
const globalData = Taro.getApp().globalData
const {token} = Profile.useContainer() const {token} = Profile.useContainer()
const [data, setData] = useState<null | HomeData>(null) const [data, setData] = useState<null | HomeData>(null)
const [enable, setEnable] = useState(true)
const [navbarOpacity, setNavbarOpacity] = useState('0')
const navbarHeight = globalData.statusBarHeight + globalData.textBarHeight;
function unLogin() { function unLogin() {
Taro.removeStorageSync('profile') Taro.clearStorage()
Taro.navigateTo({url: '/pages/login/login'}) Taro.navigateTo({url: '/pages/login/login'})
} }
useEffect(() => { Taro.useDidShow(() => {
HomeApi.home().then(res => { HomeApi.home().then(res => {
setData(res) setData(res)
}) })
}, []) setTimeout(() => {
setEnable(false)
}, 600)
})
Taro.usePageScroll((e) => {
const v = (Math.min(e.scrollTop / navbarHeight, 1) * 0.9).toFixed(6)
if (v != navbarOpacity) {
setNavbarOpacity(v)
}
})
Taro.useShareAppMessage((res) => {
if(res.from === 'button'){
}
return {
title:'信桂',
path: '/pages/home/home'
}
})
Taro.useShareTimeline(() => {
return {
title: '信桂'
}
})
return ( return (
<View className={styles.content}> <>
<Adware data={data?.adverts || []} only_flag='routine_home_top_banner' width={710}/> <NavigationBar
<Feature/> className={styles.header}
<FeatureRecommended backgroundColor={`rgba(255,255,255,${navbarOpacity})`}
illness={data?.illness.list || []} cancelBack
health={data?.health || []} leftNode={
brand={data?.brand.list || []} <>
skill={data?.skill || []} <Image src={logo} style={{height: "55%"}} mode='heightFix'/>
/> <Text className='font-36 font-weight ml-1'></Text>
<Adware data={data?.adverts || []} only_flag='routine_home_recommend_banner' width={710}/> </>
<CurRecommended/> }
{ >
!token && <View className={styles.tipsLogin} onClick={unLogin}> <View className={styles.headerDivider}
<View>~</View> style={{borderBottom: `1px solid rgba(200,200,200,${navbarOpacity})`}}></View>
<MyButton fillet size='mini'></MyButton> </NavigationBar>
</View> <Spin enable={enable} overlay/>
} <View className={styles.content} style={{paddingBottom: token ? 0 : '100rpx'}}>
</View> {/*<Search/>*/}
<Adware data={data?.adverts || []} only_flag='routine_home_top_banner' width={710}/>
<Feature/>
<FeatureRecommended
illness={data?.illness.list || []}
health={data?.health || []}
brand={data?.brand.list || []}
skill={data?.skill || []}
/>
<Adware data={data?.adverts || []} only_flag='routine_home_recommend_banner' width={710}/>
{data && <CurRecommended/>}
{
!token && <View className={styles.tipsLogin}>
<View>~</View>
<MyButton fillet size='mini' onClick={unLogin}></MyButton>
</View>
}
</View>
</>
) )
} }

@ -1,13 +1,14 @@
import {FC, useEffect, useMemo, useState} from "react"; import {FC, useEffect, useMemo, useState} from "react";
import {useDidShow} from "@tarojs/taro"; import Taro, {useDidShow} from "@tarojs/taro";
import {ScrollView, Swiper, SwiperItem, View} from "@tarojs/components"; import {ScrollView, Swiper, SwiperItem, View} from "@tarojs/components";
import {Courses, CoursesKey, publicApi} from "@/api/public"; import {Courses, CoursesKey, publicApi} from "@/api/public";
import VideoCover from "@/components/videoCover/videoCover"; import VideoCover from "@/components/videoCover/videoCover";
import styles from '../index.module.scss'
import {formatMinute} from "@/utils/time"; import {formatMinute} from "@/utils/time";
import {userApi} from "@/api"; import {userApi} from "@/api";
import Empty from "@/components/empty/empty"; import Empty from "@/components/empty/empty";
import eventsIndex from "@/hooks/eventsIndex"; import {Profile} from "@/store";
import LoginView from "@/components/loginView";
import PageScript from "@/components/pageScript/pageScript";
interface Props { interface Props {
categoryKey: CoursesKey categoryKey: CoursesKey
@ -18,11 +19,13 @@ export const VideoList: FC<Props> = ({categoryKey, setCategoryKey}) => {
const [data, setData] = useState<Courses>({ const [data, setData] = useState<Courses>({
is_required: [], is_required: [],
is_not_required: [], is_not_required: [],
is_finished: [],
is_not_finished: [],
}) })
const [page, setPage] = useState(1) const [page, setPage] = useState(1)
const [records, setRecords] = useState<LearnRecord[]>([]) const [records, setRecords] = useState<LearnRecord[]>([])
const {token} = Profile.useContainer()
const {windowHeight, textBarHeight, statusBarHeight} = Taro.getApp().globalData
const pageHeight = windowHeight - textBarHeight - statusBarHeight
function screen(oldData: Curriculum[], data: Curriculum[]): Curriculum[] { function screen(oldData: Curriculum[], data: Curriculum[]): Curriculum[] {
return data.reduce((pre, cur) => { return data.reduce((pre, cur) => {
@ -38,13 +41,11 @@ export const VideoList: FC<Props> = ({categoryKey, setCategoryKey}) => {
async function getData() { async function getData() {
try { try {
const res = await publicApi.course({page: page, pageSize: 10}) const res = await publicApi.newCourse({page: page, page_size: 10})
const old: Courses = JSON.parse(JSON.stringify(data)) const old: Courses = JSON.parse(JSON.stringify(data))
setData({ setData({
is_required: screen(old.is_required, res.is_required || []), is_required: screen(old.is_required, res.company_courses || []),
is_not_required: screen(old.is_not_required, res.is_not_required || []), is_not_required: screen(old.is_not_required, res.platform_courses || []),
is_finished: screen(old.is_finished, res.is_finished || []),
is_not_finished: screen(old.is_not_finished, res.is_not_finished || []),
}) })
} catch (e) { } catch (e) {
} }
@ -60,44 +61,35 @@ export const VideoList: FC<Props> = ({categoryKey, setCategoryKey}) => {
function rateOfLearning(id: number, class_hour: number): JSX.Element { function rateOfLearning(id: number, class_hour: number): JSX.Element {
switch (categoryKey) { const find = records.find(d => d?.course_id === id)
case "is_required": if (find) {
case "is_not_required": if (class_hour === find.finished_count) {
case "is_not_finished":
const find = records.find(d => d?.course_id === id)
if (find) {
if (class_hour === find.finished_count) {
return <View></View>
}
return (<View>{`${class_hour}节/已学${find.finished_count}`}</View>)
}
return (<View>{`${class_hour}节/已学0节`}</View>)
case "is_finished":
return <View></View> return <View></View>
}
return (<View>{`${class_hour}节/已学${find.finished_count}`}</View>)
} }
return (<View>{`${class_hour}节/已学0节`}</View>)
} }
eventsIndex.on(({id}) => { function rateOfPercent(id: number, class_hour: number): string {
if (id == null) return; const find = records.find(d => d?.course_id === id)
for (const [index, notFinished] of data.is_not_finished.entries()) { if (find) {
if (notFinished.id === id) { if (class_hour === find.finished_count) {
data.is_finished.push(notFinished) return '100%'
data.is_not_finished.splice(index, 1)
return
} }
return `${((find.finished_count / class_hour) * 100).toFixed(0)}%`
} }
}) return '0%'
}
useDidShow(() => {
getData().then()
getRecords().then()
})
useEffect(() => { const fetchData = () => {
getData().then() getData().then()
getRecords().then() getRecords().then()
}, [page]) }
useDidShow(fetchData)
useEffect(fetchData, [page])
function changeSwiper(e) { function changeSwiper(e) {
const index = e.detail.current const index = e.detail.current
@ -111,27 +103,36 @@ export const VideoList: FC<Props> = ({categoryKey, setCategoryKey}) => {
}, [data, categoryKey]) }, [data, categoryKey])
return ( return (
<Swiper className={styles.swiper} <Swiper style={{height: pageHeight}}
onChange={changeSwiper} onChange={changeSwiper}
current={categoryIndex}> current={categoryIndex}>
{ {
Object.values(data).map((value) => <SwiperItem> Object.entries(data).map(([key, value]) => <SwiperItem>
<ScrollView scrollY className={styles.swiper} onScrollToLower={() => setPage(page + 1)}> <ScrollView scrollY style={{height: pageHeight}} onScrollToLower={() => setPage(page + 1)} enhanced
<View className='py-2 flex justify-between flex-wrap'> showScrollbar={false}>
{ {
value?.length ? value?.map(c => !token && key === 'is_required'
<VideoCover ? <LoginView onSuccess={fetchData}/> : value?.length ?
thumb={c.thumb} <>
title={c.title} <View className='py-2 flex justify-between flex-wrap px-2'>
id={c.id} {
depId={c.id} value?.map(c =>
key={c.id} <VideoCover
time={formatMinute(c.course_duration)} thumb={c.thumb}
content={rateOfLearning(c.id, c.class_hour)} title={c.title}
/>) id={c.id}
depId={c.id}
key={c.id}
time={formatMinute(c.course_duration)}
content={rateOfLearning(c.id, c.class_hour)}
schedule={`学习进度${rateOfPercent(c.id, c.class_hour)}`}
/>)
}
</View>
<PageScript/>
</>
: <Empty name='暂无课程'/> : <Empty name='暂无课程'/>
} }
</View>
</ScrollView> </ScrollView>
</SwiperItem>) </SwiperItem>)
} }

@ -1,5 +1,5 @@
export default definePageConfig({ export default definePageConfig({
navigationBarTitleText: '医学道', navigationStyle: 'custom',
navigationBarBackgroundColor: '#92ecc5', enableShareTimeline: true,
navigationBarTextStyle: 'white', enableShareAppMessage: true
}) })

@ -2,30 +2,27 @@
position: fixed; position: fixed;
top: 0; top: 0;
left: 0; left: 0;
padding: 0 20px; right: 0;
bottom: 0;
min-height: 100vh; min-height: 100vh;
box-sizing: border-box; box-sizing: border-box;
width: 750rpx; width: 750rpx;
overflow: hidden; overflow: hidden;
&:after { //&:after {
position: absolute; // position: absolute;
top: 0; // top: 0;
left: -10%; // left: -10%;
width: 120%; // width: 120%;
height: 400rpx; // height: 400rpx;
content: ''; // content: '';
display: block; // display: block;
background: linear-gradient(to bottom, #92ecc5, #f1f8f6) no-repeat; // background: linear-gradient(to bottom, #92ecc5, #f1f8f6) no-repeat;
z-index: -1; // z-index: -1;
} //}
}
.swiper {
height: calc(100vh - 88rpx);
} }
.videoListBox { .videoListBox {
border-radius: 20px; border-radius: 20px;
} }

@ -1,32 +1,87 @@
import {FC, useState} from "react"; import {FC, useEffect, useState} from "react";
import {View} from "@tarojs/components"; import {View, Text} from "@tarojs/components";
import styles from './index.module.scss' import styles from './index.module.scss'
import {VideoList} from "@/pages/index/components/videoList"; import {VideoList} from "@/pages/index/components/videoList";
import Tabs, {OnChangOpt, TabList} from "@/components/tabs/tabs"; import Tabs, {OnChangOpt, TabList} from "@/components/tabs/tabs";
import {CoursesKey} from "@/api/public"; import {CoursesKey, publicApi} from "@/api/public";
import NavigationBar from "@/components/navigationBar/navigationBar";
import Spin from "@/components/spinner";
import {isBoolean} from "@tarojs/shared";
import ArticlesBox from "@/components/articlesBox/articlesBox";
import PageScript from "@/components/pageScript/pageScript";
import Taro from "@tarojs/taro";
const category: TabList[] = [
{title: "企选课程", value: 'is_required'},
{title: "平台课程", value: 'is_not_required'},
]
const Index: FC = () => { 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 [categoryKey, setCategoryKey] = useState<CoursesKey>('is_required') const [categoryKey, setCategoryKey] = useState<CoursesKey>('is_required')
function tabChange(data: OnChangOpt) { function tabChange(data: OnChangOpt) {
setCategoryKey(data.tab?.value as CoursesKey) setCategoryKey(data.tab?.value as CoursesKey)
} }
Taro.useShareAppMessage((res) => {
if(res.from === 'button'){
}
return {
title:'信桂',
path: '/pages/index/index'
}
})
Taro.useShareTimeline(() => {
return {
title: '信桂'
}
})
return (
<View className={styles.content}>
<NavigationBar
cancelBack
leftNode={<Tabs tabList={category} onChange={tabChange} current={categoryKey} scrollable={false} hiddenSliding/>}
/>
<VideoList
categoryKey={categoryKey}
setCategoryKey={(categoryKey) => setCategoryKey(categoryKey)}
/>
</View>
)
}
const AuditMode: FC = () => {
const [auditMode, setAuditMode] = useState(true)
const [articles, setArticles] = useState<any[]>([])
const [enable, setEnable] = useState(true)
useEffect(() => {
publicApi.course({page: 1, pageSize: 10}).then(res => {
setAuditMode(isBoolean(res.audit_mode) ? res.audit_mode : res.audit_mode === 'true')
setArticles(res.articles)
setEnable(false)
})
}, [])
return ( return (
<> <>
<View className={styles.content}> <Spin enable={enable} overlay/>
<Tabs tabList={category} onChange={tabChange} current={categoryKey}/> {
<VideoList categoryKey={categoryKey} setCategoryKey={(categoryKey) => setCategoryKey(categoryKey)}/> auditMode ?
</View> <>
<NavigationBar cancelBack leftNode={<Text className="bold font-36" style={{color:'#323635'}} ></Text>} />
<View className='bg-white rounded-20 clip m-3 px-3'>
{
articles.map(d => <ArticlesBox key={d.id} data={d}/>)
}
</View>
<PageScript/>
</>
: <Index/>
}
</> </>
) )
} }
export default Index export default AuditMode

@ -25,7 +25,7 @@
.brand { .brand {
width: 140px; width: 140px;
margin: 250px auto 145px; margin: 70px auto 100px;
image { image {
overflow: hidden; overflow: hidden;
@ -44,7 +44,7 @@
.errorTips { .errorTips {
position: fixed; position: fixed;
top: 10%; top: 0;
left: 24px; left: 24px;
right: 24px; right: 24px;
background: red; background: red;
@ -52,10 +52,12 @@
padding: 24px; padding: 24px;
border-radius: 20px; border-radius: 20px;
display: flex; display: flex;
align-items: center; align-items: baseline;
} }
.bing { .bing {
height: 50vh; height: 50vh;
padding: 50px 30px 0; padding: 50px 30px 0;
} }

@ -1,22 +1,23 @@
import {FC, useEffect, useState} from "react"; import {FC, useEffect, useState} from "react";
import {Profile} from "@/store"; import {Profile} from "@/store";
import {Image, Text, View} from "@tarojs/components"; import {Image, Radio, Text, View} from "@tarojs/components";
import Taro from "@tarojs/taro"; import Taro from "@tarojs/taro";
import styles from './login.module.scss' import styles from './login.module.scss'
import Icon from "@/components/icon";
import {userApi} from "@/api"; import {userApi} from "@/api";
import {loginApi, LoginParams} from "@/api/login"; import {loginApi, LoginParams} from "@/api/login";
import MyButton from "@/components/button/MyButton"; import MyButton from "@/components/button/MyButton";
import logo from '@/static/img/logo.svg'
import IconFont from "@/components/IconFont";
const Login: FC = () => { const Login: FC = () => {
const [isLoading, setLoading] = useState(false) const [isLoading, setLoading] = useState(false)
const [error, setError] = useState<string | null>(null) const {setUser, setToken, setCompany, empty} = Profile.useContainer()
const {setUser, setToken, setCompany} = Profile.useContainer()
const [h5params, setH5Params] = useState<LoginParams | null>(null) const [h5params, setH5Params] = useState<LoginParams | null>(null)
const [check, setCheck] = useState(false)
useEffect(() => { useEffect(() => {
empty(false)
if (process.env.TARO_ENV === 'h5') { if (process.env.TARO_ENV === 'h5') {
setLoading(true); setLoading(true);
loginApi.getParams().then((res) => { loginApi.getParams().then((res) => {
@ -30,9 +31,21 @@ const Login: FC = () => {
} }
}, []) }, [])
function setError(msg: any){
if(msg) {
Taro.showToast({
title:msg,
icon:'none'
})
}
}
function login() { function login() {
if (isLoading) return; if (isLoading) return;
if (!check) {
setError('请阅读并同意《用户协议》及《隐私政策》!')
return;
}
if (h5params == null && process.env.TARO_ENV === 'h5') { if (h5params == null && process.env.TARO_ENV === 'h5') {
setError('页面参数错误,请刷新页面!') setError('页面参数错误,请刷新页面!')
return; return;
@ -57,10 +70,12 @@ const Login: FC = () => {
setToken(token) setToken(token)
setCompany(company) setCompany(company)
setLoading(false) setLoading(false)
Taro.switchTab({url: '/pages/home/home'}) setTimeout(() => {
Taro.navigateBack()
})
} else { } else {
Taro.setStorageSync('openid', catch_key) Taro.setStorageSync('openid', catch_key)
Taro.reLaunch({url: '/pages/check/check'}) Taro.navigateTo({url: '/pages/check/check'})
} }
} catch (e) { } catch (e) {
} }
@ -83,21 +98,60 @@ const Login: FC = () => {
return ( return (
<View className={styles.container}> <View className={styles.container}>
<View className={styles.brand}> <View className={styles.brand}>
<Image mode={'scaleToFill'} src="https://playedu.yaojiankang.top/favicon.ico"/> <Image mode={'scaleToFill'} src={logo}/>
<View className='mt-3'></View> <View></View>
</View> </View>
<View className={styles.loginTips}> <View
<Text>使</Text> className="p-3 mb-7 flex align-baseline rounded-10 font-30 lh1_5"
style={{color:'#FF9E5F',backgroundColor:'rgba(255,158,95,0.05)',border:'1px solid rgba(0,0,0,0.03)'}}
>
<IconFont className="mr-2" name={'exclamation'} shape={'triangle'} fill></IconFont>
<View className="flex-1">
<Text></Text>
<Text></Text>
<Text></Text>
<Text onClick={()=>{
Taro.navigateTo({
url:`/pages/preview/webView/webView?url=${process.env.TARO_APP_API}/QA/zhanluehezuo.html`
})
}} className="text-success" style={{textDecoration:'underline'}}></Text>
</View>
</View> </View>
<MyButton onClick={login} loading={isLoading}></MyButton>
{error ? <View className={styles.errorTips}> {/*<View className={styles.loginTips}>*/}
<View style={{flex: 1}}>{error}</View> {/* <Text>请完成微信授权以继续使用!</Text>*/}
<Icon name={'close'} onClick={() => setError(null)}/> {/*</View>*/}
</View> : null} <MyButton onClick={login} loading={isLoading}></MyButton>
{/*{error ? <View className={styles.errorTips}>*/}
{/* <View style={{flex: 1}} className="flex align-baseline">*/}
{/* <IconFont className="mr-2" name={'exclamation'} shape={'triangle'} fill></IconFont>*/}
{/* <View className='flex-1 ml-1'>{error}</View>*/}
{/*</View>*/}
{/* <View className="p-2" onClick={() => setError(null)}>*/}
{/* <Icon name={'close'} />*/}
{/* </View>*/}
{/*</View> : null}*/}
<View className="mt-2 text-center">
<Radio style={{transform:'scale(0.8)'}} color={'#45D4A8'} checked={check} onClick={()=> setCheck(!check) }></Radio>
<Text className="ml-1"></Text>
<Text className="text-success" onClick={() => {
Taro.navigateTo({
url:`/pages/preview/webView/webView?url=${process.env.TARO_APP_API}/yonghuxieyi.html`
})
}}></Text>
<Text></Text>
<Text className="text-success" onClick={() => {
Taro.navigateTo({
url:`/pages/preview/webView/webView?url=${process.env.TARO_APP_API}/yinsizhengce.html`
})
}}></Text>
</View>
{/*{process.env.TARO_APP_LGOIN === 'true' && <MyButton onClick={TESTLOGIN}>线下登录</MyButton>}*/} {/*{process.env.TARO_APP_LGOIN === 'true' && <MyButton onClick={TESTLOGIN}>线下登录</MyButton>}*/}
<View className="fixed left right bottom text-center text-muted mb-7 font-26" style={{opacity:'0.5'}}>ICP备20015955号-4X</View>
</View> </View>
) )
} }

@ -10,7 +10,8 @@
} }
.add { .add {
position: fixed; //position: fixed;
width: 710rpx; width: 710rpx;
margin-top: 10px; margin-top: 30px;
border: none !important;
} }

@ -73,13 +73,14 @@ const AddStudent = () => {
}, [depIds]) }, [depIds])
useEffect(() => { useEffect(() => {
getDepartment()
if (params.id) { if (params.id) {
getUserInfo() getUserInfo()
} }
}, []) }, [])
Taro.useDidShow(() => {
Taro.useDidShow( async() => {
await getDepartment()
const newDepIds = storageDep.get() const newDepIds = storageDep.get()
setDepIds(newDepIds) setDepIds(newDepIds)
}) })
@ -99,6 +100,7 @@ const AddStudent = () => {
<Input <Input
placeholder='请输入手机号' placeholder='请输入手机号'
name='phone_number' name='phone_number'
maxlength={11}
value={userInfo?.phone_number} value={userInfo?.phone_number}
onInput={(event) => setUerInfo({...userInfo, phone_number: event.detail.value} as Student)}/> onInput={(event) => setUerInfo({...userInfo, phone_number: event.detail.value} as Student)}/>
</View> </View>

@ -1,4 +1,4 @@
import {FC, useCallback, useEffect, useState} from "react"; import {FC, useCallback, useState} from "react";
import {Image, Input, Radio, Text, View} from "@tarojs/components"; import {Image, Input, Radio, Text, View} from "@tarojs/components";
import styles from "../courseAdmin.module.scss"; import styles from "../courseAdmin.module.scss";
import Icon from "@/components/icon"; import Icon from "@/components/icon";
@ -6,17 +6,44 @@ import {CourseAllParam, curriculum} from "@/api";
import CustomPageContainer from "@/components/custom-page-container/custom-page-container"; import CustomPageContainer from "@/components/custom-page-container/custom-page-container";
import Empty from "@/components/empty/empty"; import Empty from "@/components/empty/empty";
import MyButton from "@/components/button/MyButton"; import MyButton from "@/components/button/MyButton";
import screen from '@/static/img/screen.png' import downArrow from '@/static/img/downArrow.png'
import omit from '@/static/img/omit.png'
import Taro from "@tarojs/taro";
import {Profile} from "@/store";
interface Props { interface Props {
param: CourseAllParam param: CourseAllParam
setParam: (data: CourseAllParam) => void setParam: (data: CourseAllParam) => void
total: number
setBatch: (data: boolean) => void
} }
export const Search: FC<Props> = ({param, setParam}) => { export const Search: FC<Props> = ({param, setParam, total, setBatch}) => {
const [show, setShow] = useState(false) const [show, setShow] = useState(false)
const [deps, setDeps] = useState<Manage[]>([]) const [deps, setDeps] = useState<Manage[]>([])
const [depId, setDepId] = useState<number>(param.dep_id) const [depId, setDepId] = useState<number>(param.dep_id)
const [title, setTitle] = useState(param.title)
const {company} = Profile.useContainer()
Taro.useDidShow(() => {
getDepList()
})
function onBatch() {
Taro.showActionSheet({
itemList: ['批量添加部门'],
success({tapIndex}) {
switch (tapIndex) {
case 0:
setBatch(true)
break
}
},
fail() {
}
})
}
async function getDepList() { async function getDepList() {
try { try {
@ -45,43 +72,62 @@ export const Search: FC<Props> = ({param, setParam}) => {
} }
}, [depId]) }, [depId])
useEffect(() => {
getDepList()
}, [])
return ( return (
<> <>
<View className={styles.searchBox}> <View className={styles.searchBox}>
<View className={styles.searchInput}> <View className={styles.searchInput}>
<Icon name='search' size={18}/> <Icon name='search' size={18} color='#909795'/>
<Input <Input
adjustPosition={false} adjustPosition={false}
type='text' type='text'
confirmType='search' confirmType='search'
placeholder='搜索名称' placeholder='搜索课程名称'
className='ml-1 flex-1' className='ml-1 flex-1'
value={param.title} value={title}
onInput={(e) => setTitle(e.detail.value)}
onConfirm={(e) => setParam({ onConfirm={(e) => setParam({
...param, ...param,
title: e.detail.value, title: e.detail.value,
page: 1 page: 1
})}/> })}
/>
</View> </View>
<View onClick={() => setShow(true)}> <View className='mt-3 flex justify-between font-24 text-dark align-center'>
<Text></Text> <View className="flex">
<Image src={screen} <View onClick={() => setShow(true)} className={styles.select}>
mode='aspectFit' {
style={{ param.dep_id > 0 ?
display: 'inline-block', <Text style={{background: 'rgba(68,215,170,.08)', color: '#44d7aa'}}>
width: '15px', {deps.find(d => d.id === param.dep_id)?.name}
height: '15px', </Text>
verticalAlign: 'middle', :
marginLeft: '5px' <Text></Text>
}}/> }
<Image src={downArrow} mode='widthFix' className={styles.icon}/>
</View>
{/*{*/}
{/* param.dep_id > 0*/}
{/* && <View className={'ml-5' + ` ${styles.select}`}*/}
{/* style={{background: 'rgba(68,215,170,.08)', color: '#44d7aa'}}>*/}
{/* {deps.find(d => d.id === param.dep_id)?.name}*/}
{/* <Image src={downArrowKey} mode='widthFix' className={styles.icon}/>*/}
{/* </View>*/}
{/*}*/}
<View>
</View>
</View>
<View className='flex align-center' onClick={onBatch}>
{total}
<Image src={omit} className={styles.icon}/>
</View>
</View> </View>
</View> </View>
{param.dep_id > 0 && <View className='mt-2'>{deps.find(d => d.id === param.dep_id)?.name}</View>}
<CustomPageContainer show={show} position='top' onClickOverlay={() => setShow(false)}> <CustomPageContainer show={show} position='top' onClickOverlay={() => setShow(false)}>
<View className={styles.custom}> <View className={styles.custom}>
@ -99,7 +145,7 @@ export const Search: FC<Props> = ({param, setParam}) => {
</View>) </View>)
} }
<View className='flex justify-around mt-2'> <View className='flex justify-around mt-2 align-center'>
<View onClick={() => setShow(false)}> <View onClick={() => setShow(false)}>
<MyButton type='default' fillet width={150}></MyButton> <MyButton type='default' fillet width={150}></MyButton>
</View> </View>
@ -108,7 +154,24 @@ export const Search: FC<Props> = ({param, setParam}) => {
</View> </View>
</View> </View>
</> </>
: <Empty name='暂无部门'/>} :
<>
<Empty name=''/>
<View className="flex flex-column align-center width100">
<Text className="font-24 mb-3" style={{color:'#ccc'}}></Text>
<MyButton
width={300}
onClick={() => {
Taro.navigateTo({url:`/pages/manage/department/addDepartment?company_id=${company?.id}&parent_id=0&sort=0`})
setTimeout(() => {
setShow(false)
},1000)
}}
></MyButton>
</View>
</>
}
</View> </View>
</CustomPageContainer> </CustomPageContainer>

@ -1,22 +1,48 @@
page {
background: #fff !important;
}
.searchBox { .searchBox {
background: #fff;
padding: 24rpx 30rpx; padding: 24rpx 30rpx;
display: flex; box-sizing: border-box;
justify-content: space-between; position: sticky;
top: 0;
border-bottom: 1px solid #F5F8F7;
z-index: 2;
background-color: #fff;
} }
.searchInput { .searchInput {
width: 550rpx; width: 100%;
height: 68rpx; height: 68rpx;
display: flex; display: flex;
align-items: center; align-items: center;
padding-left: 24rpx; padding-left: 24rpx;
background: #F5F8F7; background: #F5F8F7;
border-radius: 32rpx; border-radius: 32rpx;
color: #909795;
overflow: hidden; overflow: hidden;
} }
.select {
padding: 0 30rpx;
height: 56rpx;
background: #F6F6F6;
border-radius: 28rpx;
display: flex;
align-items: center;
justify-content: center;
font-size: 24rpx;
font-family: PingFang SC-Medium, PingFang SC;
font-weight: 500;
color: #323635;
}
.icon {
width: 20rpx;
height: 20rpx;
margin-left: 10rpx;
}
.radioBox { .radioBox {
border-bottom: 1px solid #F5F8F7; border-bottom: 1px solid #F5F8F7;
padding: 20px 0; padding: 20px 0;
@ -31,41 +57,43 @@
.curBox { .curBox {
background: #fff; background: #fff;
margin-top: 20px; margin-top: 20px;
display: flex;
justify-content: space-between;
align-items: center;
z-index: 1;
} }
.curTitle { .curTitle {
padding: 30rpx; padding: 30rpx;
display: flex; display: flex;
border-bottom: 1px solid #F5F8F7; border-bottom: 1px solid #F5F8F7;
flex:1;
box-sizing: border-box;
} }
.curImage { .curImage {
width: 280rpx;
height: 164rpx;
border-radius: 10rpx; border-radius: 10rpx;
margin: 0 20px 0 0; margin: 0 20px 0 0;
background: #eee; background: #eee;
} overflow: hidden;
position: relative;
.Operation {
display: flex;
justify-content: space-around;
taro-view-core,
View {
padding: 20rpx 0;
flex: 1;
width: 100%;
text-align: center;
&:last-child { .classHour {
border: 1px solid #F5F8F7; position: absolute;
} font-size: 24rpx;
font-family: PingFang SC-Medium, PingFang SC;
font-weight: 50;
background: rgba(#000, .6);
top: 0;
left: 0;
padding: 5rpx 20rpx;
color: #FFFFFF;
border-radius: 0 0 10rpx 0;
} }
} }
.curList { .curList {
padding-bottom: 100px; padding-bottom: calc(env(safe-area-inset-bottom) + 63rpx);
} }
.add { .add {
@ -83,4 +111,6 @@
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
padding: 30rpx; padding: 30rpx;
align-items: center;
box-sizing: border-box;
} }

@ -6,12 +6,17 @@ import styles from './courseAdmin.module.scss'
import Taro, {useReachBottom} from "@tarojs/taro"; import Taro, {useReachBottom} from "@tarojs/taro";
import MyButton from "@/components/button/MyButton"; import MyButton from "@/components/button/MyButton";
import storageDep from "@/hooks/storageDep"; import storageDep from "@/hooks/storageDep";
import Spin from "@/components/spinner";
import Img from "@/components/image/image";
import PageScript from "@/components/pageScript/pageScript";
import omit from '@/static/img/omit.png'
const CourseAdmin: FC = () => { const CourseAdmin: FC = () => {
const [total, setTotal] = useState(0) const [total, setTotal] = useState(0)
const [data, setData] = useState<Curriculum[]>([]) const [data, setData] = useState<Curriculum[]>([])
const [batch, setBatch] = useState(false) const [batch, setBatch] = useState(false)
const [curs, setCurs] = useState<number[]>([]) const [curs, setCurs] = useState<number[]>([])
const [enable, setEnable] = useState(true)
const [param, setParam] = useState<CourseAllParam>({ const [param, setParam] = useState<CourseAllParam>({
page: 1, page: 1,
page_size: 10, page_size: 10,
@ -19,6 +24,7 @@ const CourseAdmin: FC = () => {
dep_id: 0 dep_id: 0
}) })
/** /**
*@param replace *@param replace
*/ */
@ -34,6 +40,7 @@ const CourseAdmin: FC = () => {
]) ])
} }
}) })
setEnable(false)
} }
useEffect(() => { useEffect(() => {
@ -101,6 +108,27 @@ const CourseAdmin: FC = () => {
batchChangDep([id], depList, required) batchChangDep([id], depList, required)
} }
function changeCourse(data: Curriculum, index: number) {
Taro.showActionSheet({
itemList: [
'修改部门',
"删除"
],
success({tapIndex}) {
switch (tapIndex) {
case 0:
changeDep(data.id, data.data)
break
case 1:
del(data.id, index)
break
}
},
fail() {
}
})
}
/** /**
* *
* @param ids id * @param ids id
@ -121,13 +149,12 @@ const CourseAdmin: FC = () => {
Taro.useDidShow(useCallback(async () => { Taro.useDidShow(useCallback(async () => {
const dep_id = storageDep.get() const dep_id = storageDep.get()
const is_required = storageDep.getRequired()
if (!dep_id.length || !is_required.length || !curs.length) return; if (!dep_id.length || !curs.length) return;
try { try {
await ManageApi.addCur({course_id: curs, dep_id, is_required}) await ManageApi.addCur({course_id: curs, dep_id})
Taro.showToast({title: '修改成功'}) Taro.showToast({title: '修改成功'})
// deps 中没有 筛选条件中的depid 删除已选的课程 // deps 中没有 筛选条件中的depid 删除已选的课程
if (param.dep_id && dep_id.includes(param.dep_id)) { if (param.dep_id && dep_id.includes(param.dep_id)) {
@ -148,43 +175,44 @@ const CourseAdmin: FC = () => {
return ( return (
<View> <>
<Search param={param} setParam={setParam}/> <Spin enable={enable} overlay/>
<Search param={param} setParam={setParam} total={total} setBatch={setBatch}/>
<View className={styles.curList}> <View className={styles.curList}>
{ {
data?.map((d, index) => <View key={d.id} className={styles.curBox}> data?.map((d, index) => <View key={d.id} className={styles.curBox}>
<View className={styles.curTitle} onClick={() => addCurs(d.id)}> <View className={styles.curTitle} onClick={() => addCurs(d.id)}>
{batch && <Radio {batch && <Radio
color='#45D4A8'
checked={curs.includes(d.id)} checked={curs.includes(d.id)}
style={{marginTop: '30px'}} style={{marginTop: '30px'}}
onClick={() => addCurs(d.id)}/>} onClick={() => addCurs(d.id)}/>}
<Image src={d.thumb} className={styles.curImage}/> <View className={styles.curImage}>
<View>{d.title}</View> <Img src={d.thumb} width={280} height={164} errorType='course'/>
</View> <View className={styles.classHour}>{d.class_hour}</View>
<View className={styles.Operation}> </View>
<View onClick={() => del(d.id, index)}></View> <View className="flex-1">
<View onClick={() => changeDep(d.id, d.data)}></View> <View>{d.title}</View>
<View className='mt-1 text-dark font-26 word-break text-ellipsis-2'>{d.short_desc}</View>
</View>
</View> </View>
<Image src={omit} mode='widthFix' style={{width: '30rpx', height: '30rpx', padding: '30rpx'}}
onClick={() => changeCourse(d, index)}/>
</View>) </View>)
} }
<PageScript/>
</View> </View>
<View className={styles.add}> <View className={styles.add}>
{
!batch
&& data.length > 0
&& <View style={{margin: 'auto', padding: '15px'}} onClick={() => setBatch(true)}></View>
}
{ {
batch && <View className={styles.addBatch}> batch && <View className={styles.addBatch}>
<Radio onClick={all} checked={data.length === curs.length}> </Radio> <Radio onClick={all} checked={data.length === curs.length} color='#45D4A8'> </Radio>
<Text onClick={() => setBatch(false)}></Text> <Text onClick={() => setBatch(false)}></Text>
<MyButton size='mini' onClick={() => batchChangDep(curs)}>{curs.length}</MyButton> <MyButton size='mini' onClick={() => batchChangDep(curs)}>{curs.length}</MyButton>
</View> </View>
} }
</View> </View>
</View> </>
) )
} }

@ -1,14 +1,22 @@
.content {
margin-top: 20rpx;
background: white;
min-height: calc(100vh - 20rpx - env(safe-area-inset-bottom));
box-sizing: border-box;
padding-bottom: calc(env(safe-area-inset-bottom) + 100rpx);
}
.operation { .operation {
width: 100%; width: 100%;
background: #F5F8F7; background: #F5F8F7;
position: fixed; position: fixed;
bottom: 0; bottom: 0;
padding: 30px 0; align-items: center;
color: #45D4A8; color: #45D4A8;
left: 0; left: 0;
.safeAreaInsetBottom { .safeAreaInsetBottom {
justify-content: space-around; height: 100rpx;
padding-bottom: env(safe-area-inset-bottom); padding-bottom: env(safe-area-inset-bottom);
display: flex; display: flex;
} }

@ -8,6 +8,7 @@ import folder from '@/static/img/folder.png'
import ShowModel from "@/components/showModel/showModel"; import ShowModel from "@/components/showModel/showModel";
import Empty from "@/components/empty/empty"; import Empty from "@/components/empty/empty";
import {Profile} from '@/store' import {Profile} from '@/store'
import Spin from "@/components/spinner";
const DepAdmin: FC = () => { const DepAdmin: FC = () => {
const router = useRouter() const router = useRouter()
@ -17,6 +18,7 @@ const DepAdmin: FC = () => {
const [isPut, setIsPut] = useState(false) const [isPut, setIsPut] = useState(false)
const [depName, setDepName] = useState('') const [depName, setDepName] = useState('')
const [reset, setReset] = useState(false) const [reset, setReset] = useState(false)
const [enable, setEnable] = useState(true)
const {company} = Profile.useContainer() const {company} = Profile.useContainer()
@ -31,6 +33,7 @@ const DepAdmin: FC = () => {
} }
} catch (e) { } catch (e) {
} }
setEnable(false)
} }
function showPop(name: string | undefined) { function showPop(name: string | undefined) {
@ -87,8 +90,7 @@ const DepAdmin: FC = () => {
break break
} }
}, },
fail() { fail(){}
}
}) })
} }
@ -97,6 +99,10 @@ const DepAdmin: FC = () => {
Taro.navigateTo({url: '/pages/manage/addStudent/addStudent'}) Taro.navigateTo({url: '/pages/manage/addStudent/addStudent'})
} }
function jumpDepartment(){
Taro.navigateTo({url:`/pages/manage/department/addDepartment?company_id=${company?.id}&parent_id=${Number(router.params.dep_id) || 0}&sort=${manages.length}`})
}
const addDep = useCallback(async () => { const addDep = useCallback(async () => {
if (!depName) { if (!depName) {
Taro.showToast({title: '请填写部门名称!', icon: 'error'}) Taro.showToast({title: '请填写部门名称!', icon: 'error'})
@ -137,7 +143,8 @@ const DepAdmin: FC = () => {
return ( return (
<> <>
<View> <Spin overlay enable={enable}/>
<View className='content'>
{manages.map(d => <PopPut {manages.map(d => <PopPut
key={d.id} key={d.id}
title={d.name} title={d.name}
@ -146,22 +153,24 @@ const DepAdmin: FC = () => {
leftImage={folder} leftImage={folder}
/>)} />)}
{users.map(d => <PopPut {
key={d.id} users.map(d => <PopPut
leftImage={d.avatar} errorType='avatar'
title={d.name} key={d.id}
onClick={() => Taro.navigateTo({url: '/pages/manage/userInfo/userInfo?userId=' + d.id})} leftImage={d.avatar}
content={['学员', '管理员', '超级管理员'][d.role_type]} title={d.name}
/>)} onClick={() => Taro.navigateTo({url: '/pages/manage/userInfo/userInfo?userId=' + d.id})}
content={['学员', '管理员', '超级管理员'][d.role_type]}
/>)
}
{!manages.length && !users.length && <Empty name='暂无数据'/>} {!manages.length && !users.length && <Empty name='暂无数据'/>}
<View className='operation'> <View className='operation'>
<View className='safeAreaInsetBottom'> <View className='safeAreaInsetBottom'>
<View onClick={jumpAddStudent}></View> <View style={{flex:'1',textAlign:'center',lineHeight:'100rpx'}} onClick={jumpAddStudent}></View>
<View onClick={() => showPop(undefined)}></View> <View style={{flex:'1',textAlign:'center',lineHeight:'100rpx'}} onClick={jumpDepartment}></View>
{ {
router.params.id && <View onClick={managesSheet}></View> router.params.id && <View style={{flex:'1',textAlign:'center',lineHeight:'100rpx'}} onClick={managesSheet}></View>
} }
</View> </View>
</View> </View>

@ -1,4 +1,3 @@
export default definePageConfig({ export default definePageConfig({
navigationBarTitleText: '', navigationBarTitleText: '',
onReachBottomDistance: 50
}) })

@ -0,0 +1,17 @@
.dep {
background: #ddd;
padding: 5px 15px;
border-radius: 3px;
}
.depSelected {
background: #9ee0a3;
color: #3f6942;
}
.add {
//position: fixed;
width: 710rpx;
margin-top: 30px;
border: none !important;
}

@ -0,0 +1,68 @@
import {Button, Form, Input, View} from "@tarojs/components";
import {useEffect, useState} from "react";
import {ManageApi} from "@/api/manage";
import Taro from "@tarojs/taro";
import './addDepartment.scss';
import {getCurrentInstance} from "@tarojs/runtime";
const AddDepartment = () => {
const [departmentInfo, setDepartmentInfo] = useState<{ name: string } | null>(null)
const params = getCurrentInstance()?.router?.params as { id?: string;parent_id?: string;sort?: string;company_id?:string }
useEffect(() => {
Taro.setNavigationBarTitle({ title:'添加部门' })
if (params.id) {
Taro.setNavigationBarTitle({ title:'修改部门' })
}
}, [])
async function submit(e) {
const value: {name: string} = e.detail.value
for (const [_, val] of Object.entries(value)) {
if (!val) {
Taro.showToast({title: "请填写部门名称a", icon: 'error'})
return
}
}
Taro.showLoading()
try {
if (params.id) {
await ManageApi.putDep(params.id, departmentInfo!.name)
} else {
await ManageApi.addDep({
name: departmentInfo!.name,
parent_id: Number(params.parent_id),
company_id: Number(params.company_id),
sort: Number(params.sort),
})
}
Taro.hideLoading()
Taro.showToast({title: "添加成功", icon: 'success'})
setTimeout(() => {
Taro.navigateBack()
}, 500)
} catch (e) {
}
Taro.hideLoading()
}
function onBlur(e){
setDepartmentInfo(p => ({...p,name:e.detail.value}))
}
return (
<View className='bg-white px-2 mt-2'>
<Form className='form' onSubmit={submit}>
<View className='item'>
<View></View>
<Input placeholder='请输入部门名称' name='name' value={departmentInfo?.name}
onBlur={onBlur}/>
</View>
<Button className='add button' formType='submit'>{params.id ? '修改部门' : "添加部门"}</Button>
</Form>
</View>
)
}
export default AddDepartment

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save