Compare commits

...

137 Commits
main ... master

Author SHA1 Message Date
一杯沧海 88f1eb3720 更改瀑布流视频封面加载状态及请求视频数据是否需要替换最新资源 10 months ago
一杯沧海 07fed1a1e5 自定义ui视频组件部修正 1 year ago
一杯沧海 e3ced26435 自定义ui视频组件部修正 1 year ago
一杯沧海 624d7cb183 自定义ui视频组件部分样式抽离 1 year ago
一杯沧海 c421e30b71 自定义ui视频组件增加是否显示全屏可选参数 1 year ago
一杯沧海 163af32973 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. 3
      .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. 20
      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. 21
      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. 66
      src/components/collect/collect.module.scss
  25. 82
      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. 114
      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. 16
      src/components/spinner/index.tsx
  45. 19
      src/components/spinner/style.scss
  46. 48
      src/components/tabs/tabs.scss
  47. 33
      src/components/tabs/tabs.tsx
  48. 14
      src/components/textCollapse/collapse.tsx
  49. 9
      src/components/topic/single.module.scss
  50. 26
      src/components/topic/single.tsx
  51. 7
      src/components/video/video.tsx
  52. 13
      src/components/videoCover/videoCover.scss
  53. 7
      src/components/videoCover/videoCover.tsx
  54. 27
      src/components/videoList/videoList.module.scss
  55. 88
      src/components/videoList/videoList.tsx
  56. 8
      src/components/videoPro/index.module.scss
  57. 247
      src/components/videoPro/index.tsx
  58. 73
      src/components/water-full/index.module.scss
  59. 102
      src/components/water-full/index.tsx
  60. 28
      src/hooks/articlesEvent.ts
  61. 25
      src/hooks/eventsIndex.ts
  62. 40
      src/hooks/pubsub.ts
  63. 25
      src/hooks/storageDep.ts
  64. 28
      src/hooks/videoEvent.ts
  65. 28
      src/pages/business/courType/courType.tsx
  66. 6
      src/pages/business/curHistory/curHistory.tsx
  67. 5
      src/pages/business/history/history.module.scss
  68. 21
      src/pages/business/history/history.tsx
  69. 64
      src/pages/business/userInfo/userInfo.tsx
  70. 61
      src/pages/business/videoInfo/components/catalogue.tsx
  71. 15
      src/pages/business/videoInfo/components/course.tsx
  72. 25
      src/pages/business/videoInfo/components/hours.tsx
  73. 23
      src/pages/business/videoInfo/videoInfo.scss
  74. 35
      src/pages/business/videoInfo/videoInfo.tsx
  75. 3
      src/pages/business/waterFull/test.config.ts
  76. 37
      src/pages/business/waterFull/test.tsx
  77. 2
      src/pages/check/check.config.ts
  78. 4
      src/pages/check/check.module.scss
  79. 26
      src/pages/check/check.tsx
  80. 28
      src/pages/home/components/adware.tsx
  81. 57
      src/pages/home/components/curRecommended.tsx
  82. 12
      src/pages/home/components/feature.tsx
  83. 84
      src/pages/home/components/feature_recommended.tsx
  84. 23
      src/pages/home/components/search.tsx
  85. 8
      src/pages/home/home.config.ts
  86. 89
      src/pages/home/home.module.scss
  87. 71
      src/pages/home/home.tsx
  88. 75
      src/pages/index/components/videoList.tsx
  89. 6
      src/pages/index/index.config.ts
  90. 31
      src/pages/index/index.module.scss
  91. 79
      src/pages/index/index.tsx
  92. 8
      src/pages/login/login.module.scss
  93. 88
      src/pages/login/login.tsx
  94. 5
      src/pages/manage/addStudent/addStudent.scss
  95. 6
      src/pages/manage/addStudent/addStudent.tsx
  96. 111
      src/pages/manage/courseAdmin/components/search.tsx
  97. 76
      src/pages/manage/courseAdmin/courseAdmin.module.scss
  98. 62
      src/pages/manage/courseAdmin/courseAdmin.tsx
  99. 12
      src/pages/manage/depAdmin/depAdmin.scss
  100. 27
      src/pages/manage/depAdmin/depAdmin.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,20 +1,32 @@
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 = {
@ -32,11 +44,15 @@ export const brandApi = {
/** 文章列表 */ /** 文章列表 */
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',
@ -61,7 +68,8 @@ export default defineAppConfig({
'history/history', 'history/history',
'curHistory/curHistory', 'curHistory/curHistory',
'hourHistory/hourHistory', 'hourHistory/hourHistory',
'courType/courType' 'courType/courType',
'waterFull/test',
] ]
}, },
{ {
@ -71,11 +79,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 +102,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,66 @@
.collect {
width: 130rpx;
display: flex;
align-items: center;
color: #909795;
padding: 0 10rpx;
}
.collectImage {
position: relative;
margin-right: 10rpx;
}
.zoom {
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,82 @@
import {CSSProperties, FC, useCallback, useEffect, useState} from "react";
// import star from '@/static/img/star.png'
// import starLine from '@/static/img/starLine.png'
import {View,Text} 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 &&
<Text style={{fontSize:'30rpx',lineHeight:'30rpx'}}></Text>
}
{/*<Text>{!props.textHidden && '收藏'}</Text>*/}
</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,77 @@
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'
import logo from '@/static/img/logo.svg'
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())
console.log(height,'height')
console.log(loading,'loading')
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 +81,53 @@ 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',
}}>
{ loading &&
<View style={{width: "100%", height: '100%',display:'flex',justifyContent:'center',alignItems:'center'}}>
<Image
src={logo}
mode={"widthFix"}
style={{width: `100rpx`}}/>
</View>
}
{!isError && {!isError &&
<View animation={animationData} style={{height: '100%', width: '100%'}}>
<Image <Image
{...props}
src={src} src={src}
mode={mode} mode={mode}
style={{width: `${width}rpx`, height: `${height}rpx`}} lazyLoad
fadeIn
defaultSource={errorUrl}
style={{
width: width ? `${width}rpx` : "100%",
height: height ? `${height}rpx` : "100%",
verticalAlign: 'middle'
}}
onError={onErrorHandler} onError={onErrorHandler}
onLoad={onLoadHandler}> onLoad={onLoadHandler}/>
</Image> </View>
}
{
loading &&
<AtActivityIndicator mode={"center"} content='加载中...'/>
} }
{ {
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: 0;
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>
{ {

@ -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 {
@ -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) {
@ -80,7 +81,6 @@ 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') {
@ -89,14 +89,12 @@ function createController(setState: StateSetter): Controller {
status = nextStatus status = nextStatus
notifyListener() notifyListener()
} }
}, 600)
} }
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)
} }
@ -161,7 +158,8 @@ 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>

@ -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%;
@ -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,6 +1,22 @@
View::-webkit-scrollbar { .tabsBack {
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; display: none !important;
} }
}
}
.tabs { .tabs {
width: 100%; width: 100%;
@ -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;
} }
.current { &:last-child{
padding: 16rpx 0 16rpx 16rpx;
}
}
.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 = (
return (
<View className='tabs'>
<ScrollView scrollX scrollLeft={left} scrollWithAnimation showScrollbar={false}>
<View className={'tabs-scroll ' + (opt.tabList.length < 5 ? 'justify-around' : '')}> <View className={'tabs-scroll ' + (opt.tabList.length < 5 ? 'justify-around' : '')}>
{opt.tabList.map((d, index) => <View {opt.tabList.map((d, index) => <View
key={index} key={index}
className={'tabs-item ' + (is_current(d.value, index) ? 'current' : null)} className={'tabs-item ' + (is_current(d.value, index) && 'current ') + (!opt.hiddenSliding && 'sliding')}
onClick={(event) => onChange(event, index, d)}> onClick={(event) => onChange(event, index, d)}>
{d.title} {d.title}
</View>)} </View>)}
</View> </View>
)
if (opt.scrollable != false) {
children = (
<ScrollView scrollX scrollLeft={left} scrollWithAnimation showScrollbar={false} style={{height: opt.height}}>
{children}
</ScrollView> </ScrollView>
)
} else {
children = (
<View style={{height: opt.height}}>
{children}
</View>
)
}
return (
<View className={`tabs ${opt.backMode && 'tabsBack'}`} style={{height: opt.height, ...opt.style}}>
{children}
</View> </View>
) )
} }

@ -22,9 +22,8 @@ const LineEllipsis:FC<Props> = ({text}:Props) => {
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)
@ -43,7 +42,7 @@ const LineEllipsis:FC<Props> = ({text}:Props) => {
e && e.stopPropagation(); e && e.stopPropagation();
setOverflow(true) setOverflow(true)
setIsExpansioned(false) setIsExpansioned(false)
}; }
function toggle() { function toggle() {
if (disabled) return; if (disabled) return;
@ -62,7 +61,7 @@ const LineEllipsis:FC<Props> = ({text}:Props) => {
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',
}}> }}>
@ -76,15 +75,14 @@ const LineEllipsis:FC<Props> = ({text}:Props) => {
</View> </View>
{overflow && ( {overflow && (
<Text <Text
style={{position: "absolute", top: "70rpx", right: "0", background: "#fff"}}
className={styles.expansion} className={styles.expansion}
onClick={handleExpend} onClick={handleExpend}>
style={{position: 'absolute', top: '0', right: '0', background: '#fff'}}>
. . . . . .
</Text> </Text>
)} )}
</View> </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;
@ -29,9 +30,11 @@
.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,14 +77,17 @@ 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}> <>
{
props.topic && <View className={styles.singleCover} style={style}>
<View>{props.topic.question}</View> <View>{props.topic.question}</View>
<View className={judgment("true")} <View className={judgment("true")}
onClick={() => examination("true")}> onClick={() => examination("true")}>
@ -96,6 +98,8 @@ export const Single: FC<Props> = (props) => {
{props.topic.error_value} {props.topic.error_value}
</View> </View>
</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,88 @@
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 playWhite from '@/static/img/palyWhite.png'
import starWhite from '@/static/img/starWhite.png'
import {formatMinute} from "@/utils/time";
import { getWfsInfo } from '@/utils/wfs'
interface Props {
data: VideList
errorType?: ImgErrType
}
const VideoList: FC<Props> = (props) => {
const [data, setData] = useState<VideList>(props.data)
const [cover,setCover] = useState<string>('')
console.log(data,'data现在的数据')
useEffect(() => {
getWfsInfo(data.resource_id).then(res => {
setData((data) =>({...data, url_path:res.object.thumb,video_width:res.object.width,video_height:res.object.height}))
setCover(res.object.thumb)
}).catch(()=>{
console.log('获取失败')
setData((data) => ({...data,video_width:data.resource.width,video_height:data.resource.height}))
})
}, [])
useEffect(() => {
videoEvent.videoOn(data.id, ({view}) => {
setData((data) => ({
...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 (
<>
{ data.video_height && data.video_width
&&
<View className={styles.health}>
<View key={data.id} onClick={jump} className="bg-white">
<Img src={cover || data.url_path} height={data.video_height/data.video_width*348} errorType={props.errorType} />
<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={playWhite}/>
<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,8 @@
.progressBottom{
bottom: calc(env(safe-area-inset-bottom) + 100rpx);
//bottom:0;
position: fixed;
z-index: 1000;
left:0;
right:0;
}

@ -0,0 +1,247 @@
import React, {FC, useEffect, useRef, useState, useImperativeHandle} from "react";
import {Image, Text, Video, View} from "@tarojs/components";
import styles from "./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
showFull?: boolean
height?: number
progress2bottom?: boolean
}
const VideoPro:FC<Props> = ({src,onRef,showFull,height,progress2bottom}) => {
const globalData = Taro.getApp().globalData
if(onRef){
//用useImperativeHandle暴露一些外部ref能访问的属性
useImperativeHandle(onRef, () => {
return {
func: pause,
}
})
}
// 视频ui控制需要的响应式数据
const videoContext = useRef<any>()
const [isPlay, setIsPlay] = useState(true)
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<NodeJS.Timeout>()
const [videoHeight] = useState<number>(height || 600)
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 = undefined
}
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);
videoContext.current.play()
setIsPlay(true)
updateState.current = true
setSlidervalue(e)
}
}
function sliderOnChanging(){
updateState.current = false
}
return (
<View style={{width: '750rpx', height: `${videoHeight}rpx`}}>
<Video
style={{width: '750rpx', height: `${videoHeight}rpx`}}
id='video'
autoplay
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':`${videoHeight}rpx`,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 > 0 && showMenu && !progress2bottom &&
<>
{
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-2 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 className="text-white font-26 pr-2">{total_duration}</Text>
{ showFull &&
<>
{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 className={`${styles.progressBottom} flex align-center px-3`} style={{height:'40px',boxSizing:'border-box'}}>
{ duration > 0 && showMenu && progress2bottom &&
<>
{
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-2 font-26" style={{width:'60rpx'}}>{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 className="text-white font-26 pr-2">{total_duration}</Text>
{ showFull &&
<>
{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>
)
}
export default VideoPro

@ -0,0 +1,73 @@
.wang_product{
width:100%;
}
.wang_product .product_water .water {
border-radius: 16rpx !important;
//background-color: #ffffff;
overflow: hidden;
}
.wang_product .product_water .water .image {
border-radius: 16rpx 16rpx 0 0 !important;
width: 100%;
overflow: hidden;
}
.wang_product .product_water.col_2_20 .water {
margin-bottom: 20rpx;
}
.wang_product .product_water.col_2_20 .water.right {
margin-left: 10rpx;
}
.wang_product .product_water.col_2_20 .water.left {
margin-right: 10rpx;
}
/* 二列布局 */
.wang_product .product_list[class*='col-2-'] .item image,
.wang_product .product_list[class*='col-2-'] .item .img-wrap {
width: 100%;
height: 400rpx;
}
.wang_product .product_list[class*='col-2-'] .comment {
display: none;
}
.wang_product .product_list[class*='col-2-'] .content {
height: 200rpx;
}
.wang_product .product_list.col_2_20:after {
content: '';
width: calc((100% - 20rpx) / 2);
}
.wang_product .product_list.col_2_20 .item {
width: calc((100% - 20rpx) / 2);
margin-bottom: 20rpx;
}
.wang_waterfall {
display: flex;
flex-direction: row;
align-items: flex-start;
.wang_column {
display: flex;
flex: 1;
flex-direction: column;
height: auto;
width: 50%;
}
}

@ -0,0 +1,102 @@
import { FC, useEffect, useState } from 'react'
import { View } from '@tarojs/components'
import VideoList from '@/components/videoList/videoList'
import Taro from '@tarojs/taro'
import styles from './index.module.scss'
type Props = {
data: any[]
}
const WaterFull: FC<Props> = ({data}) => {
const [leftList,setLeftList] = useState<any[]>([])
const [rightList,setRightList] = useState<any[]>([])
const [tempList,setTempList] = useState<any[]>([])
const [copyFlowList,setCopyFlowList] = useState<any[]>([])
const startLength = copyFlowList.length
// const query = Taro.createSelectorQuery()
console.log(rightList.length,leftList.length,"高")
useEffect(() => {
setTempList(cloneData(data).splice(startLength))
setCopyFlowList(cloneData(data))
}, [data]);
useEffect(() => {
let tempArr = cloneData(tempList)
splitData(tempArr)
}, [tempList])
async function splitData(arr: any[]){
if (!arr.length) {
return
}
// let leftHeight = await getRect('#wang-left-column')
// let rightHeight = await getRect('#wang-right-column')
// console.log("左侧高度",leftHeight,)
let item = arr[0]
Taro.nextTick(() => {
arr.length % 2 == 0 ? setLeftList((data) => ([...data, item])) : setRightList((data) => ([...data, item]))
})
// if (leftHeight < rightHeight) {
// console.log("左侧插入",leftHeight,rightHeight)
// setLeftList((data) => ([...data,item]))
// } else if (leftHeight > rightHeight) {
// console.log("右侧插入",leftHeight,rightHeight)
// setRightList((data) => ([...data,item]))
// } else {
// console.log("左侧插入")
// setLeftList((data) => ([...data,item]))
// }
// 移除临时列表的第一项
arr.splice(0, 1)
// 如果临时数组还有数据,继续循环
Taro.nextTick(() => {
if (arr.length) {
splitData(arr) // 在当前同步流程结束后,下一个时间片执行
}
})
}
// async function getRect(selector:string): Promise<number> {
// return new Promise((resolve) => {
//
// query.select(selector).boundingClientRect()
// // 执行查询,获取结果
// query.exec((res) => {
// if (res && res[0]) {
// console.log('Element height:',res, res[0].height)
// resolve(res[0].height)
// }
// })
// })
// }
function cloneData(data:unknown[]) {
return JSON.parse(JSON.stringify(data))
}
return (
<View className={styles.wang_product}>
<View className={[styles.product_water,styles.col_2_20].join(" ")}>
<View className={styles.wang_waterfall}>
<View id="wang-left-column" className={styles.wang_column}>
<View className={`${styles.water} ${styles.left}`}>
{leftList.map(d => <VideoList key={d.id} data={d} errorType='health'/>)}
</View>
</View>
<View id="wang-right-column" className={styles.wang_column}>
<View className={[styles.water,styles.right].join(" ")}>
{rightList.map(d => <VideoList key={d.id} data={d} errorType='health'/>)}
</View>
</View>
</View>
</View>
</View>
)
}
export default WaterFull

@ -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,40 @@
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) => {
console.log("发布没有")
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 (
<>
{
data.length > 0 ?
<>
<View className='py-2 flex justify-between flex-wrap'> <View className='py-2 flex justify-between flex-wrap'>
{data.length > 0 ? data.map(c => {
<VideoCover data.map(c => <VideoCover
thumb={c.thumb} thumb={c.thumb}
title={c.title} title={c.title}
id={c.id} id={c.id}
depId={c.id} depId={c.id}
key={c.id} key={c.id}
time={formatMinute(c.course_duration)} time={formatMinute(c.course_duration)}
/>) : <Empty name='暂无数据'/>} />)
}
</View> </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,12 +27,13 @@ const History = () => {
}, []) }, [])
return ( return (
<View>
<View className='mt-3'> <View className='mt-3'>
{data.length ? data.map((d, index) => {
data.length ? <> {
data.map((d, index) =>
<View key={index} className={styles.category} onClick={() => jump(d.userCourseRecord.course_id)}> <View key={index} className={styles.category} onClick={() => jump(d.userCourseRecord.course_id)}>
<View className={styles.thumb}> <View className={styles.thumb}>
<Image src={d.thumb} className={styles.image}/> <Img src={d.thumb} className={styles.image} width={300} height={188}/>
<View <View
className={styles.count}>{d.userCourseRecord.hour_count}/{d.userCourseRecord.finished_count}</View> className={styles.count}>{d.userCourseRecord.hour_count}/{d.userCourseRecord.finished_count}</View>
</View> </View>
@ -41,8 +44,12 @@ const History = () => {
<Text>{(d.userCourseRecord.finished_count / d.userCourseRecord.hour_count * 100).toFixed(0)}%</Text> <Text>{(d.userCourseRecord.finished_count / d.userCourseRecord.hour_count * 100).toFixed(0)}%</Text>
</View> </View>
</View> </View>
</View>) : <Empty name='无观看记录'/>} </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'>
{ {
playing ? <MyButton style={{flex: '1'}} onClick={onPause}></MyButton> token && <View className='Videobutton'>
: <MyButton style={{flex: '1'}} onClick={learning}></MyButton> {
} playing ? <MyButton className='flex-1'></MyButton>
<View className='px-3' onClick={() => setShow(true)}>...</View> : <MyButton className='flex-1' onClick={learning}></MyButton>
}
<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> </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
className={'hor' + ` ${complete(d.id) ? 'complete' : undefined}`}
key={index} key={index}
onClick={() => onClick(d.id, complete(d.id), d, data?.[index - 1]?.id)}> 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'/> >
{/*<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) => {
try {
const res = await curriculum.courseDep(id, depId) const res = await curriculum.courseDep(id, depId)
if (res) { res && setData(res)
setData(res) playId != null && currentVideo(res, playing) // 用于自动播放 判断当前课程是否完成
} } catch (e) {
if (playId != null) { // 用于自动播放 判断当前课程是否完成
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,19 +102,15 @@ 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
playId
? <Course id={playId} courseId={id} curEnd={curEnd} preview={preview}/> ? <Course id={playId} courseId={id} curEnd={curEnd} preview={preview}/>
: <Image src={data?.course.thumb || ''} className='image' mode='aspectFill'/> : <Img width={750} height={500} src={data?.course.thumb || ''} errorType='health'/>}
}
</View> </View>
<View className='header'> <View className='header'>
@ -126,9 +124,8 @@ const VideoInfo: FC = () => {
<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> </View>
<Catalogue data={data} setHors={setHors} id={id} playId={playId}/> <Catalogue data={data} setHors={setHors} id={id} playId={playId} collectUpdate={changeCollect} refresh={() => getData(false)}/>
</View> </View>
</>
) )
} }

@ -0,0 +1,3 @@
export default definePageConfig({
navigationBarTitleText: 'waterFull'
})

@ -0,0 +1,37 @@
import VirtualList from '@tarojs/components/virtual-list'
import React, { Component } from 'react'
import { View } from '@tarojs/components'
function buildData(offset = 0) {
return Array(500)
.fill(0)
.map((_, i) => i + offset)
}
const Row = React.memo(({ id, index, data }:{id: any,index:any,data:any}) => {
return (
<View id={id} className={index % 2 ? 'ListItemOdd' : 'ListItemEven'}>
Row {index} : {data[index]}
</View>
)
})
export default class Index extends Component {
state = {
data: buildData(0),
}
render() {
const { data } = this.state
const dataLen = data.length
return (
<VirtualList
height={800} /* 列表的高度 */
width="100%" /* 列表的宽度 */
item={Row} /* 列表单项组件,这里只能传入一个组件 */
itemData={data} /* 渲染列表的数据 */
itemCount={dataLen} /* 渲染列表的长度 */
itemSize={100} /* 列表单项的高度 */
/>
)
}
}

@ -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,16 +30,19 @@ const Adware: FC<Props> = ({data, only_flag, width}) => {
} }
return ( return (
<View> <>
{
adverts.length > 0 && <View className={styles.adware}>
{ {
adverts.length === 1 && <Image adverts.length === 1 && <Img
src={adverts[0].image_url} src={adverts[0].image_url}
mode='scaleToFill' mode='scaleToFill'
lazyLoad lazyLoad
fadeIn fadeIn
style={{width: width + "rpx", height: (space.height / space.width) * width + "rpx"}}
onClick={() => jumpAdware(adverts[0].image_path)} onClick={() => jumpAdware(adverts[0].image_path)}
className={styles.adware}/> width={width}
errorType='profession'
height={(space.height / space.width) * width}/>
} }
{ {
adverts.length > 1 && <Swiper adverts.length > 1 && <Swiper
@ -47,19 +50,22 @@ const Adware: FC<Props> = ({data, only_flag, width}) => {
autoplay autoplay
circular circular
style={{width: width + "rpx", height: (space.height / space.width) * width + "rpx", overflow: 'hidden'}} style={{width: width + "rpx", height: (space.height / space.width) * width + "rpx", overflow: 'hidden'}}
indicatorActiveColor='rgba(255,255,255,0.5)' indicatorActiveColor='rgba(255,255,255,0.5)'>
className={styles.adware}>
{adverts.map(d => <SwiperItem key={d.id}> {adverts.map(d => <SwiperItem key={d.id}>
<Image <Img
src={d.image_url} src={d.image_url}
lazyLoad lazyLoad
fadeIn fadeIn
onClick={() => jumpAdware(d.image_path)} width={width}
style={{width: width + "rpx", height: '100%'}}/> errorType='profession'
height={(space.height / space.width) * width}
onClick={() => jumpAdware(d.image_path)}/>
</SwiperItem>)} </SwiperItem>)}
</Swiper> </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,10 +44,26 @@ 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'>
<View className='mt-3 bold text-dark flex justify-between align-center mb-2'>
<Text className='font-32'></Text>
<View className='font-24 text-muted flex align-center pl-2 py-2' onClick={jumpArticles}>
<Text></Text>
<IconFont name={'chevron-right'} size={14} />
</View>
</View>
{articles.map(d => <ArticlesBox key={d.id} data={d}/>)}
</View>
)
}
let videos: ReactNode | undefined
if (data.length > 0) {
videos = (
<View className='mt-3'>
<Image src={courseTag} mode='widthFix' className={styles.courseTag}/> <Image src={courseTag} mode='widthFix' className={styles.courseTag}/>
<View className={'pb-2 flex justify-between flex-wrap ' + styles.videoListBox}> <View className={'pb-2 flex justify-between flex-wrap ' + styles.videoListBox}>
{ {
@ -47,12 +73,19 @@ const CurRecommended: FC = () => {
id={c.id} id={c.id}
depId={c.id} depId={c.id}
key={c.id} key={c.id}
marker={`${c.class_hour}`}
/>) />)
} }
</View> </View>
</View> </View>
)
} }
<View className='text-center text-muted pb-3 font-28'>- -</View>
return (
<>
{examines}
{videos}
<PageScript/>
</> </>
) )
} }

@ -7,10 +7,9 @@ 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: '病症百科'},
] ]
@ -19,11 +18,12 @@ const Feature: FC = () => {
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,35 +138,31 @@ 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})
} }
const keys: string[] = []
const items = data.filter(d => d.data.length === 3).map(d => {
keys.push(d.url)
return ( return (
<View className={styles.feature}> <>
<Swiper nextMargin='30px' style={{height: '263px'}}>
{
data.map(d => <SwiperItem key={d.url}>
<Image <Image
mode='heightFix' mode='heightFix'
className={styles.featureTitle} className={styles.featureTitle}
onClick={() => jump(d.url)} src={d.titleUrl}/> onClick={() => jump(d.url, d)} src={d.titleUrl}/>
{ {
d.data.map((c, index) => <View d.data.length > 0 && d.data.map((c, index) => <View
className='flex mb-3' className='flex'
style={{marginBottom: '16rpx'}}
key={c.id} key={c.id}
onClick={() => jump(d.detailsUrl + c.path, c.id, d.type)}> onClick={() => jump(d.detailsUrl + c.path, d)}>
<View style={{position: 'relative'}}> <View style={{position: 'relative'}}>
<View className={styles.featureImage}> <View className={styles.featureImage}>
<Img src={c.imageUrl} height={100} width={140}/> <Img src={c.imageUrl} height={100} width={140} errorType={d.errorType}/>
</View> </View>
<Image src={[first, second, third][index]} className={styles.ranking} mode='aspectFill'/> <Image src={[first, second, third][index]} className={styles.ranking} mode='aspectFill'/>
</View> </View>
@ -174,11 +172,29 @@ const FeatureRecommended: FC<Props> = (props) => {
</View> </View>
</View>) </View>)
} }
</SwiperItem>) </>
)
})
// 没有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> </Swiper>
</View> </View>
) )
} }
// 只有一个top3
return (
<View className={styles.feature}>
{items[0]}
</View>
)
}
export default FeatureRecommended export default FeatureRecommended

@ -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 = () => {
function jump() {
Taro.navigateTo({url: '/pages/preview/search/search/index'})
} }
export const Search: FC<Props> = (props) => {
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,25 +9,73 @@ 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}> <>
<NavigationBar
className={styles.header}
backgroundColor={`rgba(255,255,255,${navbarOpacity})`}
cancelBack
leftNode={
<>
<Image src={logo} style={{height: "55%"}} mode='heightFix'/>
<Text className='font-36 font-weight ml-1'></Text>
</>
}
>
<View className={styles.headerDivider} style={{borderBottom: `1px solid rgba(200,200,200,${navbarOpacity})`}}></View>
</NavigationBar>
<Spin enable={enable} overlay/>
<View className={styles.content} style={{paddingBottom: token ? 0 : '100rpx'}}>
{/*<Search/>*/}
<Adware data={data?.adverts || []} only_flag='routine_home_top_banner' width={710}/> <Adware data={data?.adverts || []} only_flag='routine_home_top_banner' width={710}/>
<Feature/> <Feature/>
<FeatureRecommended <FeatureRecommended
@ -37,14 +85,15 @@ const Home: FC = () => {
skill={data?.skill || []} skill={data?.skill || []}
/> />
<Adware data={data?.adverts || []} only_flag='routine_home_recommend_banner' width={710}/> <Adware data={data?.adverts || []} only_flag='routine_home_recommend_banner' width={710}/>
<CurRecommended/> {data && <CurRecommended/>}
{ {
!token && <View className={styles.tipsLogin} onClick={unLogin}> !token && <View className={styles.tipsLogin}>
<View>~</View> <View>~</View>
<MyButton fillet size='mini'></MyButton> <MyButton fillet size='mini' onClick={unLogin}></MyButton>
</View> </View>
} }
</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,10 +61,6 @@ 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) {
case "is_required":
case "is_not_required":
case "is_not_finished":
const find = records.find(d => d?.course_id === id) const find = records.find(d => d?.course_id === id)
if (find) { if (find) {
if (class_hour === find.finished_count) { if (class_hour === find.finished_count) {
@ -72,32 +69,27 @@ export const VideoList: FC<Props> = ({categoryKey, setCategoryKey}) => {
return (<View>{`${class_hour}节/已学${find.finished_count}`}</View>) return (<View>{`${class_hour}节/已学${find.finished_count}`}</View>)
} }
return (<View>{`${class_hour}节/已学0节`}</View>) return (<View>{`${class_hour}节/已学0节`}</View>)
case "is_finished":
return <View></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,15 +103,20 @@ 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'
? <LoginView onSuccess={fetchData}/> : value?.length ?
<>
<View className='py-2 flex justify-between flex-wrap px-2'>
{
value?.map(c =>
<VideoCover <VideoCover
thumb={c.thumb} thumb={c.thumb}
title={c.title} title={c.title}
@ -128,10 +125,14 @@ export const VideoList: FC<Props> = ({categoryKey, setCategoryKey}) => {
key={c.id} key={c.id}
time={formatMinute(c.course_duration)} time={formatMinute(c.course_duration)}
content={rateOfLearning(c.id, c.class_hour)} content={rateOfLearning(c.id, c.class_hour)}
schedule={`学习进度${rateOfPercent(c.id, c.class_hour)}`}
/>) />)
: <Empty name='暂无课程'/>
} }
</View> </View>
<PageScript/>
</>
: <Empty name='暂无课程'/>
}
</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 Index: FC = () => {
const category: TabList[] = [ const category: TabList[] = [
{title: "必修", value: 'is_required'}, {title: "企选课程", value: 'is_required'},
{title: "选修", value: 'is_not_required'}, {title: "平台课程", value: 'is_not_required'},
{title: "已完成", value: 'is_finished'},
{title: "未完成", value: 'is_not_finished'},
] ]
const Index: FC = () => {
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 ( return (
<>
<View className={styles.content}> <View className={styles.content}>
<Tabs tabList={category} onChange={tabChange} current={categoryKey}/> <NavigationBar
<VideoList categoryKey={categoryKey} setCategoryKey={(categoryKey) => setCategoryKey(categoryKey)}/> cancelBack
leftNode={<Tabs tabList={category} onChange={tabChange} current={categoryKey} scrollable={false} hiddenSliding/>}
/>
<VideoList
categoryKey={categoryKey}
setCategoryKey={(categoryKey) => setCategoryKey(categoryKey)}
/>
</View> </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 (
<>
<Spin enable={enable} overlay/>
{
auditMode ?
<>
<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
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 className={styles.loginTips}>
<Text>使</Text>
</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 className='mt-3 flex justify-between font-24 text-dark align-center'>
<View className="flex">
<View onClick={() => setShow(true)} className={styles.select}>
{
param.dep_id > 0 ?
<Text style={{background: 'rgba(68,215,170,.08)', color: '#44d7aa'}}>
{deps.find(d => d.id === param.dep_id)?.name}
</Text>
:
<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 onClick={() => setShow(true)}>
<Text></Text>
<Image src={screen}
mode='aspectFit'
style={{
display: 'inline-block',
width: '15px',
height: '15px',
verticalAlign: 'middle',
marginLeft: '5px'
}}/>
</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}>
<Img src={d.thumb} width={280} height={164} errorType='course'/>
<View className={styles.classHour}>{d.class_hour}</View>
</View>
<View className="flex-1">
<View>{d.title}</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 className={styles.Operation}>
<View onClick={() => del(d.id, index)}></View>
<View onClick={() => changeDep(d.id, d.data)}></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 {
users.map(d => <PopPut
errorType='avatar'
key={d.id} key={d.id}
leftImage={d.avatar} leftImage={d.avatar}
title={d.name} title={d.name}
onClick={() => Taro.navigateTo({url: '/pages/manage/userInfo/userInfo?userId=' + d.id})} onClick={() => Taro.navigateTo({url: '/pages/manage/userInfo/userInfo?userId=' + d.id})}
content={['学员', '管理员', '超级管理员'][d.role_type]} 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>

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

Loading…
Cancel
Save