提交 587f5246 authored 作者: lhh's avatar lhh

wmpc开发

上级 016c9a42
......@@ -21,7 +21,9 @@
"vant": "^3.5.0",
"vue": "^3.2.37",
"vue-infinite-scroll": "^2.0.2",
"vue-router": "^4.0.15"
"vue-router": "^4.0.15",
"vue3-video-play": "^1.3.1-beta.6",
"xgplayer": "^3.0.19"
},
"devDependencies": {
"@rushstack/eslint-patch": "^1.1.3",
......@@ -1216,6 +1218,17 @@
"integrity": "sha1-JoD7uAaKSNCGVrYJgJK9r8kG9KU=",
"dev": true
},
"node_modules/core-js": {
"version": "3.38.1",
"resolved": "https://registry.npmmirror.com/core-js/-/core-js-3.38.1.tgz",
"integrity": "sha512-OP35aUorbU3Zvlx7pjsFdu1rGNnD4pgw/CWoYzRY3t2EzoVT7shKHY1dlAy3f41cGIO7ZDPQimhGFTlEYkG/Hw==",
"hasInstallScript": true,
"peer": true,
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/core-js"
}
},
"node_modules/core-util-is": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
......@@ -1261,6 +1274,26 @@
"resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.20.tgz",
"integrity": "sha512-/WwNkdXfckNgw6S5R125rrW8ez139lBHWouiBvX8dfMFtcn6V81REDqnH7+CRpRipfYlyU1CmOnOxrmGcFOjeA=="
},
"node_modules/d": {
"version": "1.0.2",
"resolved": "https://registry.npmmirror.com/d/-/d-1.0.2.tgz",
"integrity": "sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw==",
"dependencies": {
"es5-ext": "^0.10.64",
"type": "^2.7.2"
},
"engines": {
"node": ">=0.12"
}
},
"node_modules/danmu.js": {
"version": "1.1.13",
"resolved": "https://registry.npmmirror.com/danmu.js/-/danmu.js-1.1.13.tgz",
"integrity": "sha512-knFd0/cB2HA4FFWiA7eB2suc5vCvoHdqio33FyyCSfP7C+1A+zQcTvnvwfxaZhrxsGj4qaQI2I8XiTqedRaVmg==",
"dependencies": {
"event-emitter": "^0.3.5"
}
},
"node_modules/data-uri-to-buffer": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-3.0.1.tgz",
......@@ -1342,6 +1375,11 @@
"node": ">=0.4.0"
}
},
"node_modules/delegate": {
"version": "3.2.0",
"resolved": "https://registry.npmmirror.com/delegate/-/delegate-3.2.0.tgz",
"integrity": "sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw=="
},
"node_modules/depd": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
......@@ -1417,6 +1455,11 @@
"ssr-window": "^4.0.0"
}
},
"node_modules/downloadjs": {
"version": "1.4.7",
"resolved": "https://registry.npmmirror.com/downloadjs/-/downloadjs-1.4.7.tgz",
"integrity": "sha512-LN1gO7+u9xjU5oEScGFKvXhYf7Y/empUIIEAGBs1LzUq/rg5duiDrkuH5A2lQGd5jfMOb9X9usDa2oVXwJ0U/Q=="
},
"node_modules/ee-first": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
......@@ -1453,6 +1496,43 @@
"url": "https://github.com/fb55/entities?sponsor=1"
}
},
"node_modules/es5-ext": {
"version": "0.10.64",
"resolved": "https://registry.npmmirror.com/es5-ext/-/es5-ext-0.10.64.tgz",
"integrity": "sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==",
"hasInstallScript": true,
"dependencies": {
"es6-iterator": "^2.0.3",
"es6-symbol": "^3.1.3",
"esniff": "^2.0.1",
"next-tick": "^1.1.0"
},
"engines": {
"node": ">=0.10"
}
},
"node_modules/es6-iterator": {
"version": "2.0.3",
"resolved": "https://registry.npmmirror.com/es6-iterator/-/es6-iterator-2.0.3.tgz",
"integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==",
"dependencies": {
"d": "1",
"es5-ext": "^0.10.35",
"es6-symbol": "^3.1.1"
}
},
"node_modules/es6-symbol": {
"version": "3.1.4",
"resolved": "https://registry.npmmirror.com/es6-symbol/-/es6-symbol-3.1.4.tgz",
"integrity": "sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg==",
"dependencies": {
"d": "^1.0.2",
"ext": "^1.7.0"
},
"engines": {
"node": ">=0.12"
}
},
"node_modules/esbuild": {
"version": "0.14.39",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.14.39.tgz",
......@@ -1808,6 +1888,20 @@
"node": ">=8"
}
},
"node_modules/esniff": {
"version": "2.0.1",
"resolved": "https://registry.npmmirror.com/esniff/-/esniff-2.0.1.tgz",
"integrity": "sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==",
"dependencies": {
"d": "^1.0.1",
"es5-ext": "^0.10.62",
"event-emitter": "^0.3.5",
"type": "^2.7.2"
},
"engines": {
"node": ">=0.10"
}
},
"node_modules/espree": {
"version": "9.3.2",
"resolved": "https://registry.npmjs.org/espree/-/espree-9.3.2.tgz",
......@@ -1900,6 +1994,28 @@
"node": ">=0.10.0"
}
},
"node_modules/event-emitter": {
"version": "0.3.5",
"resolved": "https://registry.npmmirror.com/event-emitter/-/event-emitter-0.3.5.tgz",
"integrity": "sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==",
"dependencies": {
"d": "1",
"es5-ext": "~0.10.14"
}
},
"node_modules/eventemitter3": {
"version": "4.0.7",
"resolved": "https://registry.npmmirror.com/eventemitter3/-/eventemitter3-4.0.7.tgz",
"integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw=="
},
"node_modules/ext": {
"version": "1.7.0",
"resolved": "https://registry.npmmirror.com/ext/-/ext-1.7.0.tgz",
"integrity": "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==",
"dependencies": {
"type": "^2.7.2"
}
},
"node_modules/extend-shallow": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
......@@ -2289,6 +2405,11 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/hls.js": {
"version": "1.5.15",
"resolved": "https://registry.npmmirror.com/hls.js/-/hls.js-1.5.15.tgz",
"integrity": "sha512-6cD7xN6bycBHaXz2WyPIaHn/iXFizE5au2yvY5q9aC4wfihxAr16C9fUy4nxh2a3wOw0fEgLRa9dN6wsYjlpNg=="
},
"node_modules/html2canvas": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz",
......@@ -2891,6 +3012,11 @@
"node": ">= 0.4.0"
}
},
"node_modules/next-tick": {
"version": "1.1.0",
"resolved": "https://registry.npmmirror.com/next-tick/-/next-tick-1.1.0.tgz",
"integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ=="
},
"node_modules/normalize-path": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
......@@ -3799,6 +3925,14 @@
"node": ">=0.8"
}
},
"node_modules/throttle-debounce": {
"version": "3.0.1",
"resolved": "https://registry.npmmirror.com/throttle-debounce/-/throttle-debounce-3.0.1.tgz",
"integrity": "sha512-dTEWWNu6JmeVXY0ZYoPuH5cRIwc0MeGbJwah9KUNYSJwommQpCzTySTpEe8Gs1J23aeWEuAobe4Ag7EHVt/LOg==",
"engines": {
"node": ">=10"
}
},
"node_modules/through": {
"version": "2.3.8",
"resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
......@@ -3859,6 +3993,11 @@
"typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta"
}
},
"node_modules/type": {
"version": "2.7.3",
"resolved": "https://registry.npmmirror.com/type/-/type-2.7.3.tgz",
"integrity": "sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ=="
},
"node_modules/type-check": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
......@@ -4426,6 +4565,16 @@
"typescript": "*"
}
},
"node_modules/vue3-video-play": {
"version": "1.3.1-beta.6",
"resolved": "https://registry.npmmirror.com/vue3-video-play/-/vue3-video-play-1.3.1-beta.6.tgz",
"integrity": "sha512-Olrx2/LNAds7fuor/yX9ZKT9sOcwcfTt2g2YbbCrEaAmZ5Tb0hwBr5z+/CoLwELzzRzXCHPmWWoT0Wm5W/Nwpw==",
"dependencies": {
"hls.js": "^1.0.10",
"throttle-debounce": "^3.0.1",
"vue": "^3.2.2"
}
},
"node_modules/webpack-sources": {
"version": "3.2.3",
"resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz",
......@@ -4492,6 +4641,32 @@
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
"dev": true
},
"node_modules/xgplayer": {
"version": "3.0.19",
"resolved": "https://registry.npmmirror.com/xgplayer/-/xgplayer-3.0.19.tgz",
"integrity": "sha512-yw4yinU5EtxS5YxOpctCVnHS/WW3tLTYjT+ZHivUYihlaAdudrmBmoOCcRYykGg7xz6PVIi6B79Gn1RBnGAnpg==",
"dependencies": {
"danmu.js": ">=1.1.6",
"delegate": "^3.2.0",
"downloadjs": "1.4.7",
"eventemitter3": "^4.0.7",
"xgplayer-subtitles": "3.0.19"
},
"peerDependencies": {
"core-js": ">=3.12.1"
}
},
"node_modules/xgplayer-subtitles": {
"version": "3.0.19",
"resolved": "https://registry.npmmirror.com/xgplayer-subtitles/-/xgplayer-subtitles-3.0.19.tgz",
"integrity": "sha512-e2k1oFq1HSbnnHaK694FREqkFyq65ze4vETTIy8ABkOXItcr9/ugyNIJ4zCjD+jExSAVppM9FAF761X4+wRCeA==",
"dependencies": {
"eventemitter3": "^4.0.7"
},
"peerDependencies": {
"core-js": ">=3.12.1"
}
},
"node_modules/xml2js": {
"version": "0.4.23",
"resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz",
......@@ -5425,6 +5600,12 @@
"integrity": "sha1-JoD7uAaKSNCGVrYJgJK9r8kG9KU=",
"dev": true
},
"core-js": {
"version": "3.38.1",
"resolved": "https://registry.npmmirror.com/core-js/-/core-js-3.38.1.tgz",
"integrity": "sha512-OP35aUorbU3Zvlx7pjsFdu1rGNnD4pgw/CWoYzRY3t2EzoVT7shKHY1dlAy3f41cGIO7ZDPQimhGFTlEYkG/Hw==",
"peer": true
},
"core-util-is": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
......@@ -5461,6 +5642,23 @@
"resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.20.tgz",
"integrity": "sha512-/WwNkdXfckNgw6S5R125rrW8ez139lBHWouiBvX8dfMFtcn6V81REDqnH7+CRpRipfYlyU1CmOnOxrmGcFOjeA=="
},
"d": {
"version": "1.0.2",
"resolved": "https://registry.npmmirror.com/d/-/d-1.0.2.tgz",
"integrity": "sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw==",
"requires": {
"es5-ext": "^0.10.64",
"type": "^2.7.2"
}
},
"danmu.js": {
"version": "1.1.13",
"resolved": "https://registry.npmmirror.com/danmu.js/-/danmu.js-1.1.13.tgz",
"integrity": "sha512-knFd0/cB2HA4FFWiA7eB2suc5vCvoHdqio33FyyCSfP7C+1A+zQcTvnvwfxaZhrxsGj4qaQI2I8XiTqedRaVmg==",
"requires": {
"event-emitter": "^0.3.5"
}
},
"data-uri-to-buffer": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-3.0.1.tgz",
......@@ -5519,6 +5717,11 @@
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk="
},
"delegate": {
"version": "3.2.0",
"resolved": "https://registry.npmmirror.com/delegate/-/delegate-3.2.0.tgz",
"integrity": "sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw=="
},
"depd": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
......@@ -5577,6 +5780,11 @@
"ssr-window": "^4.0.0"
}
},
"downloadjs": {
"version": "1.4.7",
"resolved": "https://registry.npmmirror.com/downloadjs/-/downloadjs-1.4.7.tgz",
"integrity": "sha512-LN1gO7+u9xjU5oEScGFKvXhYf7Y/empUIIEAGBs1LzUq/rg5duiDrkuH5A2lQGd5jfMOb9X9usDa2oVXwJ0U/Q=="
},
"ee-first": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
......@@ -5604,6 +5812,36 @@
"integrity": "sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q==",
"dev": true
},
"es5-ext": {
"version": "0.10.64",
"resolved": "https://registry.npmmirror.com/es5-ext/-/es5-ext-0.10.64.tgz",
"integrity": "sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==",
"requires": {
"es6-iterator": "^2.0.3",
"es6-symbol": "^3.1.3",
"esniff": "^2.0.1",
"next-tick": "^1.1.0"
}
},
"es6-iterator": {
"version": "2.0.3",
"resolved": "https://registry.npmmirror.com/es6-iterator/-/es6-iterator-2.0.3.tgz",
"integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==",
"requires": {
"d": "1",
"es5-ext": "^0.10.35",
"es6-symbol": "^3.1.1"
}
},
"es6-symbol": {
"version": "3.1.4",
"resolved": "https://registry.npmmirror.com/es6-symbol/-/es6-symbol-3.1.4.tgz",
"integrity": "sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg==",
"requires": {
"d": "^1.0.2",
"ext": "^1.7.0"
}
},
"esbuild": {
"version": "0.14.39",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.14.39.tgz",
......@@ -5862,6 +6100,17 @@
"integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==",
"dev": true
},
"esniff": {
"version": "2.0.1",
"resolved": "https://registry.npmmirror.com/esniff/-/esniff-2.0.1.tgz",
"integrity": "sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==",
"requires": {
"d": "^1.0.1",
"es5-ext": "^0.10.62",
"event-emitter": "^0.3.5",
"type": "^2.7.2"
}
},
"espree": {
"version": "9.3.2",
"resolved": "https://registry.npmjs.org/espree/-/espree-9.3.2.tgz",
......@@ -5930,6 +6179,28 @@
"integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
"dev": true
},
"event-emitter": {
"version": "0.3.5",
"resolved": "https://registry.npmmirror.com/event-emitter/-/event-emitter-0.3.5.tgz",
"integrity": "sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==",
"requires": {
"d": "1",
"es5-ext": "~0.10.14"
}
},
"eventemitter3": {
"version": "4.0.7",
"resolved": "https://registry.npmmirror.com/eventemitter3/-/eventemitter3-4.0.7.tgz",
"integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw=="
},
"ext": {
"version": "1.7.0",
"resolved": "https://registry.npmmirror.com/ext/-/ext-1.7.0.tgz",
"integrity": "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==",
"requires": {
"type": "^2.7.2"
}
},
"extend-shallow": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
......@@ -6233,6 +6504,11 @@
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
"integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A=="
},
"hls.js": {
"version": "1.5.15",
"resolved": "https://registry.npmmirror.com/hls.js/-/hls.js-1.5.15.tgz",
"integrity": "sha512-6cD7xN6bycBHaXz2WyPIaHn/iXFizE5au2yvY5q9aC4wfihxAr16C9fUy4nxh2a3wOw0fEgLRa9dN6wsYjlpNg=="
},
"html2canvas": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz",
......@@ -6733,6 +7009,11 @@
"integrity": "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==",
"dev": true
},
"next-tick": {
"version": "1.1.0",
"resolved": "https://registry.npmmirror.com/next-tick/-/next-tick-1.1.0.tgz",
"integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ=="
},
"normalize-path": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
......@@ -7380,6 +7661,11 @@
"thenify": ">= 3.1.0 < 4"
}
},
"throttle-debounce": {
"version": "3.0.1",
"resolved": "https://registry.npmmirror.com/throttle-debounce/-/throttle-debounce-3.0.1.tgz",
"integrity": "sha512-dTEWWNu6JmeVXY0ZYoPuH5cRIwc0MeGbJwah9KUNYSJwommQpCzTySTpEe8Gs1J23aeWEuAobe4Ag7EHVt/LOg=="
},
"through": {
"version": "2.3.8",
"resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
......@@ -7428,6 +7714,11 @@
"tslib": "^1.8.1"
}
},
"type": {
"version": "2.7.3",
"resolved": "https://registry.npmmirror.com/type/-/type-2.7.3.tgz",
"integrity": "sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ=="
},
"type-check": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
......@@ -7836,6 +8127,16 @@
"@volar/vue-typescript": "0.37.0"
}
},
"vue3-video-play": {
"version": "1.3.1-beta.6",
"resolved": "https://registry.npmmirror.com/vue3-video-play/-/vue3-video-play-1.3.1-beta.6.tgz",
"integrity": "sha512-Olrx2/LNAds7fuor/yX9ZKT9sOcwcfTt2g2YbbCrEaAmZ5Tb0hwBr5z+/CoLwELzzRzXCHPmWWoT0Wm5W/Nwpw==",
"requires": {
"hls.js": "^1.0.10",
"throttle-debounce": "^3.0.1",
"vue": "^3.2.2"
}
},
"webpack-sources": {
"version": "3.2.3",
"resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz",
......@@ -7886,6 +8187,26 @@
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
"dev": true
},
"xgplayer": {
"version": "3.0.19",
"resolved": "https://registry.npmmirror.com/xgplayer/-/xgplayer-3.0.19.tgz",
"integrity": "sha512-yw4yinU5EtxS5YxOpctCVnHS/WW3tLTYjT+ZHivUYihlaAdudrmBmoOCcRYykGg7xz6PVIi6B79Gn1RBnGAnpg==",
"requires": {
"danmu.js": ">=1.1.6",
"delegate": "^3.2.0",
"downloadjs": "1.4.7",
"eventemitter3": "^4.0.7",
"xgplayer-subtitles": "3.0.19"
}
},
"xgplayer-subtitles": {
"version": "3.0.19",
"resolved": "https://registry.npmmirror.com/xgplayer-subtitles/-/xgplayer-subtitles-3.0.19.tgz",
"integrity": "sha512-e2k1oFq1HSbnnHaK694FREqkFyq65ze4vETTIy8ABkOXItcr9/ugyNIJ4zCjD+jExSAVppM9FAF761X4+wRCeA==",
"requires": {
"eventemitter3": "^4.0.7"
}
},
"xml2js": {
"version": "0.4.23",
"resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz",
......
......@@ -27,7 +27,9 @@
"vant": "^3.5.0",
"vue": "^3.2.37",
"vue-infinite-scroll": "^2.0.2",
"vue-router": "^4.0.15"
"vue-router": "^4.0.15",
"vue3-video-play": "^1.3.1-beta.6",
"xgplayer": "^3.0.19"
},
"devDependencies": {
"@rushstack/eslint-patch": "^1.1.3",
......
......@@ -33,8 +33,8 @@ export function getVideoView(params: { id: string }) {
return httpRequest.get('/api/psp/v1/admission/video-view', { params })
}
// 获取推荐课程列表
export function getRecommendCourse(params?: { page_size?: string; page?: string }) {
return httpRequest.get('/api/psp/v1/recommend-course/list', { params })
export function getRecommendCourse(params?: { page_size?: string; page?: string; category:string }) {
return httpRequest.get('/api/psp/v2/learning/course-list', { params })
}
// 获取导师列表
export function getTeacherList(params?: { page_size?: string; page?: string; type?: string }) {
......
......@@ -31,6 +31,8 @@ const files = computed<{ name: string; url: string }[]>(() => {
<style lang="scss" scoped>
.doc-hd {
// margin-top: 1rem;
padding: 0.1rem 0.2rem;
h1 {
font-size: 0.32rem;
font-weight: 500;
......@@ -48,7 +50,7 @@ const files = computed<{ name: string; url: string }[]>(() => {
}
}
.doc-bd {
padding: 0.5rem 0;
padding: 0.5rem 0.2rem;
font-size: 0.24rem;
font-weight: 400;
color: #666666;
......
......@@ -6,6 +6,7 @@ interface Props {
title?: string
headerAlign?: string
backgroundColor?: string
type?: string
}
const props = withDefaults(defineProps<Props>(), {
headerAlign: 'left',
......@@ -15,10 +16,12 @@ const props = withDefaults(defineProps<Props>(), {
const emit = defineEmits(['back'])
const router = useRouter()
function handleBack() {
try {
router.back()
} catch (e) {
router.replace('/')
if (!props.type) {
try {
router.back()
} catch (e) {
router.replace('/')
}
}
emit('back')
}
......@@ -46,16 +49,27 @@ const containerPaddingValue = computed(() => {
<style lang="scss">
.app-container {
width: 100%;
margin-bottom: 0.3rem;
padding: v-bind(containerPaddingValue);
// padding: 0 0.3rem;
border-radius: 0.2rem;
background: v-bind(backgroundColor);
}
.app-container-hd {
padding: 0 0.3rem;
width: 100%;
position: fixed;
top: 0;
left: 0;
background-color: #fff;
box-sizing: border-box;
display: flex;
align-items: center;
font-size: 0.32rem;
padding: 0.24rem 0;
// padding: 0.24rem 0;
height: 0.9rem;
box-sizing: border-box;
z-index: 9999999;
.van-icon-arrow-left {
margin-left: -2px;
}
......@@ -68,4 +82,10 @@ const containerPaddingValue = computed(() => {
color: #333333;
text-align: v-bind(headerAlign);
}
.app-container-hd-aside {
margin-left: auto;
}
.app-container-bd {
padding-top: 0.9rem;
}
</style>
<script setup lang="ts"></script>
<template>
<div class="foot">
<div class="foot-t">
<div class="foot-t_l">
<div class="title">联系我们</div>
<div class="info">
紫荆小秘书:13263110169(同微信)<br />
地址:北京市海淀区中关村东路1号院清华科技园7号楼5层<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;深圳市福田区博今商务广场A座22层
<br />邮箱:WMC@ezijing.com
</div>
</div>
<div class="foot-t_r">
<img class="code" src="https://webapp-pub.oss-cn-beijing.aliyuncs.com/project/wmpc-h5/er-code.png" />
<div class="wx">
<img src="https://webapp-pub.oss-cn-beijing.aliyuncs.com/project/wmpc-h5/wx.png" class="icon" />
<div class="txt">微信公众号</div>
</div>
</div>
</div>
<div class="foot-b">
Copyright @ 2013 Zijing Education.All rights reserved.京ICP证150431号
<div class="c">
<img src="https://webapp-pub.oss-cn-beijing.aliyuncs.com/project/wmpc-h5/jinghui.png" class="jh" />
<p>京公网安备 11010802023681号</p>
</div>
清控紫荆(北京)教育科技股份有限公司
</div>
</div>
</template>
<style lang="scss" scoped>
.foot {
background-color: #242424;
padding: 0.46rem 0.3rem;
box-sizing: border-box;
.foot-t {
display: flex;
justify-content: space-between;
.foot-t_l {
.title {
font-weight: bold;
font-size: 0.22rem;
color: #ffffff;
line-height: 100%;
letter-spacing: 0.03rem;
}
.info {
font-size: 0.2rem;
color: #ffffff;
line-height: 0.36rem;
text-align: left;
margin-top: 0.2rem;
}
}
.foot-t_r {
width: 1.26rem;
.code {
width: 100%;
}
.wx {
display: flex;
justify-content: space-between;
margin-top: 0.08rem;
.icon {
width: 0.21rem;
}
.txt {
font-size: 0.2rem;
color: #ffffff;
line-height: 100%;
}
}
}
}
.foot-b {
text-align: center;
font-size: 0.2rem;
color: #999999;
line-height: 100%;
margin-top: 0.5rem;
.c {
display: flex;
align-items: center;
justify-content: center;
margin: 0.24rem 0;
img {
height: 0.26rem;
margin-right: 0.16rem;
}
}
}
}
</style>
......@@ -3,7 +3,11 @@
<template>
<header class="app-header">
<div class="app-header__logo">
<router-link to="/"><img src="https://webapp-pub.ezijing.com/project/prp-h5/logo1.png" /></router-link>
<router-link to="/"><img src="https://webapp-pub.ezijing.com/project/wmpc-h5/ezijing-logo.png" /></router-link>
<div class="app-header_text">
由清华大学五道口金融学院<br />
相关知识产权创设而成
</div>
</div>
<ul class="app-header-right">
<li>
......@@ -25,10 +29,19 @@
padding: 0.44rem 0 0.4rem;
}
.app-header__logo {
height: 0.51rem;
display: flex;
height: 0.5rem;
img {
height: 100%;
}
.app-header_text {
border-left: 1px solid #707070;
padding-left: 0.15rem;
margin-left: 0.15rem;
font-size: 0.18rem;
color: #000000;
line-height: 0.22rem;
}
}
.app-header-right {
display: flex;
......
<script setup lang="ts">
import AppHeader from './Header.vue'
import Footer from './Footer.vue'
const route = useRoute()
console.log(route.path, 'route')
</script>
<template>
<div class="app-layout">
<AppHeader />
<AppHeader v-if="route.path === '/'" />
<RouterView />
</div>
<Footer v-if="route.path === '/'" />
</template>
<style lang="scss">
......@@ -15,7 +20,7 @@ import AppHeader from './Header.vue'
max-width: 750px;
margin: 0 auto;
// background: #f3f4f8 url(https://webapp-pub.ezijing.com/project/prp-h5/bg.png) no-repeat;
background: #f7f7f7;
background: #f8f8f8;
background-size: 100% auto;
padding: 0 0.3rem;
box-sizing: border-box;
......
......@@ -4,6 +4,7 @@ import App from './App.vue'
import router from './router'
import Vant from 'vant'
import 'vant/lib/index.css'
// 公共组件
import AppCard from '@/components/base/AppCard.vue'
import AppContainer from '@/components/base/AppContainer.vue'
......
import httpRequest from '@/utils/axios'
// 获取文档数据
export function getDocView(params: { id: string }) {
return httpRequest.get('/api/psp/v1/admission/doc-view', { params })
}
import type { RouteRecordRaw } from 'vue-router'
import AppLayout from '@/components/layout/Index.vue'
export const routes: Array<RouteRecordRaw> = [
{
path: '/admission',
component: AppLayout,
children: [{ path: 'doc/:id', component: () => import('./views/DocView.vue'), props: true }]
}
]
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { getDocView } from '../api'
import DocView from '@/components/DocView.vue'
const props = defineProps<{ id: string }>()
const route = useRoute()
const data = ref()
function fetchDocView() {
getDocView({ id: props.id }).then(res => {
data.value = res.data
})
}
onMounted(() => {
console.log(route.query, '111')
fetchDocView()
})
</script>
<template>
<DocView :data="data"></DocView>
</template>
import httpRequest from '@/utils/axios'
// 获取消息列表
export function getCourseDetail(params: { id: string }) {
return httpRequest.get('/api/psp/v2/learning/course-view', { params })
}
// 获取视频
export function getVideo(params: { resource_id: string, course_id: string, chapter_id: string }) {
return httpRequest.get('/api/psp/v2/learning/video-streaming', { params })
}
// 上传视频进度
export function uploadVideo(params: any) {
return httpRequest.get('/api/psp/v2/learning/upload-video', { params })
}
<script setup lang="ts">
const props = defineProps<{ data: any; isFree: string }>()
const router = useRouter()
const route = useRoute()
const handleClickItem = function (item: any) {
item.show = !item.show
}
const secondsToHms = function (d: any) {
d = Number(d)
const h = Math.floor(d / 3600)
const m = Math.floor((d % 3600) / 60)
const s = Math.floor((d % 3600) % 60)
let t = ''
if (h <= 0) {
t = `${m}${s}秒`
} else {
t = `${h}${m}${s}秒`
}
return t
}
const handleChapter = function (item: any, cItem: any) {
if (route.path === '/course/detail') {
router.push(`/course/chapter?id=${route.query?.id}&chapterId=${item.id}`)
} else {
if (props.isFree !== '收费') {
if (parseInt(cItem.type) === 2) {
router.push(
`/course/player?id=${route.query?.id}&chapterId=${cItem.id}&rId=${cItem.resource_id}&&zId=${item.id}`
)
} else {
if (cItem.status === 1) return
router.push(`/exam?id=${route.query?.id}&cId=${cItem.id}`)
}
}
}
}
</script>
<template>
<div class="chapter-box" v-if="props.data?.length">
<div class="collapse" v-for="item in props.data" :key="item.id">
<div class="h2" @click="handleClickItem(item)">
<div class="t">{{ item.name }}</div>
<van-icon name="arrow" v-if="!item.show" />
<van-icon name="arrow-down" v-else />
</div>
<div class="sections" v-if="item.show">
<div class="item" v-for="cItem in item.children" @click="handleChapter(item, cItem)" :key="cItem.id">
<div class="t">{{ cItem.name }}</div>
<div class="learn-info">
<div
class="v-time"
v-if="
parseInt(cItem.type) === 2 && ($route.path === '/course/chapter' || $route.path === '/course/player')
"
>
{{ secondsToHms(cItem.video_length) }}
</div>
<template
v-if="props.isFree !== '收费' && ($route.path === '/course/chapter' || $route.path === '/course/player')"
>
<template v-if="parseInt(cItem.type) === 2">
<div class="btn btn-s1" v-if="parseInt(cItem.progress) === 0">未开始</div>
<div class="btn btn-s2" v-if="parseInt(cItem.progress) !== 0 && parseInt(cItem.progress) !== 100">
进行中
</div>
<div class="btn btn-s3" v-if="parseInt(cItem.progress) === 100">已完成</div>
</template>
<template v-else>
<div class="btn btn-s1" v-if="parseInt(cItem.status) === 0">未开始</div>
<div class="btn btn-s2" v-if="parseInt(cItem.status) === 1">已提交</div>
<div class="btn btn-s3" v-if="parseInt(cItem.status) === 2">已完成</div>
</template>
</template>
</div>
</div>
</div>
</div>
</div>
</template>
<style lang="scss" scoped>
.chapter-box {
border-radius: 0.15rem;
padding: 0.25rem 0.2rem;
border-top: 0.06rem solid #fff5e1;
.collapse {
margin-bottom: 0.2rem;
.h2 {
background: #f5f8fb;
border-radius: 0.1rem;
padding: 0.17rem 0.07rem 0.17rem 0.18rem;
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: space-between;
.t {
font-size: 0.24rem;
color: #333333;
line-height: 100%;
text-align: left;
font-style: normal;
text-transform: none;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
width: 6rem;
}
}
.sections {
.item {
padding: 0 0.3rem;
border-bottom: 0.01rem solid #f4f4f4;
.t {
font-size: 0.22rem;
color: #666666;
line-height: 0.6rem;
text-align: left;
font-style: normal;
text-transform: none;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
width: 5.5rem;
}
.learn-info {
display: flex;
justify-content: right;
align-items: center;
margin-bottom: 0.15rem;
.v-time {
font-size: 0.2rem;
color: #acacac;
margin-right: 0.1rem;
}
.btn {
font-size: 0.18rem;
line-height: 100%;
text-align: center;
font-style: normal;
text-transform: none;
padding: 0.03rem 0.13rem;
border-radius: 44px 44px 44px 44px;
}
.btn-s1 {
color: #acacac;
border: 0.01rem solid #acacac;
}
.btn-s2 {
color: #1847a0;
border: 0.01rem solid #1847a0;
}
.btn-s3 {
color: #aa1941;
border: 0.01rem solid #aa1941;
}
}
}
}
}
}
</style>
......@@ -5,6 +5,11 @@ export const routes: Array<RouteRecordRaw> = [
{
path: '/course',
component: AppLayout,
children: [{ path: '', component: () => import('./views/Index.vue') }]
children: [
{ path: '', component: () => import('./views/Index.vue') },
{ path: 'detail', component: () => import('./views/Detail.vue') },
{ path: 'chapter', component: () => import('./views/Chapter.vue') },
{ path: 'player', component: () => import('./views/Player.vue') }
]
}
]
<script setup lang="ts">
import AppContainer from '@/components/base/AppContainer.vue'
import { getCourseDetail } from '../api'
import CourseCatalog from '../components/CourseCatalog.vue'
const route = useRoute()
let data: any = reactive({
course: {}
})
getCourseDetail({ id: route.query?.id as string }).then((res: any) => {
const d = res.data.course
if (d.chapters?.length) {
d.chapters = d.chapters.map((item: any) => {
if (item.id === route.query.chapterId) {
item.show = true
} else {
item.show = false
}
return item
})
}
data.course = d
})
</script>
<template>
<AppContainer :title="data.course.category_name" headerAlign="center"></AppContainer>
<img :src="data.course.course_picture" class="banner" />
<div class="content">
<h2>课程目录</h2>
<CourseCatalog
v-if="data.course?.chapters && data.course?.chapters.length"
:data="data.course.chapters"
:isFree="data.course.is_free_name"
></CourseCatalog>
<div class="all-btn">
<!-- 已考试 -->
<!-- <div class="btn-s2">查看课程成绩</div> -->
</div>
</div>
</template>
<style lang="scss" scoped>
.banner {
width: 100%;
display: block;
}
.content {
background: #ffffff;
border-radius: 0.12rem;
margin-top: 0.54rem;
padding: 0 0.2rem;
box-sizing: border-box;
margin-bottom: 1.7rem;
h2 {
font-weight: bold;
font-size: 0.28rem;
color: #333333;
line-height: 0.78rem;
}
.course-tj {
background: #f4f8fb;
border-radius: 0.16rem;
padding: 0.3rem 0.2rem;
box-sizing: border-box;
font-size: 0.24rem;
color: #666666;
line-height: 0.4rem;
text-align: left;
font-style: normal;
text-transform: none;
}
.lecturer {
background: #f4f8fb;
border-radius: 0.16rem;
padding: 0.27rem 0.2rem 0.37rem;
box-sizing: border-box;
.lecturer-li {
margin-top: 0.5rem;
&:nth-child(1) {
margin-top: 0;
}
}
.info {
display: flex;
align-items: center;
img {
width: 0.86rem;
height: 0.86rem;
border-radius: 50%;
object-fit: cover;
}
.name {
font-weight: bold;
font-size: 0.28rem;
color: #333333;
line-height: 100%;
margin-left: 0.36rem;
}
}
.li {
font-size: 0.24rem;
color: #666666;
// line-height: 100%;
padding-left: 0.2rem;
position: relative;
margin-top: 0.18rem;
&::after {
content: '';
position: absolute;
top: 50%;
left: 0;
margin-top: -0.04rem;
width: 0.08rem;
height: 0.08rem;
background-color: #c1ab85;
border-radius: 50%;
}
}
}
}
.all-btn {
.btn-s1 {
position: fixed;
bottom: 0;
left: 0;
width: 100%;
height: 1.2rem;
background-color: #fff;
display: flex;
justify-content: space-between;
padding: 0 0.3rem;
box-sizing: border-box;
align-items: center;
box-shadow: 0 10px 10px 5px #000;
.price {
display: flex;
font-size: 0.24rem;
color: #333333;
line-height: 100%;
text-align: left;
font-style: normal;
text-transform: none;
align-items: center;
span {
font-weight: 500;
font-size: 0.24rem;
color: #aa1941;
line-height: 100%;
text-align: left;
font-style: normal;
text-transform: none;
}
p {
font-size: 0.4rem;
color: #aa1941;
line-height: 100%;
text-align: left;
font-style: normal;
text-transform: none;
}
}
.btn {
border-radius: 0.4rem;
background: #aa1941;
font-weight: 500;
font-size: 0.28rem;
color: #ffffff;
line-height: 100%;
padding: 0.26rem 0.43rem;
}
}
.btn-s2 {
position: fixed;
left: 50%;
bottom: 0.46rem;
transform: translateX(-50%);
width: 4rem;
line-height: 0.8rem;
background: #aa1941;
border-radius: 0.4rem;
font-weight: 500;
font-size: 0.28rem;
color: #ffffff;
text-align: center;
box-shadow: 0 0 5px 1px rgba(170, 25, 65, 0.5);
}
}
</style>
<script setup lang="ts">
import AppContainer from '@/components/base/AppContainer.vue'
import { getCourseDetail } from '../api'
import CourseCatalog from '../components/CourseCatalog.vue'
import { useUserStore } from '@/stores/user'
const userStore = useUserStore()
const route = useRoute()
const router = useRouter()
let data: any = reactive({
course: {}
})
getCourseDetail({ id: route.query?.id as string }).then((res: any) => {
const d = res.data.course
if (d.chapters?.length) {
d.chapters = d.chapters.map((item: any) => {
item.show = false
return item
})
}
data.course = d
})
const handleStudy = function () {
const videoId = data.course.last_play_video
let query = {
cId: '',
rId: '',
zId: ''
}
data.course.chapters.forEach((item: any) => {
const findItem = item.children.find((cItem: any) => cItem.resource_id === videoId)
query = {
cId: findItem?.id,
rId: findItem?.resource_id,
zId: item.id
}
})
if (videoId === '') {
router.push(`/course/chapter?id=${route.query?.id}&chapterId=${data.course?.id}`)
} else {
router.push(`/course/player?id=${route.query?.id}&chapterId=${query.cId}&rId=${query.rId}&&zId=${query.zId}`)
}
}
</script>
<template>
<AppContainer :title="data.course.category_name" headerAlign="center"></AppContainer>
<img :src="data.course.course_picture" class="banner" />
<div class="content">
<h2>课程推荐</h2>
<div class="course-tj" v-html="data.course?.course_represent"></div>
<h2>讲师介绍</h2>
<div class="lecturer">
<div class="lecturer-li" v-for="item in data.course?.course_lectures" :key="item.id">
<div class="info">
<img :src="item.lecturer_avatar" />
<div class="name">{{ item.lecturer_name }}</div>
</div>
<div class="li" v-if="item.lecturer_office !== ''">{{ item.lecturer_office }}</div>
<div class="li" v-if="item.lecturer_title !== ''">{{ item.lecturer_title }}</div>
</div>
</div>
<h2>课程目录</h2>
<CourseCatalog
v-if="data.course?.chapters && data.course?.chapters.length"
:data="data.course.chapters"
:isFree="data.course.is_free"
:isBtn="false"
></CourseCatalog>
<div class="all-btn">
<!-- 没购买 -->
<div class="btn-s1" v-if="data.course?.is_free_name === '收费'">
<div class="price">
课程价格<span></span>
<p>1999.00</p>
</div>
<div class="btn">立即购买</div>
</div>
<!-- 已购买没考试 -->
<div class="btn-s2" v-else @click="handleStudy">立即学习</div>
<!-- 已考试 -->
<!-- <div class="btn-s2">查看课程成绩</div> -->
</div>
</div>
</template>
<style lang="scss" scoped>
.banner {
width: 100%;
display: block;
}
.content {
background: #ffffff;
border-radius: 0.12rem;
margin-top: 0.54rem;
padding: 0 0.2rem;
box-sizing: border-box;
margin-bottom: 1.7rem;
h2 {
font-weight: bold;
font-size: 0.28rem;
color: #333333;
line-height: 0.78rem;
}
.course-tj {
background: #f4f8fb;
border-radius: 0.16rem;
padding: 0.3rem 0.2rem;
box-sizing: border-box;
font-size: 0.24rem;
color: #666666;
line-height: 0.4rem;
text-align: left;
font-style: normal;
text-transform: none;
}
.lecturer {
background: #f4f8fb;
border-radius: 0.16rem;
padding: 0.27rem 0.2rem 0.37rem;
box-sizing: border-box;
.lecturer-li {
margin-top: 0.5rem;
&:nth-child(1) {
margin-top: 0;
}
}
.info {
display: flex;
align-items: center;
img {
width: 0.86rem;
height: 0.86rem;
border-radius: 50%;
object-fit: cover;
}
.name {
font-weight: bold;
font-size: 0.28rem;
color: #333333;
line-height: 100%;
margin-left: 0.36rem;
}
}
.li {
font-size: 0.24rem;
color: #666666;
// line-height: 100%;
padding-left: 0.2rem;
position: relative;
margin-top: 0.18rem;
&::after {
content: '';
position: absolute;
top: 50%;
left: 0;
margin-top: -0.04rem;
width: 0.08rem;
height: 0.08rem;
background-color: #c1ab85;
border-radius: 50%;
}
}
}
}
.all-btn {
.btn-s1 {
position: fixed;
bottom: 0;
left: 0;
width: 100%;
height: 1.2rem;
background-color: #fff;
display: flex;
justify-content: space-between;
padding: 0 0.3rem;
box-sizing: border-box;
align-items: center;
box-shadow: 0 10px 10px 5px #000;
.price {
display: flex;
font-size: 0.24rem;
color: #333333;
line-height: 100%;
text-align: left;
font-style: normal;
text-transform: none;
align-items: center;
span {
font-weight: 500;
font-size: 0.24rem;
color: #aa1941;
line-height: 100%;
text-align: left;
font-style: normal;
text-transform: none;
}
p {
font-size: 0.4rem;
color: #aa1941;
line-height: 100%;
text-align: left;
font-style: normal;
text-transform: none;
}
}
.btn {
border-radius: 0.4rem;
background: #aa1941;
font-weight: 500;
font-size: 0.28rem;
color: #ffffff;
line-height: 100%;
padding: 0.26rem 0.43rem;
}
}
.btn-s2 {
position: fixed;
left: 50%;
bottom: 0.46rem;
transform: translateX(-50%);
width: 4rem;
line-height: 0.8rem;
background: #aa1941;
border-radius: 0.4rem;
font-weight: 500;
font-size: 0.28rem;
color: #ffffff;
text-align: center;
box-shadow: 0 0 5px 1px rgba(170, 25, 65, 0.5);
}
}
</style>
<script setup lang="ts">
import AppContainer from '@/components/base/AppContainer.vue'
import { getRecommendCourse } from '@/api/base'
import type { IRecommendCourse } from '@/types'
import num from '@/utils/numTo'
const router = useRouter()
interface IRecommendCourseAllList {
loading: boolean
page: number
total: number
list: IRecommendCourse[]
list: any[]
}
const categoryType = ref('')
const hasMore = ref(false)
const courseList = reactive<IRecommendCourseAllList>({ loading: false, page: 1, total: 0, list: [] })
const handleGetCourseList = () => {
const params: any = { page_size: 20, page: courseList.page }
courseList.list = []
const params: any = { page_size: 100, page: courseList.page, category: categoryType.value }
courseList.loading = true
getRecommendCourse(params)
.then(res => {
......@@ -47,32 +52,44 @@ onMounted(() => {
handleGetCourseList()
})
const handleClickItem = (item: any) => {
if (item.url) {
location.href = item.url
}
router.push(`/course/detail?id=${item.id}`)
}
const handleCategory = function (n: string) {
categoryType.value = n
handleGetCourseList()
}
</script>
<template>
<a href="https://fi.ezijing.com/shop" target="_blank">
<img src="https://webapp-pub.ezijing.com/project/prp-h5/course_banner.png" style="width: 100%" />
</a>
<AppCard title="推荐课程">
<div ref="el">
<div class="list_item" v-for="(item, index) in courseList.list" :key="index" @click="handleClickItem(item)">
<img :src="item.cover" class="item_img" />
<div class="item_right">
<div class="right_tit">{{ item.name }}</div>
<div class="right_bottom">
<div class="views">{{ num(item.pv) }}播放</div>
<div class="is_free" :class="item.is_free === '1' ? 'free' : 'unfree'">
{{ item.is_free_name }}
</div>
<AppContainer title="课程列表" headerAlign="center"></AppContainer>
<div class="screen">
<div :class="categoryType === '' ? 'tag active' : 'tag'" @click="handleCategory('')">全部</div>
<div class="tag-group">
<div :class="categoryType === '1' ? 'tag active' : 'tag'" @click="handleCategory('1')">财富管理基础知识</div>
<div :class="categoryType === '2' ? 'tag active' : 'tag'" @click="handleCategory('2')">金融投资工具</div>
<div :class="categoryType === '3' ? 'tag active' : 'tag'" @click="handleCategory('3')">财富管理实务应用</div>
</div>
</div>
<div ref="el" v-if="courseList.list?.length">
<div class="list_item" v-for="(item, index) in courseList.list" :key="index" @click="handleClickItem(item)">
<img :src="item.course_picture" class="item_img" />
<div class="item_right">
<div :class="`right_tag c${item.category}`">{{ item.category_name }}</div>
<div class="right_tit">{{ item.course_name }}</div>
<div class="right_bottom">
<div class="views">已学习{{ item.times }}</div>
<div
class="is_free"
:class="item.is_free_name === '免费' ? 'free' : item.is_free_name === '已购买' ? 'c' : 'unfree'"
>
{{ item.is_free_name }}
</div>
</div>
</div>
</div>
</AppCard>
</div>
<van-empty description="无数据" image-size="100" v-else />
</template>
<style lang="scss" scoped>
......@@ -95,7 +112,7 @@ img {
}
.item_right {
margin-left: 0.21rem;
padding-top: 0.17rem;
// padding-top: 0.17rem;
display: flex;
flex-direction: column;
justify-content: space-between;
......@@ -107,7 +124,7 @@ img {
color: #333333;
}
.right_bottom {
margin-top: 0.21rem;
// margin-top: 0.21rem;
display: flex;
justify-content: space-between;
align-items: center;
......@@ -130,7 +147,54 @@ img {
.unfree {
color: #e9a724;
}
.c {
color: #1847a0;
}
}
}
}
.el {
background-color: #fff;
}
.right_tag {
font-weight: bold;
font-size: 0.24rem;
color: #ffffff;
line-height: 100%;
border-radius: 0.24rem;
padding: 0.08rem 0.19rem;
width: fit-content;
margin-bottom: 0.15rem;
&.c1 {
background: #b51e4d;
}
&.c2 {
background: #1847a0;
}
&.c3 {
background: #bc9854;
}
}
.screen {
margin-bottom: 0.4rem;
.tag {
font-size: 0.24rem;
color: #000000;
line-height: 100%;
padding: 0.08rem 0.2rem;
background: #ffffff;
border-radius: 0.24rem;
border: 0.01rem solid #acacac;
width: fit-content;
&.active {
color: #aa1941;
border: 0.01rem solid #aa1941;
}
}
.tag-group {
display: flex;
justify-content: space-between;
margin-top: 0.2rem;
}
}
</style>
<script setup lang="ts">
import AppContainer from '@/components/base/AppContainer.vue'
import { getCourseDetail, getVideo, uploadVideo } from '../api'
import { getUser } from '@/api/base'
import CourseCatalog from '../components/CourseCatalog.vue'
import Player, { Events } from 'xgplayer'
import 'xgplayer/dist/index.min.css'
const route = useRoute()
const router = useRouter()
let data: any = reactive({
course: {}
})
getCourseDetail({ id: route.query?.id as string }).then((res: any) => {
const d = res.data.course
if (d.chapters?.length) {
d.chapters = d.chapters.map((item: any) => {
console.log(item.id, route.query.chapterId, 'item.id === route.query.chapterId')
if (item.id === route.query.zId) {
item.show = true
} else {
item.show = false
}
return item
})
}
data.course = d
})
// 提交视频
const params = reactive({
c: route.query.id,
v: route.query.rId,
uid: '',
_m: 0,
_c: 0,
_p: 0,
ps: ''
})
// 播放器
let m = 0
let player = null
let ps = [0, 0]
onMounted(() => {
getVideo({
resource_id: route.query?.rId as string,
course_id: route.query?.id as string,
chapter_id: route.query?.chapterId as string
}).then((res: any) => {
// videoSrc.value = res.data.info?.FD
player = new Player({
id: 'mse',
url: res.data.info?.FD,
height: '200px',
width: '100%',
startTime: res.data.cache?.cpt || 0
})
player.on(Events.TIME_UPDATE, ev => {
m++
// 最大学习时刻
params._m = Math.max(ev.currentTime, params._m)
params._c = ev.currentTime
params._p = ev.currentTime
if (!ps.includes(parseInt(ev.currentTime))) {
ps.push(parseInt(ev.currentTime))
}
if (m % 20 === 0) {
// console.log('-播放开始-', ev)
params.ps = ps.join(',')
uploadVideo(params)
// console.log(params)
ps = []
}
})
})
})
// 缓存
function getUserInfo() {
getUser().then((res: any) => {
params.uid = res.data.info?.sso_id
})
}
getUserInfo()
watch(
() => route.query.chapterId,
newVal => {
router.go(0)
}
)
</script>
<template>
<AppContainer title="课程列表" headerAlign="center"></AppContainer>
<div id="mse"></div>
<!-- <img :src="data.course.course_picture" class="banner" /> -->
<div class="content">
<h2>课程目录</h2>
<CourseCatalog
v-if="data.course?.chapters && data.course?.chapters.length"
:data="data.course.chapters"
:isFree="data.course.is_free_name"
></CourseCatalog>
<div class="all-btn">
<!-- 已考试 -->
<!-- <div class="btn-s2">查看课程成绩</div> -->
</div>
</div>
</template>
<style lang="scss" scoped>
.banner {
width: 100%;
display: block;
}
.content {
background: #ffffff;
border-radius: 0.12rem;
margin-top: 0.54rem;
padding: 0 0.2rem;
box-sizing: border-box;
margin-bottom: 1.7rem;
h2 {
font-weight: bold;
font-size: 0.28rem;
color: #333333;
line-height: 0.78rem;
}
.course-tj {
background: #f4f8fb;
border-radius: 0.16rem;
padding: 0.3rem 0.2rem;
box-sizing: border-box;
font-size: 0.24rem;
color: #666666;
line-height: 0.4rem;
text-align: left;
font-style: normal;
text-transform: none;
}
.lecturer {
background: #f4f8fb;
border-radius: 0.16rem;
padding: 0.27rem 0.2rem 0.37rem;
box-sizing: border-box;
.lecturer-li {
margin-top: 0.5rem;
&:nth-child(1) {
margin-top: 0;
}
}
.info {
display: flex;
align-items: center;
img {
width: 0.86rem;
height: 0.86rem;
border-radius: 50%;
object-fit: cover;
}
.name {
font-weight: bold;
font-size: 0.28rem;
color: #333333;
line-height: 100%;
margin-left: 0.36rem;
}
}
.li {
font-size: 0.24rem;
color: #666666;
// line-height: 100%;
padding-left: 0.2rem;
position: relative;
margin-top: 0.18rem;
&::after {
content: '';
position: absolute;
top: 50%;
left: 0;
margin-top: -0.04rem;
width: 0.08rem;
height: 0.08rem;
background-color: #c1ab85;
border-radius: 50%;
}
}
}
}
.all-btn {
.btn-s1 {
position: fixed;
bottom: 0;
left: 0;
width: 100%;
height: 1.2rem;
background-color: #fff;
display: flex;
justify-content: space-between;
padding: 0 0.3rem;
box-sizing: border-box;
align-items: center;
box-shadow: 0 10px 10px 5px #000;
.price {
display: flex;
font-size: 0.24rem;
color: #333333;
line-height: 100%;
text-align: left;
font-style: normal;
text-transform: none;
align-items: center;
span {
font-weight: 500;
font-size: 0.24rem;
color: #aa1941;
line-height: 100%;
text-align: left;
font-style: normal;
text-transform: none;
}
p {
font-size: 0.4rem;
color: #aa1941;
line-height: 100%;
text-align: left;
font-style: normal;
text-transform: none;
}
}
.btn {
border-radius: 0.4rem;
background: #aa1941;
font-weight: 500;
font-size: 0.28rem;
color: #ffffff;
line-height: 100%;
padding: 0.26rem 0.43rem;
}
}
.btn-s2 {
position: fixed;
left: 50%;
bottom: 0.46rem;
transform: translateX(-50%);
width: 4rem;
line-height: 0.8rem;
background: #aa1941;
border-radius: 0.4rem;
font-weight: 500;
font-size: 0.28rem;
color: #ffffff;
text-align: center;
box-shadow: 0 0 5px 1px rgba(170, 25, 65, 0.5);
}
}
</style>
import httpRequest from '@/utils/axios'
// 获取消息列表
export function getExamList(params?: { type: number }) {
return httpRequest.get('/api/psp/v1/exam/list', { params })
// 获取考卷
export function getExam(params: any) {
return httpRequest.get('/api/psp/v2/learning/get-question', { params })
}
// 获取文档数据····
export function chooseExam(data: {
exam_id: string
id_number: string
address: string
company: string
position: string
need_receive: string
}) {
return httpRequest.post('/api/psp/v1/exam/choose', data)
// 提交
export function submitQuestion(data: { id: string; type: string; answers: string; status: string }) {
return httpRequest.post('/api/psp/v2/learning/submit-question', data)
}
<script setup lang="ts">
import dayjs from 'dayjs'
import { getExamList } from '../api'
import type { ExamType } from '../types'
import FormDialog from '../components/FormDialog.vue'
interface Info {
address: string
can_choose: boolean
id_number: string
list: ExamType[]
}
import { getExam, submitQuestion } from '../api'
import { Dialog } from 'vant'
const route = useRoute()
const router = useRouter()
const tabActive = $ref<number>(2)
// 当前题号
let currentIndex = $ref(0)
// 请求全部数据
let data: any = $ref()
// 题目数据
let questions: any = $ref()
getExam({ type: 'chapter', course_id: route.query?.id, chapter_id: route.query?.cId }).then((res: any) => {
data = res.data
if (res.data?.questions || res.data?.questions !== '') {
questions = changeData(JSON.parse(res.data.questions).question_items)
}
})
const dataset = reactive<Info>({ address: '', can_choose: true, id_number: '', list: [] })
// 日期转换
function formatDate(startTime: string, endTime: string) {
return dayjs(startTime).format('YYYY年MM月DD日 HH:mm') + '-' + dayjs(endTime).format('HH:mm')
}
// 获取列表
const fetchList = () => {
getExamList({ type: tabActive }).then(res => {
Object.assign(dataset, res.data)
// 重构题目数据
const changeData = function (data: any) {
const qTypeCnName: any = {
'1': '单选',
'2': '多选',
'6': '判断'
}
return data.map((item: any) => {
item.question_item_typeName = qTypeCnName[item.question_item_type]
if (item.question_list?.length) {
item.question_list = item.question_list.map((qList: any) => {
if (qList?.options || qList.options !== '') {
// 答案转成对象
qList.options = JSON.parse(qList.options)
// 单选v-m和多选不同
const t = parseInt(item.question_item_type)
qList.answer = t === 1 || t === 6 ? '' : []
}
return qList
})
}
return item
})
}
onMounted(() => {
fetchList()
// 选出当前题
const currentQ = $computed(() => {
return questions?.length ? questions[currentIndex] : []
})
let dialogShow = $ref<boolean>(false)
let activeExam = $ref<ExamType>()
function handleClick(data: ExamType) {
if (data.choose) {
router.push(`/exam/${data.exam_id}`)
// 切换题
const changeQ = function (n: string) {
if (n === 'prev') {
currentIndex !== 0 && currentIndex--
} else {
currentIndex + 2 <= questions.length && currentIndex++
}
if (!dataset.can_choose) return
activeExam = data
dialogShow = true
}
// 进页面的提示
Dialog.alert({
title: '重要提示',
message:
'一旦开始考试,您将不能再次进行考试,请确保考试期间不要关闭或退出本页面,否则将会导致本次考试异常结束,影响您的课程成绩!'
}).then(() => {
// on close
})
// 左上角返回按钮
const handleBack = function () {
Dialog.confirm({
title: '重要提示',
message: '您尚未完成本次考试,回退操作将会导致本次考试异常结束,影响您的课程成绩!',
confirmButtonText: '结束考试'
})
.then(() => {
// end
router.go(-1)
})
.catch(() => {
// on cancel
})
}
// 倒计时结束
const finish = function () {
Dialog.alert({
title: '重要提示',
message: '考试结束,已自动提交试卷!'
}).then(() => {
// end
router.go(-1)
})
}
// 提交
const submit = function () {
const params: any = {}
questions.forEach((item: any) => {
item.question_list.forEach((cItem: any) => {
params[item.question_item_id] = {
[cItem.id]: {
answer: Array.isArray(cItem.answer) ? cItem.answer : [cItem.answer]
}
}
})
})
submitQuestion({ id: data.id, type: 'chapter', answers: JSON.stringify(params), status: '1' }).then(res => {
router.go(-1)
})
}
</script>
<template>
<AppContainer title="考试系统">
<van-tabs
v-model:active="tabActive"
shrink
background="transparent"
title-active-color="#033974"
title-inactive-color="#4E4E4E"
line-height="0"
@change="fetchList"
>
<van-tab title="已选" :name="2"></van-tab>
<van-tab title="未选" :name="1">
<p class="tips">请选择以下一场考试。每人一次正式考试机会,一次补考机会。</p></van-tab
<AppContainer title="考试系统" headerAlign="center" @back="handleBack" type="1">
<div class="exam-t">
<van-count-down @finish="finish" :time="data?.end_time * 1000" />
<div class="count">{{ currentIndex + 1 }}/{{ questions?.length }}</div>
</div>
<div class="q" v-for="item in currentQ?.question_list" :key="item.id">
<!-- <van-radio-group v-model="checked"> -->
<div class="question">
<van-tag type="primary">{{ currentQ?.question_item_typeName }}</van-tag>
<div class="t">{{ item?.content }}</div>
</div>
<!-- 单选 -->
<van-radio-group
v-model="item.answer"
v-if="currentQ.question_item_type === '1' || currentQ.question_item_type === '6'"
>
</van-tabs>
<div class="exam-list" v-if="dataset.list.length">
<div class="exam-item" v-for="item in dataset.list" :key="item.exam_id" @click="handleClick(item)">
<h3 class="exam-item__name">{{ item.name }}</h3>
<p class="exam-item__time">考试时间:{{ formatDate(item.start_time, item.end_time) }}</p>
<van-cell-group inset>
<van-cell :title="opt?.option" clickable v-for="opt in item?.options" :key="opt.id">
<template #right-icon>
<van-radio :name="opt?.id" />
</template>
</van-cell>
</van-cell-group>
</van-radio-group>
<!-- 多选 -->
<van-checkbox-group v-model="item.answer" v-else>
<van-cell-group inset>
<van-cell v-for="opt in item?.options" clickable :key="opt.id" :title="opt?.option">
<template #right-icon>
<van-checkbox checked-color="#aa1941" :name="opt.id" @click.stop shape="square" />
</template>
</van-cell>
</van-cell-group>
</van-checkbox-group>
<div class="btn-box">
<van-button type="primary" @click="changeQ('prev')">上一题</van-button>
<van-button type="primary" style="margin-left: 0.1rem" @click="changeQ('next')">下一题</van-button>
</div>
</div>
<van-empty description="暂无数据" v-else />
</AppContainer>
<FormDialog :data="activeExam" v-model:show="dialogShow" @update="fetchList"></FormDialog>
<van-button type="primary" class="submit" @click="submit">交卷</van-button>
</template>
<style lang="scss" scoped>
:deep(.van-tabs__nav) {
padding: 0 !important;
}
:deep(.van-tab) {
padding: 0 15px;
}
:deep(.van-tab + .van-tab) {
position: relative;
&:before {
content: '';
position: absolute;
left: 0;
width: 1px;
height: 12px;
background-color: #fff;
.btn-box {
padding-top: 1rem;
display: flex;
justify-content: space-around;
flex: 1;
button {
width: 100%;
}
}
.tips {
color: #033974;
font-size: 0.24rem;
font-weight: 400;
padding: 0.2rem 0;
text-align: center;
}
.exam-item {
padding: 0.3rem 0.45rem;
background-color: #fff;
border-radius: 0.2rem;
margin-bottom: 0.2rem;
cursor: pointer;
.submit {
position: fixed;
bottom: 0;
left: 0;
width: 100%;
background-color: #aa1941;
border-color: #aa1941;
}
.exam-item-hd {
.exam-t {
height: 0.7rem;
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 0.1rem;
line-height: 0.4rem;
align-items: center;
padding-top: 0.2rem;
border-bottom: 1px solid #ccc;
}
.exam-item__name {
font-size: 0.28rem;
font-weight: 400;
color: #333;
:deep(.van-cell-group) {
margin: 0;
background: none;
}
.exam-item__time {
margin-top: 0.07rem;
font-size: 0.24rem;
color: #999;
:deep(.van-cell) {
background: none;
}
:deep(.van-tag) {
height: fit-content;
}
:deep(.van-radio__icon--checked .van-icon) {
background-color: #aa1941;
border-color: #aa1941;
}
:deep(.van-tag--primary) {
background: #aa1941;
}
:deep(.van-button--primary) {
background: #aa1941;
border-color: #aa1941;
}
.question {
display: flex;
margin: 0.3rem 0;
.t {
font-size: 0.3rem;
color: #000;
width: 5.8rem;
margin-left: 0.2rem;
line-height: 0.36rem;
}
}
</style>
......@@ -2,7 +2,7 @@ import httpRequest from '@/utils/axios'
// 获取首页数据
export function getHomeData() {
return httpRequest.get('/api/psp/v1/index/index')
return httpRequest.get('/api/psp/v2/index/index')
}
// 获取导学视频列表
export function getVideoList(params?: { page_size?: number; page?: number }) {
......
<script setup lang="ts">
import { useRouter } from 'vue-router'
import { Notify } from 'vant'
import { Swiper, SwiperSlide } from 'swiper/vue'
import 'swiper/css'
import VideoItem from '@/components/VideoItem.vue'
import type { IDocItem, IVideoItem } from '../types'
defineProps<{ docs: IDocItem[]; videos: IVideoItem[] }>()
const router = useRouter()
function handleViewDoc(data: IDocItem) {
if (data.desc_type === '2') {
location.href = data.url
} else {
router.push('/admission/doc/' + data.id)
}
}
function showTips() {
Notify({ type: 'primary', message: '尚未开放' })
}
</script>
<template>
<AppCard title="入学指南" id="admission">
<div class="admission">
<div class="admission-left">
<h2>解释文档</h2>
<div class="box">
<ul>
<li v-for="item in docs" :key="item.id" @click="handleViewDoc(item)">
<p>{{ item.title }}</p>
<span>{{ item.pv }}</span>
<van-icon name="arrow" />
</li>
</ul>
</div>
</div>
<div class="admission-right">
<div class="box box-test">
<h2>入学评测</h2>
<p class="t1">系统专业知识</p>
<p class="t2"><a @click="showTips">去看看 ></a></p>
</div>
<div class="box box-notice">
<h2>入学通知书</h2>
<p class="t1">报名之后即可查询</p>
<p class="t2"><a href="https://prp.ezijing.com/personal">去看看 ></a></p>
</div>
</div>
</div>
<div class="admission-box" v-if="videos.length">
<h2>免费视频观看</h2>
<swiper slides-per-view="auto" :space-between="10">
<swiper-slide v-for="item in videos" :key="item.id" class="video-swiper-slide">
<VideoItem :data="item"></VideoItem>
</swiper-slide>
</swiper>
</div>
</AppCard>
</template>
<style lang="scss" scoped>
.admission {
display: flex;
justify-content: space-between;
}
.admission-left {
padding: 0.1rem;
width: 3.33rem;
margin-right: 0.2rem;
background: url(https://webapp-pub.ezijing.com/project/prp-h5/admission_bg.png) no-repeat;
background-size: contain;
box-sizing: border-box;
h2 {
margin: 0.1rem 0.1rem 0.14rem;
font-size: 0.26rem;
font-weight: 500;
line-height: 1.4;
color: #fff;
}
.box {
width: 3.13rem;
height: 3.43rem;
background: #fff url(https://webapp-pub.ezijing.com/project/prp-h5/query_bg_1.png) no-repeat center bottom;
background-size: 100% auto;
border-radius: 0.1rem;
ul {
overflow: hidden;
}
li {
margin: 0.3rem 0.14rem 0;
display: flex;
align-items: center;
font-size: 0.2rem;
color: #adadad;
cursor: pointer;
}
p {
flex: 1;
font-size: 0.24rem;
color: #4e4e4e;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
span {
min-width: 0.44rem;
font-size: 0.2rem;
color: #c6c6c6;
}
}
}
.admission-right {
flex: 1;
.box {
padding: 0.22rem 0.2rem;
// width: 3.33rem;
height: 2.01rem;
background: #fff;
border-radius: 0.2rem;
box-sizing: border-box;
h2 {
font-size: 0.26rem;
line-height: 1.4;
font-weight: 600;
color: #333;
}
.t1 {
margin-top: 0.14rem;
font-size: 0.22rem;
font-weight: 400;
color: #666666;
}
.t2 {
margin-top: 0.35rem;
font-size: 0.22rem;
font-weight: 400;
color: #033974;
}
}
.box + .box {
margin-top: 0.2rem;
}
.box-test {
background: #fff url(https://webapp-pub.ezijing.com/project/prp-h5/admission_1.png) no-repeat right bottom;
background-size: 1.7rem 1.36rem;
}
.box-notice {
background: #fff url(https://webapp-pub.ezijing.com/project/prp-h5/admission_2.png) no-repeat right bottom;
background-size: 1.7rem 1.36rem;
}
}
.admission-box {
margin-top: 0.28rem;
padding: 0.2rem;
background: #fff;
border-radius: 0.2rem;
overflow: hidden;
box-sizing: border-box;
h2 {
margin-bottom: 0.15rem;
font-size: 0.26rem;
font-weight: 600;
color: #333333;
}
}
.video-swiper-slide {
width: 2rem;
}
</style>
......@@ -10,7 +10,7 @@ defineProps<{ list: IBanner[] }>()
function handleClick(item: IBanner) {
// 外链
if (item.type === '2') {
location.href = item.url
// location.href = item.url
}
}
</script>
......
<script setup lang="ts">
// import { useRouter } from 'vue-router'
import { Notify } from 'vant'
// import { Swiper, SwiperSlide } from 'swiper/vue'
// import 'swiper/css'
// import VideoItem from '@/components/VideoItem.vue'
// import type { IDocItem, IVideoItem } from '../types'
// defineProps<{ docs: IDocItem[]; videos: IVideoItem[] }>()
// const router = useRouter()
const docs = [
{
title: 'PRP认证培训',
url: 'https://prp.ezijing.com/'
},
{
title: 'PAA系列认证',
url: 'https://paa.ezijing.com/'
},
{
title: '新型人才培育',
url: ''
},
{
title: '更多FI项目',
url: 'https://fi.ezijing.com/'
}
]
function handleViewDoc(data: any) {
// window.open(data.url)
if (data.url) {
location.href = data.url
} else {
Notify({ type: 'primary', message: '尚未开放' })
}
}
// function showTips() {
// Notify({ type: 'primary', message: '尚未开放' })
// }
</script>
<template>
<AppCard id="admission">
<div class="admission">
<div class="admission-right">
<div class="box box-test">
<h2>免费课程</h2>
<p class="t1">实战精华等你来看</p>
<p class="t2"><a href="https://fi.ezijing.com/shop/?activeIndex=2&type=free_course">去看看 ></a></p>
</div>
<div class="box box-notice">
<h2>系统小课</h2>
<p class="t1">专业思维 赢战未来</p>
<p class="t2"><a href="https://fi.ezijing.com/shop/?activeIndex=3&type=system_course">去看看 ></a></p>
</div>
</div>
<div class="admission-left">
<h2>认证课程包</h2>
<!-- <div class="box"> -->
<ul>
<li v-for="(item, index) in docs" :key="index" @click="handleViewDoc(item)">
<p>{{ item.title }}</p>
<!-- <span>{{ item.pv }}</span> -->
<van-icon name="arrow" />
</li>
</ul>
<!-- </div> -->
</div>
</div>
<!-- <div class="admission-box" v-if="videos.length">
<h2>免费视频观看</h2>
<swiper slides-per-view="auto" :space-between="10">
<swiper-slide v-for="item in videos" :key="item.id" class="video-swiper-slide">
<VideoItem :data="item"></VideoItem>
</swiper-slide>
</swiper>
</div> -->
</AppCard>
</template>
<style lang="scss" scoped>
.admission {
display: flex;
justify-content: space-between;
}
.admission-left {
padding: 0.1rem;
width: 3.33rem;
margin-left: 0.24rem;
background: url(https://webapp-pub.ezijing.com/project/prp-h5/certifation_course_bg.png) no-repeat;
background-size: contain;
box-sizing: border-box;
h2 {
margin: 0.1rem 0.1rem 0.14rem;
font-size: 0.28rem;
font-weight: 500;
line-height: 1.5;
color: #fff;
}
// .box {
// width: 3.13rem;
// height: 3.43rem;
// background: #fff url(https://webapp-pub.ezijing.com/project/prp-h5/query_bg_1.png) no-repeat center bottom;
// background-size: 100% auto;
// border-radius: 0.1rem;
ul {
overflow: hidden;
}
li {
margin: 0.2rem 0.14rem 0;
display: flex;
align-items: center;
font-size: 0.24rem;
color: #333333;
cursor: pointer;
background: #ffffff;
padding: 0.14rem 0.2rem;
border-radius: 0.3rem;
cursor: pointer;
}
p {
flex: 1;
font-size: 0.24rem;
color: #333333;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
span {
min-width: 0.44rem;
font-size: 0.2rem;
color: #c6c6c6;
}
}
// }
.admission-right {
flex: 1;
.box {
padding: 0.22rem 0.2rem;
// width: 3.33rem;
height: 2.01rem;
background: #fff;
border-radius: 0.2rem;
box-sizing: border-box;
cursor: pointer;
h2 {
font-size: 0.28rem;
line-height: 1.5;
font-weight: 500;
color: #333;
}
.t1 {
margin-top: 0.14rem;
font-size: 0.24rem;
font-weight: 400;
color: #666666;
}
.t2 {
margin-top: 0.35rem;
font-size: 0.24rem;
font-weight: 400;
color: #e6a522;
}
}
.box + .box {
margin-top: 0.2rem;
}
.box-test {
background: #ffffff url(https://webapp-pub.oss-cn-beijing.aliyuncs.com/project/prp-h5/free_course_logo.png)
no-repeat right bottom;
background-size: 1.7rem 1.36rem;
}
.box-notice {
background: #fff5e3 url(https://webapp-pub.oss-cn-beijing.aliyuncs.com/project/prp-h5/system_course_logo.png)
no-repeat right bottom;
background-size: 1.7rem 1.36rem;
}
}
</style>
<script setup lang="ts">
import { useRouter } from 'vue-router'
import type { IDocItem } from '../types'
defineProps<{ docs: IDocItem[] }>()
const router = useRouter()
function handleViewDoc(data: IDocItem) {
if (data.desc_type === '2') {
location.href = data.url
} else {
router.push('/learn/doc/' + data.id)
}
}
</script>
<template>
<AppCard title="考试攻略" id="exam">
<div class="exam">
<div class="box box-doc">
<h2>解释文档</h2>
<ul v-if="docs.length">
<li v-for="item in docs" :key="item.id" @click="handleViewDoc(item)">
<p>{{ item.title }}</p>
<span>{{ item.pv }}</span>
<van-icon name="arrow" />
</li>
</ul>
<van-empty description="暂无内容" v-else />
</div>
<div class="box box-exam">
<h2>考试系统</h2>
<router-link to="/exam" class="box-exam-content">
<img src="https://webapp-pub.ezijing.com/project/prp-h5/exam_bg.png" />
<p class="t2">去看看</p>
</router-link>
</div>
</div>
</AppCard>
</template>
<style lang="scss" scoped>
.exam {
display: flex;
justify-content: space-between;
}
.box {
padding: 0.2rem;
width: 3.33rem;
background: #fff;
border-radius: 0.2rem;
overflow: hidden;
box-sizing: border-box;
h2 {
margin-bottom: 0.15rem;
font-size: 0.26rem;
font-weight: 600;
color: #4e4e4e;
}
}
.box-doc {
height: 3.43rem;
background: #fff url(https://webapp-pub.ezijing.com/project/prp-h5/query_bg_1.png) no-repeat center bottom;
background-size: 100% auto;
ul {
overflow: hidden;
}
li {
margin: 0.3rem 0 0;
display: flex;
align-items: center;
font-size: 0.2rem;
color: #adadad;
cursor: pointer;
}
p {
flex: 1;
font-size: 0.24rem;
color: #4e4e4e;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
span {
min-width: 0.44rem;
font-size: 0.2rem;
color: #c6c6c6;
}
}
.box-exam {
.t2 {
display: inline-block;
padding: 0 0.2rem;
margin-top: 0.35rem;
font-size: 0.22rem;
line-height: 0.4rem;
color: #ffffff;
background-color: #033974;
}
}
.box-exam-content {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
img {
width: 1.61rem;
}
}
</style>
<script setup lang="ts"></script>
<template>
<div class="output_main">
<AppCard title="知识输出者" id="team">
<template #header-aside>
<div class="more">
<router-link to="/qa">知识IP践行 <van-icon name="arrow" /></router-link>
</div>
</template>
<div class="output_banner">
<a href="https://mp.weixin.qq.com/s/wUOtrmOttyNCyIecswqC4w" target="_blank">
<img src="https://webapp-pub.oss-cn-beijing.aliyuncs.com/project/prp-h5/output_banner.png" alt="" />
</a>
</div>
</AppCard>
</div>
<div class="input_main">
<AppCard title="知识输入者" id="team">
<template #header-aside>
<div class="more">
<router-link to="/learn/course">如何成为知识获得者 <van-icon name="arrow" /></router-link>
</div>
</template>
<div class="output_banner">
<a href="https://mp.weixin.qq.com/s/fDf4NpPZH4BNI_KEopiSwQ" target="_blank">
<img src="https://webapp-pub.oss-cn-beijing.aliyuncs.com/project/prp-h5/input_banner.png" alt="" />
</a>
</div>
</AppCard>
</div>
</template>
<style lang="scss" scoped>
.output_main {
.app-card {
background: url('https://webapp-pub.oss-cn-beijing.aliyuncs.com/project/prp-h5/output_bg.png') no-repeat;
background-size: 100% 100%;
}
.app-card-hd {
margin-top: 0.2rem !important;
}
.more {
font-size: 0.24rem;
font-weight: 500;
color: #f39929;
}
.output_banner {
padding: 0.06rem;
img {
width: 100%;
-webkit-filter: drop-shadow(0px 5px 5px rgba(0, 0, 0, 0.1)); /*考虑浏览器兼容性:兼容 Chrome, Safari, Opera */
filter: drop-shadow(0px 5px 2px rgba(0, 0, 0, 0.1));
}
}
}
.input_main {
.app-card {
background: url('https://webapp-pub.oss-cn-beijing.aliyuncs.com/project/prp-h5/input_bg.png') no-repeat;
background-size: 100% 100%;
}
.more {
font-size: 0.24rem;
font-weight: 500;
color: #bc2d29;
}
.output_banner {
padding: 0.06rem;
img {
width: 100%;
-webkit-filter: drop-shadow(0px 5px 5px rgba(0, 0, 0, 0.1)); /*考虑浏览器兼容性:兼容 Chrome, Safari, Opera */
filter: drop-shadow(0px 5px 5px rgba(0, 0, 0, 0.1));
}
}
}
</style>
<script setup lang="ts">
import { ref } from 'vue'
import { useRouter } from 'vue-router'
import { Notify } from 'vant'
import type { IDocItem } from '../types'
import LearningMapVideo from './LearningMapVideo.vue'
import LearningMapCourse from './LearningMapCourse.vue'
defineProps<{ docs: IDocItem[] }>()
const router = useRouter()
const active = ref<number>(0)
function handleViewDoc(data: IDocItem) {
if (data.desc_type === '2') {
location.href = data.url
} else {
router.push('/learn/doc/' + data.id)
}
}
function showTips() {
Notify({ type: 'primary', message: '尚未开放' })
}
</script>
<template>
<AppCard title="学习地图" id="learning">
<!-- <template #header-aside>
<div class="button"><a href="https://mp.weixin.qq.com/s/EdS6wpcdL0IEMK11WQ1Oyg" target="_blank">去学习</a></div>
</template> -->
<van-tabs
v-model:active="active"
shrink
background="transparent"
title-active-color="#E9A724"
title-inactive-color="#4E4E4E"
>
<van-tab title="课程导学">
<div class="learn-box">
<LearningMapVideo></LearningMapVideo>
</div>
</van-tab>
<van-tab title="学习进度">
<div class="learn-box learn-course">
<LearningMapCourse></LearningMapCourse>
</div>
</van-tab>
<van-tab title="解释文档" v-if="false">
<div class="learn-box learn-docs">
<ul>
<li v-for="item in docs" :key="item.id" @click="handleViewDoc(item)">
<p>{{ item.title }}</p>
<span>{{ item.pv }}</span>
<van-icon name="arrow" />
</li>
</ul>
</div>
</van-tab>
<van-tab title="学前测评">
<div class="learn-box learn-test">
<h2>查漏补缺 建立系统概念</h2>
<p>了解PRP学习前系统专业知识的情况</p>
<a class="button" @click="showTips">去测评</a>
</div>
</van-tab>
</van-tabs>
<!-- <div class="learn-banner">
<router-link to="/learn/course">
<img src="https://webapp-pub.ezijing.com/project/prp-h5/learning_map_banner.png" />
</router-link>
</div> -->
</AppCard>
</template>
<style lang="scss" scoped>
.learn-box {
height: 3.1rem;
padding: 0.3rem 0;
background: #fff;
border-radius: 0.2rem;
overflow: hidden;
box-sizing: border-box;
}
// 解释文档
.learn-docs {
background: url(https://webapp-pub.ezijing.com/project/prp-h5/learning_map_bg.png) no-repeat;
background-size: contain;
ul {
overflow: hidden;
}
li {
position: relative;
display: flex;
align-items: center;
font-size: 0.2rem;
color: #adadad;
cursor: pointer;
&::before {
content: '';
width: 0.08rem;
height: 0.08rem;
background: #033974;
border-radius: 50%;
}
}
p {
margin-left: 0.1rem;
flex: 1;
font-size: 0.24rem;
color: #4e4e4e;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
span {
margin: 0 0.1rem;
min-width: 0.44rem;
font-size: 0.2rem;
color: #c6c6c6;
}
}
.learn-banner {
margin-top: 0.2rem;
margin-left: -0.3rem;
margin-right: -0.3rem;
img {
width: 100%;
}
}
// 学前测评
.learn-test {
background: #fff url(https://webapp-pub.ezijing.com/project/prp-h5/learning_map_test.png) no-repeat right 0.2rem
bottom 0.5rem;
background-size: 2.08rem;
h2 {
margin-top: 0.2rem;
margin-left: 0.3rem;
font-size: 0.32rem;
font-weight: 500;
line-height: 1.4;
color: #4e4e4e;
}
p {
margin-top: 0.3rem;
margin-left: 0.3rem;
font-size: 0.28rem;
font-weight: 400;
line-height: 1.4;
color: #666666;
}
.button {
margin-top: 0.4rem;
display: block;
height: 0.8rem;
font-size: 0.28rem;
line-height: 0.8rem;
color: #fff;
text-align: center;
background: linear-gradient(90deg, #f7c988 0%, #e5a448 100%);
border-radius: 0.4rem;
}
}
.learn-course {
background-color: #f4e6d3;
padding: 0.3rem 0 0 0.3rem;
margin-top: 0.2rem;
}
:deep(.van-tabs__line) {
background: #e9a724;
width: 55px;
}
:deep(.van-tabs) {
margin-left: -0.05rem;
}
:deep(.van-tabs__wrap) {
margin-left: -0.2rem;
}
.app-card-hd {
display: flex;
align-items: flex-start;
justify-content: flex-start;
}
:deep(.van-tabs__content) {
margin-right: -0.2rem;
}
</style>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { Swiper, SwiperSlide } from 'swiper/vue'
import 'swiper/css'
import CourseItem from '@/components/CourseItem.vue'
import type { ICourseItem } from '../types'
import { getCourseList } from '../api'
// 学习进度
const dataset = ref<{ total: number; list: ICourseItem[] }>({ total: 0, list: [] })
const fetchCourseList = () => {
getCourseList({ page_size: 100 }).then(res => {
dataset.value = res.data
})
}
onMounted(() => {
fetchCourseList()
})
</script>
<template>
<swiper slides-per-view="auto" :space-between="10">
<swiper-slide v-for="item in dataset.list" :key="item.id" class="course-swiper-slide">
<CourseItem :data="item"></CourseItem>
</swiper-slide>
</swiper>
</template>
<style lang="scss" scoped>
.course-swiper-slide {
width: 4.7rem;
}
</style>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { Swiper, SwiperSlide } from 'swiper/vue'
import 'swiper/css'
import VideoItem from '@/components/VideoItem.vue'
import type { IVideoItem } from '../types'
import { getVideoList } from '../api'
// 课程导学
const dataset = ref<{ total: number; list: IVideoItem[] }>({ total: 0, list: [] })
const fetchVideoList = () => {
getVideoList({ page_size: 100 }).then(res => {
dataset.value = res.data
})
}
onMounted(() => {
fetchVideoList()
})
</script>
<template>
<swiper slides-per-view="auto" :space-between="10">
<swiper-slide v-for="item in dataset.list" :key="item.id" class="video-swiper-slide">
<VideoItem :data="item"></VideoItem>
</swiper-slide>
</swiper>
</template>
<style lang="scss" scoped>
.video-swiper-slide {
width: 2.2rem;
}
</style>
<script setup lang="ts">
import { Swiper, SwiperSlide } from 'swiper/vue'
import 'swiper/css'
const menus: Array<{
name: string
path: string
icon: string
}> = [
{
path: '#admission',
name: '入学指南',
icon: 'https://webapp-pub.ezijing.com/project/prp-h5/home_menu_1.png'
},
{
path: '#learning',
name: '学习地图',
icon: 'https://webapp-pub.ezijing.com/project/prp-h5/home_menu_2.png'
},
{
path: '#query',
name: '权益查看',
icon: 'https://webapp-pub.ezijing.com/project/prp-h5/home_menu_3.png'
},
{
path: '#team',
name: '荣誉总榜',
icon: 'https://webapp-pub.ezijing.com/project/prp-h5/home_menu_4.png'
},
{
path: '#exam',
name: '考试攻略',
icon: 'https://webapp-pub.ezijing.com/project/prp-h5/home_menu_6.png'
},
{
path: '#qa',
name: '陪伴问答',
icon: 'https://webapp-pub.ezijing.com/project/prp-h5/home_menu_5.png'
}
]
</script>
<template>
<nav class="home-nav">
<swiper slides-per-view="auto" :space-between="12">
<swiper-slide v-for="(item, index) in menus" :key="index" class="nav-item">
<a :href="item.path">
<img :src="item.icon" />
<p>{{ item.name }}</p>
</a>
</swiper-slide>
</swiper>
</nav>
</template>
<style lang="scss">
.home-nav {
margin-top: 0.3rem;
text-align: center;
.nav-item {
width: 1.2rem;
}
img {
width: 1.2rem;
height: 1.2rem;
}
p {
font-size: 0.24rem;
}
}
</style>
<script setup lang="ts">
import type { INews } from '../types'
const router = useRouter()
defineProps<{ docs: INews[] }>()
const handleViewNews = (item: INews) => {
if (item.desc_type === '2') {
location.href = item.url
} else {
router.push('/news/doc/' + item.id)
}
}
</script>
<template>
<div>
<van-swipe vertical :autoplay="3000">
<van-swipe-item v-for="(item, index) in docs" :key="index" @click="handleViewNews(item)">
<div class="item_news">
<div class="news_tips"></div>
<div class="news_tit">{{ item.title }}</div>
<div class="news_arrow">
<van-icon name="arrow" />
</div>
</div>
</van-swipe-item>
</van-swipe>
</div>
</template>
<style lang="scss" scoped>
:deep(.van-swipe) {
width: 6.9rem;
height: 0.84rem;
background: #ffffff;
border-radius: 0.16rem;
}
:deep(.van-swipe-item) {
display: flex;
align-items: center;
justify-content: space-between;
padding-left: 0.2rem;
cursor: pointer;
box-sizing: border-box;
}
:deep(.van-swipe__indicator) {
display: none;
}
.item_news {
display: flex;
align-items: center;
width: 100%;
.news_tips {
width: 0.33rem;
height: 0.33rem;
background: linear-gradient(313deg, #ef9446 0%, #de3a39 100%);
border-radius: 0.06rem;
font-size: 0.22rem;
font-weight: 400;
line-height: 0.33rem;
color: #ffffff;
text-align: center;
}
.news_tit {
width: 5rem;
margin-left: 0.12rem;
font-size: 0.24rem;
font-weight: 400;
color: #4e4e4e;
line-height: 0.33rem;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.news_arrow {
margin-left: auto;
padding-right: 0.3rem;
}
}
</style>
<template>
<AppCard title="权益查看" id="query">
<div class="query">
<div class="query-left">
<div class="box">
<router-link to="/query?active=1">
<h2><img src="https://webapp-pub.ezijing.com/project/prp-h5/query_icon_5.png" />证书查询</h2>
<p class="t1">考试通过后可查看</p>
</router-link>
</div>
<div class="box box1">
<router-link to="/query?active=0">
<h2><img src="https://webapp-pub.ezijing.com/project/prp-h5/query_icon_6.png" />名片展示</h2>
<p class="t1">持证人专属</p>
</router-link>
</div>
</div>
<div class="query-right">
<div class="box">
<router-link to="/query?active=2">
<h2><img src="https://webapp-pub.ezijing.com/project/prp-h5/query_icon_7.png" />持证人展示</h2>
<p class="t1">考试通过后可查看</p>
<!-- <p class="t2">去看看</p> -->
</router-link>
</div>
<div class="box box1">
<router-link to="/exam">
<h2><img src="https://webapp-pub.ezijing.com/project/prp-h5/query_icon_8.png" />考试系统</h2>
<p class="t1">持证人专属</p>
</router-link>
</div>
</div>
</div>
</AppCard>
</template>
<style lang="scss" scoped>
.query {
display: flex;
justify-content: space-between;
.box {
padding: 0.2rem;
width: 3.15rem;
height: 1.42rem;
background: #fff url(https://webapp-pub.ezijing.com/project/prp-h5/query_bg_2.png) no-repeat;
background-size: contain;
border-radius: 0.2rem;
box-sizing: border-box;
h2 {
display: flex;
align-items: center;
font-size: 0.3rem;
font-weight: 500;
color: #4e4e4e;
}
img {
margin-right: 0.1rem;
width: 0.5rem;
}
.t1 {
font-size: 0.26rem;
color: #666666;
margin-top: 0.12rem;
}
.t2 {
display: inline-block;
padding: 0 0.2rem;
margin-top: 0.35rem;
font-size: 0.22rem;
line-height: 0.4rem;
color: #ffffff;
background-color: #033974;
}
}
.box1 {
margin-top: 0.2rem;
}
.box-avatar {
height: 3.36rem;
background: #fff url(https://webapp-pub.ezijing.com/project/prp-h5/query_bg_1.png) no-repeat;
background-size: contain;
}
}
</style>
<script setup lang="ts">
import { Toast } from 'vant'
import PublishItem from '@/components/PublishItem.vue'
import { createQuestionComment } from '../api'
const props = defineProps<{ data: { total: number; list: Record<string, any>[] } }>()
const isMoreClick = ref(false)
const commentList: any = ref([])
// 评论
const onSubmitComment = (data: any, action: string) => {
if (action === 'comment') {
// 评论
createQuestionComment({
question_id: data.id,
content: data.comment
}).then(() => {
Toast.success('评论成功')
})
} else {
// 回复
createQuestionComment({
question_id: data.entity_id,
content: data.comment,
to_comment_id: data.id
}).then(() => {
Toast.success('回复成功')
})
}
}
watchEffect(() => {
if (isMoreClick.value === false) {
commentList.value = props.data.list.slice(0, 2)
} else {
commentList.value = props.data.list
}
})
const handleViewMore = () => {
isMoreClick.value = true
commentList.value = props.data.list
}
</script>
<template>
<AppCard title="陪伴问答" id="qa">
<template #header-aside>
<div class="button"><router-link to="/qa/publish">发表问答</router-link></div>
</template>
<template v-if="data.list?.length">
<PublishItem
v-for="(item, index) in commentList"
:data="item"
:key="index"
@submitComment="onSubmitComment"
></PublishItem>
<div class="line"></div>
<div class="btn" color="#FCEDD0" round size="large" @click="handleViewMore" v-if="data.list.length > 2">
查看更多问答
</div>
</template>
<van-empty description="暂无内容" v-else />
</AppCard>
</template>
<style lang="scss" scoped>
:deep(.publish-item) {
padding: 0.24rem;
margin-bottom: 0.2rem;
background: #fff;
border-radius: 0.2rem;
}
.btn {
margin: 0.2rem auto;
width: 5.78rem;
height: 0.69rem;
background: #fcedd0;
text-align: center;
line-height: 0.69rem;
color: #e2a022;
font-size: 0.24rem;
border-radius: 0.35rem;
cursor: pointer;
}
.line {
width: 5.97rem;
height: 0px;
border-top: 0.01rem solid #d3d3d3;
margin: auto;
}
</style>
<script setup lang="ts">
import { getRecommendCourse } from '@/api/base'
import type { IRecommendCourse } from '@/types'
import num from '@/utils/numTo'
const courseList = ref<{ total: number; list: IRecommendCourse[] }>({ total: 0, list: [] })
getRecommendCourse().then(res => {
courseList.value = res.data
if (res.data.length > 3) {
courseList.value = res.data.slice(0, 3)
}
})
const router = useRouter()
const props = defineProps<{ list: any }>()
const handleClickItem = (item: any) => {
if (item.url) {
location.href = item.url
}
router.push(`/course/detail?id=${item.id}`)
}
</script>
<template>
<a href="https://fi.ezijing.com/shop" target="_blank">
<a href="#" target="_blank">
<img src="https://webapp-pub.ezijing.com/project/prp-h5/course_banner.png" class="exam_banner" />
</a>
<AppCard title="推荐课程" id="team">
......@@ -26,25 +18,25 @@ const handleClickItem = (item: any) => {
<router-link to="/course">查看更多 <van-icon name="arrow" /></router-link>
</div>
</template>
<div class="course_list">
<div
class="list_item"
v-for="(item, index) in courseList.list.slice(0, 3)"
:key="index"
@click="handleClickItem(item)"
>
<img :src="item.cover" class="item_img" />
<div class="course_list" v-if="props.list?.length">
<div class="list_item" v-for="(item, index) in props.list" :key="index" @click="handleClickItem(item)">
<img :src="item.course_picture" class="item_img" />
<div class="item_right">
<div class="right_tit">{{ item.name }}</div>
<div :class="`right_tag c${item.category}`">{{ item.category_name }}</div>
<div class="right_tit">{{ item.course_name }}</div>
<div class="right_bottom">
<div class="views">{{ num(item.pv) }}播放</div>
<div class="is_free" :class="item.is_free === '1' ? 'free' : 'unfree'">
<div class="views">已学习{{ item.times }}人</div>
<div
class="is_free"
:class="item.is_free_name === '免费' ? 'free' : item.is_free_name === '已购买' ? 'c' : 'unfree'"
>
{{ item.is_free_name }}
</div>
</div>
</div>
</div>
</div>
<van-empty description="无数据" image-size="100" v-else />
</AppCard>
</template>
......@@ -68,7 +60,7 @@ const handleClickItem = (item: any) => {
display: flex;
flex-direction: column;
justify-content: space-between;
padding-top: 0.17rem;
// padding-top: 0.17rem;
box-sizing: border-box;
.right_tit {
font-size: 0.28rem;
......@@ -90,7 +82,7 @@ const handleClickItem = (item: any) => {
.is_free {
font-size: 0.22rem;
font-weight: 400;
line-height: 30px;
// line-height: 30px;
padding-right: 0.3rem;
}
.free {
......@@ -99,8 +91,30 @@ const handleClickItem = (item: any) => {
.unfree {
color: #e9a724;
}
.c {
color: #1847a0;
}
}
}
}
}
.right_tag {
font-weight: bold;
font-size: 0.24rem;
color: #ffffff;
line-height: 100%;
border-radius: 0.24rem;
padding: 0.08rem 0.19rem;
width: fit-content;
margin-bottom: 0.15rem;
&.c1 {
background: #b51e4d;
}
&.c2 {
background: #1847a0;
}
&.c3 {
background: #bc9854;
}
}
</style>
<script setup lang="ts">
import { Swiper, SwiperSlide } from 'swiper/vue'
import 'swiper/css'
import { getTeacherList } from '@/api/base'
import type { ITeacherList } from '@/types'
// import { Empty } from 'vant'
// import { getTeacherList } from '@/api/base'
// import type { ITeacherList } from '@/types'
const teacherList = ref<{ total: number; list: ITeacherList[] }>({ total: 0, list: [] })
// const teacherList = ref<{ total: number; list: ITeacherList[] }>({ total: 0, list: [] })
getTeacherList().then(res => {
teacherList.value = res.data
})
// getTeacherList().then(res => {
// teacherList.value = res.data
// })
const props = defineProps<{ list: any }>()
</script>
<template>
......@@ -20,12 +22,13 @@ getTeacherList().then(res => {
</div>
</div>
<div class="output_banner">
<swiper slides-per-view="auto" :space-between="10">
<swiper-slide v-for="(item, index) in teacherList.list" :key="index" class="video-swiper-slide">
<swiper slides-per-view="auto" :space-between="10" v-if="props.list?.length">
<swiper-slide v-for="(item, index) in props.list" :key="index" class="video-swiper-slide">
<img :src="item.avatar" class="img" />
<div class="name">{{ item.name }}</div>
</swiper-slide>
</swiper>
<van-empty description="无数据" image-size="100" v-else />
</div>
</div>
</template>
......
<script setup lang="ts">
import type { ITeam } from '../types'
defineProps<{ teams: ITeam[] }>()
</script>
<template>
<div id="team">
<div class="team_header">
<div class="title">团队荣誉总榜</div>
<div class="more">
<router-link to="/team">查看更多</router-link>
</div>
</div>
<div class="team-ranking">
<!-- <h2>团队荣誉总榜</h2> -->
<ul>
<li v-for="item in teams.slice(0, 3)" :key="item.id">
<router-link :to="{ name: 'teamView', params: { id: item.id } }">
<h4>{{ item.name }}</h4>
<p>{{ item.slogan }}<em>|</em>{{ item.members_count }}</p>
</router-link>
</li>
</ul>
</div>
</div>
</template>
<style lang="scss" scoped>
#team {
border-radius: 0.2rem;
margin-bottom: 0.29rem;
background: #ffffff;
.team_header {
background: #fff url(https://webapp-pub.oss-cn-beijing.aliyuncs.com/project/prp-h5/ranking_bg.png) no-repeat;
background-size: 100% 100%;
padding: 0.4rem 0.37rem 0.47rem 0.37rem;
height: 1.37rem;
box-sizing: border-box;
display: flex;
justify-content: space-between;
.title {
color: #955801;
font-size: 0.36rem;
font-weight: 600;
}
.more {
color: #955815;
color: 0.22rem;
}
}
.team-ranking {
// background: #fff url(https://webapp-pub.ezijing.com/project/prp-h5/team_bg.png) no-repeat;
background-size: 100%;
border-radius: 0.2rem;
li {
padding: 0.3rem 0.3rem 0.3rem 1.88rem;
h4 {
font-size: 0.3rem;
color: #4e4e4e;
line-height: 0.42rem;
}
p {
margin-top: 0.08rem;
font-size: 0.26rem;
color: #999999;
line-height: 0.38rem;
em {
padding: 0 0.2rem;
}
}
&:nth-child(1) {
background: url(https://webapp-pub.ezijing.com/project/prp-h5/team_1.png) no-repeat 0.68rem center;
background-size: 0.54rem;
}
&:nth-child(2) {
background: url(https://webapp-pub.ezijing.com/project/prp-h5/team_2.png) no-repeat 0.68rem center;
background-size: 0.54rem;
}
&:nth-child(3) {
background: url(https://webapp-pub.ezijing.com/project/prp-h5/team_3.png) no-repeat 0.68rem center;
background-size: 0.54rem;
}
}
li + li {
border-top: 0.01rem solid #e4e4e4;
}
}
}
</style>
......@@ -2,13 +2,32 @@ import type { IVideoItem, ICourseItem, ITeam } from '@/types'
export interface HomeInfo {
banner: IBanner[]
admission_guide_docs: IDocItem[]
admission_guide_videos: IVideoItem[]
learning_map_docs: IDocItem[]
exam_strategy_docs: IDocItem[]
questions: { total: number; list: Record<string, any>[] }
ranking: ITeam[]
hot_message_docs: INews[]
course: ICourse[]
lecture: ILecture[]
// admission_guide_docs: IDocItem[]
// admission_guide_videos: IVideoItem[]
// learning_map_docs: IDocItem[]
// exam_strategy_docs: IDocItem[]
// questions: { total: number; list: Record<string, any>[] }
// ranking: ITeam[]
// hot_message_docs: INews[]
}
export interface ILecture {
id: string
lecturer_name: string
lecturer_title: string
lecturer_office: string
}
export interface ICourse {
id: string
category_name: string
category: string
is_free_name: string
is_free: string
prices: string
times: string
}
export interface IBanner {
......@@ -41,4 +60,4 @@ export interface INews {
url: string
}
export { IVideoItem, ICourseItem, ITeam }
export { IVideoItem, ICourseItem, ITeam }
\ No newline at end of file
......@@ -3,30 +3,21 @@ import { ref, onMounted } from 'vue'
import type { HomeInfo } from '../types'
import * as api from '../api'
import Banner from '../components/Banner.vue'
// import Menu from '../components/Menu.vue'
// import AdmissionGuide from '../components/AdmissionGuide.vue'
import CourseCard from '../components/CourseCard.vue'
import News from '../components/News.vue'
import RecommendCourse from '../components/RecommendCourse.vue'
import KnowledgeOut from '../components/KnowledgeOut.vue'
import Teacher from '../components/Teacher.vue'
import LearningMap from '../components/LearningMap.vue'
import QueryView from '../components/QueryView.vue'
// import ExamStrategy from '../components/ExamStrategy.vue'
import TeamRanking from '../components/TeamRanking.vue'
import Questions from '../components/Questions.vue'
import useWXShare from '@/utils/wx'
const data = ref<HomeInfo>({
banner: [],
admission_guide_docs: [],
admission_guide_videos: [],
learning_map_docs: [],
exam_strategy_docs: [],
questions: { total: 0, list: [] },
ranking: [],
hot_message_docs: []
course: [],
lecture: []
// admission_guide_docs: [],
// admission_guide_videos: [],
// learning_map_docs: [],
// exam_strategy_docs: [],
// questions: { total: 0, list: [] },
// ranking: [],
// hot_message_docs: []
})
// 获取首页数据
const fetchHomeData = () => {
......@@ -41,31 +32,11 @@ onMounted(() => {
</script>
<template>
<Banner :list="data.banner"></Banner>
<!-- <Menu></Menu> -->
<!-- 入学指南 -->
<!-- <AdmissionGuide :docs="data.admission_guide_docs" :videos="data.admission_guide_videos"></AdmissionGuide> -->
<CourseCard :docs="data.admission_guide_docs" :videos="data.admission_guide_videos" />
<!-- 新闻轮播 -->
<News :docs="data.hot_message_docs" />
<!-- 推荐课程 -->
<RecommendCourse />
<!-- 知识输出者 -->
<KnowledgeOut />
<!-- 讲师团 -->
<Teacher />
<!-- 学习地图 -->
<LearningMap :docs="data.learning_map_docs"></LearningMap>
<!-- 权益查看 -->
<QueryView></QueryView>
<!-- <RouterLink to="/qa">
<img src="https://webapp-pub.ezijing.com/project/prp-h5/qa_banner.png" style="width: 100%" />
</RouterLink> -->
<!-- 陪伴问答 -->
<Questions :data="data.questions"></Questions>
<!-- 荣誉总榜 -->
<TeamRanking :teams="data.ranking"></TeamRanking>
<!-- 考试攻略 -->
<!-- <ExamStrategy :docs="data.exam_strategy_docs"></ExamStrategy> -->
<img src="https://webapp-pub.ezijing.com/project/prp-h5/exam_banner.png" style="width: 100%; margin-bottom: 0.5rem" />
<div style="padding-bottom: 0.6rem">
<Banner :list="data.banner" />
<!-- 推荐课程 -->
<RecommendCourse :list="data.course" />
<!-- 讲师团 -->
<Teacher :list="data.lecture" />
</div>
</template>
import httpRequest from '@/utils/axios'
// 获取课程列表
export function getCourseList(params?: { page_size?: number; page?: number }) {
return httpRequest.get('/api/psp/v1/learning/course-list', { params })
}
// 获取课程列表
export function getCourseView(params: { id: string }) {
return httpRequest.get('/api/psp/v1/learning/course-view', { params })
}
// 获取课程列表
export function getChapterView(params: { chapter_id: string; page_size?: number; page?: number }) {
return httpRequest.get('/api/psp/v1/learning/chapter-view', { params })
}
// 课程章节打卡
export function createCourseRecord(data: {
chapter_id: string
course_id: string
content: string
picture?: string
file?: string
}) {
return httpRequest.post('/api/psp/v1/learning/upload', data)
}
// 打卡记录评论
export function createCourseComment(data: { entity_id: string; to_comment_id?: string; content: string }) {
return httpRequest.post('/api/psp/v1/learning/comment', data)
}
// 获取打卡记录评论
export function getRecordComment(params: { id: string; page_size?: number; page?: number }) {
return httpRequest.get('/api/psp/v1/learning/record-comments', { params })
}
// 获取文档数据
export function getDocView(params: { id: string }) {
return httpRequest.get('/api/psp/v1/learning/doc-view', { params })
}
<script setup lang="ts">
import { ref, reactive } from 'vue'
import { useInfiniteScroll } from '@vueuse/core'
import ChapterItemRecord from './ChapterItemRecord.vue'
import { getChapterView } from '../api'
interface Info {
loading: boolean
page: number
total: number
list: any[]
}
const props = defineProps<{ courseId: string; data: any }>()
// 章节详情
const chapterVisible = ref<boolean>(false)
const dataset = reactive<Info>({ loading: false, page: 1, total: 0, list: [] })
// 查看本章所有打卡记录
const showChapterRecord = () => {
chapterVisible.value = true
dataset.page = 1
dataset.list = []
getChapterRecord()
}
// 获取章节打卡记录
const getChapterRecord = () => {
dataset.loading = true
getChapterView({ chapter_id: props.data.id, page: dataset.page, page_size: 10 })
.then(res => {
const { total, list } = res.data
dataset.total = total
dataset.list = dataset.list.concat(list)
if (dataset.list.length <= total) {
dataset.page++
}
})
.finally(() => {
dataset.loading = false
})
}
// 滚动加载
const el = ref<HTMLElement>()
useInfiniteScroll(
el,
() => {
!dataset.loading && getChapterRecord()
},
{ distance: 50 }
)
</script>
<template>
<div class="course-chapter-item">
<h2 class="chapter-title">{{ data.chapter_name }}</h2>
<ChapterItemRecord v-for="publish in data.records.list" :data="publish" :key="publish.id"></ChapterItemRecord>
<div class="chapter-view" @click="showChapterRecord">查看本章所有打卡记录</div>
</div>
<van-popup v-model:show="chapterVisible" round position="bottom" :style="{ height: '80%' }">
<div class="course-chapter" ref="el">
<ChapterItemRecord v-for="publish in dataset.list" :data="publish" :key="publish.id"></ChapterItemRecord>
<van-button
block
round
class="my-button button-fixed"
:to="{ path: '/learn/publish', query: { course_id: courseId, chapter_id: data.id } }"
>我也要拍照得星星</van-button
>
</div>
</van-popup>
</template>
<style lang="scss">
.chapter-title {
margin-bottom: 0.22rem;
font-size: 0.28rem;
font-weight: bold;
color: #333333;
line-height: 0.28rem;
}
.chapter-view {
padding: 0.36rem 0;
font-size: 0.24rem;
color: #033974;
line-height: 0.36rem;
text-align: center;
border-top: 0.01rem solid #d3d3d3;
cursor: pointer;
}
.course-chapter {
height: 100%;
overflow-y: auto;
padding: 0.38rem 0.24rem 1rem;
box-sizing: border-box;
}
.button-fixed {
position: fixed;
bottom: 0.2rem;
left: 0.2rem;
right: 0.2rem;
width: auto;
}
</style>
<script setup lang="ts">
import { ref, reactive } from 'vue'
import { uniqWith } from 'lodash-es'
import PublishItem from '@/components/PublishItem.vue'
import { getRecordComment, createCourseComment } from '../api'
const props = defineProps<{ data: any }>()
const page = ref<number>(1)
const dataset = reactive(props.data)
// 数据合并
const mergeList = (arr: any[], arr2: any[]) => {
return uniqWith(arr.concat(arr2), (a, b) => a.id === b.id)
}
// 获取打卡评论
const getRecordCommentList = () => {
getRecordComment({ id: props.data.id, page: page.value, page_size: 5 }).then(res => {
const { total, list } = res.data.comments
dataset.comments.total = total
dataset.comments.list = page.value === 1 ? list : mergeList(dataset.comments.list, list)
})
}
// 评论
const onSubmitComment = (data: any, action: string) => {
const params =
action === 'comment'
? { entity_id: data.id, content: data.comment } // 评论
: { entity_id: data.entity_id, content: data.comment, to_comment_id: data.id } // 回复
createCourseComment(params).then(() => {
page.value = 1
getRecordCommentList()
})
}
const onLoadMore = () => {
page.value++
getRecordCommentList()
}
</script>
<template>
<PublishItem :data="dataset" :key="dataset.id" @submitComment="onSubmitComment" @load="onLoadMore"></PublishItem>
</template>
import type { RouteRecordRaw } from 'vue-router'
import AppLayout from '@/components/layout/Index.vue'
export const routes: Array<RouteRecordRaw> = [
{
path: '/learn',
component: AppLayout,
children: [
{ name: 'learnCourse', path: 'course', component: () => import('./views/Course.vue') },
{ name: 'learnCourseView', path: 'course/:id', component: () => import('./views/CourseView.vue'), props: true },
{ path: 'doc/:id', component: () => import('./views/DocView.vue'), props: true },
{ path: 'publish', component: () => import('./views/Publish.vue'), meta: { requireLogin: true } }
]
}
]
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import CourseItem from '@/components/CourseItem.vue'
import type { ICourseItem } from '@/types'
import { getCourseList } from '../api'
import useWXShare from '@/utils/wx'
// 学习进度
const dataset = ref<{ total: number; list: ICourseItem[] }>({ total: 0, list: [] })
const fetchCourseList = () => {
getCourseList({ page_size: 100 }).then(res => {
dataset.value = res.data
})
}
onMounted(() => {
fetchCourseList()
useWXShare({ desc: '夯实【系统新知】与顶级高校专家同创【知识获得者】' })
})
</script>
<template>
<a href="https://mp.weixin.qq.com/s/fDf4NpPZH4BNI_KEopiSwQ" target="_blank">
<img src="https://webapp-pub.ezijing.com/project/prp-h5/banner_my_course.png" style="width: 100%" />
</a>
<p class="tips">如果你也是知识获得者,点击课程,晒出你的海报、说出你的感想,得到你的星星。</p>
<CourseItem v-for="item in dataset.list" :data="item" :key="item.id"></CourseItem>
</template>
<style lang="scss" scoped>
.tips {
padding: 0.1rem 0 0.2rem;
font-size: 0.24rem;
font-weight: 400;
color: #033974;
line-height: 0.36rem;
}
.course-item {
margin-bottom: 0.3rem;
}
</style>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { Toast } from 'vant'
import ChapterItem from '../components/ChapterItem.vue'
import { getCourseView } from '../api'
const props = defineProps<{ id: string }>()
// 课程
const data = ref()
const getCourse = () => {
const toast = Toast.loading({ message: '加载中...', forbidClick: true })
getCourseView({ id: props.id }).then(res => {
data.value = res.data.course
toast.clear()
})
}
onMounted(() => {
getCourse()
})
</script>
<template>
<div class="course" v-if="data">
<div class="course-top">
<div class="course-info">
<img :src="data.course_picture" class="course-info-pic" />
<div class="course-info-content">
<h1>{{ data.course_name }}</h1>
<ul>
<li class="l1">
<span>{{ data.course_chapters.big_total }}章节</span>
<span>{{ data.course_chapters.small_total }}小节</span>
</li>
<li class="l2">
<span>{{ data.pv }}人看过</span>
<span>{{ data.records_total }}人评论</span>
</li>
</ul>
<div class="star">
<p>
共计可得<b>{{ data.star_total }}</b
>个星星
</p>
</div>
<div class="lecturer" v-for="(lecturer, index) in data.course_lectures" :key="index">
<h4>{{ lecturer.lecturer_name }}</h4>
<p>{{ lecturer.lecturer_title }}</p>
<!-- <p>{{ lecturer.lecturer_office }}</p> -->
</div>
</div>
</div>
<div class="course-desc">
<div v-html="data.course_represent"></div>
</div>
</div>
<div class="course-bottom">
<div class="course-tips">如果你也是知识获得者,请晒出你的海报、说出你的感想,得到你的星星。</div>
<div class="course-chapters">
<ChapterItem v-for="item in data.course_chapters.list" :courseId="id" :data="item" :key="item.id"></ChapterItem>
</div>
</div>
</div>
</template>
<style lang="scss" scoped>
.course-info {
display: flex;
}
.course-info-pic {
width: 2.5rem;
height: 2rem;
background-color: #fff;
border-radius: 0.2rem;
overflow: hidden;
object-fit: cover;
}
.course-info-content {
flex: 1;
margin-left: 0.16rem;
h1 {
font-size: 0.36rem;
line-height: 0.5rem;
font-weight: 500;
color: #333333;
}
ul {
display: flex;
justify-content: space-between;
li {
font-size: 0.2rem;
font-weight: 300;
line-height: 0.28rem;
span + span {
margin-left: 0.1rem;
}
}
.l1 {
color: #033974;
}
.l2 {
color: #999;
}
}
.star {
margin: 0.07rem 0;
p {
display: inline-block;
padding: 0 0.15rem 0 0.38rem;
height: 0.3rem;
font-size: 0.2rem;
line-height: 0.3rem;
border-radius: 0.15rem;
border: 0.01rem solid #80b0e5;
background: url('https://webapp-pub.ezijing.com/project/prp-h5/icon_star.png') no-repeat 0.1rem center;
background-size: 0.22rem;
b {
color: #033974;
}
}
}
.lecturer {
h4 {
margin-bottom: 0.05rem;
font-size: 0.24rem;
line-height: 0.33rem;
font-weight: 500;
color: #333333;
}
p {
font-size: 0.22rem;
font-weight: 400;
line-height: 0.3rem;
color: #666666;
}
}
}
.course-desc {
margin-top: 0.15rem;
font-size: 0.24rem;
line-height: 0.3rem;
color: #333;
}
.course-bottom {
margin-top: 0.28rem;
margin-bottom: 0.28rem;
background-color: #033974;
border-radius: 0.2rem;
overflow: hidden;
}
.course-tips {
padding: 0.3rem;
font-size: 0.24rem;
color: #ffffff;
line-height: 0.36rem;
}
.course-chapters {
padding: 0.38rem 0.24rem;
background-color: #fff;
border-top-left-radius: 0.2rem;
border-top-right-radius: 0.2rem;
}
</style>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { getDocView } from '../api'
import DocView from '@/components/DocView.vue'
const props = defineProps<{ id: string }>()
const data = ref()
function fetchDocView() {
getDocView({ id: props.id }).then(res => {
data.value = res.data
})
}
onMounted(() => {
fetchDocView()
})
</script>
<template>
<DocView :data="data"></DocView>
</template>
<script setup lang="ts">
import { reactive } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { createCourseRecord } from '../api'
import AppUpload from '@/components/base/AppUpload.vue'
import { Toast } from 'vant'
const router = useRouter()
const route = useRoute()
const form = reactive({ ...{ chapter_id: '', course_id: '', content: '', picture: [] }, ...route.query })
const pictureValidator = () => !!form.picture.length
function onSubmit() {
const params = Object.assign({}, form, { picture: JSON.stringify(form.picture) })
createCourseRecord(params).then(() => {
Toast.success('发布成功')
router.push({ name: 'learnCourseView', params: { id: form.course_id } })
})
}
</script>
<template>
<AppContainer title="打卡" backgroundColor="#fff" headerAlign="center">
<van-form @submit="onSubmit">
<van-field
v-model="form.content"
type="textarea"
placeholder="请输入打卡内容"
:autosize="{ minHeight: 200 }"
:rules="[{ required: true, message: '请输入打卡内容' }]"
/>
<van-field :rules="[{ validator: pictureValidator, message: '请上传图片' }]">
<template #input>
<AppUpload v-model="form.picture"></AppUpload>
</template>
</van-field>
<van-button block round native-type="submit" class="my-button">发表</van-button>
</van-form>
</AppContainer>
</template>
<style lang="scss" scoped>
:deep(.van-cell) {
padding-left: 0;
padding-right: 0;
&::after {
left: 0;
right: 0;
}
}
.my-button {
margin: 1rem 0;
}
</style>
......@@ -99,6 +99,9 @@ onMounted(() => {
</template>
<style lang="scss">
.message-list{
padding-top: .2rem;
}
.message-item {
padding: 0.24rem 0.3rem;
background-color: #fff;
......
......@@ -5,6 +5,11 @@ export function getMyInfo() {
return httpRequest.get('/api/psp/v1/my/info')
}
// 获取学习
export function getStudyInfo() {
return httpRequest.get('/api/psp/v2/my/info')
}
// 获取课程列表
export function getCourseList(params?: { page_size?: number; page?: number }) {
return httpRequest.get('/api/psp/v1/learning/course-list', { params })
......
<script setup lang="ts">
const props = defineProps<{ data: any }>()
const router = useRouter()
const handleClickItem = (item: any) => {
router.push(`/my/cert?c=${item.certificate}`)
}
const isEmpty = $computed(() => {
if (props?.data || props.data?.length === 0) return false
if (props.data?.findIndex((item: any) => item.certificate === '') !== -1) {
return false
} else {
return true
}
})
console.log(isEmpty, 'isEmpty')
</script>
<template>
<AppCard title="我的电子学习证明" id="team">
<template #header-aside> </template>
<div class="course_list" v-if="isEmpty">
<template v-for="item in data" :key="item.id">
<div class="list_item" v-if="item.certificate !== ''" @click="handleClickItem(item)">
<img :src="item.certificate" class="item_img" />
<div class="item_right">
<div :class="`right_tag c${item.category}`">{{ item.category_name }}</div>
<div class="right_tit">{{ item.course_name }}</div>
</div>
</div>
</template>
</div>
<van-empty description="无数据" image-size="100" v-else />
</AppCard>
</template>
<style lang="scss" scoped>
.exam_banner {
width: 6.9rem;
margin-top: 0.3rem;
}
.course_list {
.list_item {
display: flex;
margin-bottom: 0.2rem;
.item_img {
width: 2.2rem;
height: 1.4rem;
object-fit: cover;
// border-radius: 0.1rem;
}
.item_right {
margin-left: 0.21rem;
// // display: flex;
// flex-direction: column;
// justify-content: space-between;
// padding-top: 0.17rem;
box-sizing: border-box;
.right_tit {
font-size: 0.28rem;
font-weight: 500;
line-height: 0.36rem;
color: #333333;
}
.right_bottom {
width: 4.09rem;
margin-top: 0.21rem;
display: flex;
justify-content: space-between;
align-items: center;
.views {
font-size: 0.22rem;
font-weight: 400;
color: #999999;
}
.is_free {
font-size: 0.22rem;
font-weight: 400;
// line-height: 30px;
padding-right: 0.3rem;
}
.free {
color: #4ad1a3;
}
.unfree {
color: #e9a724;
}
.c {
color: #1847a0;
}
}
}
}
}
.right_tag {
font-weight: bold;
font-size: 0.24rem;
color: #ffffff;
line-height: 100%;
border-radius: 0.24rem;
padding: 0.08rem 0.19rem;
width: fit-content;
margin-bottom: 0.15rem;
&.c1 {
background: #b51e4d;
}
&.c2 {
background: #1847a0;
}
&.c3 {
background: #bc9854;
}
}
</style>
<script setup lang="ts">
defineProps<{ data: any }>()
const router = useRouter()
const handleClickItem = (item: any) => {
router.push(`/course/detail?id=${item.id}`)
}
</script>
<template>
<AppCard title="我的课程" id="team">
<template #header-aside> </template>
<div class="course_list" v-if="data?.length">
<div class="list_item" v-for="item in data" :key="item.id" @click="handleClickItem(item)">
<img :src="item.course_picture" class="item_img" />
<div class="item_right">
<div :class="`right_tag c${item.category}`">{{ item.category_name }}</div>
<div class="right_tit">{{ item.course_name }}</div>
<div class="right_bottom">
<div class="views">已学习{{ item.times }}人</div>
</div>
</div>
</div>
</div>
<van-empty description="无数据" image-size="100" v-else />
</AppCard>
</template>
<style lang="scss" scoped>
.exam_banner {
width: 6.9rem;
margin-top: 0.3rem;
}
.course_list {
.list_item {
display: flex;
margin-bottom: 0.2rem;
.item_img {
width: 2.2rem;
height: 1.4rem;
object-fit: cover;
border-radius: 0.1rem;
}
.item_right {
margin-left: 0.21rem;
display: flex;
flex-direction: column;
justify-content: space-between;
// padding-top: 0.17rem;
box-sizing: border-box;
.right_tit {
font-size: 0.28rem;
font-weight: 500;
line-height: 0.36rem;
color: #333333;
}
.right_bottom {
width: 4.09rem;
margin-top: 0.21rem;
display: flex;
justify-content: space-between;
align-items: center;
.views {
font-size: 0.22rem;
font-weight: 400;
color: #999999;
}
.is_free {
font-size: 0.22rem;
font-weight: 400;
// line-height: 30px;
padding-right: 0.3rem;
}
.free {
color: #4ad1a3;
}
.unfree {
color: #e9a724;
}
.c {
color: #1847a0;
}
}
}
}
}
.right_tag {
font-weight: bold;
font-size: 0.24rem;
color: #ffffff;
line-height: 100%;
border-radius: 0.24rem;
padding: 0.08rem 0.19rem;
width: fit-content;
margin-bottom: 0.15rem;
&.c1 {
background: #b51e4d;
}
&.c2 {
background: #1847a0;
}
&.c3 {
background: #bc9854;
}
}
</style>
......@@ -8,7 +8,8 @@ export const routes: Array<RouteRecordRaw> = [
meta: { requireLogin: true },
children: [
{ path: '', component: () => import('./views/Index.vue') },
{ path: 'course', component: () => import('./views/Course.vue') }
{ path: 'cert', component: () => import('./views/Cert.vue') },
{ path: 'data', component: () => import('./views/Data.vue') }
]
}
]
<script setup lang="ts"></script>
<template>
<AppContainer title="我的电子学习证明" headerAlign="center"></AppContainer>
<AppCard>
<img :src="$route.query.c" />
</AppCard>
<a href="#" :download="$route.query.c" class="btn">
下载电子学习证明
<!-- <div class="btn">下载电子学习证明</div> -->
</a>
</template>
<style lang="scss" scoped>
img {
width: 100%;
}
.btn {
position: absolute;
bottom: 3rem;
left: 50%;
transform: translateX(-50%);
border-radius: 0.4rem;
background: #aa1941;
font-weight: 500;
font-size: 0.28rem;
color: #ffffff;
line-height: 100%;
padding: 0.26rem 0.43rem;
text-align: center;
width: 4rem;
}
</style>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import CourseItem from '@/components/CourseItem.vue'
import type { ICourseItem } from '@/types'
import { getCourseList } from '../api'
// 学习进度
const dataset = ref<{ total: number; list: ICourseItem[] }>({ total: 0, list: [] })
const fetchCourseList = () => {
getCourseList({ page_size: 100 }).then(res => {
dataset.value = res.data
})
}
onMounted(() => {
fetchCourseList()
})
</script>
<template>
<a href="https://mp.weixin.qq.com/s/fDf4NpPZH4BNI_KEopiSwQ" target="_blank">
<img src="https://webapp-pub.ezijing.com/project/prp-h5/banner_my_course.png" style="width: 100%" />
</a>
<p class="tips">如果你也是知识获得者,点击课程,晒出你的海报、说出你的感想,得到你的星星。</p>
<CourseItem v-for="item in dataset.list" :data="item" :key="item.id"></CourseItem>
</template>
<style lang="scss" scoped>
.tips {
padding: 0.1rem 0 0.2rem;
font-size: 0.24rem;
font-weight: 400;
color: #033974;
line-height: 0.36rem;
}
.course-item {
margin-bottom: 0.3rem;
}
</style>
<script setup lang="ts">
import { getStudyInfo } from '../api'
let studyInfo: any = $ref()
getStudyInfo().then(res => {
studyInfo = res.data
})
const lastStudy = $computed(() => {
return studyInfo?.courses[0] || []
})
</script>
<template>
<AppContainer title="我的学习数据" headerAlign="center"></AppContainer>
<div class="data-t">
<div class="data-t_l">
<div class="t">我的学习总时长</div>
<div class="time" v-if="studyInfo?.learn_duration">
<span>{{ (parseInt(studyInfo?.learn_duration) / 60).toFixed(1) }}</span
>分钟
</div>
<div class="time" v-else><span>0</span>分钟</div>
</div>
<div class="data-t_r">
<div class="top">
<div class="t-tit">我的学习总次数</div>
<div class="t-time">
<span>{{ studyInfo?.learn_times }}</span
>
</div>
</div>
<div class="b">
<div class="b-item">
<div class="t">报名课程数</div>
<div class="time">
<span>{{ studyInfo?.course_count }}</span
>
</div>
</div>
<div class="b-item">
<div class="t">学习课程数</div>
<div class="time">
<span>{{ studyInfo?.learn_count }}</span
>
</div>
</div>
</div>
</div>
</div>
<div class="card">
<div class="tit">最近一次学习</div>
<div class="course-name" v-if="lastStudy?.course_name">{{ lastStudy?.course_name }}</div>
<div class="chapter-name" v-if="lastStudy?.last_chapter">
{{ lastStudy?.last_chapter ? lastStudy?.last_chapter : '您还没有学习,赶紧去报名课程学习吧!' }}
</div>
<div class="time" v-if="studyInfo?.learn_duration">
学习时长: <span>{{ (parseInt(studyInfo?.learn_duration) / 60).toFixed(1) }}</span
>分钟
</div>
<div class="time" v-else>学习时长:<span>0</span>分钟</div>
<!-- <div class="time" v-if="lastStudy?.learn_duration">{{ lastStudy?.learn_duration }}</div> -->
</div>
<div class="course-list" v-if="studyInfo?.courses && studyInfo?.courses.length !== 0">
<div class="item" v-for="item in studyInfo?.courses" :key="item.id">
<div class="name">{{ item?.course_name }}</div>
<div class="pro">
学习进度:<span>{{ item?.last_chapter }}</span>
</div>
<div class="scu">
完成百分比:<span>{{ item?.percent }}%</span>
</div>
</div>
</div>
</template>
<style lang="scss" scoped>
.data-t {
display: flex;
justify-content: space-between;
.data-t_l {
width: 3.35rem;
height: 3.35rem;
background: linear-gradient(180deg, #ffffff 0%, #f8ecef 100%);
box-shadow: 0px 2px 8px 1px rgba(0, 0, 0, 0.16);
border-radius: 0.2rem;
text-align: center;
.t {
font-size: 0.28rem;
color: #000000;
line-height: 100%;
padding-top: 0.7rem;
}
.time {
font-size: 0.28rem;
color: #000000;
line-height: 100%;
padding-top: 0.7rem;
span {
font-size: 0.6rem;
color: #aa1941;
}
}
}
.data-t_r {
.top {
width: 3.35rem;
height: 1.58rem;
background: #ffffff;
box-shadow: 0px 2px 8px 1px rgba(0, 0, 0, 0.16);
border-radius: 0.2rem;
text-align: center;
.t-tit {
font-size: 0.28rem;
color: #656565;
line-height: 100%;
padding-top: 0.2rem;
}
.t-time {
font-size: 0.28rem;
color: #000000;
line-height: 100%;
padding-top: 0.4rem;
span {
font-size: 0.6rem;
color: #aa1941;
}
}
}
.b {
display: flex;
justify-content: space-between;
margin-top: 0.2rem;
.b-item {
width: 1.58rem;
height: 1.58rem;
background: linear-gradient(180deg, #ffffff 0%, #eaeef7 100%);
box-shadow: 0px 2px 8px 1px rgba(0, 0, 0, 0.16);
border-radius: 0.2rem;
text-align: center;
.t {
font-size: 0.24rem;
color: #656565;
line-height: 100%;
padding-top: 0.2rem;
}
.time {
font-size: 0.24rem;
color: #656565;
line-height: 100%;
padding-top: 0.4rem;
span {
font-size: 0.48rem;
color: #1847a0;
}
}
}
}
}
}
.card {
background: #ffffff;
box-shadow: 0px 2px 8px 1px rgba(0, 0, 0, 0.16);
border-radius: 0.2rem;
overflow: hidden;
text-align: center;
padding-bottom: 0.2rem;
margin-top: 0.5rem;
.tit {
background: #aa1941;
line-height: 0.6rem;
text-align: center;
}
.tit {
font-size: 0.26rem;
color: #ffffff;
}
.course-name {
font-weight: 500;
font-size: 0.36rem;
color: #aa1941;
text-align: center;
margin-top: 0.3rem;
}
.chapter-name {
font-size: 0.22rem;
color: #656565;
margin-top: 0.2rem;
}
.time {
margin-top: 0.1rem;
font-size: 0.22rem;
color: #656565;
}
}
.course-list {
margin-top: 0.5rem;
.item {
margin-bottom: 0.2rem;
background: #ffffff;
border-radius: 0.2rem;
padding: 0.2rem 0.25rem;
.name {
font-weight: 500;
font-size: 0.3rem;
color: #000000;
}
.pro,
.scu {
margin-top: 0.2rem;
font-size: 0.22rem;
line-height: 0.36rem;
color: #000000;
span {
color: #656565;
}
}
.scu {
margin-top: 0;
}
}
}
</style>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import AppUploadSignImage from '@/components/base/AppUploadSignImage.vue'
import { getMyInfo, updateAvatar } from '../api'
import AppContainer from '@/components/base/AppContainer.vue'
import { getMyInfo, updateAvatar, getStudyInfo } from '../api'
import { logout } from '@/api/base'
import { Toast } from 'vant'
import Course from '../components/Course.vue'
import Cert from '../components/Cert.vue'
let info = $ref<{ avatar: string; name: string; star: string }>()
let teamInfo = $ref<{ star: number; name?: string; team_id?: string }>({ star: 0 })
const router = useRouter()
let info = $ref<{ avatar: string; name: string }>()
let studyInfo: any = $ref()
function fetchMyInfo() {
getMyInfo().then(res => {
info = res.data.info || {}
teamInfo = res.data.team_info || { star: 0 }
})
getStudyInfo().then(res => {
studyInfo = res.data
})
}
onMounted(() => {
fetchMyInfo()
})
const menus = computed<
Array<{
name: string
path?: string
icon: string
href?: string
}>
>(() => {
return [
{
path: '/learn/course',
name: '我的课程',
icon: 'https://webapp-pub.ezijing.com/project/prp-h5/my_menu_1.png'
},
{
path: teamInfo.team_id ? `/team/view/${teamInfo.team_id}` : '/team',
name: '我的团队',
icon: 'https://webapp-pub.ezijing.com/project/prp-h5/my_menu_2.png'
},
{
href: 'https://account-show.ezijing.com/h5/payment',
name: '我的发票',
icon: 'https://webapp-pub.ezijing.com/project/prp-h5/my_menu_7.png'
},
{
path: '/qa',
name: '知识输出者',
icon: 'https://webapp-pub.ezijing.com/project/prp-h5/my_menu_3.png'
}
// {
// path: '/',
// name: '申请导师',
// icon: 'https://webapp-pub.ezijing.com/project/prp-h5/my_menu_4.png'
// },
// {
// path: '/',
// name: '申请紫荆奖',
// icon: 'https://webapp-pub.ezijing.com/project/prp-h5/my_menu_5.png'
// },
// {
// path: '/',
// name: '参加PRP大会',
// icon: 'https://webapp-pub.ezijing.com/project/prp-h5/my_menu_6.png'
// }
]
})
// 退出登录
const onLogout = () => {
logout().then(() => {
......@@ -76,8 +37,19 @@ function onUploadSuccess(url: string) {
Toast.success('上传成功')
})
}
const viewData = function () {
router.push('/my/data')
}
const editPassword = function () {
location.href = `https://login.ezijing.com/auth/password?rd=${encodeURIComponent(location.href)}`
}
</script>
<template>
<AppContainer>
<template #header-aside><div @click="editPassword">修改密码</div></template>
</AppContainer>
<div class="my" v-if="info">
<div class="user">
<div class="user-avatar">
......@@ -90,30 +62,26 @@ function onUploadSuccess(url: string) {
</div>
<div class="quantity">
<dl>
<dt>{{ info.star }}</dt>
<dd>我的星星</dd>
<dt>{{ studyInfo?.course_count }}<span></span></dt>
<dd>已报名课程</dd>
</dl>
<dl>
<dt>{{ teamInfo.star }}</dt>
<dd>我的积分</dd>
<dt>{{ studyInfo?.learn_count }}<span></span></dt>
<dd>已学习课程</dd>
</dl>
<dl>
<dt v-if="studyInfo?.learn_duration">
{{ (parseInt(studyInfo?.learn_duration) / 60).toFixed(1) }}<span>分钟</span>
</dt>
<dt v-else>0<span>分钟</span></dt>
<dd>已学习时长</dd>
</dl>
</div>
<div class="box">
<nav class="menus">
<ul>
<li v-for="(item, index) in menus" :key="index">
<a :href="item.href" target="_blank" v-if="item.href">
<img :src="item.icon" />
<p>{{ item.name }}</p>
</a>
<router-link :to="item.path" v-if="item.path">
<img :src="item.icon" />
<p>{{ item.name }}</p>
</router-link>
</li>
</ul>
</nav>
</div>
<div class="view-data" @click="viewData">查看我的学习数据</div>
<!-- 我的课程 -->
<Course :data="studyInfo?.courses" />
<!-- 电子证书 -->
<Cert :data="studyInfo?.courses" />
<div class="logout" @click="onLogout">退出登录</div>
</div>
</template>
......@@ -162,6 +130,10 @@ function onUploadSuccess(url: string) {
.quantity {
display: flex;
margin-top: 0.4rem;
background-color: #fff;
padding: 0.52rem 0;
border-radius: 0.2rem;
box-shadow: 0px 2px 8px 1px rgba(0, 0, 0, 0.16);
dl {
flex: 1;
text-align: center;
......@@ -173,11 +145,14 @@ function onUploadSuccess(url: string) {
font-size: 0.42rem;
font-weight: 500;
line-height: 0.6rem;
color: #033974;
color: #aa1941;
span {
font-size: 0.28rem;
}
}
dd {
font-size: 0.3rem;
color: #666666;
font-size: 0.28rem;
color: #000;
line-height: 0.42rem;
}
}
......@@ -212,11 +187,25 @@ function onUploadSuccess(url: string) {
height: 0.8rem;
font-size: 0.28rem;
line-height: 0.8rem;
color: #666666;
color: #fff;
text-align: center;
border-radius: 0.4rem;
border: 0.01rem solid #999999;
background-color: #aa1941;
cursor: pointer;
margin-bottom: 1rem;
}
}
.view-data {
margin-top: 0.6rem;
margin-bottom: 0.6rem;
height: 0.8rem;
font-size: 0.28rem;
line-height: 0.8rem;
color: #fff;
text-align: center;
border-radius: 0.4rem;
// border: 0.01rem solid #999999;
background-color: #aa1941;
cursor: pointer;
}
</style>
import httpRequest from '@/utils/axios'
// 获取文档数据
export function getNewsView(params: { id: string }) {
return httpRequest.get('/api/psp/v1/hot-message/doc-view', { params })
}
import type { RouteRecordRaw } from 'vue-router'
import AppLayout from '@/components/layout/Index.vue'
export const routes: Array<RouteRecordRaw> = [
{
path: '/news',
component: AppLayout,
children: [{ path: 'doc/:id', component: () => import('./views/newsDoc.vue'), props: true }]
}
]
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { getNewsView } from '../api'
import DocView from '@/components/DocView.vue'
const props = defineProps<{ id: string }>()
const data = ref()
function fetchDocView() {
getNewsView({ id: props.id }).then(res => {
data.value = res.data
})
}
onMounted(() => {
fetchDocView()
})
</script>
<template>
<DocView :data="data"></DocView>
</template>
import httpRequest from '@/utils/axios'
// 获取问答列表
export function getQuestionList(params?: { page_size?: number; page?: number }) {
return httpRequest.get('/api/psp/v1/question/list', { params })
}
// 获取问答详情
export function getQuestionView(params: { id: string; page_size?: number; page?: number }) {
return httpRequest.get('/api/psp/v1/question/view', { params })
}
// 发布问答
export function createQuestion(data: { title: string; desc: string; picture?: string; file?: string }) {
return httpRequest.post('/api/psp/v1/question/create-question', data)
}
// 发布问答评论
export function createQuestionComment(data: { question_id: string; to_comment_id?: string; content: string }) {
return httpRequest.post('/api/psp/v1/question/create-comment', data)
}
import type { RouteRecordRaw } from 'vue-router'
import AppLayout from '@/components/layout/Index.vue'
export const routes: Array<RouteRecordRaw> = [
{
path: '/qa',
component: AppLayout,
children: [
{ path: '', component: () => import('./views/Index.vue') },
{ path: 'publish', component: () => import('./views/Publish.vue'), meta: { requireLogin: true } }
]
}
]
<script setup lang="ts">
import { Toast } from 'vant'
import PublishItem from '@/components/PublishItem.vue'
import { getQuestionList, createQuestionComment } from '../api'
import useWXShare from '@/utils/wx'
interface Info {
loading: boolean
page: number
total: number
list: any[]
}
const dataset = reactive<Info>({ loading: false, page: 1, total: 0, list: [] })
// 获取打卡评论
const getQuestions = (isReset?: boolean) => {
if (isReset) {
dataset.page = 1
}
dataset.loading = true
getQuestionList({ page: dataset.page, page_size: 10 })
.then(res => {
const { total, list } = res.data
dataset.total = total
dataset.list = isReset ? list : dataset.list.concat(list)
if (dataset.list.length <= total) {
dataset.page++
}
})
.finally(() => {
dataset.loading = false
})
}
// 滚动加载
useInfiniteScroll(
document,
() => {
!dataset.loading && getQuestions()
},
{ distance: 50 }
)
// 评论
const onSubmitComment = (data: any, action: string) => {
if (action === 'comment') {
// 评论
createQuestionComment({
question_id: data.id,
content: data.comment
}).then(() => {
Toast.success('评论成功')
getQuestions(true)
})
} else {
// 回复
createQuestionComment({
question_id: data.entity_id,
content: data.comment,
to_comment_id: data.id
}).then(() => {
Toast.success('回复成功')
getQuestions(true)
})
}
}
onMounted(() => {
getQuestions()
useWXShare({ desc: '输出即是输入 成就专业【知识输出者】' })
})
</script>
<template>
<RouterLink to="/share">
<img src="https://webapp-pub.ezijing.com/project/prp-h5/banner_qa.png" style="width: 100%" />
</RouterLink>
<div class="list-card">
<h2 class="title">最新动态</h2>
<div class="tips">
<p>晒出你的【知识输出者】 留下知识践行的足迹 得到你的星星权益</p>
<RouterLink to="/qa/publish" class="tips-button">去拍照得星星</RouterLink>
</div>
<div style="margin: 0.2rem">
<template v-if="dataset.list?.length">
<PublishItem
v-for="item in dataset.list"
:data="item"
:key="item.id"
@submitComment="onSubmitComment"
></PublishItem>
</template>
<van-empty description="暂无内容" v-else />
</div>
</div>
</template>
<style lang="scss" scoped>
.list-card {
margin-top: 0.52rem;
margin-bottom: 0.3rem;
background: #fff;
border-radius: 0.2rem;
overflow: hidden;
.title {
padding: 0 0.3rem;
font-size: 0.28rem;
line-height: 0.9rem;
background: #f7f7f7;
}
.tips {
position: relative;
margin: 0.2rem;
padding: 0.24rem;
background-color: #f7f7f7;
border-radius: 0.2rem;
p {
font-size: 0.24rem;
line-height: 0.36rem;
color: #033974;
}
.tips-button {
position: absolute;
right: 0.24rem;
bottom: 0.2rem;
padding: 0 0.15rem;
height: 0.4rem;
font-size: 0.22rem;
line-height: 0.4rem;
color: #fff;
background: linear-gradient(164deg, #f7c988 0%, #e5a448 100%);
border-radius: 0.2rem;
}
}
}
</style>
<script setup lang="ts">
import { reactive } from 'vue'
import { useRouter } from 'vue-router'
import { createQuestion } from '../api'
import AppUpload from '@/components/base/AppUpload.vue'
import { Toast } from 'vant'
const router = useRouter()
const form = reactive({ title: '', desc: '', picture: [] })
function onSubmit() {
const params = Object.assign({}, form, { picture: JSON.stringify(form.picture) })
createQuestion(params).then(() => {
Toast.success('发布成功')
router.push({ path: '/qa' })
})
}
</script>
<template>
<AppContainer title="发布问题" backgroundColor="#fff" headerAlign="center">
<van-form @submit="onSubmit">
<van-field v-model="form.title" placeholder="请输入问题标题" :rules="[{ required: true, message: '请输入问题标题' }]" />
<van-field v-model="form.desc" type="textarea" placeholder="请输入问题内容" :autosize="{ minHeight: 200 }" :rules="[{ required: true, message: '请输入问题内容' }]" />
<van-field>
<template #input>
<AppUpload v-model="form.picture"></AppUpload>
</template>
</van-field>
<van-button block round native-type="submit" class="my-button">发布</van-button>
</van-form>
</AppContainer>
</template>
<style lang="scss" scoped>
:deep(.van-cell) {
padding-left: 0;
padding-right: 0;
&::after {
left: 0;
right: 0;
}
}
.my-button {
margin: 1rem 0;
}
</style>
import httpRequest from '@/utils/axios'
// 查询名片
export function getBusinessCard(params: {email: string;address: string}) {
return httpRequest.get('/api/psp/v1/welfare/get-card',{params})
}
// 获取验证码
export function getPhoneCode(params: {phone:string;}) {
return httpRequest.get('/api/psp/v1/welfare/send-code',{params})
}
// 查询证书
export function getCertificate(params: {phone:string;code:string}) {
return httpRequest.get('/api/psp/v1/welfare/get-certificate',{params})
}
// 持证人权益-持证人列表
export function getLicenseList(params:{page_size?: number; page?: number}) {
return httpRequest.get('/api/psp/v1/welfare/avatar-list',{params})
}
// 获取用户信息
export function getUserInfo() {
return httpRequest.get('/api/psp/v1/my/info')
}
<script setup lang="ts">
import * as api from '../api'
import { reactive, ref, onMounted } from 'vue'
// 表单数据
const FormInfo = reactive({
email: '',
address: ''
})
// 个人信息
const userInfo = reactive({
name: '',
mobile: '',
certificate_number: ''
})
onMounted(() => {
// 获取用户个人信息
getUserInfo()
})
// 名片图片
const imgUrl1 = ref('')
const imgUrl2 = ref('')
// 获取用户信息
function getUserInfo() {
api.getUserInfo().then(res => {
userInfo.name = res.data.info.name
userInfo.mobile = res.data.info.mobile
userInfo.certificate_number = res.data.info.certificate_number
})
}
// 查询名片
function handleSubmit() {
const params = {
email: FormInfo.email,
address: FormInfo.address
}
api.getBusinessCard(params).then(res => {
console.log(res, '0000')
imgUrl1.value = res.data.url.jpg1
imgUrl2.value = res.data.url.jpg2
})
}
</script>
<template>
<div class="main_content">
<van-form v-if="imgUrl1 === ''">
<van-cell-group inset>
<van-field
v-model="userInfo.name"
name="姓名"
label="姓名"
placeholder="请输入姓名"
:rules="[{ required: true, message: '请输入姓名' }]"
disabled
/>
<van-field
v-model="userInfo.mobile"
name="手机号"
label="手机号"
placeholder="请输入手机号"
:rules="[{ required: true, message: '请输入手机号' }]"
disabled
/>
<van-field
v-model="userInfo.certificate_number"
name="证书号"
label="证书号"
placeholder="请输入证书号"
:rules="[{ required: true, message: '请输入证书号' }]"
disabled
/>
<van-field
v-model="FormInfo.email"
name="邮箱"
label="邮箱"
placeholder="请输入邮箱"
:rules="[{ required: true, message: '请输入邮箱' }]"
/>
<van-field
v-model="FormInfo.address"
name="地址"
label="地址"
placeholder="请输入地址"
:rules="[{ required: true, message: '请输入地址' }]"
/>
</van-cell-group>
<div style="margin-top: 0.93rem">
<van-button
round
block
native-type="submit"
style="background: linear-gradient(149deg, #f7c988 0%, #e5a448 100%)"
@click="handleSubmit"
:disabled="FormInfo.email === '' || FormInfo.address === ''"
>
确认信息正确,查询名片
</van-button>
</div>
</van-form>
<div v-else>
<div class="main_content_card">
<!-- <div class="img">{{ imgUrl }}</div> -->
<img :src="imgUrl1" alt="" />
<img :src="imgUrl2" alt="" class="imgUrl2" />
</div>
<!-- <div class="main_content_bottom"></div> -->
</div>
</div>
</template>
<style lang="scss" scoped>
.main_content {
padding: 0.23rem 0.3rem 0 0.3rem;
background: #ffffff;
border-radius: 0.2rem;
.main_content_card {
margin: 0.83rem 0.38rem 0 0.34rem;
background: #ffffff;
// background-color: red;
img {
width: 100%;
display: block;
}
.imgUrl2 {
margin-top: 0.66rem;
}
}
}
:deep(.van-button) {
font-size: 0.28rem;
font-family: PingFang SC-Regular, PingFang SC;
font-weight: 400;
color: #ffffff;
}
</style>
<script setup lang="ts">
import { Toast } from 'vant'
import * as api from '../api'
import { ref, reactive } from 'vue'
const FormInfo = reactive({
phone: '',
code: ''
})
const url = ref('')
const time = ref(60)
const isDisposed = ref(false)
const isShowForm = ref(false)
function getCode() {
if (FormInfo.phone === '') {
Toast('请输入手机号')
return
}
const params = {
phone: FormInfo.phone
}
api.getPhoneCode(params).then(res => {
console.log('获取验证码结果', res)
isDisposed.value = true
handleTimeChange()
})
}
// 倒计时
const handleTimeChange = () => {
if (time.value <= 0) {
isDisposed.value = false
time.value = 60
} else {
setTimeout(() => {
time.value--
handleTimeChange()
}, 1000)
}
}
// 查询名片
function handleQuery() {
const params = {
phone: FormInfo.phone,
code: FormInfo.code
}
api.getCertificate(params).then(res => {
url.value = res.data.url
FormInfo.phone = ''
FormInfo.code = ''
isDisposed.value = false
})
}
// 他人代查
function handleOther() {
isShowForm.value = true
}
</script>
<template>
<div class="main_content">
<van-form v-if="url === '' || isShowForm">
<van-cell-group inset>
<van-field
v-model="FormInfo.phone"
name="手机号"
label="手机号"
placeholder="请输入手机号"
:rules="[{ required: true, message: '请输入手机号' }]"
/>
<van-field
v-model="FormInfo.code"
:center="true"
clearable
label="验证码"
placeholder="请输入验证码"
:rules="[{ required: true, message: '请输入验证码' }]"
>
<template #button>
<van-button
class="btn_code"
size="small"
type="default"
round
block
@click="getCode"
:disabled="isDisposed"
>{{ isDisposed ? `${time}s后重新获取` : '获取验证码' }}</van-button
>
</template>
</van-field>
</van-cell-group>
<div class="btn_query">
<van-button
round
block
native-type="submit"
style="background: linear-gradient(149deg, #f7c988 0%, #e5a448 100%)"
@click="handleQuery"
:disabled="FormInfo.code === '' || FormInfo.code === ''"
>
查询证书
</van-button>
</div>
</van-form>
<div class="main_content_card" v-else-if="url !== '' || !isShowForm">
<img :src="url" alt="" class="main_content_card_img" />
<div class="main_content_card_btn" @click="handleOther">他人代查</div>
</div>
</div>
</template>
<style lang="scss" scoped>
.main_content {
height: 8.02rem;
padding-top: 0.53rem;
background: #ffffff;
border-radius: 0.2rem;
.btn_query {
padding: 0.23rem 0.3rem 0 0.3rem;
}
.btn_code {
background-color: rgb(3, 57, 116);
}
.main_content_card {
padding: 0 0.3rem 0 0.3rem;
.main_content_card_img {
width: 100%;
display: block;
}
.main_content_card_btn {
width: 1.18rem;
height: 0.4rem;
background: linear-gradient(164deg, #f7c988 0%, #e5a448 100%);
border-radius: 0.2rem;
opacity: 1;
font-size: 0.22rem;
font-weight: 400;
color: #ffffff;
line-height: 0.4rem;
text-align: center;
float: right;
margin-top: 0.34rem;
}
}
}
:deep(.van-button) {
font-size: 0.28rem;
font-family: PingFang SC-Regular, PingFang SC;
font-weight: 400;
color: #ffffff;
}
:deep(.van-field__label) {
width: 1.2rem;
}
</style>
<script setup lang="ts">
import { useInfiniteScroll } from '@vueuse/core'
import { ref, onMounted, reactive } from 'vue'
import * as api from '../api'
interface licenseeList {
loading: boolean
page: number
total: number
list: any[]
}
const dataset = reactive<licenseeList>({ loading: false, page: 1, total: 0, list: [] })
const hasMore = ref(false)
const props = defineProps<{ type: string }>()
// 获取持证人列表
const handleLicenseList = () => {
const params = { page_size: 10, page: dataset.page, type: props.type }
dataset.loading = true
api
.getLicenseList(params)
.then(res => {
const { total, list } = res.data
dataset.total = total
dataset.list = dataset.list.concat(list)
if (dataset.list.length < total) {
hasMore.value = true
dataset.page++
} else if (dataset.list.length === total) {
hasMore.value = false
}
})
.finally(() => {
dataset.loading = false
})
}
// 滚动加载
const el = ref<HTMLElement>()
useInfiniteScroll(
document,
() => {
// load more
if (hasMore.value) {
!dataset.loading && handleLicenseList()
}
},
{ distance: 10 }
)
onMounted(() => {
// 获取持证人列表
handleLicenseList()
})
</script>
<template>
<div>
<div v-if="dataset.list.length > 0" class="main_content" ref="el">
<div class="main_content_list" v-for="(item, index) in dataset.list" :key="index">
<div class="img_top">
<img class="img" :src="item.avatar" />
</div>
<div :class="props.type === 'paa' ? 'img_bottom bg' : 'img_bottom'">
<div class="img_bottom_people">{{ item.batch_name }}</div>
<div class="img_bottom_name">{{ item.name }}</div>
<div class="img_bottom_cardnum">证书编号</div>
<div class="img_bottom_num">{{ item.certificate_number }}</div>
</div>
</div>
</div>
<van-empty v-else description="暂无持证人" />
</div>
</template>
<style lang="scss" scoped>
.main_content {
padding: 0.2rem 0.2rem 0 0.31rem;
background: #ffffff;
border-radius: 0 0 0.2rem;
display: flex;
justify-content: flex-start;
flex-wrap: wrap;
.main_content_list {
// margin-top: 0.2rem;
margin-left: 0.15rem;
border-radius: 0.2rem;
box-sizing: border-box;
.img_top {
width: 3.04rem;
height: 3.6rem;
border-top-left-radius: 0.2rem;
border-top-right-radius: 0.2rem;
.img {
width: 100%;
height: 100%;
display: block;
border-top-left-radius: 0.2rem;
border-top-right-radius: 0.2rem;
object-fit: cover;
}
}
.img_bottom {
height: 1.94rem;
border-bottom-left-radius: 0.2rem;
border-bottom-right-radius: 0.2rem;
padding-left: 0.21rem;
padding-top: 0.15rem;
background: url(https://webapp-pub.ezijing.com/prp_h5/license_bg.png) no-repeat;
background-size: contain;
&.bg {
background: url(https://webapp-pub.ezijing.com/prp_h5/license_bg2.png) no-repeat;
background-size: contain;
}
.img_bottom_people {
font-size: 0.24rem;
font-weight: 400;
color: #ffffff;
}
.img_bottom_name {
font-size: 0.32rem;
font-weight: 500;
color: #cfb181;
margin-top: 0.1rem;
}
.img_bottom_cardnum {
font-size: 0.24rem;
font-weight: 400;
color: #ffffff;
margin-top: 0.2rem;
}
.img_bottom_num {
font-size: 0.28rem;
font-weight: 400;
color: #cfb181;
margin-top: 0.1rem;
}
}
}
}
// .main_content:last-child {
// display: flex;
// justify-content: flex-start;
// }
</style>
import type { RouteRecordRaw } from 'vue-router'
import AppLayout from '@/components/layout/Index.vue'
export const routes: Array<RouteRecordRaw> = [
{
path: '/query',
component: AppLayout,
children: [{ path: '', component: () => import('./views/Index.vue') }]
}
]
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { useRoute } from 'vue-router'
import BusinessCardQuery from '../components/BusinessCardQuery.vue'
import CertificateQuery from '../components/CertificateQuery.vue'
import LicenseeView from '../components/LicenseeView.vue'
const active = ref<number>(0)
const route = useRoute()
onMounted(() => {
if (route.query.active === '0') {
active.value = 0
} else if (route.query.active === '1') {
active.value = 1
} else if (route.query.active === '2') {
active.value = 2
}
})
const activeChild: any = ref(0)
</script>
<template>
<AppContainer title="权益查看">
<div class="query-box">
<van-tabs
v-model:active="active"
shrink
background="transparent"
title-active-color="#033974"
title-inactive-color="#4E4E4E"
line-height="0"
>
<van-tab title="名片查询&nbsp;&nbsp;&nbsp;|"><BusinessCardQuery /> </van-tab>
<van-tab title="证书查询&nbsp;&nbsp;&nbsp;|"> <CertificateQuery /></van-tab>
<van-tab title="&nbsp;持证人查看">
<van-tabs v-model="activeChild" swipeable>
<van-tab title="PRP">
<LicenseeView type="prp" />
</van-tab>
<van-tab title="PAA">
<LicenseeView type="paa"/>
</van-tab>
</van-tabs>
</van-tab>
</van-tabs>
</div>
</AppContainer>
</template>
<style lang="scss" scoped>
.van-tab {
font-size: 0.26rem;
font-weight: 400;
color: #868686;
}
// .query-box {
// }
:deep(.van-tabs__line) {
background-color: rgb(3, 57, 116) !important;
}
</style>
import httpRequest from '@/utils/axios'
// 获取分享信息
export function getShare() {
return httpRequest.get('/api/psp/v1/share/index')
}
// 获取二维码
export function getQrcode(params: { url: string }): Promise<{ url: string }> {
return httpRequest.get('https://learn-api.ezijing.com/api/lms-ep/util/pcode', { params, headers: { tenant: 'prp' } })
}
// 获取二维码
export function imageTransfer(params: { source: string }) {
return httpRequest.get('/api/usercenter/tool/transfer-image', { params })
}
import type { RouteRecordRaw } from 'vue-router'
export const routes: Array<RouteRecordRaw> = [
{
path: '/share',
component: () => import('./views/Index.vue'),
meta: { requireLogin: true }
}
]
/* 用户信息 */
export interface TypeUser {
name: string
avatar: string
time: number
}
/* 背景 */
export interface TypeBackground {
url: string
color: string
}
/* 金句 */
export interface TypeText {
text: string
isCustom?: boolean
}
export interface TypeTextGroup {
id: string
title: string
content: TypeText[]
}
/* tab */
export interface TypeTab {
index: number
text: string
}
<script setup lang="ts">
import dayjs from 'dayjs'
import 'dayjs/locale/zh-cn'
import html2canvas from 'html2canvas'
import { saveAs } from 'file-saver'
import { Toast } from 'vant'
import { getShare, getQrcode } from '../api'
import type { TypeUser, TypeBackground, TypeTextGroup, TypeText, TypeTab } from '../types'
import { useUserStore } from '@/stores/user'
const userStore = useUserStore()
dayjs.locale('zh-cn')
const today = dayjs().format('MM月DD日 dddd')
// 控制栏数据
const controls = reactive({ show: true, active: 0, color: '', bg: '', title: '', titleId: '', text: '', avatar: '' })
const controlsTabs: TypeTab[] = [
{ index: 0, text: '背景' },
{ index: 1, text: '标题' },
{ index: 2, text: '金句' },
{ index: 3, text: '头像' }
]
// 用户信息
let user = $ref<TypeUser>()
// 背景
let bgList = $ref<TypeBackground[]>([
{ url: 'https://webapp-pub.ezijing.com/project/prp-h5/share/bg/bg1.png', color: '#2F7CED' },
{ url: 'https://webapp-pub.ezijing.com/project/prp-h5/share/bg/bg2.png', color: '#2F86BC' },
{ url: 'https://webapp-pub.ezijing.com/project/prp-h5/share/bg/bg3.png', color: '#EB9027' }
])
// 金句
let textGroupList = $ref<TypeTextGroup[]>()
// 当前选中标题的金句
const currentTextList = $computed<TypeText[]>(() => {
const found = textGroupList.find(item => item.id === controls.titleId)
return found?.content || []
})
let avatarList = $ref<string[]>([
'https://webapp-pub.ezijing.com/project/prp-h5/share/avatar/avatar_1.jpg',
'https://webapp-pub.ezijing.com/project/prp-h5/share/avatar/avatar_2.jpg',
'https://webapp-pub.ezijing.com/project/prp-h5/share/avatar/avatar_3.jpg',
'https://webapp-pub.ezijing.com/project/prp-h5/share/avatar/avatar_4.jpg',
'https://webapp-pub.ezijing.com/project/prp-h5/share/avatar/avatar_5.jpg'
])
// 获取信息
async function getShareInfo() {
const {
data: { info, share }
} = await getShare()
user = info
textGroupList = share.map((item: any) => {
item.content = item.content.map((text: string) => ({ text }))
return item
})
if (info.avatar) {
avatarList.unshift(info.avatar)
}
init()
}
onMounted(() => {
getShareInfo()
})
// 生成分享二维码
let qrcodeUrl = $ref<string>()
async function genQrcode() {
const params = {
url: `https://pages.ezijing.com/prp/mobile204001490416.html?channel_num=93613&user_id=${userStore.user?.id}&user_name=${user.name}`
}
const { url } = await getQrcode(params)
qrcodeUrl = url
}
function init() {
controls.color = bgList[0]?.color
controls.bg = bgList[0]?.url
controls.title = textGroupList[0]?.title
controls.titleId = textGroupList[0]?.id
controls.text = currentTextList[0]?.text
controls.avatar = avatarList[0]
// 生成二维码地址
genQrcode()
}
const refShare = $ref<HTMLElement>()
let previewVisible = $ref<boolean>(false)
let previewUrl = $ref<string>('')
function handleHtmlToImg() {
Toast.loading({ duration: 0, forbidClick: true, message: '海报生成中' })
html2canvas(refShare, { scale: 2, useCORS: true }).then(canvas => {
previewUrl = canvas.toDataURL('image/png')
previewVisible = true
Toast.clear()
})
}
function handleDownload() {
saveAs(previewUrl, Date.now().toString())
}
function handleTabClick(index: number) {
controls.active = index
}
// 控制栏显示与隐藏
function toggleControls() {
controls.show = !controls.show
}
// 选择背景
function onSelectBackground(data: TypeBackground) {
controls.color = data.color
controls.bg = data.url
}
// 选择标题
function onSelectTitle(data: TypeTextGroup) {
controls.title = data.title
controls.titleId = data.id
controls.text = currentTextList[0]?.text
}
// 选择金句
function onSelectText(data: TypeText) {
controls.text = data.text
}
// 选择头像
function onSelectAvatar(url: string) {
controls.avatar = url
}
// 自定义金句
let popupVisible = $ref<boolean>(false)
let popupText = $ref<string>('')
function showAddText() {
popupVisible = true
}
// 添加金句
function handleSubmitAddText() {
if (!popupText) {
return
}
textGroupList = textGroupList.map(item => {
if (item.id === controls.titleId) {
item.content.unshift({ text: popupText, isCustom: true })
}
return item
})
controls.text = popupText
popupVisible = false
popupText = ''
}
// 删除金句
function handleRemoveText(data: TypeText) {
textGroupList = textGroupList.map(item => {
if (item.id === controls.titleId) {
item.content = item.content.filter(item => item.text !== data.text)
}
return item
})
controls.text = currentTextList[0]?.text
}
</script>
<template>
<div class="share" v-if="user">
<div class="share-design">
<div class="share-design-inner" ref="refShare">
<img :src="controls.bg" class="share-design_bg" />
<div class="share-design_main">
<div class="share-design_date">{{ today }}</div>
<div class="share-design_title" :style="{ color: controls.color }">{{ controls.title }}</div>
<div class="share-design_text">{{ controls.text }}</div>
<div class="share-design-user">
<img :src="controls.avatar" class="share-design-user_avatar" />
<div class="share-design-user-content">
<div class="share-design-user_name">{{ user.name }}</div>
<div class="share-design-user_day">
<em></em>
<span>{{ user.time }}</span>
<em></em>
</div>
</div>
</div>
<img :src="qrcodeUrl" class="share-design_qrcode" />
</div>
</div>
<div class="share-buttons">
<img src="https://webapp-pub.ezijing.com/weapp/share/controls_up.png" class="button-image" @click="toggleControls" v-show="!controls.show" />
<img src="https://webapp-pub.ezijing.com/weapp/share/controls_down.png" class="button-image" @click="toggleControls" v-show="controls.show" />
<div class="button" @click="handleHtmlToImg">
<p>生成<br />海报</p>
</div>
</div>
</div>
<div class="share-controls" :class="controls.show ? 'has-controls' : ''">
<div class="share-controls-tabs">
<ul class="share-controls-tabs-header">
<li v-for="(tab, index) in controlsTabs" :key="index" :class="{ 'is-active': index === controls.active }" @click="handleTabClick(index)">
{{ tab.text }}
</li>
</ul>
<div class="share-controls-tabs-content">
<!-- 背景 -->
<div class="share-controls-tab-pane" v-if="controls.active === 0">
<div
class="share-controls-bg-item"
v-for="(item, index) in bgList"
:key="index"
:class="{ 'is-active': item.url === controls.bg }"
@click="onSelectBackground(item)"
:style="`background-color: ${item.color};`"
>
<!-- <img :src="item.url" /> -->
</div>
</div>
<!-- 标题 -->
<div class="share-controls-tab-pane" v-if="controls.active === 1">
<div class="share-controls-title-item" v-for="(item, index) in textGroupList" :key="index" :class="{ 'is-active': item.title === controls.title }" @click="onSelectTitle(item)">
<p class="text-overflow">
{{ item.title }}
</p>
</div>
</div>
<!-- 金句 -->
<div class="share-controls-tab-pane" v-if="controls.active === 2">
<div class="share-controls-text-item" @click="showAddText">自定义金句</div>
<div class="share-controls-text-item" v-for="(item, index) in currentTextList" :key="index" :class="{ 'is-active': item.text === controls.text }" @click="onSelectText(item)">
<van-icon name="clear" size="20" color="#d78c4a" v-if="item.isCustom" @click="handleRemoveText(item)" />
<p class="text-overflow">
{{ item.text }}
</p>
</div>
</div>
<!-- 头像 -->
<div class="share-controls-tab-pane" v-if="controls.active === 3">
<div class="share-controls-avatar-item" v-for="(url, index) in avatarList" :key="index" :class="{ 'is-active': url === controls.avatar }" @click="onSelectAvatar(url)">
<img :src="url" />
</div>
</div>
</div>
</div>
</div>
</div>
<div class="share-preview" v-if="previewVisible">
<div class="close" @click="previewVisible = false"></div>
<img :src="previewUrl" width="300" />
<div class="share-preview-button" @click="handleDownload">长按图片保存到相册</div>
<p class="share-preview-tips">分享图片到朋友圈<br />最高可获得5000朵紫荆花</p>
</div>
<!-- 自定义金句 -->
<van-popup v-model:show="popupVisible" position="bottom" closeable close-icon="close">
<van-field v-model="popupText" rows="4" type="textarea" maxlength="160" placeholder="请输入金句" show-word-limit></van-field>
<van-button type="success" size="small" block @click="handleSubmitAddText" style="width: 120px; margin: 40px auto">完成</van-button>
</van-popup>
</template>
<style lang="scss">
.share {
display: flex;
flex-direction: column;
height: 100vh;
color: #666;
background: #3f3f3f;
}
.share-design {
position: relative;
flex: 1;
display: flex;
align-items: center;
justify-content: center;
overflow-y: auto;
}
.share-design-inner {
width: 300px;
margin: 0 auto;
position: relative;
}
.share-design_main {
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
padding-top: 131px;
}
.share-design_bg {
display: block;
width: 100%;
}
.share-design_date {
height: 28px;
font-size: 14px;
line-height: 28px;
color: #ffffff;
padding-left: 110px;
}
.share-design_title {
margin-top: 25px;
text-align: center;
font-size: 14px;
line-height: 1;
}
.share-design_text {
margin-top: 12px;
width: 210px;
margin: 12px auto 0;
font-size: 11px;
line-height: 23px;
background: url(https://webapp-pub.ezijing.com/project/prp-h5/share/line.png) repeat left top;
max-height: 161px;
overflow: hidden;
}
.share-design-user {
display: flex;
align-items: center;
position: absolute;
left: 44px;
bottom: 60px;
}
.share-design-user_avatar {
width: 36px;
height: 36px;
border-radius: 50%;
object-fit: cover;
overflow: hidden;
}
.share-design-user-content {
margin-left: 8px;
flex: 1;
}
.share-design-user_name {
font-size: 10px;
font-weight: 400;
line-height: 1;
color: #483222;
}
.share-design-user_day {
margin-top: 5px;
display: flex;
align-items: center;
font-size: 10px;
font-weight: 400;
line-height: 1;
color: #483222;
vertical-align: middle;
span {
padding: 0 3px;
font-size: 16px;
}
}
.share-design_qrcode {
position: absolute;
right: 39px;
bottom: 32px;
width: 64px;
}
.share-buttons {
position: absolute;
bottom: 0;
left: 0;
right: 0;
display: flex;
justify-content: space-between;
padding: 20px;
.button-image {
width: 50px;
height: 50px;
cursor: pointer;
}
.button {
position: relative;
width: 50px;
height: 50px;
background: #ffffff;
box-shadow: 0px 1px 4px 0px rgba(0, 0, 0, 0.2);
border-radius: 50%;
font-size: 13px;
color: #d78c4a;
line-height: 15px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
}
}
.share-controls {
height: 0;
background-color: #fff;
padding-bottom: env(safe-area-inset-bottom);
transition: height 0.3s;
&.has-controls {
height: 130px;
}
}
.share-controls-tabs-header {
display: flex;
background: #ccc;
li {
width: 75px;
height: 30px;
background: #e6e6e6;
color: #000;
text-align: center;
line-height: 30px;
cursor: pointer;
&.is-active {
background: #fff;
color: #d78c4a;
}
}
}
.share-controls-tabs-content {
overflow-x: auto;
}
.share-controls-tab-pane {
flex: 1;
height: 100px;
padding: 0 10px;
display: flex;
align-items: center;
flex-wrap: nowrap;
}
.share-controls-tab-pane .is-active {
border: 3px solid #d78c4a;
}
.share-controls-bg-item {
flex: 0 0 46px;
width: 46px;
height: 80px;
margin: 0 15px;
border: 3px solid #fff;
border-radius: 3px;
overflow: hidden;
cursor: pointer;
}
.share-controls-title-item {
margin: 0 15px;
flex: 0 0 228px;
width: 228px;
height: 60px;
display: flex;
align-items: center;
justify-content: center;
border: 3px solid #ccc;
border-radius: 6px;
padding: 10px;
box-sizing: border-box;
cursor: pointer;
}
.share-controls-text-item {
position: relative;
margin: 0 15px;
flex: 0 0 228px;
width: 228px;
height: 60px;
display: flex;
align-items: center;
justify-content: center;
border: 3px solid #ccc;
border-radius: 6px;
padding: 10px;
box-sizing: border-box;
cursor: pointer;
.van-icon-clear {
position: absolute;
right: 2px;
top: 2px;
font-size: 12px;
color: #ccc;
}
}
.text-overflow {
font-size: 13px;
color: #1a1a1a;
line-height: 18px;
display: -webkit-box;
text-overflow: ellipsis;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.share-controls-avatar-item {
position: relative;
margin: 0 15px;
flex: 0 0 60px;
width: 60px;
height: 60px;
border: 3px solid #ccc;
border-radius: 50%;
overflow: hidden;
box-sizing: border-box;
cursor: pointer;
img {
width: 100%;
height: 100%;
object-fit: cover;
}
}
.share-preview {
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
z-index: 2;
background: rgba(0, 0, 0, 0.7);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
.close {
position: absolute;
right: 8px;
top: 8px;
width: 48px;
height: 48px;
background: url('https://webapp-pub.ezijing.com/weapp/share/close.png') no-repeat;
background-size: contain;
z-index: 999;
cursor: pointer;
}
.share-preview-button {
margin-top: 40px;
width: 250px;
height: 50px;
border-radius: 25px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: 13px;
font-weight: 400;
color: #d78c4a;
line-height: 15px;
background: #fff;
cursor: pointer;
}
.share-preview-tips {
margin-top: 20px;
font-size: 13px;
font-weight: 400;
color: #fff;
line-height: 15px;
text-align: center;
}
}
</style>
import httpRequest from '@/utils/axios'
// 获取首页数据
export function getHomeData() {
return httpRequest.get('/api/psp/v2/index/index')
}
\ No newline at end of file
<script setup lang="ts">
import { getTeacherList } from '@/api/base'
import type { ITeacherList } from '@/types'
import TeacherList from '../components/TeacherList.vue'
import { getHomeData } from '../api'
interface ITeacherAllList {
loading: boolean
page: number
total: number
list: ITeacherList[]
}
const teacherList = $ref<ITeacherAllList>({
loading: false,
page: 1,
total: 0,
list: []
let data: any = $ref()
getHomeData().then(res => {
data = res.data.lecture
})
const hasMore = ref(false)
let tabIndex = $ref(0)
const handleGetTeacherList = (val: any) => {
const params: any = { page_size: 20, page: teacherList.page, type: (val + 1).toString() }
teacherList.loading = true
getTeacherList(params)
.then(res => {
const { total, list } = res.data
teacherList.total = total
teacherList.list = teacherList.list.concat(list)
if (teacherList.list.length < total) {
hasMore.value = true
teacherList.page++
} else if (teacherList.list.length === total) {
hasMore.value = false
}
})
.finally(() => {
teacherList.loading = false
})
}
onMounted(() => {
handleGetTeacherList(0)
})
const handleTabClick = (val: number) => {
tabIndex = val
teacherList.page = 1
teacherList.total = 0
teacherList.list = []
handleGetTeacherList(val)
}
// 滚动加载
const el = ref<HTMLElement>()
useInfiniteScroll(
document,
() => {
// load more
if (tabIndex === 0 && hasMore.value) {
!teacherList.loading && handleGetTeacherList(0)
} else if (tabIndex === 1 && hasMore.value) {
!teacherList.loading && handleGetTeacherList(1)
}
},
{ distance: 50 }
)
</script>
<template>
<img src="https://webapp-pub.ezijing.com/project/prp-h5/teacher_banner.png" style="width: 100%" />
<div class="teacher_tab">
<div class="tab_name" :class="tabIndex === 0 ? 'active_tab' : 'inactive_tab'" @click="handleTabClick(0)">
清华博导讲师团
</div>
<div class="tab_line"></div>
<div class="tab_name" :class="tabIndex === 1 ? 'active_tab' : 'inactive_tab'" @click="handleTabClick(1)">
紫荆实战导师团
<AppContainer title="教师列表" headerAlign="center"></AppContainer>
<div class="teacher-list">
<div class="item" v-for="item in data" :key="item?.id">
<img :src="item?.avatar" />
<div class="info">
<div class="name">{{ item?.name }}</div>
<div class="li" v-if="item.office !== ''">{{ item.office }}</div>
<div class="li" v-if="item.title !== ''">{{ item.title }}</div>
</div>
</div>
</div>
<TeacherList :teacherList="teacherList.list" ref="el" />
</template>
<style lang="scss" scoped>
.teacher_tab {
width: 5.06rem;
height: 0.8rem;
background: #ffffff;
border-top-left-radius: 0.4rem;
border-top-right-radius: 0.4rem;
position: relative;
z-index: 100;
margin: -20px auto;
display: flex;
justify-content: space-around;
align-items: center;
.tab_line {
width: 0px;
height: 0.25rem;
border: 1px solid #d3d3d3;
}
.active_tab {
color: #e9a724;
}
.tab_name {
font-size: 0.28rem;
font-weight: 400;
cursor: pointer;
}
.inactive_tab {
color: #999999;
}
}
.teacher_list {
margin-top: 0.7rem;
display: flex;
justify-content: space-between;
flex-wrap: wrap;
.list_item {
width: 3.35rem;
margin-bottom: 0.2rem;
.teacher-list {
.item {
background: #ffffff;
box-shadow: 0px 2px 8px 1px rgba(0, 0, 0, 0.16);
border-radius: 0.16rem;
padding: 0.3rem 0.2rem;
display: flex;
img {
width: 100%;
height: 3rem;
width: 1.15rem;
height: 1.4rem;
object-fit: cover;
border-top-left-radius: 0.2rem;
border-top-right-radius: 0.2rem;
border-radius: 0.1rem;
}
.item_bottom {
background: url(https://webapp-pub.ezijing.com/project/prp-h5/teacher_bg.png) no-repeat;
background-size: cover;
width: 100%;
height: 2.24rem;
border-bottom-left-radius: 0.2rem;
border-bottom-right-radius: 0.2rem;
padding: 0.13rem 0 0 0.16rem;
box-sizing: border-box;
.info {
margin-left: 0.18rem;
.name {
font-size: 0.3rem;
font-weight: 500;
font-size: 0.28rem;
color: #333333;
line-height: 0.34rem;
}
.desc {
.li {
font-size: 0.24rem;
font-weight: 400;
line-height: 0.32rem;
color: #666666;
margin-top: 0.07rem;
// line-height: 100%;
padding-left: 0.2rem;
position: relative;
margin-top: 0.18rem;
&::after {
content: '';
position: absolute;
top: 50%;
left: 0;
margin-top: -0.04rem;
width: 0.08rem;
height: 0.08rem;
background-color: #c1ab85;
border-radius: 50%;
}
}
}
}
......
import httpRequest from '@/utils/axios'
// 获取团队列表
export function getTeamList(params?: { page?: number; page_size?: number }) {
return httpRequest.get('/api/psp/v1/team/list', { params })
}
// 创建团队
export function createTeam(data: { name: string; slogan: string; logo: string; brief: string }) {
return httpRequest.post('/api/psp/v1/team/create-team', data)
}
// 获取团队详情
export function getTeam(params: { id: string }) {
return httpRequest.get('/api/psp/v1/team/view', { params })
}
// 加入团队
export function joinTeam(data: { id: string }) {
return httpRequest.post('/api/psp/v1/team/join', data)
}
// 团队-上传资料/发布讨论
export function createTeamPosts(data: { type: string; team_visible: string; title: string; desc: string; picture?: string; file?: string }) {
return httpRequest.post('/api/psp/v1/team/upload', data)
}
// 团队-成员列表
export function getTeamMemberList(params: { id: string; type: string; page?: number; page_size?: number }) {
return httpRequest.get('/api/psp/v1/team/members', { params })
}
// 团队-团队文件/讨论列表
export function getTeamFileOrQuestions(params: { id: string; type: string; page?: number; page_size?: number }) {
return httpRequest.get('/api/psp/v1/team/files-or-questions', { params })
}
// 团队-团队文件/讨论 创建评论
export function createTeamComment(data: { entity_id: string; type: string; content: string; to_comment_id?: string }) {
return httpRequest.post('/api/psp//v1/team/create-comment', data)
}
<script setup lang="ts">
import { Toast } from 'vant'
import PublishItem from '@/components/PublishItem.vue'
import { getTeamFileOrQuestions, createTeamComment } from '../api'
const props = defineProps<{ id: string }>()
// 获取列表数据
const page = ref<number>(1)
const dataset = reactive<{ total: number; list: Record<string, any>[] }>({ total: 0, list: [] })
async function fetchList(isRefresh?: boolean) {
if (isRefresh) {
page.value = 1
}
const { data } = await getTeamFileOrQuestions({ id: props.id, type: 'file', page: page.value, page_size: 20 })
dataset.total = data.total
dataset.list = isRefresh ? data.list : [...dataset.list, ...data.list]
}
onMounted(() => {
fetchList()
})
// 评论
const onSubmitComment = (data: any, action: string) => {
if (action === 'comment') {
// 评论
createTeamComment({
type: 'file',
entity_id: data.id,
content: data.comment
}).then(() => {
Toast.success('评论成功')
fetchList(true)
})
} else {
// 回复
createTeamComment({
type: 'file',
entity_id: data.entity_id,
content: data.comment,
to_comment_id: data.id
}).then(() => {
Toast.success('回复成功')
fetchList(true)
})
}
}
</script>
<template>
<AppCard>
<template #header-aside>
<div class="button"><router-link :to="{ name: 'teamFilePublish', params: { id } }">上传</router-link></div>
</template>
<template v-if="dataset.list?.length">
<PublishItem v-for="item in dataset.list" :data="item" :key="item.id" @submitComment="onSubmitComment"></PublishItem>
</template>
<van-empty description="暂无内容" v-else />
</AppCard>
</template>
<style lang="scss" scoped>
:deep(.publish-item) {
padding: 0.24rem;
margin-bottom: 0.2rem;
background: #fff;
border-radius: 0.2rem;
}
</style>
<script setup lang="ts">
import { getTeamMemberList } from '../api'
const props = defineProps<{ id: string }>()
// 获取列表数据
const page = ref<number>(1)
const dataset = reactive<{ total: number; list: Record<string, any>[] }>({ total: 0, list: [] })
async function fetchList(isRefresh?: boolean) {
if (isRefresh) {
page.value = 1
}
const { data } = await getTeamMemberList({ id: props.id, type: 'file', page: page.value, page_size: 20 })
dataset.total = data.total
dataset.list = isRefresh ? data.list : [...dataset.list, ...data.list]
}
onMounted(() => {
fetchList()
})
</script>
<template>
<AppCard>
<div class="member-item" v-for="item in dataset.list" :key="item.user_id">
<Avatar :src="item.user_info.avatar" class="member-item-avatar"></Avatar>
<div class="member-item-content">
<h5>{{ item.user_info.name }}</h5>
<ul>
<li v-for="label in item.user_info.label" :key="label">{{ label }}</li>
</ul>
</div>
</div>
</AppCard>
</template>
<style lang="scss" scoped>
.member-item {
display: flex;
align-items: center;
background-color: #fff;
padding: 0.24rem;
margin-bottom: 0.2rem;
border-radius: 0.2rem;
}
.member-item-avatar {
width: 0.68rem;
height: 0.68rem;
border-radius: 50%;
overflow: hidden;
object-fit: cover;
}
.member-item-content {
flex: 1;
margin-left: 0.18rem;
h5 {
font-size: 0.24rem;
font-weight: 400;
line-height: 0.34rem;
color: #333333;
}
ul {
display: flex;
}
li {
margin-top: 0.05rem;
padding: 0 0.2rem;
font-size: 0.18rem;
font-weight: 300;
line-height: 0.28rem;
color: #666666;
background: #edf6ff;
}
li + li {
margin-left: 0.1rem;
}
}
</style>
<script setup lang="ts">
import { Toast } from 'vant'
import PublishItem from '@/components/PublishItem.vue'
import { getTeamFileOrQuestions, createTeamComment } from '../api'
const props = defineProps<{ id: string }>()
// 获取列表数据
const dataset = reactive<{ total: number; list: Record<string, any>[] }>({ total: 0, list: [] })
async function fetchList() {
const { data } = await getTeamFileOrQuestions({ id: props.id, type: 'question' })
dataset.total = data.total
dataset.list = [...dataset.list, ...data.list]
}
onMounted(() => {
fetchList()
})
// 评论
const onSubmitComment = (data: any, action: string) => {
if (action === 'comment') {
// 评论
createTeamComment({
type: 'question',
entity_id: data.id,
content: data.comment
}).then(() => {
Toast.success('评论成功')
})
} else {
// 回复
createTeamComment({
type: 'question',
entity_id: data.entity_id,
content: data.comment,
to_comment_id: data.id
}).then(() => {
Toast.success('回复成功')
})
}
}
</script>
<template>
<AppCard>
<template #header-aside>
<div class="button"><router-link :to="{ name: 'teamQuestionPublish', params: { id } }"> 发布</router-link></div>
</template>
<template v-if="dataset.list?.length">
<PublishItem v-for="item in dataset.list" :data="item" :key="item.id" @submitComment="onSubmitComment"></PublishItem>
</template>
<van-empty description="暂无内容" v-else />
</AppCard>
</template>
<style lang="scss" scoped>
:deep(.publish-item) {
padding: 0.24rem;
margin-bottom: 0.2rem;
background: #fff;
border-radius: 0.2rem;
}
</style>
<script setup lang="ts">
import { onMounted, ref } from 'vue'
import { getTeamList } from '../api'
import type { ITeam } from '@/types'
const router = useRouter()
const list = ref<ITeam[]>([])
function fetchTeamList() {
getTeamList({ page_size: 100 }).then(res => {
list.value = res.data.list.list
})
}
onMounted(() => {
fetchTeamList()
})
</script>
<template>
<ul class="team-list">
<li v-for="(item, index) in list" :key="index" @click="router.push({ name: 'teamView', params: { id: item.id } })">
<div class="team-order">
<div class="order">{{ index > 2 ? index + 1 : '' }}</div>
</div>
<div class="team-info">
<div class="title">{{ item.name }}</div>
<div class="desc">{{ item.slogan }} | {{ item.members_count }}</div>
</div>
</li>
</ul>
</template>
<style lang="scss">
.team-list {
background: #ffffff;
border-radius: 0.15rem;
opacity: 1;
li {
border-bottom: 0.01rem solid #e4e4e4;
padding: 0.35rem 0;
display: flex;
.team-order {
width: 1.8rem;
display: flex;
justify-content: center;
align-items: center;
.order {
position: relative;
font-size: 0.4rem;
color: #e68626;
&::before {
content: '';
position: absolute;
bottom: 0.05rem;
left: 50%;
opacity: 0.5;
transform: translateX(-50%);
background: linear-gradient(270deg, #ffeae6 0%, #ffffff 100%);
width: 0.7rem;
height: 0.1rem;
border-radius: 25%;
}
}
}
&:last-child {
border: none;
}
&:nth-child(1) {
.order {
width: 0.54rem;
height: 0.6rem;
background: url(https://webapp-pub.ezijing.com/project/prp-h5/team-rk1.png);
background-size: 100% 100%;
&::before {
display: none;
}
}
}
&:nth-child(2) {
.order {
width: 0.54rem;
height: 0.6rem;
background: url(https://webapp-pub.ezijing.com/project/prp-h5/team-rk2.png);
background-size: 100% 100%;
&::before {
display: none;
}
}
}
&:nth-child(3) {
.order {
width: 0.54rem;
height: 0.6rem;
background: url(https://webapp-pub.ezijing.com/project/prp-h5/team-rk3.png);
background-size: 100% 100%;
&::before {
display: none;
}
}
}
.team-info {
.title {
font-size: 0.3rem;
font-weight: 400;
line-height: 100%;
color: #4e4e4e;
}
.desc {
font-size: 0.26rem;
line-height: 100%;
margin-top: 0.1rem;
color: #999999;
}
}
}
}
</style>
import type { RouteRecordRaw } from 'vue-router'
import AppLayout from '@/components/layout/Index.vue'
export const routes: Array<RouteRecordRaw> = [
{
path: '/team',
component: AppLayout,
children: [
{ name: 'teamList', path: '', component: () => import('./views/Index.vue') },
{
name: 'teamCreate',
path: 'create',
component: () => import('./views/Create.vue'),
meta: { requireLogin: true }
},
{ name: 'teamView', path: 'view/:id', component: () => import('./views/View.vue'), props: true },
{
name: 'teamFilePublish',
path: 'view/:id/file/publish',
component: () => import('./views/PublishFile.vue'),
meta: { requireLogin: true },
props: true
},
{
name: 'teamQuestionPublish',
path: 'view/:id/question/publish',
component: () => import('./views/PublishQuestion.vue'),
meta: { requireLogin: true },
props: true
}
]
}
]
export interface TeamType {
id: string
name: string
slogan: string
brief: string
logo: string
star: string
members_count: string
files_count: string
questions_count: string
}
<script setup lang="ts">
import { reactive } from 'vue'
import { useRouter } from 'vue-router'
import { createTeam } from '../api'
import AppUpload from '@/components/base/AppUpload.vue'
import { Toast } from 'vant'
const router = useRouter()
const form = reactive({ name: '', slogan: '', logo: '', brief: '' })
function onSubmit() {
createTeam(form).then(() => {
Toast.success('创建成功')
router.replace('/team')
})
}
</script>
<template>
<AppContainer title="团队创建" backgroundColor="#fff" headerAlign="center">
<van-form @submit="onSubmit">
<van-field v-model="form.name" placeholder="团队名称" :rules="[{ required: true, message: '请输入团队名称' }]" />
<van-field
v-model="form.slogan"
placeholder="团队口号"
:rules="[{ required: true, message: '请输入团队口号' }]"
/>
<van-field
v-model="form.brief"
type="textarea"
placeholder="团队简介"
:autosize="{ minHeight: 200 }"
:rules="[{ required: true, message: '请输入团队简介' }]"
/>
<van-field :rules="[{ required: true, message: '请上传团队Logo' }]">
<template #input>
<AppUpload v-model="form.logo"></AppUpload>
</template>
</van-field>
<van-button block round native-type="submit" class="my-button">立即创建</van-button>
</van-form>
</AppContainer>
</template>
<style lang="scss" scoped>
:deep(.van-cell) {
padding-left: 0;
padding-right: 0;
&::after {
left: 0;
right: 0;
}
}
.my-button {
margin: 1rem 0;
}
</style>
<script setup lang="ts">
import { Toast } from 'vant'
import { getTeamList } from '../api'
import TeamList from '../components/TeamList.vue'
import useWXShare from '@/utils/wx'
const router = useRouter()
// let list = $ref<Record<string, any>[]>()
let myTeam = $ref<{ team_id: string }>()
async function fetchList() {
const { data = {} } = await getTeamList()
// list = data.list?.list || []
myTeam = data.my_team
}
onMounted(() => {
fetchList()
useWXShare({ desc: '加入【PRP学友会】 与团队一起 共享共建PRP成果' })
})
// 创建团队
function createTeam() {
if (myTeam) {
Toast.fail('您已经加入了团队')
} else {
router.push({ name: 'teamCreate' })
}
}
// 我的团队
function viewMyTeam() {
if (myTeam) {
router.push({ name: 'teamView', params: { id: myTeam.team_id } })
} else {
Toast.fail('您还没有加入任何团队')
}
}
</script>
<template>
<AppContainer title="团队总榜">
<div class="team-header">
<div class="btn-box" @click="createTeam">
<img src="https://webapp-pub.ezijing.com/project/prp-h5/team-h2.png" />
<div class="b-text">团队创建</div>
</div>
<div class="btn-box" @click="viewMyTeam">
<img src="https://webapp-pub.ezijing.com/project/prp-h5/team-h1.png" />
<div class="b-text">我的团队</div>
</div>
</div>
<TeamList></TeamList>
</AppContainer>
</template>
<style lang="scss">
.team-header {
display: flex;
padding: 0.46rem 0 0.42rem;
justify-content: space-evenly;
.btn-box {
img {
height: 0.53rem;
display: block;
margin: 0 auto;
}
.b-text {
margin-top: 0.15rem;
font-size: 0.28rem;
color: #666666;
line-height: 100%;
}
}
}
</style>
<script setup lang="ts">
import { createTeamPosts } from '../api'
import AppUpload from '@/components/base/AppUpload.vue'
import { Toast } from 'vant'
const props = defineProps<{ id: string }>()
const router = useRouter()
const form = reactive({
type: 'file',
team_visible: '1',
title: '',
desc: '',
file: []
})
function onSubmit() {
const params = Object.assign({}, form, { file: JSON.stringify(form.file) })
createTeamPosts(params).then(() => {
Toast.success('上传成功')
router.push({ name: 'teamView', params: { id: props.id } })
})
}
</script>
<template>
<AppContainer title="上传资料" backgroundColor="#fff" headerAlign="center">
<van-form @submit="onSubmit">
<van-field v-model="form.title" placeholder="标题" :rules="[{ required: true, message: '请输入标题' }]" />
<van-field
v-model="form.desc"
type="textarea"
placeholder="正文"
:autosize="{ minHeight: 200 }"
:rules="[{ required: true, message: '请输入资料内容' }]"
/>
<van-field>
<template #input>
<AppUpload accept="*" v-model="form.file"></AppUpload>
</template>
</van-field>
<van-button block round native-type="submit" class="my-button">上传</van-button>
</van-form>
</AppContainer>
</template>
<style lang="scss" scoped>
:deep(.van-cell) {
padding-left: 0;
padding-right: 0;
&::after {
left: 0;
right: 0;
}
}
.my-button {
margin: 1rem 0;
}
</style>
<script setup lang="ts">
import { createTeamPosts } from '../api'
import AppUpload from '@/components/base/AppUpload.vue'
import { Toast } from 'vant'
const props = defineProps<{ id: string }>()
const router = useRouter()
const form = reactive({
type: 'question',
team_visible: '1',
title: '',
desc: '',
picture: []
})
function onSubmit() {
const params = Object.assign({}, form, { picture: JSON.stringify(form.picture) })
createTeamPosts(params).then(() => {
Toast.success('发布成功')
router.push({ name: 'teamView', params: { id: props.id } })
})
}
</script>
<template>
<AppContainer title="发布问题" backgroundColor="#fff" headerAlign="center">
<van-form @submit="onSubmit">
<van-field v-model="form.title" placeholder="标题" :rules="[{ required: true, message: '请输入标题' }]" />
<van-field
v-model="form.desc"
type="textarea"
placeholder="正文"
:autosize="{ minHeight: 200 }"
:rules="[{ required: true, message: '请输入问题内容' }]"
/>
<van-field>
<template #input>
<AppUpload v-model="form.picture"></AppUpload>
</template>
</van-field>
<van-button block round native-type="submit" class="my-button">发布</van-button>
</van-form>
</AppContainer>
</template>
<style lang="scss" scoped>
:deep(.van-cell) {
padding-left: 0;
padding-right: 0;
&::after {
left: 0;
right: 0;
}
}
.my-button {
margin: 1rem 0;
}
</style>
<script setup lang="ts">
import { Toast } from 'vant'
import MemberList from '../components/MemberList.vue'
import FileList from '../components/FileList.vue'
import QuestionList from '../components/QuestionList.vue'
import * as api from '../api'
import type { TeamType } from '../types'
const props = defineProps<{ id: string }>()
let detail = $ref<{ my_team: null | { team_id: string }; team_info: TeamType }>()
// 获取详情信息
async function fetchDetailInfo() {
const { data } = await api.getTeam({ id: props.id })
detail = data
}
onMounted(() => {
fetchDetailInfo()
})
// 是否可以加入团队
const canJoin = computed(() => {
return !detail.my_team?.team_id
})
// 是否加入该团队
const isJoined = computed(() => {
return detail.my_team?.team_id === detail.team_info.id
})
// 加入团队
async function joinTeam() {
await api.joinTeam({ id: props.id })
Toast.success('加入成功')
}
</script>
<template>
<AppContainer title="团队详情">
<div class="info" v-if="detail">
<div class="info-pic"><img :src="detail.team_info.logo" /></div>
<div class="info-main">
<h2>{{ detail.team_info.name }}</h2>
<h6>{{ detail.team_info.slogan }}</h6>
<p>
<span>人员数 {{ detail.team_info.members_count }}</span>
<span>积分数 {{ detail.team_info.star }}</span>
</p>
<p>
<span>资料数 {{ detail.team_info.files_count }}</span>
<span>问答数 {{ detail.team_info.questions_count }}</span>
</p>
<div class="info-join" @click="joinTeam" v-if="canJoin">加入</div>
<div class="info-joined" v-if="isJoined">已加入</div>
</div>
</div>
<van-tabs shrink color="#033974" line-width="30px" background="transparent" title-active-color="#033974" title-inactive-color="#4E4E4E">
<van-tab title="成员">
<MemberList :id="id"></MemberList>
</van-tab>
<van-tab title="资料">
<FileList :id="id"></FileList>
</van-tab>
<van-tab title="问答">
<QuestionList :id="id"></QuestionList>
</van-tab>
</van-tabs>
</AppContainer>
</template>
<style lang="scss" scoped>
.info {
display: flex;
}
.info-pic {
margin-right: 0.16rem;
width: 2rem;
height: 2.4rem;
border-radius: 0.2rem;
overflow: hidden;
img {
width: 100%;
height: 100%;
object-fit: cover;
}
}
.info-main {
flex: 1;
overflow: hidden;
h2 {
font-size: 0.32rem;
font-weight: 500;
line-height: 0.45rem;
color: #333333;
}
h6 {
margin-top: 0.02rem;
margin-bottom: 0.12rem;
font-size: 0.28rem;
font-weight: 400;
line-height: 0.4rem;
color: #4e4e4e;
}
p {
margin-top: 0.02rem;
font-size: 0.24rem;
line-height: 0.3rem;
color: #999999;
span + span {
margin-left: 0.4rem;
}
}
}
.info-join {
margin-top: 0.3rem;
display: inline-block;
min-width: 1.3rem;
height: 0.44rem;
font-size: 0.24rem;
line-height: 0.44rem;
color: #fff;
text-align: center;
background: #033974;
border-radius: 0.22rem;
}
.info-joined {
margin-top: 0.3rem;
font-size: 0.24rem;
line-height: 0.44rem;
color: #033974;
}
:deep(.van-tabs__nav) {
padding-left: 0;
padding-right: 0;
padding-bottom: 8px;
}
:deep(.van-tab--shrink) {
padding: 0;
margin-right: 30px;
}
</style>
......@@ -7,6 +7,7 @@ const httpRequest = axios.create({
timeout: 60000,
withCredentials: true,
headers: {
tenant: 'wmpc',
'Content-Type': 'application/x-www-form-urlencoded'
}
})
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论