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. 34
      src/api/brand.ts
  8. 3
      src/api/curriculum.ts
  9. 43
      src/api/home.ts
  10. 18
      src/api/illness.ts
  11. 1
      src/api/manage.ts
  12. 10
      src/api/meeting.ts
  13. 30
      src/api/public.ts
  14. 22
      src/api/request.ts
  15. 11
      src/api/search.ts
  16. 24
      src/api/user.ts
  17. 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. 116
      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. 30
      src/components/spinner/index.tsx
  45. 21
      src/components/spinner/style.scss
  46. 48
      src/components/tabs/tabs.scss
  47. 33
      src/components/tabs/tabs.tsx
  48. 2
      src/components/textCollapse/collapse.module.scss
  49. 34
      src/components/textCollapse/collapse.tsx
  50. 13
      src/components/topic/single.module.scss
  51. 26
      src/components/topic/single.tsx
  52. 7
      src/components/video/video.tsx
  53. 13
      src/components/videoCover/videoCover.scss
  54. 7
      src/components/videoCover/videoCover.tsx
  55. 27
      src/components/videoList/videoList.module.scss
  56. 88
      src/components/videoList/videoList.tsx
  57. 8
      src/components/videoPro/index.module.scss
  58. 247
      src/components/videoPro/index.tsx
  59. 73
      src/components/water-full/index.module.scss
  60. 102
      src/components/water-full/index.tsx
  61. 28
      src/hooks/articlesEvent.ts
  62. 25
      src/hooks/eventsIndex.ts
  63. 40
      src/hooks/pubsub.ts
  64. 25
      src/hooks/storageDep.ts
  65. 28
      src/hooks/videoEvent.ts
  66. 30
      src/pages/business/courType/courType.tsx
  67. 6
      src/pages/business/curHistory/curHistory.tsx
  68. 5
      src/pages/business/history/history.module.scss
  69. 21
      src/pages/business/history/history.tsx
  70. 64
      src/pages/business/userInfo/userInfo.tsx
  71. 61
      src/pages/business/videoInfo/components/catalogue.tsx
  72. 15
      src/pages/business/videoInfo/components/course.tsx
  73. 25
      src/pages/business/videoInfo/components/hours.tsx
  74. 23
      src/pages/business/videoInfo/videoInfo.scss
  75. 35
      src/pages/business/videoInfo/videoInfo.tsx
  76. 3
      src/pages/business/waterFull/test.config.ts
  77. 37
      src/pages/business/waterFull/test.tsx
  78. 2
      src/pages/check/check.config.ts
  79. 4
      src/pages/check/check.module.scss
  80. 26
      src/pages/check/check.tsx
  81. 28
      src/pages/home/components/adware.tsx
  82. 57
      src/pages/home/components/curRecommended.tsx
  83. 20
      src/pages/home/components/feature.tsx
  84. 88
      src/pages/home/components/feature_recommended.tsx
  85. 25
      src/pages/home/components/search.tsx
  86. 8
      src/pages/home/home.config.ts
  87. 91
      src/pages/home/home.module.scss
  88. 71
      src/pages/home/home.tsx
  89. 75
      src/pages/index/components/videoList.tsx
  90. 6
      src/pages/index/index.config.ts
  91. 31
      src/pages/index/index.module.scss
  92. 81
      src/pages/index/index.tsx
  93. 8
      src/pages/login/login.module.scss
  94. 88
      src/pages/login/login.tsx
  95. 5
      src/pages/manage/addStudent/addStudent.scss
  96. 6
      src/pages/manage/addStudent/addStudent.tsx
  97. 111
      src/pages/manage/courseAdmin/components/search.tsx
  98. 76
      src/pages/manage/courseAdmin/courseAdmin.module.scss
  99. 62
      src/pages/manage/courseAdmin/courseAdmin.tsx
  100. 12
      src/pages/manage/depAdmin/depAdmin.scss
  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://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://playedu.yaojiankang.top
TARO_APP_LGOIN=true

@ -27,6 +27,7 @@ const config = {
"@": path.resolve(__dirname, '..', 'src')
},
mini: {
debugReact: true,
postcss: {
pxtransform: {
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",
"dayjs": "^1.11.9",
"marked": "^7.0.4",
"pnpm": "^8.9.0",
"react": "^18.0.0",
"react-dom": "^18.0.0",
"react-refresh": "^0.11.0",

@ -1,4 +1,4 @@
lockfileVersion: '6.1'
lockfileVersion: '6.0'
settings:
autoInstallPeers: true
@ -56,6 +56,9 @@ dependencies:
marked:
specifier: ^7.0.4
version: 7.0.4
pnpm:
specifier: ^8.9.0
version: 8.9.0
react:
specifier: ^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):
resolution: {integrity: sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q==}
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:
'@babel/core': ^7.0.0-0
dependencies:
@ -727,6 +731,7 @@ packages:
/@babel/plugin-proposal-numeric-separator@7.18.6(@babel/core@7.8.0):
resolution: {integrity: sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q==}
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:
'@babel/core': ^7.0.0-0
dependencies:
@ -11623,6 +11628,12 @@ packages:
semver-compare: 1.0.0
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:
resolution: {integrity: sha512-on2ZJVVDXRADWE6jnQaX0ioEylzgBpQk8r55NE4wjXW1ZxO+BgDlY6DXwj20i0V8eB4SenDQ00WEaxfiIQPcxg==}
engines: {node: '>= 0.12.0'}
@ -12192,6 +12203,7 @@ packages:
/prr@1.0.1:
resolution: {integrity: sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==}
requiresBuild: true
dev: true
optional: true

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

@ -1,42 +1,58 @@
import {request} from "@/api/request";
import {Illness} from "@/api/illness";
export type BrandRecord = {
logo: string;
name: string;
id: number
introductory_video: string
brand_album: string[]
brand_album: string
graphic_introduction: string
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 = {
id: number;
title: string
page_view: number
created_at: string
content: string
brands: BrandRecord[]
illness: Illness[]
collect: boolean
cover: string
owner_type: 1 | 2 // 疾病
}
export const brandApi = {
/** 品牌列表 */
list(page: number , page_size: number) {
list(page: number, page_size: number) {
return request<{
list: BrandRecord[],
total: number
}>(`/home/v1/brand/list?page=${page}&page_size=${page_size}` , "GET")
}>(`/home/v1/brand/list?page=${page}&page_size=${page_size}`, "GET")
},
/** 品牌详情 */
info(id: number) {
return request<BrandRecord>(`/home/v1/brand/${id}`, "GET")
},
/** 文章列表 */
articleList(owner_id: number,page:number) {
articleList(owner_id: number, page: number) {
return request<{
list: ArticleRecord[],
list: Articles[],
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 ) {
return request<ArticleRecord>(`/home/v1/article/${id}` , "GET")
articleInfo(id: number) {
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 {
audit_mode: boolean
/** 完成 */
finished_count: number;
/** 未完成 */
@ -81,7 +82,7 @@ export const curriculum = {
},
/** 查看课程课时数据 */
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) {

@ -31,50 +31,40 @@ export interface AdwareType {
export interface HomeData {
adverts: AdwareType[]
skill: Kill[]
health: Health[]
skill: VideList[]
health: VideList[]
brand: {
list: Brand[]
}
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 = {
advert(only_flag: string) {
return request<AdwareType[]>("/home/v1/advert/unique?only_flag=" + only_flag, "GET")
},
course(page: number, page_size: number) {
return request<{ data: Curriculum[], total: number }>('/home/v1/course/course', "GET", {page, page_size})
},
/** 健康管理 */
healthTop(count: number) {
return request<Health[]>('/home/v1/health/top', "GET", {count})
return request<Course>('/home/v1/course/course', "GET", {page, page_size})
},
health(page: number, page_size: number) {
return request<{ data: Health[], total: number }>('/home/v1/health/index', "GET", {page, page_size})
},
/** 增加播放量 */
healthSetPlay(id) {
return request(`/home/v1/health/set_play/${id}`, "PUT")
return request<{ data: VideList[], total: number }>('/home/v1/health/index', "GET", {page, page_size})
},
/** 品牌 */
brand(page: number, page_size: number) {
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() {
return request<Category[]>('/home/v1/skill/category', "GET")
},
skillList(categoryId: number, page: number, page_size: number) {
return request<{ data: Kill[], total: number }>('/home/v1/skill/index', "GET", {categoryId, page, page_size})
},
skillSetPlay(id: number) {
return request(`/home/v1/skill/set_play/${id}`, "PUT")
return request<{ data: VideList[], total: number }>('/home/v1/skill/index', "GET", {categoryId, page, page_size})
},
/** 疾病知识 */
illness(page: number, page_size: number) {
@ -85,5 +75,12 @@ export const HomeApi = {
},
home() {
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";
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 = {
/** 疾病列表 */
@ -7,6 +23,6 @@ export const illnessApi = {
return request<{ list: any[], total: number }>(`/home/v1/illness/list`, "GET", {page, page_size, id})
},
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 {
course_id: number[]
dep_id: number[]
is_required: (1 | 0)[]
}

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

@ -9,15 +9,19 @@ export interface Category {
resource_category?: Category[]
}
interface CategoryList {
categories: Record<number, Category[]>
export interface CoursesMode {
articles: any[]
audit_mode: boolean
course: Courses
}
export interface NewCoursesMode {
articles: any[]
audit_mode: boolean
platform_courses: Curriculum[]
company_courses: Curriculum[]
}
export interface Courses {
/** 已完成 */
is_finished: Curriculum[]
/** 未完成 */
is_not_finished: Curriculum[]
/** 选秀 */
is_not_required: Curriculum[]
/** 必修 */
@ -30,12 +34,14 @@ export type CoursesKey = keyof Omit<Courses, 'total_course_duration'>
export const publicApi = {
/** 分类 */
category() {
return request<CategoryList>('/api/v1/category/all', "GET")
},
/** 课程 */
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': '请求越界~'
}
let notLoging = false
export function request<T = unknown>(
url: string,
method: keyof Method,
@ -82,15 +84,19 @@ export function request<T = unknown>(
const data = res?.data as any
if (data?.code === 0 && res?.statusCode === 200) {
resolve(data?.data)
notLoging = false
} else if (res.statusCode === 401) {
// Taro.showModal({
// title: "登录过期,需重新登陆",
// showCancel: false,
// success() {
Taro.clearStorageSync()
Taro.redirectTo({url: '/pages/login/login'})
// }
// })
if (notLoging || !token) return
notLoging = true
Taro.showModal({
title: "登录过期",
confirmText: "登录",
showCancel: false,
success() {
Taro.clearStorage()
Taro.navigateTo({url: '/pages/login/login'})
}
})
} else {
reject(null)
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) {
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) {
return request<User>(`/api/v1/statistics/${user_id}`, "GET")
},
/**获取指定学员指定时间学习记录 */
statistics(user_id: string, data: StatisticsParam) {
statistics(user_id: string | number, data: StatisticsParam) {
return request<{
data: Record<number, number>
}>(`/api/v1/statistics/statistics/${user_id}?start_time=${data.start_time}&end_time=${data.end_time}`, "GET")
},
getCode(phone_number: number) {
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/check/check',
'pages/my/my',
'pages/search/index'
],
window: {
backgroundTextStyle: 'light',
navigationBarBackgroundColor: '#fff',
navigationBarTitleText: '课程',
navigationBarTitleText: '信桂',
navigationBarTextStyle: 'black'
},
tabBar: {
color: '#909795',
color: '#606563',
selectedColor: '#45D4A8',
list: [
{
@ -29,6 +30,12 @@ export default defineAppConfig({
iconPath: "static/tabs/study.png",
selectedIconPath: "static/tabs/study-selected.png",
},
{
text: '搜索',
pagePath: 'pages/search/index',
iconPath: "static/img/graySearch.png",
selectedIconPath: "static/img/cyanSearch.png",
},
{
text: "我的",
pagePath: 'pages/my/my',
@ -61,7 +68,8 @@ export default defineAppConfig({
'history/history',
'curHistory/curHistory',
'hourHistory/hourHistory',
'courType/courType'
'courType/courType',
'waterFull/test',
]
},
{
@ -71,11 +79,14 @@ export default defineAppConfig({
'student/student',
'curriculum/curriculum',
'addStudent/addStudent',
'department/addDepartment',
'depCur/depCur',
'addCur/addCur',
'spotMeeting/spotMeeting',
'selectDep/selectDep',
'meetings/meetings',
'meetings/list',
'meetings/form/form',
'userInfo/userInfo',
'courseAdmin/courseAdmin',
]
@ -91,8 +102,10 @@ export default defineAppConfig({
'videoFull/videoFull', // 资源id 视频全屏
'illness/sort/sort',
'illness/list/list',
'illness/article/article',
'webView/webView',
'search/search/index',
'collect/collect', // 收藏列表
'jumpArticles/jumpArticles', // 推荐文章
]
},
],

@ -1,5 +1,7 @@
@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-row{ flex-direction:row!important}
@ -13,12 +15,14 @@
.justify-around{justify-content:space-around}
.justify-between{justify-content:space-between}
.justify-center{justify-content:center}
.justify-stretch{justify-content:stretch}
.flex-wrap{flex-wrap:wrap}
.align-center{ align-items: center}
.align-stretch{ align-items: stretch}
.align-start{ align-items: flex-start}
.align-baseline{align-items: baseline}
.align-end{ align-items: flex-end}
.content-start {align-content: flex-start}
@ -35,6 +39,10 @@
.flex-5{flex: 5}
.flex-shrink{flex-shrink: 0}
.gap20rpx{gap: 20rpx}
.gap30rpx{gap: 30rpx}
.gap60rpx{gap: 60rpx}
.w-1 {width: 10%;min-width: 75rpx}
.w-2 {width: 20%;min-width: 150rpx}
.w-3 {width: 30%;min-width: 225rpx}
@ -57,7 +65,6 @@
.h-9 {height: 90vh}
.h-10 {height: 100vh}
.m-0 {margin: 0}
.m-auto{margin: auto}
.m-1 {margin: 10rpx}
@ -85,6 +92,7 @@
.mb-1 {margin-bottom: 10rpx}
.mb-1_5 {margin-bottom: 15rpx}
.mb-2 {margin-bottom: 20rpx}
.mb-2_4 {margin-bottom: 24rpx}
.mb-3 {margin-bottom: 30rpx}
.mb-4 {margin-bottom: 40rpx}
.mb-5 {margin-bottom: 50rpx}
@ -231,6 +239,8 @@
.font-68{font-size: 68rpx;line-height: 1;}
.font-weight{font-weight: bold}
.bold {font-weight: bold}
.text-indent{text-indent:2;}
.text-through{text-decoration:line-through;}
.text-left { text-align: left;}
@ -265,14 +275,16 @@
.text-hover-danger { color: #a71d2a;}
.text-light { color: #f8f9fa;}
.text-hover-light { color: #cbd3da;}
.text-dark { color: #343a40;}
.text-dark { color: #323635;}
.text-hover-dark{ color: #121416;}
.text-body { color: #212529;}
.text-muted { color: #909795;}
.text-black {color: #000 }
.lh-28 {line-height: 28rpx}
.lh-40 {line-height: 40rpx}
.lh1_2 {line-height: 1.2}
.lh1_5 {line-height: 1.5}
/* 圆角 */
.rounded { border-radius: 8rpx;}
@ -329,6 +341,10 @@
.border-none{border: none}
.clip {
overflow: hidden;
}
.text-row1 {
display: -webkit-box;
text-overflow: ellipsis;
@ -337,4 +353,67 @@
-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) {
Taro.useLaunch(() => {
updateApp()
storageDep.remove()
})
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 = {
statusBarHeight: res.statusBarHeight || 0,
screenWidth: res.screenWidth,
screenHeight: res.screenHeight,
safeArea: res.safeArea,
statusBarHeight,
screenWidth,
screenHeight,
windowHeight,
safeArea,
isIos,
textBarHeight,
pageHeight: screenHeight - statusBarHeight - textBarHeight - (screenHeight - (safeArea?.bottom || 0)),
menu: Taro.getMenuButtonBoundingClientRect(),
}
}
})
@ -58,7 +65,6 @@ function App(props) {
useDidHide(() => {
})
return (
<CustomWrapper>
<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 {
from {
transform: scale(0);
transform: scale(0) translateY(calc(-50% - 100px));
padding: 0;
}
to {
transform: scale(1);
transform: scale(1) translateY(calc(-50% - 100px));
padding: 0 30px;
}
}

@ -49,7 +49,9 @@ export const PageContainerInner: FC<PageContainerProps> = (props) => {
case 'center':
style.width = '90%'
style.borderRadius = '10px'
style.padding = '0'
style.padding = '0 !important'
style.top = '50%'
// style.transform = 'translateY(-100px) !important'
break
}
@ -89,14 +91,14 @@ export const PageContainerInner: FC<PageContainerProps> = (props) => {
const CustomPageContainer: FC<PageContainerProps> = (props) => {
if (props.position === 'center') {
return <PageContainerInner {...props} />
return <PageContainerInner {...props} onAfterLeave={props.onClickOverlay}/>
}
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>
</PageContainer>)
}
return (<PageContainerInner {...props} />);
return (<PageContainerInner {...props} onAfterLeave={props.onClickOverlay}/>);
}
export default CustomPageContainer

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

@ -1,17 +1,29 @@
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 styles from './empty.module.scss'
import MyButton from "@/components/button/MyButton";
import Taro from "@tarojs/taro";
interface Props {
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 (
<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>
{(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>
)
}

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

@ -1,28 +1,77 @@
import {FC, useEffect, useState} from "react";
import {Image, ImageProps, View} from "@tarojs/components";
import {AtActivityIndicator} from "taro-ui";
import { Image, ImageProps, View } from "@tarojs/components";
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 {
width: number
height: number
width?: number | string
height?: number | 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 [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(() => {
if (!src) {
setIsError(true)
setLoading(false)
} else {
setIsError(false)
setLoading(false)
if (!isError && props.fit) {
Taro.getImageInfo({
src,
success() {
setHeight(undefined)
}
})
}
setIsError(!src)
setLoading(!!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() {
setLoading(false)
@ -32,26 +81,53 @@ const Img: FC<Props> = ({src, mode = 'aspectFill', width, height, fallback = sha
function onLoadHandler() {
setLoading(false)
setIsError(false)
imgAnimation.opacity(1).step({duration: 200})
setAnimationData(imgAnimation.export())
}
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 &&
<View animation={animationData} style={{height: '100%', width: '100%'}}>
<Image
{...props}
src={src}
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}
onLoad={onLoadHandler}>
</Image>
}
{
loading &&
<AtActivityIndicator mode={"center"} content='加载中...'/>
onLoad={onLoadHandler}/>
</View>
}
{
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>
)

@ -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;
justify-content: left;
flex-wrap: nowrap;
height: 420px;
height: 380px;
position: relative;
padding-bottom: 10rpx;
}
.overlay {
width: 100%;
height: 10rpx;
background: #fff;
position: absolute;
bottom: 0;
left: 0
}
.empty {
@ -13,12 +23,31 @@
bottom: 0;
left: 0;
right: 0;
border-radius: 10rpx;
color: #00D6AC;
display: flex;
justify-content: center;
align-items: center;
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 {
@ -31,10 +60,27 @@
width: 30rpx;
background: linear-gradient(180deg, #03D9B3 0%, #05BF88 100%);
border-radius: 30rpx;
margin-bottom: 20rpx;
margin-bottom: 6rpx;
overflow: hidden;
animation: rise 300ms ease-in-out forwards;
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 {

@ -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 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 {
time: string
@ -12,10 +13,11 @@ interface Props {
data: lineData[]
}
const height = 180
const height = 150
const LineChart: FC<Props> = ({data}) => {
const [maxHeight, setMaxHeight] = useState<lineData>({time: '', value: 0})
const [lineChartList, setLineChartList] = useState(data)
console.log(maxHeight,lineChartList,'maxHeight')
useEffect(() => {
setLineChartList(data)
@ -24,28 +26,42 @@ const LineChart: FC<Props> = ({data}) => {
return cur
}
return pre
}, {time: '', value: 0}))
}, {time: formatDateTime(new Date(), 'MM/dd'), value: 0}))
}, [data])
return (
<>
<ScrollView scrollX={!!maxHeight.value}>
<View style={{marginBottom: '30px'}}>{maxHeight.time}{formatMinute(maxHeight.value)}</View>
<View style={{width: '100%', position: 'relative'}}>
<View className={style.records}>
{/*<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}>
{!maxHeight.value && <View className={style.empty}></View>}
{
!!maxHeight.value && lineChartList.map(d => <View key={d.time}>
<View className={style.columnBox} style={{width: "100px"}}>
<View className={style.line} style={{height: height - 10 - (d.value / maxHeight.value * height) + "px"}}></View>
{d.value > 0 && <View>{formatMinute(d.value)}</View>}
<View className={style.column} style={{height: d.value / maxHeight.value * height + "px"}}> </View>
<View>{d.time}</View>
!maxHeight.value && <View className={style.empty}>
<View></View>
<Image src={emptyLineChart} mode='widthFix' style={{width: '100%'}}/>
</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>
</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 {View, Image} from "@tarojs/components";
import {View} from "@tarojs/components";
import Icon from "@/components/icon";
import CustomPageContainer from "@/components/custom-page-container/custom-page-container";
import Img from "@/components/image/image";
interface Props {
height?: number | string
@ -15,6 +16,7 @@ interface Props {
onClick?: () => void
no_border?: boolean
leftImage?: string
errorType?: "acquiesce" | 'avatar'
}
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='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>
<View className='card-content'>
<View>{content}</View>
{image && <Img src={image} mode='scaleToFill' className='image' width={68} height={68}/>}
{!chevron && <Icon name='chevron-right'/>}
{image && <Image src={image} mode='scaleToFill' className='image'/>}
</View>
</View>
{

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

@ -1,8 +1,24 @@
.spinner-wrapper {
background-color: rgba( #fff, 1.0);
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 {
background-color: rgba(#fff, 1.0);
z-index: 99999;
position: fixed;
width: 100%;
@ -15,7 +31,7 @@
&.reverse,
&.dismissed {
background-color: rgba( #fff, 0.0);
background-color: rgba(#fff, 0.0);
}
&.dismissed {
@ -36,6 +52,7 @@
flex-direction: column;
justify-content: center;
align-items: center;
width: 100%;
}
.spinner {

@ -1,5 +1,21 @@
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;
}
}
}
.tabs {
@ -15,22 +31,31 @@ View::-webkit-scrollbar {
display: -webkit-flex;
display: flex;
text-align: center;
align-items: center;
}
.tabs-item {
padding: 20rpx;
font-size: 36rpx;
padding: 16rpx 25rpx;
font-size: 30rpx;
font-family: PingFang SC-Medium, PingFang SC;
font-weight: 500;
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;
font-size: 36rpx;
font-family: PingFang SC-Bold, PingFang SC;
font-weight: bold;
color: #323635;
&:after {
position: absolute;
@ -48,6 +73,13 @@ View::-webkit-scrollbar {
transition: all 200ms;
}
}
.current {
font-size: 36rpx;
font-family: PingFang SC-Bold, PingFang SC;
font-weight: bold;
color: #323635;
}
}
@keyframes spread {

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

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

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

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

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

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

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

@ -1,7 +1,8 @@
import {Image, View} from "@tarojs/components";
import {View} from "@tarojs/components";
import {FC} from "react";
import './videoCover.scss'
import Taro from "@tarojs/taro";
import Img from "@/components/image/image";
interface VideoCoverProps {
thumb: string
@ -28,9 +29,9 @@ const VideoCover: FC<VideoCoverProps> = (opt: VideoCoverProps) => {
<View className='videoBox'>
<View className='video' onClick={jump}>
<View className='upper'>
<Image src={opt.thumb} mode='widthFix'/>
{opt.content && <View className='content'>{opt.content}</View>}
<Img height={180} src={opt.thumb} mode='widthFix' errorType='course'/>
{opt.marker && <View className='marker'>{opt.marker}</View>}
{opt.content && <View className='content'>{opt.content}</View>}
</View>
<View className='box'>
<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";
const KET = 'SELECT_DEP'
const KET_REQUIRED = 'SELECT_REQUIRED'
/** 部门 */
function set(data: number[]) {
Taro.setStorageSync(KET, data)
}
function get(): number[] {
const deps = Taro.getStorageSync(KET)
if (deps && deps.length) {
@ -23,28 +21,7 @@ function removeDeps() {
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() {
removeRequired()
removeDeps()
}
@ -52,6 +29,4 @@ export default {
set,
get,
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 {formatMinute} from "@/utils/time";
import Empty from "@/components/empty/empty";
import eventsIndex from "@/hooks/eventsIndex";
import PageScript from "@/components/pageScript/pageScript";
const CourType: FC = () => {
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(() => {
getData()
@ -68,17 +58,27 @@ const CourType: FC = () => {
})
return (
<View className='py-2 flex justify-between flex-wrap '>
{data.length > 0 ? data.map(c =>
<VideoCover
<>
{
data.length > 0 ?
<>
<View className='py-2 flex justify-between flex-wrap'>
{
data.map(c => <VideoCover
thumb={c.thumb}
title={c.title}
id={c.id}
depId={c.id}
key={c.id}
time={formatMinute(c.course_duration)}
/>) : <Empty name='暂无数据'/>}
/>)
}
</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 styles from './curHistory.module.scss'
import {useState} from "react";
@ -6,6 +6,7 @@ import {userApi} from "@/api";
import {formatDateTime, formatMinute} from "@/utils/time";
import CustomPageContainer from "@/components/custom-page-container/custom-page-container";
import Empty from "@/components/empty/empty";
import PageScript from "@/components/pageScript/pageScript";
const CurHistory = () => {
const [show, setShow] = useState(false)
@ -106,6 +107,7 @@ const CurHistory = () => {
/>
</View>)
}
<PageScript/>
</View>
: <Empty name='暂无学习记录'/>
}
@ -116,6 +118,7 @@ const CurHistory = () => {
round
position='bottom'
onClickOverlay={() => setShow(false)}>
<ScrollView showScrollbar={false} scrollY={true} style={{maxHeight:'40vh',paddingBottom:'40px'}}>
<View className={styles.hourRecord}>
{hours?.length ?
hours?.map(d => <View key={d.id}>
@ -136,6 +139,7 @@ const CurHistory = () => {
</View>)
: <Empty name='暂无学习记录'/>}
</View>
</ScrollView>
</CustomPageContainer>
</View>
)

@ -6,11 +6,6 @@
background: #fff;
}
.image {
width: 100%;
height: 100%;
display: block;
}
.thumb {
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 Taro from "@tarojs/taro";
import {useEffect, useState} from "react";
import React, {useEffect, useState} from "react";
import {formatMinute} from "@/utils/time";
import Empty from "@/components/empty/empty";
import {userApi} from "@/api";
import Img from "@/components/image/image";
import PageScript from "@/components/pageScript/pageScript";
const History = () => {
const [data, setData] = useState<HourHistory[]>([])
@ -25,12 +27,13 @@ const History = () => {
}, [])
return (
<View>
<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 className={styles.thumb}>
<Image src={d.thumb} className={styles.image}/>
<Img src={d.thumb} className={styles.image} width={300} height={188}/>
<View
className={styles.count}>{d.userCourseRecord.hour_count}/{d.userCourseRecord.finished_count}</View>
</View>
@ -41,8 +44,12 @@ const History = () => {
<Text>{(d.userCourseRecord.finished_count / d.userCourseRecord.hour_count * 100).toFixed(0)}%</Text>
</View>
</View>
</View>) : <Empty name='无观看记录'/>}
</View>
</View>)
}
<PageScript/>
</>
: <Empty name='无观看记录'/>
}
</View>
)
}

@ -1,6 +1,5 @@
import {useState} from "react";
import {Profile} from '@/store'
import avatar from "@/static/img/avatar.png"
import PopPut from "@/components/popPut/popPut";
import {Input, View} from "@tarojs/components";
import Taro from "@tarojs/taro";
@ -10,7 +9,7 @@ import MyButton from "@/components/button/MyButton";
const List = () => {
const {empty, user, setUser} = Profile.useContainer()
const {empty, user, setUser, token} = Profile.useContainer()
const [show, setShow] = useState(false)
const [name, setName] = useState<string>(user?.name || '')
@ -45,17 +44,72 @@ const List = () => {
Taro.showModal({
title: '是否确定退出登录',
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 (
<>
<View className={styles.box}>
<PopPut title='头像' image={avatar} chevron no_border/>
<PopPut title='手机号' content={user?.phone_number} chevron no_border/>
<PopPut title='头像' errorType={'avatar'} image={user?.avatar} no_border onClick={selectAvatar}/>
<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>
<View className='p-2'>
<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 curRecord from '@/static/img/curRecord.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 {
data: CourseDepData | null
setHors: (is_complete: boolean, id: number) => void
id: number //课程
playId: number | null
refresh: () => void
collectUpdate?: (v: boolean) => void
}
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 [show, setShow] = useState(false)
const [playing, setPlaying] = useState(false)
const {token} = Profile.useContainer()
videoEvents.onVideoState(({name}) => {
switch (name) {
@ -42,14 +49,11 @@ const Catalogue: FC<Props> = ({data, setHors, id, playId}) => {
}
})
function onPause() {
videoEvents.setVideoState('pause')
}
function jumCurHistory() {
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 (
<>
<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 === 1 && <View>
<View className='my-2'></View>
{
!token && <LoginView className="absolute-impt left top" />
}
{data?.chapters.length
? Object.values(data?.chapters || {}).map((d, index) => <View key={d.id}>
<Collapse title={`${index + 1}.${d.name}`}>
@ -173,33 +179,48 @@ const Catalogue: FC<Props> = ({data, setHors, id, playId}) => {
</View>
</View>
<View className='Videobutton'>
{
playing ? <MyButton style={{flex: '1'}} onClick={onPause}></MyButton>
: <MyButton style={{flex: '1'}} onClick={learning}></MyButton>
}
<View className='px-3' onClick={() => setShow(true)}>...</View>
token && <View className='Videobutton'>
{
playing ? <MyButton className='flex-1'></MyButton>
: <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>
}
<PageContainerInner
<CustomPageContainer
show={show}
position='bottom'
onClickOverlay={() => setShow(false)}>
onClickOverlay={() => setShow(false)}
>
<View className='more'>
<View></View>
<View className='flex justify-around'>
<View onClick={() => Taro.navigateTo({url: '/pages/business/curHistory/curHistory?course_id=' + id})}>
<Image src={curRecord} className='image'/>
<View className='mt-2'></View>
</View>
<View onClick={jumCurHistory} className={playId ? undefined : 'filter-saturate'}>
<Image src={hourRecord} className='image'/>
<View className='mt-2'></View>
</View>
</View>
<MyButton onClick={() => setShow(false)} type='default' fillet></MyButton>
</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 {curriculum, HourPlayData} from "@/api";
import {Profile} from '@/store'
@ -13,11 +13,9 @@ interface Props {
curEnd: (test?: boolean) => void
}
const Course: FC<Props> = ({id, courseId, preview, curEnd}) => {
const [breakpoints, setBreakpoints] = useState<number[]>([]) // 断点
const [isFull, setIsFull] = useState(false)
const [examAll, setExamAll] = useState<Record<number, ShareSubject[]>>([]) // 题库
const [data, setData] = useState<HourPlayData | null>(null)
const [time, setTime] = useState<number>(0) // 进入断点的时间
@ -67,7 +65,8 @@ const Course: FC<Props> = ({id, courseId, preview, curEnd}) => {
getData()
}, [id])
function examination(result: boolean) {
const examination = useCallback((result: boolean) => {
if (!time) return
const {id: question_id, question_type} = examAll?.[time]?.[0]
curriculum.answerRecord(id, {
is_pass: result,
@ -77,8 +76,7 @@ const Course: FC<Props> = ({id, courseId, preview, curEnd}) => {
question_id
})
setTime(0)
}
}, [time])
return (
<>
@ -88,9 +86,10 @@ const Course: FC<Props> = ({id, courseId, preview, curEnd}) => {
src={data?.url || ''}
onEnded={onEnded}
breakpoint={breakpoints}
onBreakpoint={(time) => setTime(time)}
fullChange={(fullScreen) => setIsFull(fullScreen)}
>
onBreakpoint={(now) => {
time !== now && setTime(now)
}}>
<Single
full={isFull}
examination={examination}

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

@ -7,12 +7,6 @@
height: 500rpx;
}
.image {
width: 100%;
height: 100%;
display: block;
}
.header {
margin-bottom: 10px;
border-radius: 0 0 40rpx 40rpx;
@ -25,19 +19,28 @@
.catalogue {
background: #fff;
border-radius: 40rpx;
padding: 0 24rpx 24rpx;
padding: 0;
margin-top: 30rpx;
.short_desc {
padding: 0 24rpx 24rpx;
color: #606563;
line-height: 1.75;
word-break: break-word;
}
.hours {
padding: 0 24rpx 24rpx;
position: relative;
min-height: 100vw;
}
}
.hor {
padding: 20rpx 0;
display: flex;
justify-content: flex-start;
align-items: baseline;
color: #323635;
.image {
@ -46,7 +49,6 @@
margin-top: 8rpx;
}
.title {
flex: 1;
width: 710rpx;
@ -67,7 +69,6 @@
margin-left: 10rpx;
margin-top: 10rpx;
}
}
}
@ -76,6 +77,7 @@
}
.Videobutton {
align-items: center;
display: flex;
background: #fff;
position: fixed;
@ -84,6 +86,8 @@
bottom: env(safe-area-inset-bottom);
box-sizing: border-box;
padding: 10px 20px;
color: #909795;
font-size: 24rpx;
}
.more {
@ -106,4 +110,3 @@
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 {CourseDepData, curriculum} from "@/api";
import './videoInfo.scss'
import Catalogue from "./components/catalogue";
import Course from "./components/course";
import Taro from "@tarojs/taro";
import eventsIndex from "@/hooks/eventsIndex";
import {formatMinute} from "@/utils/time";
import videoEvents from "@/hooks/videoEvents";
import unique_ident from "@/hooks/unique_ident";
import Img from "@/components/image/image";
const VideoInfo: FC = () => {
const {id, depId} = Taro.getCurrentInstance()?.router?.params as any
@ -18,15 +18,20 @@ const VideoInfo: FC = () => {
const [playing, setPlaying] = useState(false) // 学习中
const getData = useCallback(async (playing: boolean) => {
try {
const res = await curriculum.courseDep(id, depId)
if (res) {
setData(res)
}
if (playId != null) { // 用于自动播放 判断当前课程是否完成
currentVideo(res, playing)
res && setData(res)
playId != null && currentVideo(res, playing) // 用于自动播放 判断当前课程是否完成
} catch (e) {
}
}, [playing, playId])
function changeCollect(v: boolean){
let temp = data
temp!.course.collect = v
setData(temp)
}
const curEnd = (test?: boolean) => {
setPlaying(false)
if (!test) { // 没有考题才会请求
@ -51,7 +56,6 @@ const VideoInfo: FC = () => {
const flats: Hour[] = Object.values(data?.hours || {}).flat(Infinity) as Hour[]
if (playId === flats?.[flats.length - 1]?.id && !preview) {
Taro.showModal({title: '当前课程结束'})
eventsIndex.trigger({id: Number(id)})
return;
}
for (const [index, flat] of flats.entries()) {
@ -73,9 +77,7 @@ const VideoInfo: FC = () => {
}
}, [playId, data, preview])
/**
*
*/
/** 判断当前课程是否完成 */
const currentVideo = useCallback((data: CourseDepData, playing: boolean) => {
const courseHourRecordsFinish = data?.learn_hour_records.find(d => d.id === playId)?.courseHourRecordsFinish
if (typeof courseHourRecordsFinish === 'number') {
@ -100,19 +102,15 @@ const VideoInfo: FC = () => {
data && getData(playing)
})
Taro.useUnload(() => {
videoEvents.videoOff()
})
return (
<>
<View className='content'>
<View className='content-video'>
{
playId
{playId
? <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 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>
</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>
</>
)
}

@ -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({
navigationBarTitleText: '绑定手机号'
navigationBarTitleText: '员工验证',
})

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

@ -1,5 +1,5 @@
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 {userApi} from "@/api";
import Taro from "@tarojs/taro";
@ -8,6 +8,7 @@ import styles from './check.module.scss'
import code from '@/static/img/vCode.png'
import tel from '@/static/img/tel.png'
import MyButton from "@/components/button/MyButton";
import IconFont from "@/components/IconFont";
const Bing: FC = () => {
const form = useRef<HTMLFormElement | null>(null)
@ -76,6 +77,23 @@ const Bing: FC = () => {
return (
<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}>
<View className={styles.formItem}>
@ -85,7 +103,7 @@ const Bing: FC = () => {
<Input
type='number'
name='phone_number'
placeholder={'请输入手机号'}
placeholder={'手机号'}
value={phone_number as unknown as string}
onInput={(e) => setPhone_number(Number(e.detail.value))}/>
</View>
@ -96,16 +114,16 @@ const Bing: FC = () => {
</View>
<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}>
{codeTime > 0 ? `${codeTime}` : '获取验证码'}
</View>
</View>
</View>
<MyButton
className={styles.submit}
style={{margin: '30px auto'}}
formType='submit'
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 {AdwareType} from "@/api";
import Taro from "@tarojs/taro";
import styles from '../home.module.scss'
import Img from "@/components/image/image";
interface Props {
data: any[]
@ -14,7 +15,6 @@ const Adware: FC<Props> = ({data, only_flag, width}) => {
const [adverts, setAdverts] = useState<AdwareType[]>([])
const [space, setSpace] = useState<any | null>(null)
useEffect(() => {
const res = data.find(d => d.only_flag === only_flag)
setSpace(res)
@ -30,16 +30,19 @@ const Adware: FC<Props> = ({data, only_flag, width}) => {
}
return (
<View>
<>
{
adverts.length > 0 && <View className={styles.adware}>
{
adverts.length === 1 && <Image
adverts.length === 1 && <Img
src={adverts[0].image_url}
mode='scaleToFill'
lazyLoad
fadeIn
style={{width: width + "rpx", height: (space.height / space.width) * width + "rpx"}}
onClick={() => jumpAdware(adverts[0].image_path)}
className={styles.adware}/>
width={width}
errorType='profession'
height={(space.height / space.width) * width}/>
}
{
adverts.length > 1 && <Swiper
@ -47,19 +50,22 @@ const Adware: FC<Props> = ({data, only_flag, width}) => {
autoplay
circular
style={{width: width + "rpx", height: (space.height / space.width) * width + "rpx", overflow: 'hidden'}}
indicatorActiveColor='rgba(255,255,255,0.5)'
className={styles.adware}>
indicatorActiveColor='rgba(255,255,255,0.5)'>
{adverts.map(d => <SwiperItem key={d.id}>
<Image
<Img
src={d.image_url}
lazyLoad
fadeIn
onClick={() => jumpAdware(d.image_path)}
style={{width: width + "rpx", height: '100%'}}/>
width={width}
errorType='profession'
height={(space.height / space.width) * width}
onClick={() => jumpAdware(d.image_path)}/>
</SwiperItem>)}
</Swiper>
}
</View>
}
</>
)
}

@ -1,20 +1,26 @@
import {FC, useEffect, useState} from "react";
import {Image, View} from "@tarojs/components";
import {FC, ReactNode, useEffect, useState} from "react";
import {Image, Text, View} from "@tarojs/components";
import {HomeApi} from "@/api";
import {useReachBottom} from "@tarojs/taro";
import Taro, {useReachBottom} from "@tarojs/taro";
import styles from "../home.module.scss";
import VideoCover from "@/components/videoCover/videoCover";
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 [page, setPage] = useState(1)
const [data, setData] = useState<Curriculum[]>([])
const [total, setTotal] = useState(0)
const [articles, setArticles] = useState<Articles[]>([])
async function getData() {
const res = await HomeApi.course(page, 4)
setTotal(res.total)
const newData = res.data.reduce((pre, cut) => {
const res = await HomeApi.home()
setArticles(res.articles)
setTotal(res.courses.total)
const newData = res.courses.data?.reduce((pre, cut) => {
const index = pre.findIndex(d => d.id === cut.id)
if (index === -1) {
pre.push(cut)
@ -23,7 +29,11 @@ const CurRecommended: FC = () => {
}
return pre
}, JSON.parse(JSON.stringify(data)) as Curriculum[])
setData(newData)
setData(newData ?? [])
}
function jumpArticles() {
Taro.navigateTo({url: '/pages/preview/jumpArticles/jumpArticles'})
}
useEffect(() => {
@ -34,10 +44,26 @@ const CurRecommended: FC = () => {
data.length < total && setPage(page + 1)
})
return (
<>
{
data.length > 0 && <View>
let examines: ReactNode | undefined
if (articles.length > 0) {
examines = (
<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}/>
<View className={'pb-2 flex justify-between flex-wrap ' + styles.videoListBox}>
{
@ -47,12 +73,19 @@ const CurRecommended: FC = () => {
id={c.id}
depId={c.id}
key={c.id}
marker={`${c.class_hour}`}
/>)
}
</View>
</View>
)
}
<View className='text-center text-muted pb-3 font-28'>- -</View>
return (
<>
{examines}
{videos}
<PageScript/>
</>
)
}

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

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

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

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

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

@ -1,5 +1,5 @@
import {FC, useEffect, useState} from "react";
import {View} from "@tarojs/components";
import {FC, useState} from "react";
import {Image, View, Text} from "@tarojs/components";
import styles from "./home.module.scss";
import Adware from "@/pages/home/components/adware";
import Feature from "@/pages/home/components/feature";
@ -9,25 +9,73 @@ import MyButton from "@/components/button/MyButton";
import {Profile} from "@/store";
import Taro from "@tarojs/taro";
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 globalData = Taro.getApp().globalData
const {token} = Profile.useContainer()
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() {
Taro.removeStorageSync('profile')
Taro.clearStorage()
Taro.navigateTo({url: '/pages/login/login'})
}
useEffect(() => {
Taro.useDidShow(() => {
HomeApi.home().then(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 (
<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}/>
<Feature/>
<FeatureRecommended
@ -37,14 +85,15 @@ const Home: FC = () => {
skill={data?.skill || []}
/>
<Adware data={data?.adverts || []} only_flag='routine_home_recommend_banner' width={710}/>
<CurRecommended/>
{data && <CurRecommended/>}
{
!token && <View className={styles.tipsLogin} onClick={unLogin}>
<View>~</View>
<MyButton fillet size='mini'></MyButton>
!token && <View className={styles.tipsLogin}>
<View>~</View>
<MyButton fillet size='mini' onClick={unLogin}></MyButton>
</View>
}
</View>
</>
)
}

@ -1,13 +1,14 @@
import {FC, useEffect, useMemo, useState} from "react";
import {useDidShow} from "@tarojs/taro";
import Taro, {useDidShow} from "@tarojs/taro";
import {ScrollView, Swiper, SwiperItem, View} from "@tarojs/components";
import {Courses, CoursesKey, publicApi} from "@/api/public";
import VideoCover from "@/components/videoCover/videoCover";
import styles from '../index.module.scss'
import {formatMinute} from "@/utils/time";
import {userApi} from "@/api";
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 {
categoryKey: CoursesKey
@ -18,11 +19,13 @@ export const VideoList: FC<Props> = ({categoryKey, setCategoryKey}) => {
const [data, setData] = useState<Courses>({
is_required: [],
is_not_required: [],
is_finished: [],
is_not_finished: [],
})
const [page, setPage] = useState(1)
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[] {
return data.reduce((pre, cur) => {
@ -38,13 +41,11 @@ export const VideoList: FC<Props> = ({categoryKey, setCategoryKey}) => {
async function getData() {
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))
setData({
is_required: screen(old.is_required, res.is_required || []),
is_not_required: screen(old.is_not_required, res.is_not_required || []),
is_finished: screen(old.is_finished, res.is_finished || []),
is_not_finished: screen(old.is_not_finished, res.is_not_finished || []),
is_required: screen(old.is_required, res.company_courses || []),
is_not_required: screen(old.is_not_required, res.platform_courses || []),
})
} catch (e) {
}
@ -60,10 +61,6 @@ export const VideoList: FC<Props> = ({categoryKey, setCategoryKey}) => {
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)
if (find) {
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}节/已学0节`}</View>)
case "is_finished":
return <View></View>
}
}
eventsIndex.on(({id}) => {
if (id == null) return;
for (const [index, notFinished] of data.is_not_finished.entries()) {
if (notFinished.id === id) {
data.is_finished.push(notFinished)
data.is_not_finished.splice(index, 1)
return
function rateOfPercent(id: number, class_hour: number): string {
const find = records.find(d => d?.course_id === id)
if (find) {
if (class_hour === find.finished_count) {
return '100%'
}
return `${((find.finished_count / class_hour) * 100).toFixed(0)}%`
}
return '0%'
}
})
useDidShow(() => {
getData().then()
getRecords().then()
})
useEffect(() => {
const fetchData = () => {
getData().then()
getRecords().then()
}, [page])
}
useDidShow(fetchData)
useEffect(fetchData, [page])
function changeSwiper(e) {
const index = e.detail.current
@ -111,15 +103,20 @@ export const VideoList: FC<Props> = ({categoryKey, setCategoryKey}) => {
}, [data, categoryKey])
return (
<Swiper className={styles.swiper}
<Swiper style={{height: pageHeight}}
onChange={changeSwiper}
current={categoryIndex}>
{
Object.values(data).map((value) => <SwiperItem>
<ScrollView scrollY className={styles.swiper} onScrollToLower={() => setPage(page + 1)}>
<View className='py-2 flex justify-between flex-wrap'>
Object.entries(data).map(([key, value]) => <SwiperItem>
<ScrollView scrollY style={{height: pageHeight}} onScrollToLower={() => setPage(page + 1)} enhanced
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
thumb={c.thumb}
title={c.title}
@ -128,10 +125,14 @@ export const VideoList: FC<Props> = ({categoryKey, setCategoryKey}) => {
key={c.id}
time={formatMinute(c.course_duration)}
content={rateOfLearning(c.id, c.class_hour)}
schedule={`学习进度${rateOfPercent(c.id, c.class_hour)}`}
/>)
: <Empty name='暂无课程'/>
}
</View>
<PageScript/>
</>
: <Empty name='暂无课程'/>
}
</ScrollView>
</SwiperItem>)
}

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

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

@ -1,32 +1,87 @@
import {FC, useState} from "react";
import {View} from "@tarojs/components";
import {FC, useEffect, useState} from "react";
import {View, Text} from "@tarojs/components";
import styles from './index.module.scss'
import {VideoList} from "@/pages/index/components/videoList";
import Tabs, {OnChangOpt, TabList} from "@/components/tabs/tabs";
import {CoursesKey} from "@/api/public";
import {CoursesKey, publicApi} from "@/api/public";
import NavigationBar from "@/components/navigationBar/navigationBar";
import Spin from "@/components/spinner";
import {isBoolean} from "@tarojs/shared";
import ArticlesBox from "@/components/articlesBox/articlesBox";
import PageScript from "@/components/pageScript/pageScript";
import Taro from "@tarojs/taro";
const category: TabList[] = [
{title: "企选课程", value: 'is_required'},
{title: "平台课程", value: 'is_not_required'},
]
const Index: FC = () => {
const category: TabList[] = [
{title: "必修", value: 'is_required'},
{title: "选修", value: 'is_not_required'},
{title: "已完成", value: 'is_finished'},
{title: "未完成", value: 'is_not_finished'},
]
const [categoryKey, setCategoryKey] = useState<CoursesKey>('is_required')
function tabChange(data: OnChangOpt) {
setCategoryKey(data.tab?.value as CoursesKey)
}
Taro.useShareAppMessage((res) => {
if(res.from === 'button'){
}
return {
title:'信桂',
path: '/pages/index/index'
}
})
Taro.useShareTimeline(() => {
return {
title: '信桂'
}
})
return (
<>
<View className={styles.content}>
<Tabs tabList={category} onChange={tabChange} current={categoryKey}/>
<VideoList categoryKey={categoryKey} setCategoryKey={(categoryKey) => setCategoryKey(categoryKey)}/>
<NavigationBar
cancelBack
leftNode={<Tabs tabList={category} onChange={tabChange} current={categoryKey} scrollable={false} hiddenSliding/>}
/>
<VideoList
categoryKey={categoryKey}
setCategoryKey={(categoryKey) => setCategoryKey(categoryKey)}
/>
</View>
)
}
const AuditMode: FC = () => {
const [auditMode, setAuditMode] = useState(true)
const [articles, setArticles] = useState<any[]>([])
const [enable, setEnable] = useState(true)
useEffect(() => {
publicApi.course({page: 1, pageSize: 10}).then(res => {
setAuditMode(isBoolean(res.audit_mode) ? res.audit_mode : res.audit_mode === 'true')
setArticles(res.articles)
setEnable(false)
})
}, [])
return (
<>
<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 {
width: 140px;
margin: 250px auto 145px;
margin: 70px auto 100px;
image {
overflow: hidden;
@ -44,7 +44,7 @@
.errorTips {
position: fixed;
top: 10%;
top: 0;
left: 24px;
right: 24px;
background: red;
@ -52,10 +52,12 @@
padding: 24px;
border-radius: 20px;
display: flex;
align-items: center;
align-items: baseline;
}
.bing {
height: 50vh;
padding: 50px 30px 0;
}

@ -1,22 +1,23 @@
import {FC, useEffect, useState} from "react";
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 styles from './login.module.scss'
import Icon from "@/components/icon";
import {userApi} from "@/api";
import {loginApi, LoginParams} from "@/api/login";
import MyButton from "@/components/button/MyButton";
import logo from '@/static/img/logo.svg'
import IconFont from "@/components/IconFont";
const Login: FC = () => {
const [isLoading, setLoading] = useState(false)
const [error, setError] = useState<string | null>(null)
const {setUser, setToken, setCompany} = Profile.useContainer()
const {setUser, setToken, setCompany, empty} = Profile.useContainer()
const [h5params, setH5Params] = useState<LoginParams | null>(null)
const [check, setCheck] = useState(false)
useEffect(() => {
empty(false)
if (process.env.TARO_ENV === 'h5') {
setLoading(true);
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() {
if (isLoading) return;
if (!check) {
setError('请阅读并同意《用户协议》及《隐私政策》!')
return;
}
if (h5params == null && process.env.TARO_ENV === 'h5') {
setError('页面参数错误,请刷新页面!')
return;
@ -57,10 +70,12 @@ const Login: FC = () => {
setToken(token)
setCompany(company)
setLoading(false)
Taro.switchTab({url: '/pages/home/home'})
setTimeout(() => {
Taro.navigateBack()
})
} else {
Taro.setStorageSync('openid', catch_key)
Taro.reLaunch({url: '/pages/check/check'})
Taro.navigateTo({url: '/pages/check/check'})
}
} catch (e) {
}
@ -83,21 +98,60 @@ const Login: FC = () => {
return (
<View className={styles.container}>
<View className={styles.brand}>
<Image mode={'scaleToFill'} src="https://playedu.yaojiankang.top/favicon.ico"/>
<View className='mt-3'></View>
<Image mode={'scaleToFill'} src={logo}/>
<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 className={styles.loginTips}>
<Text>使</Text>
</View>
<MyButton onClick={login} loading={isLoading}></MyButton>
{error ? <View className={styles.errorTips}>
<View style={{flex: 1}}>{error}</View>
<Icon name={'close'} onClick={() => setError(null)}/>
</View> : null}
{/*<View className={styles.loginTips}>*/}
{/* <Text>请完成微信授权以继续使用!</Text>*/}
{/*</View>*/}
<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>}*/}
<View className="fixed left right bottom text-center text-muted mb-7 font-26" style={{opacity:'0.5'}}>ICP备20015955号-4X</View>
</View>
)
}

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

@ -73,13 +73,14 @@ const AddStudent = () => {
}, [depIds])
useEffect(() => {
getDepartment()
if (params.id) {
getUserInfo()
}
}, [])
Taro.useDidShow(() => {
Taro.useDidShow( async() => {
await getDepartment()
const newDepIds = storageDep.get()
setDepIds(newDepIds)
})
@ -99,6 +100,7 @@ const AddStudent = () => {
<Input
placeholder='请输入手机号'
name='phone_number'
maxlength={11}
value={userInfo?.phone_number}
onInput={(event) => setUerInfo({...userInfo, phone_number: event.detail.value} as Student)}/>
</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 styles from "../courseAdmin.module.scss";
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 Empty from "@/components/empty/empty";
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 {
param: CourseAllParam
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 [deps, setDeps] = useState<Manage[]>([])
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() {
try {
@ -45,43 +72,62 @@ export const Search: FC<Props> = ({param, setParam}) => {
}
}, [depId])
useEffect(() => {
getDepList()
}, [])
return (
<>
<View className={styles.searchBox}>
<View className={styles.searchInput}>
<Icon name='search' size={18}/>
<Icon name='search' size={18} color='#909795'/>
<Input
adjustPosition={false}
type='text'
confirmType='search'
placeholder='搜索名称'
placeholder='搜索课程名称'
className='ml-1 flex-1'
value={param.title}
value={title}
onInput={(e) => setTitle(e.detail.value)}
onConfirm={(e) => setParam({
...param,
title: e.detail.value,
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 onClick={() => setShow(true)}>
<Text></Text>
<Image src={screen}
mode='aspectFit'
style={{
display: 'inline-block',
width: '15px',
height: '15px',
verticalAlign: 'middle',
marginLeft: '5px'
}}/>
</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)}>
<View className={styles.custom}>
@ -99,7 +145,7 @@ export const Search: FC<Props> = ({param, setParam}) => {
</View>)
}
<View className='flex justify-around mt-2'>
<View className='flex justify-around mt-2 align-center'>
<View onClick={() => setShow(false)}>
<MyButton type='default' fillet width={150}></MyButton>
</View>
@ -108,7 +154,24 @@ export const Search: FC<Props> = ({param, setParam}) => {
</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>
</CustomPageContainer>

@ -1,22 +1,48 @@
page {
background: #fff !important;
}
.searchBox {
background: #fff;
padding: 24rpx 30rpx;
display: flex;
justify-content: space-between;
box-sizing: border-box;
position: sticky;
top: 0;
border-bottom: 1px solid #F5F8F7;
z-index: 2;
background-color: #fff;
}
.searchInput {
width: 550rpx;
width: 100%;
height: 68rpx;
display: flex;
align-items: center;
padding-left: 24rpx;
background: #F5F8F7;
border-radius: 32rpx;
color: #909795;
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 {
border-bottom: 1px solid #F5F8F7;
padding: 20px 0;
@ -31,41 +57,43 @@
.curBox {
background: #fff;
margin-top: 20px;
display: flex;
justify-content: space-between;
align-items: center;
z-index: 1;
}
.curTitle {
padding: 30rpx;
display: flex;
border-bottom: 1px solid #F5F8F7;
flex:1;
box-sizing: border-box;
}
.curImage {
width: 280rpx;
height: 164rpx;
border-radius: 10rpx;
margin: 0 20px 0 0;
background: #eee;
}
.Operation {
display: flex;
justify-content: space-around;
taro-view-core,
View {
padding: 20rpx 0;
flex: 1;
width: 100%;
text-align: center;
overflow: hidden;
position: relative;
&:last-child {
border: 1px solid #F5F8F7;
}
.classHour {
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 {
padding-bottom: 100px;
padding-bottom: calc(env(safe-area-inset-bottom) + 63rpx);
}
.add {
@ -83,4 +111,6 @@
display: flex;
justify-content: space-between;
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 MyButton from "@/components/button/MyButton";
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 [total, setTotal] = useState(0)
const [data, setData] = useState<Curriculum[]>([])
const [batch, setBatch] = useState(false)
const [curs, setCurs] = useState<number[]>([])
const [enable, setEnable] = useState(true)
const [param, setParam] = useState<CourseAllParam>({
page: 1,
page_size: 10,
@ -19,6 +24,7 @@ const CourseAdmin: FC = () => {
dep_id: 0
})
/**
*@param replace
*/
@ -34,6 +40,7 @@ const CourseAdmin: FC = () => {
])
}
})
setEnable(false)
}
useEffect(() => {
@ -101,6 +108,27 @@ const CourseAdmin: FC = () => {
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
@ -121,13 +149,12 @@ const CourseAdmin: FC = () => {
Taro.useDidShow(useCallback(async () => {
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 {
await ManageApi.addCur({course_id: curs, dep_id, is_required})
await ManageApi.addCur({course_id: curs, dep_id})
Taro.showToast({title: '修改成功'})
// deps 中没有 筛选条件中的depid 删除已选的课程
if (param.dep_id && dep_id.includes(param.dep_id)) {
@ -148,43 +175,44 @@ const CourseAdmin: FC = () => {
return (
<View>
<Search param={param} setParam={setParam}/>
<>
<Spin enable={enable} overlay/>
<Search param={param} setParam={setParam} total={total} setBatch={setBatch}/>
<View className={styles.curList}>
{
data?.map((d, index) => <View key={d.id} className={styles.curBox}>
<View className={styles.curTitle} onClick={() => addCurs(d.id)}>
{batch && <Radio
color='#45D4A8'
checked={curs.includes(d.id)}
style={{marginTop: '30px'}}
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 className='mt-1 text-dark font-26 word-break text-ellipsis-2'>{d.short_desc}</View>
</View>
<View className={styles.Operation}>
<View onClick={() => del(d.id, index)}></View>
<View onClick={() => changeDep(d.id, d.data)}></View>
</View>
<Image src={omit} mode='widthFix' style={{width: '30rpx', height: '30rpx', padding: '30rpx'}}
onClick={() => changeCourse(d, index)}/>
</View>)
}
<PageScript/>
</View>
<View className={styles.add}>
{
!batch
&& data.length > 0
&& <View style={{margin: 'auto', padding: '15px'}} onClick={() => setBatch(true)}></View>
}
{
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>
<MyButton size='mini' onClick={() => batchChangDep(curs)}>{curs.length}</MyButton>
</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 {
width: 100%;
background: #F5F8F7;
position: fixed;
bottom: 0;
padding: 30px 0;
align-items: center;
color: #45D4A8;
left: 0;
.safeAreaInsetBottom {
justify-content: space-around;
height: 100rpx;
padding-bottom: env(safe-area-inset-bottom);
display: flex;
}

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

Loading…
Cancel
Save