8
.env
|
|
@ -1,8 +0,0 @@
|
|||
#VITE_BASE_URL=https://system.qhdsafety.com/qa-education-org/
|
||||
#VITE_BASE_URL=http://192.168.0.31:7091/qa-education-stu/
|
||||
VITE_BASE_URL=http://192.168.0.30:8893/
|
||||
#VITE_BASE_URL=https://pxapp.qhdsafety.com/api
|
||||
VITE_PROXY=/api
|
||||
|
||||
VITE_FILE_PATH=https://file.zcloudchina.com/JYPXFile
|
||||
|
||||
|
|
@ -1 +0,0 @@
|
|||
VITE_BASE=/
|
||||
|
|
@ -1 +0,0 @@
|
|||
VITE_BASE=/app
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
public
|
||||
dist
|
||||
package.json
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
module.exports = {
|
||||
env: {
|
||||
browser: true,
|
||||
es2021: true,
|
||||
node: true
|
||||
},
|
||||
extends: [
|
||||
"plugin:vue/vue3-essential",
|
||||
"standard",
|
||||
"@vue/prettier",
|
||||
"eslint:recommended",
|
||||
],
|
||||
parserOptions: {
|
||||
ecmaVersion: "latest",
|
||||
sourceType: "module",
|
||||
},
|
||||
plugins: ["vue"],
|
||||
rules: {
|
||||
"no-console": "warn",
|
||||
"vue/multi-word-component-names": "off",
|
||||
camelcase: "off",
|
||||
eqeqeq: "error",
|
||||
"vue/eqeqeq": "error",
|
||||
"no-unused-vars": [
|
||||
"error",
|
||||
{vars: "all", args: "after-used", ignoreRestSiblings: false},
|
||||
],
|
||||
},
|
||||
globals: {
|
||||
defineOptions: "readonly",
|
||||
},
|
||||
};
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
# Vue 3 + Vite
|
||||
|
||||
This template should help get you started developing with Vue 3 in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
|
||||
|
||||
## Recommended IDE Setup
|
||||
|
||||
- [VS Code](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur) + [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin).
|
||||
16
index.html
|
|
@ -1,16 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no" />
|
||||
<title>学员端</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<noscript>
|
||||
<strong>很抱歉,如果没有启用JavaScript,网站无法正常工作,请启用JavaScript使其正常工作。</strong>
|
||||
</noscript>
|
||||
<script type="module" src="/src/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": [
|
||||
"src/*"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.md]
|
||||
insert_final_newline = false
|
||||
trim_trailing_whitespace = false
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
/.idea
|
||||
/.claude
|
||||
/.nuxt
|
||||
/dist
|
||||
|
||||
/node_modules
|
||||
*.local
|
||||
yarn.lock
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
# Nuxt Minimal Starter
|
||||
|
||||
Look at the [Nuxt documentation](https://nuxt.com/docs/getting-started/introduction) to learn more.
|
||||
|
||||
## Setup
|
||||
|
||||
Make sure to install dependencies:
|
||||
|
||||
```bash
|
||||
# npm
|
||||
npm install
|
||||
|
||||
# pnpm
|
||||
pnpm install
|
||||
|
||||
# yarn
|
||||
yarn install
|
||||
|
||||
# bun
|
||||
bun install
|
||||
```
|
||||
|
||||
## Development Server
|
||||
|
||||
Start the development server on `http://localhost:3000`:
|
||||
|
||||
```bash
|
||||
# npm
|
||||
npm run dev
|
||||
|
||||
# pnpm
|
||||
pnpm dev
|
||||
|
||||
# yarn
|
||||
yarn dev
|
||||
|
||||
# bun
|
||||
bun run dev
|
||||
```
|
||||
|
||||
## Production
|
||||
|
||||
Build the application for production:
|
||||
|
||||
```bash
|
||||
# npm
|
||||
npm run build
|
||||
|
||||
# pnpm
|
||||
pnpm build
|
||||
|
||||
# yarn
|
||||
yarn build
|
||||
|
||||
# bun
|
||||
bun run build
|
||||
```
|
||||
|
||||
Locally preview production build:
|
||||
|
||||
```bash
|
||||
# npm
|
||||
npm run preview
|
||||
|
||||
# pnpm
|
||||
pnpm preview
|
||||
|
||||
# yarn
|
||||
yarn preview
|
||||
|
||||
# bun
|
||||
bun run preview
|
||||
```
|
||||
|
||||
Check out the [deployment documentation](https://nuxt.com/docs/getting-started/deployment) for more information.
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
<template>
|
||||
<div>
|
||||
<NuxtRouteAnnouncer />
|
||||
<NuxtLayout>
|
||||
<NuxtPage />
|
||||
</NuxtLayout>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
</script>
|
||||
<style>
|
||||
body{
|
||||
font-family: -apple-system, BlinkMacSystemFont, Arial, PingFang SC, Hiragino Sans GB, Microsoft YaHei, WenQuanYi Micro Hei, sans-serif;
|
||||
}
|
||||
</style>
|
||||
|
After Width: | Height: | Size: 906 KiB |
|
After Width: | Height: | Size: 350 KiB |
|
After Width: | Height: | Size: 32 KiB |
|
After Width: | Height: | Size: 78 KiB |
|
After Width: | Height: | Size: 4.5 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
|
|
@ -0,0 +1,141 @@
|
|||
<template>
|
||||
<header class="head_container">
|
||||
<div class="logo">
|
||||
<img src="../../assets/images/logo.png" alt="河北秦安安全科技股份有限公司"/>
|
||||
</div>
|
||||
<div style="display: flex">
|
||||
<nav class="nav_list">
|
||||
<ul>
|
||||
<li><NuxtLink to="">首页</NuxtLink></li>
|
||||
<li
|
||||
@mouseenter="isProductHovered = true"
|
||||
@mouseleave="isProductHovered = false"
|
||||
>
|
||||
<NuxtLink to="">产品</NuxtLink>
|
||||
<span v-if="isProductHovered">
|
||||
<a
|
||||
v-for="(item, index) in 5"
|
||||
:key="index"
|
||||
v-motion
|
||||
href=""
|
||||
:initial="{ opacity: 0, y: -10 }"
|
||||
:enter="{ opacity: 1, y: 0, transition: { delay: index * 50, duration: 300 } }"
|
||||
:leave="{ opacity: 0, y: -10, transition: { duration: 200 } }"
|
||||
>
|
||||
产品{{index}}
|
||||
</a>
|
||||
</span>
|
||||
</li>
|
||||
<li><NuxtLink to="">解决方案</NuxtLink></li>
|
||||
<li><NuxtLink to="">新闻资讯</NuxtLink></li>
|
||||
<li><NuxtLink to="">合作案例</NuxtLink></li>
|
||||
<li><NuxtLink to="">服务流程</NuxtLink></li>
|
||||
<li><NuxtLink to="">关于秦安</NuxtLink></li>
|
||||
<li><NuxtLink to="">秦安安全大模型</NuxtLink></li>
|
||||
</ul>
|
||||
<div class="num">股票代码:871771</div>
|
||||
</nav>
|
||||
</div>
|
||||
</header>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
const isProductHovered = ref(false);
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
.head_container{
|
||||
min-width: 1400px;
|
||||
width: 100%;
|
||||
height: 70px;
|
||||
background: rgba(4,17,40,0.52);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
border-bottom: 1px solid rgba(255,255,255,0.2);
|
||||
align-items: center;
|
||||
padding: 0 20px;
|
||||
box-sizing: border-box;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 999;
|
||||
.nav_list{
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
.num{
|
||||
color: #ffffff;
|
||||
font-size: 14px;
|
||||
margin-left: 50px;
|
||||
}
|
||||
ul{
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
li{
|
||||
list-style: none;
|
||||
color: #ffffff;
|
||||
padding: 0 15px;
|
||||
font-size: 14px;
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
a{
|
||||
color: #ffffff;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
height: 70px;
|
||||
line-height: 70px;
|
||||
&:before{
|
||||
content: "";
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
-webkit-transition-property: left, right;
|
||||
transition-property: left, right;
|
||||
bottom: 0;
|
||||
background-color: #ffffff;
|
||||
height: 2px;
|
||||
width: 100%;
|
||||
border-radius: 2px 2px 0 0;
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
&:hover{
|
||||
a{
|
||||
&:before{
|
||||
opacity: 1;
|
||||
left: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
span{
|
||||
position: absolute;
|
||||
top: 70px;
|
||||
background: #fff;
|
||||
width: 150px;
|
||||
text-align: center;
|
||||
left: 50%;
|
||||
margin-left: -75px;
|
||||
box-shadow: 0 0 12px 0 rgba(0, 0, 0, 0.08);
|
||||
padding: 10px 0;
|
||||
border-radius: 4px;
|
||||
a{
|
||||
color: #333333;
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 50px;
|
||||
line-height: 50px;
|
||||
&:hover{
|
||||
color: #1d78ff;
|
||||
background: #ddebff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,471 @@
|
|||
|
||||
<template>
|
||||
<div class="container">
|
||||
<AppHead/>
|
||||
<!-- banner-->
|
||||
<div class="banner">
|
||||
<swiper
|
||||
:modules="modules"
|
||||
:slides-per-view="1"
|
||||
:space-between="0"
|
||||
:autoplay="{ delay: 5000, disableOnInteraction: false }"
|
||||
:pagination="{ clickable: true }"
|
||||
class="banner-swiper"
|
||||
>
|
||||
<swiper-slide>
|
||||
<div class="banner-slide banner-slide-1">
|
||||
<div class="content">
|
||||
<h1>具有专业化背景的全国领先数字化服务公司</h1>
|
||||
<p>深入垂直行业,以数字化技术为引擎,为客户提供战略、业务、技术一体化的解决方案。</p>
|
||||
<div class="btn"><a>了解详情</a></div>
|
||||
</div>
|
||||
</div>
|
||||
</swiper-slide>
|
||||
<swiper-slide>
|
||||
<div class="banner-slide banner-slide-2">
|
||||
<div class="content">
|
||||
<h1>具有专业化背景的全国领先数字化服务公司</h1>
|
||||
<p>深入垂直行业,以数字化技术为引擎,为客户提供战略、业务、技术一体化的解决方案。</p>
|
||||
<div class="btn"><a>了解详情</a></div>
|
||||
</div>
|
||||
</div>
|
||||
</swiper-slide>
|
||||
</swiper>
|
||||
<div class="info">
|
||||
<div class="item">
|
||||
<p class="num">10+</p>
|
||||
<p class="text">安全生产业务评价检测资质</p>
|
||||
<p class="notes">24小时随时匹配覆盖</p>
|
||||
</div>
|
||||
<div class="item">
|
||||
<p class="num">10+</p>
|
||||
<p class="text">软件著作知识产权</p>
|
||||
<p class="notes">辨识风险,瞬息到位</p>
|
||||
</div>
|
||||
<div class="item">
|
||||
<p class="num">20000 +</p>
|
||||
<p class="text">自制研发安全培训课件</p>
|
||||
<p class="notes">自带考题,完美闭环</p>
|
||||
</div>
|
||||
<div class="item">
|
||||
<p class="num">100000 +</p>
|
||||
<p class="text">安全评价、职业卫生评价</p>
|
||||
<p class="notes">检测报告、标准化评审报告</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 新闻中心-->
|
||||
<div class="news_container">
|
||||
<div class="title">
|
||||
<div class="tit">新闻中心</div>
|
||||
<p>助力提升安全管理效益,满足多行业的安全生产需求</p>
|
||||
</div>
|
||||
<div class="new_main">
|
||||
<div class="new_left">
|
||||
<swiper
|
||||
:modules="modules"
|
||||
:slides-per-view="1"
|
||||
:space-between="0"
|
||||
:autoplay="{ delay: 3000, disableOnInteraction: false }"
|
||||
:pagination="{ clickable: true }"
|
||||
class="news-swiper"
|
||||
>
|
||||
<swiper-slide>
|
||||
<div class="item">
|
||||
<div class="img"><img src="@/assets/images/img1.png" alt="河北秦安安全科技股份有限公司"/></div>
|
||||
<div class="text">
|
||||
<p class="title">厚积薄发十五载,上市一年更精彩,秦安充满期待,值得信赖!</p>
|
||||
</div>
|
||||
</div>
|
||||
</swiper-slide>
|
||||
<swiper-slide>
|
||||
<div class="item">
|
||||
<div class="img"><img src="@/assets/images/img1.png" alt="新闻标题2"/></div>
|
||||
<div class="text">
|
||||
<p class="title">数字化赋能安全生产,科技创新引领行业发展新方向</p>
|
||||
</div>
|
||||
</div>
|
||||
</swiper-slide>
|
||||
<swiper-slide>
|
||||
<div class="item">
|
||||
<div class="img"><img src="@/assets/images/img1.png" alt="新闻标题3"/></div>
|
||||
<div class="text">
|
||||
<p class="title">公司荣获多项行业大奖,专业实力再获认可</p>
|
||||
</div>
|
||||
</div>
|
||||
</swiper-slide>
|
||||
</swiper>
|
||||
</div>
|
||||
<div class="new_right">
|
||||
<div class="item">
|
||||
<div class="img">
|
||||
<img src="@/assets/images/img2.png" alt="河北秦安安全科技股份有限公司"/>
|
||||
</div>
|
||||
<div class="info">
|
||||
<div class="title">
|
||||
喜报·我司荣获2025“数据要素X”大赛河北分赛三等奖
|
||||
</div>
|
||||
<div class="time">2025-4-10</div>
|
||||
<div class="text">4月10日,2025年“数据要素X”大赛启动仪式在雄安新区成功举办。河北秦安安全科技股份有限公司申报的“危化智融-多模态智能...</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item">
|
||||
<div class="img">
|
||||
<img src="@/assets/images/img2.png" alt="河北秦安安全科技股份有限公司"/>
|
||||
</div>
|
||||
<div class="info">
|
||||
<div class="title">
|
||||
喜报·我司荣获2025“数据要素X”大赛河北分赛三等奖
|
||||
</div>
|
||||
<div class="time">2025-4-10</div>
|
||||
<div class="text">4月10日,2025年“数据要素X”大赛启动仪式在雄安新区成功举办。河北秦安安全科技股份有限公司申报的“危化智融-多模态智能...</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item">
|
||||
<div class="img">
|
||||
<img src="@/assets/images/img2.png" alt="河北秦安安全科技股份有限公司"/>
|
||||
</div>
|
||||
<div class="info">
|
||||
<div class="title">
|
||||
喜报·我司荣获2025“数据要素X”大赛河北分赛三等奖
|
||||
</div>
|
||||
<div class="time">2025-4-10</div>
|
||||
<div class="text">4月10日,2025年“数据要素X”大赛启动仪式在雄安新区成功举办。河北秦安安全科技股份有限公司申报的“危化智融-多模态智能...</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item">
|
||||
<div class="img">
|
||||
<img src="@/assets/images/img2.png" alt="河北秦安安全科技股份有限公司"/>
|
||||
</div>
|
||||
<div class="info">
|
||||
<div class="title">
|
||||
喜报·我司荣获2025“数据要素X”大赛河北分赛三等奖
|
||||
</div>
|
||||
<div class="time">2025-4-10</div>
|
||||
<div class="text">4月10日,2025年“数据要素X”大赛启动仪式在雄安新区成功举办。河北秦安安全科技股份有限公司申报的“危化智融-多模态智能...</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 核心产品-->
|
||||
|
||||
|
||||
<div class="product_container">
|
||||
<div class="product_main">
|
||||
<div class="title">
|
||||
<div class="tit">核心产品</div>
|
||||
<p>安全生产数智化管理平台,满足多行业的安全生产需求</p>
|
||||
</div>
|
||||
<div class="product_wrap">
|
||||
<div class="top_container">
|
||||
<div class="item active">
|
||||
基层监管平台
|
||||
</div>
|
||||
<div class="item">
|
||||
GBS数字底座
|
||||
</div>
|
||||
<div class="item">
|
||||
一体化智能云平台
|
||||
</div>
|
||||
<div class="item">
|
||||
安全培训开发者平台
|
||||
</div>
|
||||
<div class="item">
|
||||
服务机构自我赋能平台
|
||||
</div>
|
||||
<div class="item">
|
||||
应急管理大数据中心
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { Swiper, SwiperSlide } from "swiper/vue";
|
||||
import { Autoplay, Pagination } from "swiper/modules";
|
||||
import "swiper/css";
|
||||
import "swiper/css/pagination";
|
||||
const modules = [Autoplay, Pagination];
|
||||
</script>
|
||||
<style>
|
||||
body{
|
||||
margin: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.container{
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
.banner{
|
||||
width: 100%;
|
||||
height: 716px;
|
||||
text-align: center;
|
||||
position: relative;
|
||||
|
||||
.banner-swiper {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
:deep(.swiper-pagination) {
|
||||
bottom: 100px;
|
||||
|
||||
.swiper-pagination-bullet {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
background: rgba(255, 255, 255, 0.5);
|
||||
opacity: 1;
|
||||
margin: 0 5px;
|
||||
|
||||
&.swiper-pagination-bullet-active {
|
||||
background: #fff;
|
||||
width: 30px;
|
||||
border-radius: 6px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.banner-slide {
|
||||
width: 100%;
|
||||
height: 716px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.banner-slide-1 {
|
||||
background: url('@/assets/images/banner1.jpg') no-repeat center center;
|
||||
background-size: cover;
|
||||
}
|
||||
|
||||
.banner-slide-2 {
|
||||
background: url('@/assets/images/banner1.jpg') no-repeat center center;
|
||||
background-size: cover;
|
||||
}
|
||||
|
||||
.content{
|
||||
width: 1400px;
|
||||
margin: 0 auto;
|
||||
text-align: center;
|
||||
color: #ffffff;
|
||||
|
||||
h1{
|
||||
font-size: 60px;
|
||||
margin: 0;
|
||||
}
|
||||
p{
|
||||
font-size: 22px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
.btn{
|
||||
border-radius: 50px;
|
||||
padding: 10px 30px;
|
||||
display: inline-block;
|
||||
background-color: rgb(65, 106, 234);
|
||||
box-shadow: 4.974px 3.355px 20px 0 rgba(6, 22, 46, 0.5);
|
||||
}
|
||||
}
|
||||
.info{
|
||||
width: 1400px;
|
||||
position: absolute;
|
||||
bottom: -40px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
left: 50%;
|
||||
transform: translate(-50%);
|
||||
z-index: 999;
|
||||
.item{
|
||||
border-style: solid;
|
||||
border-width: 2px;
|
||||
border-color: rgb(255, 255, 255);
|
||||
border-radius: 4px;
|
||||
background: #ffffff;
|
||||
box-shadow: 0 5px 10px 0 rgba(35, 24, 21, 0.1);
|
||||
width: 22%;
|
||||
padding: 10px;
|
||||
p{
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
}
|
||||
.num{
|
||||
font-size: 26px;
|
||||
color: #1483ff;
|
||||
}
|
||||
.text{
|
||||
color: #222222;
|
||||
font-size: 16px;
|
||||
}
|
||||
.notes{
|
||||
color: #697c9e;
|
||||
font-size: 14px;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.news_container{
|
||||
width: 1400px;
|
||||
margin: 80px auto 0;
|
||||
|
||||
|
||||
.title{
|
||||
text-align: center;
|
||||
.tit{
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-size: 36px;
|
||||
color: #4c4b4b;
|
||||
|
||||
}
|
||||
p{
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
color: #999999;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
.new_main{
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-top: 20px;
|
||||
|
||||
.new_left{
|
||||
width: 50%;
|
||||
background: #ffffff;
|
||||
box-shadow: 0 5px 10px 0 rgba(35, 24, 21, 0.1);
|
||||
padding: 10px;
|
||||
.news-swiper {
|
||||
width: 100%;
|
||||
|
||||
.item{
|
||||
.img{
|
||||
width: 100%;
|
||||
img{
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.text{
|
||||
.title{
|
||||
text-align: left;
|
||||
margin: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
:deep(.swiper-pagination) {
|
||||
right: 10px;
|
||||
left: auto;
|
||||
bottom: 10px;
|
||||
text-align: right;
|
||||
.swiper-pagination-bullet {
|
||||
background: #ddd;
|
||||
opacity: 1;
|
||||
margin: 0 3px;
|
||||
|
||||
&.swiper-pagination-bullet-active {
|
||||
background: #1483ff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.new_right{
|
||||
flex: 1;
|
||||
margin-left: 20px;
|
||||
|
||||
.item{
|
||||
display: flex;
|
||||
border-bottom: 1px solid #eeeeee;
|
||||
padding-bottom: 10px;
|
||||
margin-bottom: 15px;
|
||||
.img{
|
||||
width: 170px;
|
||||
}
|
||||
.info{
|
||||
width: calc(100% - 170px);
|
||||
text-align: left;
|
||||
padding-left: 20px;
|
||||
.title{
|
||||
text-align: left;
|
||||
font-size: 18px;
|
||||
}
|
||||
.time{
|
||||
color: #999999;
|
||||
margin: 10px 0;
|
||||
}
|
||||
.text{
|
||||
color: #434343;
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.product_container{
|
||||
width: 100%;
|
||||
background: #f8f8f8 url("@/assets/images/img3.png") no-repeat right bottom;
|
||||
padding: 50px;
|
||||
box-sizing: border-box;
|
||||
margin-top: 50px;
|
||||
.product_main{
|
||||
width: 1400px;
|
||||
margin: 0 auto;
|
||||
.title{
|
||||
text-align: center;
|
||||
.tit{
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-size: 36px;
|
||||
color: #4c4b4b;
|
||||
}
|
||||
p{
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
color: #999999;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
.product_wrap{
|
||||
width: 100%;
|
||||
.top_container{
|
||||
width: 100%;
|
||||
display: flex;
|
||||
margin-top: 30px;
|
||||
justify-content: space-around;
|
||||
.item{
|
||||
border-radius: 29px;
|
||||
padding: 10px 30px;
|
||||
color: #222222;
|
||||
cursor: pointer;
|
||||
&:hover{
|
||||
background: #1d78ff url("@/assets/images/img4.png") no-repeat right bottom;
|
||||
color: #ffffff;
|
||||
}
|
||||
}
|
||||
.active{
|
||||
background: #1d78ff url("@/assets/images/img4.png") no-repeat right bottom;
|
||||
color: #ffffff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
<script setup lang="ts">
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>me</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
// @ts-check
|
||||
import withNuxt from "./.nuxt/eslint.config.mjs";
|
||||
|
||||
export default withNuxt(
|
||||
// Your custom configs here
|
||||
{
|
||||
rules: {
|
||||
// Vue 规则
|
||||
"vue/multi-word-component-names": "off", // 允许单词组件名
|
||||
"vue/no-v-html": "warn", // v-html 使用时警告
|
||||
"vue/eqeqeq": "error", // 在模板中强制使用 === 和 !==
|
||||
"vue/v-on-event-hyphenation": [
|
||||
"error",
|
||||
"always",
|
||||
{
|
||||
autofix: true, // 自动修复连字符
|
||||
},
|
||||
], // 事件名使用连字符
|
||||
"vue/require-explicit-emits": "error", // 要求显式声明 emits
|
||||
"vue/component-name-in-template-casing": ["error", "kebab-case"], // 组件名使用短横线命名
|
||||
"vue/no-template-shadow": "error", // 禁止模板变量遮蔽
|
||||
"vue/attribute-hyphenation": "error", // 属性名使用连字符
|
||||
"vue/html-end-tags": "error", // HTML 标签必须闭合
|
||||
"vue/html-indent": ["error", 2], // HTML 缩进 2 个空格
|
||||
"vue/max-attributes-per-line": [
|
||||
"error",
|
||||
{
|
||||
singleline: 3, // 单行最多 3 个属性
|
||||
multiline: 1, // 多行每行 1 个属性
|
||||
},
|
||||
], // 每行最多属性数
|
||||
"vue/html-self-closing": [
|
||||
"error",
|
||||
{
|
||||
html: {
|
||||
void: "always", // void 元素必须自闭合
|
||||
normal: "never", // 非元素不自闭合
|
||||
component: "always", // 组件必须自闭合
|
||||
},
|
||||
},
|
||||
], // 自闭合标签规则
|
||||
"vue/singleline-html-element-content-newline": "off", // 允许单行元素内容
|
||||
"vue/multiline-html-element-content-newline": "error", // 多行元素内容需要换行
|
||||
|
||||
// JavaScript/TypeScript 规则
|
||||
"no-console": process.env.NODE_ENV === "production" ? "warn" : "off", // 生产环境警告 console
|
||||
"no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off", // 生产环境警告 debugger
|
||||
"no-unused-vars": ["error", {
|
||||
argsIgnorePattern: "^_",
|
||||
varsIgnorePattern: "^_",
|
||||
}], // 未使用的变量警告
|
||||
"prefer-const": "warn", // 优先使用 const
|
||||
"no-var": "error", // 禁止使用 var
|
||||
|
||||
// 代码风格
|
||||
"semi": ["error", "always"], // 使用分号
|
||||
"quotes": ["error", "double", { avoidEscape: true }], // 使用双引号
|
||||
"comma-dangle": ["error", "always-multiline"], // 多行时允许尾随逗号
|
||||
"arrow-parens": ["error", "as-needed"], // 箭头函数参数括号按需添加
|
||||
"object-curly-spacing": ["error", "always"], // 对象大括号内空格
|
||||
"array-bracket-spacing": ["error", "never"], // 数组方括号内无空格
|
||||
|
||||
// 最佳实践
|
||||
"eqeqeq": ["error", "always"], // 强制使用 === 和 !==
|
||||
"no-duplicate-imports": "error", // 禁止重复导入
|
||||
"no-else-return": "warn", // if-return 后不允许 else
|
||||
"no-nested-ternary": "warn", // 禁止嵌套三元表达式
|
||||
"prefer-template": "warn", // 优先使用模板字符串
|
||||
"no-useless-concat": "error", // 禁止无意义的字符串拼接
|
||||
},
|
||||
},
|
||||
);
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
// https://nuxt.com/docs/api/configuration/nuxt-config
|
||||
export default defineNuxtConfig({
|
||||
compatibilityDate: "2025-07-15",
|
||||
devtools: { enabled: true },
|
||||
modules: ["@nuxt/eslint", "@nuxt/image", "@nuxt/scripts", "@vueuse/motion/nuxt"],
|
||||
app: {
|
||||
head: {
|
||||
title: "河北秦安安全科技股份有限公司",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
{
|
||||
"name": "nuxt-demo",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "nuxt build",
|
||||
"dev": "nuxt dev --open",
|
||||
"generate": "nuxt generate",
|
||||
"preview": "nuxt preview",
|
||||
"postinstall": "nuxt prepare",
|
||||
"lint": "eslint .",
|
||||
"lint:fix": "eslint . --fix"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nuxt/eslint": "1.13.0",
|
||||
"@nuxt/image": "2.0.0",
|
||||
"@nuxt/scripts": "0.13.2",
|
||||
"@unhead/vue": "^2.0.3",
|
||||
"@vueuse/motion": "^3.0.3",
|
||||
"eslint": "^9.0.0",
|
||||
"motion": "^12.29.2",
|
||||
"nuxt": "^4.3.0",
|
||||
"Swiper": "^12.0.3",
|
||||
"vue": "^3.5.27",
|
||||
"vue-router": "^4.6.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"sass": "^1.97.3"
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 4.2 KiB |
|
|
@ -0,0 +1,2 @@
|
|||
User-Agent: *
|
||||
Disallow:
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
// https://nuxt.com/docs/guide/concepts/typescript
|
||||
"files": [],
|
||||
"references": [
|
||||
{
|
||||
"path": "./.nuxt/tsconfig.app.json"
|
||||
},
|
||||
{
|
||||
"path": "./.nuxt/tsconfig.server.json"
|
||||
},
|
||||
{
|
||||
"path": "./.nuxt/tsconfig.shared.json"
|
||||
},
|
||||
{
|
||||
"path": "./.nuxt/tsconfig.node.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
59
package.json
|
|
@ -1,59 +0,0 @@
|
|||
{
|
||||
"name": "qa_h5_edu",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"lint": "eslint --ext .js,.vue --fix src"
|
||||
},
|
||||
"dependencies": {
|
||||
"@vant/use": "^1.5.2",
|
||||
"@vueuse/core": "^9.13.0",
|
||||
"@zxing/library": "^0.20.0",
|
||||
"animate.css": "^4.1.1",
|
||||
"axios": "^1.3.4",
|
||||
"dayjs": "^1.11.7",
|
||||
"hls.js": "^1.4.7",
|
||||
"lodash-es": "^4.17.21",
|
||||
"mitt": "^3.0.0",
|
||||
"normalize.css": "^8.0.1",
|
||||
"pinia": "^2.0.33",
|
||||
"pinia-plugin-persistedstate": "^3.1.0",
|
||||
"plyr": "^3.7.8",
|
||||
"qs": "^6.11.1",
|
||||
"throttle-debounce": "^5.0.0",
|
||||
"vant": "^4.1.0",
|
||||
"vue": "^3.2.45",
|
||||
"vue-esign": "^1.1.4",
|
||||
"vue-router": "^4.1.6",
|
||||
"vue3-qr-reader": "^1.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^18.14.6",
|
||||
"@vitejs/plugin-basic-ssl": "^1.0.1",
|
||||
"@vitejs/plugin-vue": "^4.0.0",
|
||||
"@vue/eslint-config-prettier": "^7.1.0",
|
||||
"autoprefixer": "^10.4.13",
|
||||
"cnjm-postcss-px-to-viewport": "^1.0.0",
|
||||
"eslint": "^8.35.0",
|
||||
"eslint-config-prettier": "^8.7.0",
|
||||
"eslint-config-standard": "^17.0.0",
|
||||
"eslint-plugin-import": "^2.27.5",
|
||||
"eslint-plugin-n": "^15.6.1",
|
||||
"eslint-plugin-prettier": "^4.2.1",
|
||||
"eslint-plugin-promise": "^6.1.1",
|
||||
"eslint-plugin-vue": "^9.9.0",
|
||||
"prettier": "^2.8.4",
|
||||
"sass": "^1.58.3",
|
||||
"unplugin-auto-import": "^0.12.2",
|
||||
"unplugin-vue-components": "^0.22.12",
|
||||
"unplugin-vue-define-options": "^0.6.2",
|
||||
"vconsole": "3.15.1",
|
||||
"vite": "4.1.5",
|
||||
"vite-plugin-eslint": "^1.8.1",
|
||||
"vue-eslint-parser": "^9.1.0"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,30 +0,0 @@
|
|||
const path = require('path')
|
||||
module.exports = {
|
||||
plugins: {
|
||||
autoprefixer: {
|
||||
overrideBrowserslist: ['Android 4.1', 'iOS 7.1', 'Chrome > 31', 'ff > 31', 'ie >= 8', '> 1%'],
|
||||
grid: true,
|
||||
},
|
||||
"cnjm-postcss-px-to-viewport": {
|
||||
unitToConvert: "px", // 要转化的单位
|
||||
viewportWidth: 750, // UI设计稿的宽度
|
||||
unitPrecision: 6, // 转换后的精度,即小数点位数
|
||||
propList: ["*"], // 指定转换的css属性的单位,*代表全部css属性的单位都进行转换
|
||||
viewportUnit: "vw", // 指定需要转换成的视窗单位,默认vw
|
||||
fontViewportUnit: "vw", // 指定字体需要转换成的视窗单位,默认vw
|
||||
selectorBlackList: [".ignore", ".hairlines"],
|
||||
minPixelValue: 1, // 默认值1,小于或等于1px则不进行转换
|
||||
mediaQuery: true, // 是否在媒体查询的css代码中也进行转换,默认false
|
||||
replace: true, // 是否转换后直接更换属性值
|
||||
exclude: [], // 设置忽略文件,用正则做目录名匹配
|
||||
landscape: false, // 是否处理横屏情况
|
||||
// 如果没有使用其他的尺寸来设计,下面这个方法可以不需要,比如vant是375的
|
||||
customFun: ({ file }) => {
|
||||
// 这个自定义的方法是针对处理vant组件下的设计稿为375问题
|
||||
return path.join(file).includes(path.join("node_modules", "vant"))
|
||||
? 375
|
||||
: 750;
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
@ -1 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
||||
|
Before Width: | Height: | Size: 1.5 KiB |
51
src/App.vue
|
|
@ -1,51 +0,0 @@
|
|||
<template>
|
||||
<suspense>
|
||||
<template #default>
|
||||
<div>
|
||||
<van-overlay :show="miscellaneousStore.getLoading">
|
||||
<van-loading color="#fff" size="24px" vertical>加载中...</van-loading>
|
||||
</van-overlay>
|
||||
<nav-bar v-if="$route.meta.navbar !== false" />
|
||||
<router-view />
|
||||
</div>
|
||||
</template>
|
||||
<template #fallback>
|
||||
<div>加载中...</div>
|
||||
</template>
|
||||
</suspense>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { useRoute } from "vue-router";
|
||||
import { watchEffect } from "vue";
|
||||
import { useMiscellaneousStore } from "@/pinia/miscellaneous.js";
|
||||
import NavBar from "@/components/layout/NavBar";
|
||||
|
||||
const miscellaneousStore = useMiscellaneousStore();
|
||||
const route = useRoute();
|
||||
watchEffect(() => {
|
||||
window.document.title = route.meta.title;
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
:root {
|
||||
--van-uploader-size: 146px !important;
|
||||
--van-button-mini-padding: 0 20px !important;
|
||||
--van-nav-bar-z-index: 9 !important;
|
||||
--van-nav-bar-background: rgb(51, 119, 255) !important;
|
||||
--van-nav-bar-title-text-color: var(--van-white) !important;
|
||||
--van-nav-bar-icon-color: var(--van-white) !important;
|
||||
--van-nav-bar-text-color: var(--van-white) !important;
|
||||
}
|
||||
#app {
|
||||
background-color: #fafafa;
|
||||
min-height: 100vh;
|
||||
font-size: 30px;
|
||||
}
|
||||
.van-loading {
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
// 文字超出几行隐藏,最多5行
|
||||
// 使用超出1行隐藏,如果使用了flex,则需要给父元素设置min-width: 0;
|
||||
@for $i from 1 through 5 {
|
||||
.line-#{$i} {
|
||||
@if $i == 1 {
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
} @else {
|
||||
display: -webkit-box !important;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
word-break: break-all;
|
||||
-webkit-line-clamp: $i;
|
||||
-webkit-box-orient: vertical !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.plyr {
|
||||
--plyr-control-icon-size: 4vmin;
|
||||
--plyr-font-size-time: 3.5vmin;
|
||||
|
||||
.plyr__time + .plyr__time {
|
||||
display: block !important;
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 399 KiB |
|
Before Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 7.1 KiB |
|
Before Width: | Height: | Size: 6.8 KiB |
|
Before Width: | Height: | Size: 6.2 KiB |
|
Before Width: | Height: | Size: 6.7 KiB |
|
Before Width: | Height: | Size: 110 KiB |
|
Before Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 104 KiB |
|
Before Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 983 B |
|
Before Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 1.6 KiB |
|
|
@ -1,2 +0,0 @@
|
|||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1659405195490" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2119" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css">@font-face { font-family: feedback-iconfont; src: url("//at.alicdn.com/t/font_1031158_u69w8yhxdu.woff2?t=1630033759944") format("woff2"), url("//at.alicdn.com/t/font_1031158_u69w8yhxdu.woff?t=1630033759944") format("woff"), url("//at.alicdn.com/t/font_1031158_u69w8yhxdu.ttf?t=1630033759944") format("truetype"); }
|
||||
</style></defs><path d="M853.617778 85.333333c23.381333 0 42.382222 18.944 42.382222 42.325334v768.682666a42.666667 42.666667 0 0 1-42.382222 42.325334H170.382222a42.382222 42.382222 0 0 1-42.382222-42.325334V127.658667a42.666667 42.666667 0 0 1 42.382222-42.325334h683.235556zM739.555556 768H284.444444V853.333333h455.111112v-85.333333z m0-142.222222H284.444444v85.333333h455.111112V625.777778zM540.444444 170.666667H298.666667v384l120.888889-85.333334L540.444444 554.666667V170.666667z" p-id="2120" fill="#9fa7bc"></path></svg>
|
||||
|
Before Width: | Height: | Size: 1.2 KiB |
|
|
@ -1,2 +0,0 @@
|
|||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1659405147450" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1905" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css">@font-face { font-family: feedback-iconfont; src: url("//at.alicdn.com/t/font_1031158_u69w8yhxdu.woff2?t=1630033759944") format("woff2"), url("//at.alicdn.com/t/font_1031158_u69w8yhxdu.woff?t=1630033759944") format("woff"), url("//at.alicdn.com/t/font_1031158_u69w8yhxdu.ttf?t=1630033759944") format("truetype"); }
|
||||
</style></defs><path d="M651.377778 513.422222l187.164444-155.875555-55.751111-62.008889-268.174222 223.175111-273.635556-223.402667-55.239111 62.520889 328.590222 268.288a277.731556 277.731556 0 0 0-16.270222 274.773333H128c-23.552 0-42.666667-18.318222-42.666667-40.96V203.377778c0-22.584889 19.114667-40.96 42.666667-40.96h768c23.552 0 42.666667 18.375111 42.666667 40.96V542.151111a305.607111 305.607111 0 0 0-170.666667-51.484444 307.768889 307.768889 0 0 0-116.622222 22.755555z m61.44 375.523556c48.924444 22.528 107.349333 12.913778 145.749333-24.007111 38.343111-36.920889 48.355556-93.070222 24.917333-140.117334l-170.666666 164.067556v0.056889z m-60.359111-58.026667l170.666666-164.124444a131.811556 131.811556 0 0 0-145.692444 24.007111 119.580444 119.580444 0 0 0-24.974222 140.117333z m115.541333 152.064c-117.816889 0-213.333333-91.875556-213.333333-205.141333 0-113.265778 95.516444-205.141333 213.333333-205.141334 117.816889 0 213.333333 91.875556 213.333333 205.141334 0 113.265778-95.516444 205.141333-213.333333 205.141333z" p-id="1906" fill="#9fa7bc"></path></svg>
|
||||
|
Before Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 9.7 KiB |
|
Before Width: | Height: | Size: 78 KiB |
|
Before Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 4.3 KiB |
|
Before Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 4.5 KiB |
|
Before Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 2.4 KiB |
|
|
@ -1,27 +0,0 @@
|
|||
export default function packingGetUserMedia() {
|
||||
if (navigator.mediaDevices === undefined) {
|
||||
navigator.mediaDevices = {};
|
||||
}
|
||||
// 缺少getUserMedia属性
|
||||
if (navigator.mediaDevices.getUserMedia === undefined) {
|
||||
navigator.mediaDevices.getUserMedia = (constraints) => {
|
||||
// 首先获取现存的getUserMedia(如果存在)
|
||||
const getUserMedia =
|
||||
navigator.getUserMedia ||
|
||||
navigator.webkitGetUserMedia ||
|
||||
navigator.mozGetUserMedia ||
|
||||
navigator.msGetUserMedia ||
|
||||
navigator.oGetUserMedia;
|
||||
// 浏览器不支持,返回错误信息
|
||||
if (!getUserMedia) {
|
||||
return Promise.reject(
|
||||
new Error("getUserMedia is not implemented in this browser")
|
||||
);
|
||||
}
|
||||
// 使用Promise将调用包装到navigator.getUserMedia
|
||||
return new Promise((resolve, reject) => {
|
||||
getUserMedia.call(navigator, constraints, resolve, reject);
|
||||
});
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
import mitt from "mitt";
|
||||
export default mitt();
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
<template>
|
||||
<router-view></router-view>
|
||||
</template>
|
||||
|
|
@ -1,176 +0,0 @@
|
|||
<template>
|
||||
<div class="face-area">
|
||||
<div class="area-top">
|
||||
<p>请将人脸置于圆圈内</p>
|
||||
<video id="video" autoPlay playsInline webkit-playsinline />
|
||||
</div>
|
||||
<div class="area-bottom">
|
||||
<p v-html="text" />
|
||||
<van-button type="primary" v-if="!autoUpload" @click="fnCaptureFaceImage">
|
||||
拍照/上传
|
||||
</van-button>
|
||||
<van-button
|
||||
v-show="hasJur"
|
||||
type="primary"
|
||||
@click="fnChoiceFile"
|
||||
class="ml"
|
||||
>
|
||||
视频验证
|
||||
</van-button>
|
||||
<canvas id="canvas" width="600" height="600" style="display: none" />
|
||||
<input
|
||||
class="fileInput"
|
||||
type="file"
|
||||
accept="video/*"
|
||||
capture="user"
|
||||
style="display: none"
|
||||
@change="fnFileUpload"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { onMounted, onUnmounted, ref } from "vue";
|
||||
import { showToast } from "vant";
|
||||
import packingGetUserMedia from "@/assets/js/getUserMedia.js";
|
||||
|
||||
const props = defineProps({
|
||||
autoUpload: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
});
|
||||
const emits = defineEmits(["facePictures"]);
|
||||
let canvas, video, context, mediaStreamTrack, timer;
|
||||
const text = ref("人脸识别准备中...");
|
||||
const hasJur = ref(false);
|
||||
onMounted(() => {
|
||||
fnOpenVideo();
|
||||
});
|
||||
// 关闭摄像头
|
||||
const fnCloseMedia = () => {
|
||||
mediaStreamTrack &&
|
||||
mediaStreamTrack.getVideoTracks().forEach((track) => {
|
||||
track.stop();
|
||||
context.clearRect(0, 0, context.width, context.height); // 清除画布
|
||||
});
|
||||
};
|
||||
// 打开摄像头
|
||||
const fnOpenVideo = () => {
|
||||
packingGetUserMedia();
|
||||
const constraints = {
|
||||
audio: false,
|
||||
video: {
|
||||
facingMode: "user",
|
||||
width: 600,
|
||||
height: 600,
|
||||
},
|
||||
};
|
||||
navigator.mediaDevices
|
||||
.getUserMedia(constraints)
|
||||
.then((stream) => {
|
||||
text.value = "人脸识别中...";
|
||||
canvas = document.querySelector("canvas");
|
||||
context = canvas.getContext("2d");
|
||||
video = document.querySelector("video");
|
||||
mediaStreamTrack = stream;
|
||||
// 旧的浏览器可能没有srcObject
|
||||
if ("srcObject" in video) {
|
||||
video.srcObject = stream;
|
||||
} else {
|
||||
// 避免在新的浏览器中使用它,因为它正在被弃用。
|
||||
video.src = window.URL.createObjectURL(stream);
|
||||
}
|
||||
setTimeout(() => {
|
||||
// video.onloadedmetadata = () => {
|
||||
video.play();
|
||||
// 获取video播放状态
|
||||
|
||||
// 开始识别
|
||||
if (props.autoUpload) {
|
||||
fnCaptureFaceImage();
|
||||
}
|
||||
// };
|
||||
}, 1000);
|
||||
})
|
||||
.catch(() => {
|
||||
hasJur.value = true;
|
||||
text.value = `浏览器不支持或者没有开启摄像头权限<br />可以点击视频验证(视频时长请小于10秒)`;
|
||||
});
|
||||
};
|
||||
// 截取人脸图片
|
||||
const fnCaptureFaceImage = () => {
|
||||
timer && clearTimeout(timer);
|
||||
timer = setTimeout(
|
||||
() => {
|
||||
// canvas画图
|
||||
context.drawImage(video, 0, 0, 600, 600);
|
||||
// 获取图片base64链接
|
||||
const image = canvas.toDataURL("image/png");
|
||||
// 定义一个img
|
||||
const img = new Image();
|
||||
// 设置属性和src
|
||||
img.id = "imgBox";
|
||||
img.src = image;
|
||||
emits("facePictures", { image, type: "image" });
|
||||
},
|
||||
props.autoUpload ? 2000 : 0
|
||||
);
|
||||
};
|
||||
const fnChoiceFile = () => {
|
||||
const fileInput = document.querySelector(".fileInput");
|
||||
fileInput.click();
|
||||
};
|
||||
const fnFileUpload = (event) => {
|
||||
const file = event.target.files[0];
|
||||
const size = 1048576 * 100;
|
||||
if (file.size <= size) emits("facePictures", { image: file, type: "video" });
|
||||
else showToast("验证失败,请重新录制视频");
|
||||
};
|
||||
onUnmounted(() => {
|
||||
fnCloseMedia();
|
||||
timer && clearTimeout(timer);
|
||||
timer = null;
|
||||
});
|
||||
defineExpose({
|
||||
fnCaptureFaceImage,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.face-area {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.area-top {
|
||||
height: 630px;
|
||||
text-align: center;
|
||||
|
||||
p {
|
||||
font-size: 36px;
|
||||
}
|
||||
}
|
||||
|
||||
.area-bottom {
|
||||
p {
|
||||
margin-top: 100px;
|
||||
font-size: 30px;
|
||||
text-align: center;
|
||||
padding: 0 30px;
|
||||
}
|
||||
|
||||
.ml {
|
||||
margin-left: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
#video {
|
||||
width: 600px;
|
||||
height: 600px;
|
||||
transform: rotateY(180deg);
|
||||
border-radius: 50%;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
<template>
|
||||
<van-nav-bar
|
||||
:left-arrow="route.meta.navbarLeft ?? true"
|
||||
:left-text="route.meta.navbarLeft !== false ? '返回' : ''"
|
||||
:right-text="route.meta.rightText"
|
||||
:title="route.meta.title"
|
||||
fixed
|
||||
placeholder
|
||||
@click-left="fnClickLeft"
|
||||
@click-right="fnClickRight"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
import mitt from "@/assets/js/mitt";
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const fnClickLeft = () => {
|
||||
router.back();
|
||||
};
|
||||
const fnClickRight = () => {
|
||||
mitt.emit("click-right");
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
<template>
|
||||
<van-tabbar route placeholder>
|
||||
<van-tabbar-item :to="{ name: 'index' }">
|
||||
<span>首页</span>
|
||||
<template #icon="props">
|
||||
<img v-if="!props.active" src="/src/assets/images/tabbar/home.png" />
|
||||
<img v-else src="/src/assets/images/tabbar/home-select.png" />
|
||||
</template>
|
||||
</van-tabbar-item>
|
||||
<van-tabbar-item :to="{ name: 'study', params: { tabsCurrent: 0 } }">
|
||||
<span>学习</span>
|
||||
<template #icon="props">
|
||||
<img v-if="!props.active" src="/src/assets/images/tabbar/notes.png" />
|
||||
<img v-else src="/src/assets/images/tabbar/notes-select.png" />
|
||||
</template>
|
||||
</van-tabbar-item>
|
||||
<van-tabbar-item :to="{ name: 'mine' }">
|
||||
<span>我的</span>
|
||||
<template #icon="props">
|
||||
<img v-if="!props.active" src="/src/assets/images/tabbar/people.png" />
|
||||
<img v-else src="/src/assets/images/tabbar/people-select.png" />
|
||||
</template>
|
||||
</van-tabbar-item>
|
||||
</van-tabbar>
|
||||
</template>
|
||||
|
||||
<script setup></script>
|
||||
|
||||
<style scoped></style>
|
||||
|
|
@ -1,188 +0,0 @@
|
|||
<template>
|
||||
<van-dialog
|
||||
:show="show"
|
||||
className="esign_dialog"
|
||||
:show-confirm-button="false"
|
||||
>
|
||||
<div class="footer">
|
||||
<van-button @click="fnReset">
|
||||
<div>重</div>
|
||||
<div>签</div>
|
||||
</van-button>
|
||||
<van-button @click="fnGenerate">
|
||||
<div>确</div>
|
||||
<div>定</div>
|
||||
</van-button>
|
||||
</div>
|
||||
<div class="content">
|
||||
<vue-esign
|
||||
ref="esignRef"
|
||||
:width="width - 85"
|
||||
:height="800"
|
||||
:isCrop="false"
|
||||
:lineWidth="10"
|
||||
lineColor="#000000"
|
||||
v-model:bgColor="bgColor"
|
||||
/>
|
||||
</div>
|
||||
<div class="header">
|
||||
<div></div>
|
||||
<div>
|
||||
<div class="title">签</div>
|
||||
<div class="title">字</div>
|
||||
</div>
|
||||
<div>
|
||||
<van-icon name="cross" @click="fnClose" />
|
||||
</div>
|
||||
</div>
|
||||
</van-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import VueEsign from "vue-esign";
|
||||
import { nextTick, ref } from "vue";
|
||||
import { showToast } from "vant";
|
||||
import { useWindowSize } from "@vant/use";
|
||||
|
||||
const { width } = useWindowSize();
|
||||
|
||||
defineProps({
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
const emits = defineEmits(["update:show", "confirm"]);
|
||||
const esignRef = ref(null);
|
||||
const bgColor = ref("#fff");
|
||||
const fnReset = async () => {
|
||||
esignRef.value.reset();
|
||||
await nextTick();
|
||||
bgColor.value = "#fff";
|
||||
};
|
||||
|
||||
function rotateBase64Img(src) {
|
||||
return new Promise((resolve) => {
|
||||
const canvas = document.createElement("canvas");
|
||||
const ctx = canvas.getContext("2d");
|
||||
let imgW;
|
||||
let imgH;
|
||||
let size;
|
||||
const cutCor = { sx: 0, sy: 0, ex: 0, ey: 0 };
|
||||
const image = new Image();
|
||||
image.crossOrigin = "anonymous";
|
||||
image.src = src;
|
||||
image.onload = function () {
|
||||
imgW = image.width;
|
||||
imgH = image.height;
|
||||
size = imgW > imgH ? imgW : imgH;
|
||||
canvas.width = size * 2;
|
||||
canvas.height = size * 2;
|
||||
cutCor.sx = size;
|
||||
cutCor.sy = size - imgW;
|
||||
cutCor.ex = size + imgH;
|
||||
cutCor.ey = size + imgW;
|
||||
ctx?.translate(size, size);
|
||||
ctx?.rotate((270 * Math.PI) / 180);
|
||||
// ctx?.scale(0.16, 0.16);
|
||||
ctx?.drawImage(image, 0, 0);
|
||||
const imgData = ctx?.getImageData(
|
||||
cutCor.sx,
|
||||
cutCor.sy,
|
||||
cutCor.ex,
|
||||
cutCor.ey
|
||||
);
|
||||
canvas.width = imgH;
|
||||
canvas.height = imgW;
|
||||
ctx?.putImageData(imgData, 0, 0);
|
||||
resolve(canvas.toDataURL("image/png"));
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
const fnGenerate = async () => {
|
||||
try {
|
||||
const resultImg = await esignRef.value.generate();
|
||||
const base64 = await rotateBase64Img(resultImg);
|
||||
fnClose();
|
||||
emits("confirm", base64);
|
||||
} catch (e) {
|
||||
showToast({ message: "请签字!", className: "esign_toast" });
|
||||
}
|
||||
};
|
||||
const fnClose = () => {
|
||||
fnReset();
|
||||
emits("update:show", false);
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.esign_dialog {
|
||||
width: 100vw;
|
||||
max-width: 100vw;
|
||||
height: 100vh;
|
||||
max-height: 100vh;
|
||||
top: 50%;
|
||||
border-radius: 0;
|
||||
|
||||
.van-dialog__content {
|
||||
display: flex;
|
||||
height: 100vh;
|
||||
|
||||
.header {
|
||||
padding: 50px 0;
|
||||
width: 80px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
font-size: 40px;
|
||||
color: #000;
|
||||
|
||||
.title {
|
||||
margin-top: 10px;
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
canvas {
|
||||
border: 1px dashed var(--van-button-default-border-color);
|
||||
}
|
||||
}
|
||||
|
||||
.footer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.van-button {
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
flex: 1;
|
||||
border-top: 1px solid var(--van-button-default-border-color);
|
||||
|
||||
&:first-child {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.van-button__text {
|
||||
div {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.esign_toast {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
</style>
|
||||
18
src/main.js
|
|
@ -1,18 +0,0 @@
|
|||
import { createApp } from "vue";
|
||||
import "dayjs/locale/zh-cn";
|
||||
import "@/assets/css/common.scss";
|
||||
import "normalize.css";
|
||||
import "animate.css";
|
||||
import "vant/es/toast/style";
|
||||
import "vant/es/dialog/style";
|
||||
import "vant/es/notify/style";
|
||||
import "vant/es/image-preview/style";
|
||||
import App from "./App.vue";
|
||||
import pinia from "./pinia";
|
||||
import router from "./router";
|
||||
// import vconsole from "vconsole";
|
||||
|
||||
// eslint-disable-next-line new-cap,no-new
|
||||
// new vconsole();
|
||||
|
||||
createApp(App).use(pinia).use(router).mount("#app");
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
import { defineStore } from "pinia";
|
||||
|
||||
export const localStore = defineStore("localStore", {
|
||||
state: () => ({
|
||||
localStore: {},
|
||||
}),
|
||||
getters: {
|
||||
getLocalStoreInfo: (state) => state.localStore,
|
||||
},
|
||||
actions: {
|
||||
setLocalStoreInfo(localStore) {
|
||||
this.localStore = localStore;
|
||||
},
|
||||
},
|
||||
persist: {
|
||||
storage: window.localStorage,
|
||||
},
|
||||
});
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
import { createPinia } from "pinia";
|
||||
import piniaPersistedstate from "pinia-plugin-persistedstate";
|
||||
|
||||
const pinia = createPinia();
|
||||
pinia.use(piniaPersistedstate);
|
||||
|
||||
export default pinia;
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
import { defineStore } from "pinia";
|
||||
|
||||
export const useMiscellaneousStore = defineStore("miscellaneousStore", {
|
||||
state: () => ({
|
||||
verification: false, // 学习时人脸验证状态
|
||||
loading: false, // 全局加载动画
|
||||
appVersion: "2.2.2.1", // 当前版本
|
||||
}),
|
||||
getters: {
|
||||
getVerification: (state) => state.verification,
|
||||
getLoading: (state) => state.loading,
|
||||
getAppVersion: (state) => state.appVersion,
|
||||
},
|
||||
actions: {
|
||||
setVerification(verification) {
|
||||
this.verification = verification;
|
||||
},
|
||||
setLoading(loading) {
|
||||
this.loading = loading;
|
||||
},
|
||||
},
|
||||
persist: {
|
||||
storage: window.sessionStorage,
|
||||
paths: ["loading"],
|
||||
},
|
||||
});
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
import { defineStore } from "pinia";
|
||||
|
||||
export const useSavedUsers = defineStore("savedUsersStore", {
|
||||
state: () => ({
|
||||
savedUsers: {
|
||||
userName: "",
|
||||
userPwd: "",
|
||||
startTime: "",
|
||||
endTime: "",
|
||||
}, // 勾选免登陆之后保存的账号密码
|
||||
}),
|
||||
getters: {
|
||||
getSavedUsers: (state) => state.savedUsers,
|
||||
},
|
||||
actions: {
|
||||
setSavedUsers(savedUsers) {
|
||||
this.savedUsers = savedUsers;
|
||||
},
|
||||
},
|
||||
persist: {
|
||||
storage: window.localStorage,
|
||||
},
|
||||
});
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
import { defineStore } from "pinia";
|
||||
|
||||
export const useUserStore = defineStore("userStore", {
|
||||
state: () => ({
|
||||
userInfo: {},
|
||||
}),
|
||||
getters: {
|
||||
getUserInfo: (state) => state.userInfo,
|
||||
},
|
||||
actions: {
|
||||
setUserInfo(userInfo) {
|
||||
this.userInfo = userInfo;
|
||||
},
|
||||
},
|
||||
persist: {
|
||||
storage: window.sessionStorage,
|
||||
},
|
||||
});
|
||||
|
|
@ -1,98 +0,0 @@
|
|||
import { post, upload } from "./axios";
|
||||
|
||||
export const Login = (params) => post("/api/admin/check", params); // 登录
|
||||
export const setUserFace = (params) =>
|
||||
post("/api/app/user/editUserFace", params); // 提交人脸信息
|
||||
export const getOemInfo = (params) =>
|
||||
post("/api/app/oemmanage/getOemInfo", params); // oem信息
|
||||
export const setScanCodeToVerifyFace = (params) =>
|
||||
post("/api/app/user/compareFaceForH5", params); // 扫码认证人脸
|
||||
export const getRecommendedCourseList = (params) =>
|
||||
post("/api/app/curriculum/goHotList", params); // 推荐课程列表
|
||||
export const getPersonalInformation = (params) =>
|
||||
post("/api/app/user/getUser", params); // 个人信息
|
||||
export const addArchivesStudent = (params) =>
|
||||
upload("/api/app/archivesstudent/add", params); // 登记信息保存
|
||||
export const editArchivesStudent = (params) =>
|
||||
upload("/api/app/archivesstudent/edit", params); // 登记信息修改
|
||||
export const setStudentCertificate = (params) =>
|
||||
upload("/api/app/user/updateUserIdFile", params); // 上传学员证照
|
||||
export const setSignatureInformation = (params) =>
|
||||
upload("/api/app/user/updateUserSign", params); // 更新签字信息
|
||||
export const setChangePassword = (params) =>
|
||||
post("/api/app/user/editUserPwd", params); // 修改密码
|
||||
export const setFeedbackImg = (params) =>
|
||||
upload("/api/app/feedback/upload", params); // 上传问题反馈图片
|
||||
export const setFeedback = (params) => post("/api/app/feedback/add", params); // 提交问题反馈
|
||||
export const getSurveyList = (params) =>
|
||||
post("/api/app/survey/userSurveylistPage", params); // 调查问卷列表
|
||||
export const getSurveyInfo = (params) => post("/api/app/survey/goInfo", params); // 调查问卷详情
|
||||
export const setSurveySubmit = (params) =>
|
||||
post("/api/app/survey/submit", params); // 调查问卷提交
|
||||
export const getCourseDetails = (params) =>
|
||||
post("/api/app/curriculum/goEdit", params); // 课程详情
|
||||
export const getCoursePlayInfo = (params) =>
|
||||
post("/api/app/audioOrVideo/getPlatPlayInfo", params); // 课程视频播放路径
|
||||
export const getMyLearningList = (params) =>
|
||||
post("/api/app/stagestudentrelation/pageTaskByUser", params); // 我的学习列表
|
||||
export const setLearnToSignNow = (params) =>
|
||||
upload("/api/app/stagestudentrelation/sign", params); // 立即学习签字
|
||||
export const getCourseList = (params) =>
|
||||
post("/api/app/stagestudentrelation/getClassCurriculum", params); // 课程列表
|
||||
export const getCorpState = (params) =>
|
||||
post("/api/app/stagestudentrelation/getCorpState", params); // 获取班级所在机构的状态
|
||||
export const getMyTask = (params) =>
|
||||
post("/api/app/stagestudentrelation/getMyTask", params); // 学习任务列表
|
||||
export const getVideoPlayInfo = (params) =>
|
||||
post("/api/app/audioOrVideo/getPlayInfo", params); // 视频播放信息
|
||||
export const getVideoPlayProgress = (params) =>
|
||||
post("/api/app/coursestudyvideorecord/getVideoProgress", params); // 视频播放进度
|
||||
export const setVideoPlayProgress = (params) =>
|
||||
post("/api/app/coursestudyvideorecord/save", params); // 提交视频播放进度
|
||||
export const setVerifyFace = (params) =>
|
||||
post("/api/app/user/compareFace", params); // 学习人脸验证
|
||||
export const getExercises = (params) =>
|
||||
post("/api/app/question/listAllByVideo", params); // 练习习题
|
||||
export const getTaskScoreList = (params) =>
|
||||
post("/api/app/stagestudentrelation/pageTaskScoreByUser", params); // 考试成绩列表
|
||||
export const getTaskScoreInfo = (params) =>
|
||||
post("/api/app/stageexam/findResult", params); // 考试成绩详情
|
||||
export const getExamExercises = (params) =>
|
||||
post("/api/app/stageexam/getExam", params); // 考试习题
|
||||
export const setTestPaperSubmission = (params) =>
|
||||
post("/api/app/stageexam/submit", params); // 考试交卷
|
||||
export const setUserVideoFace = (params) =>
|
||||
upload("/api/app/user/editUserVideoFace", params); // 提交人脸信息(视频验证)
|
||||
export const setVerifyVideoFace = (params) =>
|
||||
upload("/api/app/user/compareVideoFace", params); // 学习人脸验证(视频验证)
|
||||
export const setScanCodeToVerifyVideoFace = (params) =>
|
||||
upload("/api/app/user/compareVideoFaceForH5", params); // 扫码人脸验证(视频验证)
|
||||
export const listStrengthenVideo = (params) =>
|
||||
post("/api/app/stagestudentrelation/listStrengthenVideo", params); // 获取效果评估信息(试题、课件)
|
||||
export const getStrengthenExam = (params) =>
|
||||
post("/api/app/stageexam/getStrengthenExam", params); // 效果评估考试考试习题
|
||||
|
||||
export const getVideoPermissions = (params) =>
|
||||
post("/api/app/coursestudyvideorecord/getVideoPermissions", params); // 视频播放token
|
||||
|
||||
export const getDictionaries = (params) =>
|
||||
post("/api/app/dictionaries/list", params); // 获取数据字典
|
||||
|
||||
export const getRegiUserInfo = (params) =>
|
||||
post("/api/app/user/goEditUser", params); // 获取数据字典
|
||||
export const getUserEnterprise = (params) =>
|
||||
post("/api/app/user/getUserEnterprise", params); // 获取学员单位列表
|
||||
export const getAppVersion = () => post("/api/app/versionlog/getAppVersion"); // 获取APP版本
|
||||
|
||||
export const getMeetingList = (params) =>
|
||||
post("/api/app/meetingRecordUser/listPage", params); // 获取例会列表
|
||||
export const setMeetingSign = (params) =>
|
||||
upload("/api/app/meetingRecordUser/meetingSign", params); // 例会签字
|
||||
export const setMeetingVideoFace = (params) =>
|
||||
upload("/api/app/meetingRecordUser/meetingVideoFace", params); // 人脸认证(视频)
|
||||
export const setMeetingFace = (params) =>
|
||||
upload("/api/app/meetingRecordUser/setMeetingFace", params); // 人脸认证(照片)
|
||||
export const goSign = (params) =>
|
||||
upload("/api/app/meetingRecordUser/goSign", params); // 例会详情
|
||||
|
||||
export const getCount = (params) => upload("/api/app/user/getCount", params); // 首页获取代办数
|
||||
|
|
@ -1,153 +0,0 @@
|
|||
import axios from "axios";
|
||||
import router from "../router";
|
||||
import QS from "qs";
|
||||
import { showToast } from "vant";
|
||||
import { useMiscellaneousStore } from "../pinia/miscellaneous";
|
||||
import { useUserStore } from "../pinia/user";
|
||||
import { useSavedUsers } from "@/pinia/savedUsers.js";
|
||||
|
||||
const savedUsersStore = useSavedUsers();
|
||||
const miscellaneousStore = useMiscellaneousStore();
|
||||
const userStore = useUserStore();
|
||||
|
||||
function startLoading() {
|
||||
miscellaneousStore.setLoading(true);
|
||||
}
|
||||
|
||||
function endLoading() {
|
||||
miscellaneousStore.setLoading(false);
|
||||
}
|
||||
|
||||
axios.defaults.timeout = 1000 * 60 * 10;
|
||||
// axios.defaults.withCredentials = true;
|
||||
axios.interceptors.request.use(
|
||||
(config) => {
|
||||
if (config.method === "get" || config.method === "GET") {
|
||||
if (QS.parse(config.params)?.loading !== "false") startLoading();
|
||||
}
|
||||
if (config.method === "post" || config.method === "POST") {
|
||||
if (QS.parse(config.data)?.loading !== "false") startLoading();
|
||||
}
|
||||
if (userStore.getUserInfo.TOKEN) {
|
||||
// config.headers.common.Token = userStore.getUserInfo.TOKEN;
|
||||
config.headers.Token = userStore.getUserInfo.TOKEN;
|
||||
}
|
||||
return config;
|
||||
},
|
||||
(error) => Promise.reject(error)
|
||||
);
|
||||
|
||||
axios.interceptors.response.use(
|
||||
(config) => {
|
||||
if (config.config.method === "get" || config.config.method === "GET") {
|
||||
if (QS.parse(config.config.params)?.loading !== "false") endLoading();
|
||||
}
|
||||
if (config.config.method === "post" || config.config.method === "POST") {
|
||||
if (QS.parse(config.config.data)?.loading !== "false") endLoading();
|
||||
}
|
||||
return config;
|
||||
},
|
||||
(error) => {
|
||||
if (error && error.response) {
|
||||
switch (error.response.status) {
|
||||
case 0:
|
||||
case 302:
|
||||
endLoading();
|
||||
showToast("登录失效,请重新登陆");
|
||||
router.push("/login").then();
|
||||
break;
|
||||
case 401:
|
||||
endLoading();
|
||||
showToast("您的账号已在其他端登录");
|
||||
localStorage.clear();
|
||||
savedUsersStore.$reset();
|
||||
router.push("/login").then();
|
||||
break;
|
||||
default:
|
||||
error.message = `连接错误${error.response.status}`;
|
||||
showToast(`连接错误${error.response.status}`);
|
||||
}
|
||||
} else {
|
||||
error.message = "连接到服务器失败";
|
||||
showToast("连接到服务器失败");
|
||||
}
|
||||
return Promise.reject(error.message);
|
||||
}
|
||||
);
|
||||
|
||||
export function post(url, params) {
|
||||
return new Promise((resolve, reject) => {
|
||||
axios
|
||||
.post(
|
||||
url,
|
||||
QS.stringify({
|
||||
USER_ID: userStore.getUserInfo.USER_ID ?? "",
|
||||
...params,
|
||||
}),
|
||||
{
|
||||
headers: {
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
},
|
||||
}
|
||||
)
|
||||
.then((res) => {
|
||||
if (res.data.result === "success" && res.data.code !== "9999") {
|
||||
resolve(res.data);
|
||||
} else {
|
||||
showToast(res.data.msg || "系统开小差了");
|
||||
reject(res.data);
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function get(url, params) {
|
||||
return new Promise((resolve, reject) => {
|
||||
axios
|
||||
.get(url, {
|
||||
headers: {
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
},
|
||||
params: QS.stringify({
|
||||
USER_ID: userStore.getUserInfo.USER_ID ?? "",
|
||||
...params,
|
||||
}),
|
||||
})
|
||||
.then((res) => {
|
||||
if (res.data.result === "success" && res.data.code !== "9999") {
|
||||
resolve(res.data);
|
||||
} else {
|
||||
showToast(res.data.msg || "系统开小差了");
|
||||
reject(res.data);
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function upload(url, params) {
|
||||
return new Promise((resolve, reject) => {
|
||||
axios
|
||||
.post(url, params, {
|
||||
headers: {
|
||||
"Content-Type": "multipart/form-data",
|
||||
},
|
||||
})
|
||||
.then((res) => {
|
||||
if (res.data.result === "success" && res.data.code !== "9999") {
|
||||
resolve(res.data);
|
||||
} else {
|
||||
showToast(res.data.msg || "系统开小差了");
|
||||
reject(res.data);
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
@ -1,268 +0,0 @@
|
|||
import { createRouter, createWebHashHistory } from "vue-router";
|
||||
import { useUserStore } from "@/pinia/user.js";
|
||||
import children from "@/components/children/index.vue";
|
||||
import { useMiscellaneousStore } from "@/pinia/miscellaneous.js";
|
||||
|
||||
const routes = [
|
||||
{ path: "/", redirect: "/index" },
|
||||
{
|
||||
path: "/login",
|
||||
name: "login",
|
||||
meta: { title: "登录", isLogin: false, navbar: false },
|
||||
component: () => import("@/views/login/index"),
|
||||
},
|
||||
{
|
||||
path: "/index",
|
||||
name: "index",
|
||||
meta: { title: "首页", navbar: false },
|
||||
component: () => import("@/views/index/index"),
|
||||
},
|
||||
{
|
||||
path: "/course",
|
||||
name: "course",
|
||||
meta: { title: "课程详情" },
|
||||
component: () => import("@/views/index/course"),
|
||||
},
|
||||
{
|
||||
path: "/hot",
|
||||
name: "hot",
|
||||
meta: { title: "热门推荐" },
|
||||
component: () => import("@/views/index/hot"),
|
||||
},
|
||||
{
|
||||
path: "/recommend",
|
||||
name: "recommend",
|
||||
meta: { title: "精品推荐" },
|
||||
component: () => import("@/views/index/recommend"),
|
||||
},
|
||||
{
|
||||
path: "/face_auth",
|
||||
name: "face_auth",
|
||||
meta: { title: "人脸认证" },
|
||||
component: () => import("@/views/index/face_auth"),
|
||||
},
|
||||
{
|
||||
path: "/face_authentication",
|
||||
name: "face_authentication",
|
||||
meta: { title: "人脸认证", navbarLeft: false },
|
||||
component: () => import("@/views/mine/face_authentication"),
|
||||
},
|
||||
{
|
||||
path: "/scanQRCode",
|
||||
name: "scanQRCode",
|
||||
meta: { title: "扫描二维码" },
|
||||
component: () => import("@/views/index/scanQRCode"),
|
||||
},
|
||||
{
|
||||
path: "/noWeChat",
|
||||
name: "noWeChat",
|
||||
meta: { title: "请使用微信访问", isLogin: false, navbar: false },
|
||||
component: () => import("@/views/index/noWeChat"),
|
||||
},
|
||||
{
|
||||
path: "/blankJump",
|
||||
name: "blankJump",
|
||||
meta: { title: "空白页跳转", navbar: false },
|
||||
component: () => import("@/views/mine/blank_jump"),
|
||||
},
|
||||
{
|
||||
path: "/study",
|
||||
component: children,
|
||||
children: [
|
||||
{
|
||||
path: ":tabsCurrent",
|
||||
name: "study",
|
||||
meta: { title: "学习", navbarLeft: false },
|
||||
component: () => import("@/views/study/index"),
|
||||
},
|
||||
{
|
||||
path: "curriculum_schedule",
|
||||
name: "curriculum_schedule",
|
||||
meta: { title: "课程列表" },
|
||||
component: () => import("@/views/study/curriculum_schedule"),
|
||||
},
|
||||
{
|
||||
path: "strengthen_curriculum_schedule",
|
||||
name: "strengthen_curriculum_schedule",
|
||||
meta: { title: "加强学习课程列表" },
|
||||
component: () => import("@/views/study/strengthen_curriculum_schedule"),
|
||||
},
|
||||
{
|
||||
path: "video_study",
|
||||
name: "video_study",
|
||||
meta: { title: "学习详情" },
|
||||
component: () => import("@/views/study/video_study"),
|
||||
},
|
||||
{
|
||||
path: "strengthen_video_study",
|
||||
name: "strengthen_video_study",
|
||||
meta: { title: "加强学习" },
|
||||
component: () => import("@/views/study/strengthen_video_study"),
|
||||
},
|
||||
{
|
||||
path: "exercises",
|
||||
name: "exercises",
|
||||
meta: { title: "习题练习", rightText: "答案解析" },
|
||||
component: () => import("@/views/study/exercises"),
|
||||
},
|
||||
{
|
||||
path: "exam_details",
|
||||
name: "exam_details",
|
||||
meta: { title: "考试详情" },
|
||||
component: () => import("@/views/study/exam_details"),
|
||||
},
|
||||
{
|
||||
path: "course_exam",
|
||||
name: "course_exam",
|
||||
meta: { title: "课程考试", navbarLeft: false },
|
||||
component: () => import("@/views/study/course_exam"),
|
||||
},
|
||||
{
|
||||
path: "video_study_face_auth",
|
||||
name: "video_study_face_auth",
|
||||
meta: { title: "人脸认证", navbarLeft: false },
|
||||
component: () => import("@/views/study/video_study_face_auth"),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "/mine",
|
||||
component: children,
|
||||
children: [
|
||||
{
|
||||
path: "",
|
||||
name: "mine",
|
||||
meta: { title: "我的", navbar: false },
|
||||
component: () => import("@/views/mine/index"),
|
||||
},
|
||||
{
|
||||
path: "info",
|
||||
name: "info",
|
||||
meta: { title: "个人信息" },
|
||||
component: () => import("@/views/mine/info"),
|
||||
},
|
||||
{
|
||||
path: "student_license",
|
||||
name: "student_license",
|
||||
meta: { title: "学员证照" },
|
||||
component: () => import("@/views/mine/student_license"),
|
||||
},
|
||||
{
|
||||
path: "change_password",
|
||||
name: "change_password",
|
||||
meta: { title: "修改密码" },
|
||||
component: () => import("@/views/mine/change_password"),
|
||||
},
|
||||
{
|
||||
path: "feedback",
|
||||
name: "feedback",
|
||||
meta: { title: "问题反馈" },
|
||||
component: () => import("@/views/mine/feedback"),
|
||||
},
|
||||
{
|
||||
path: "meeting_list",
|
||||
name: "meeting_list",
|
||||
meta: { title: "会议列表" },
|
||||
component: () => import("@/views/mine/meeting_list"),
|
||||
},
|
||||
{
|
||||
path: "meeting_info",
|
||||
name: "meeting_info",
|
||||
meta: { title: "会议详情" },
|
||||
component: () => import("@/views/mine/meeting_info"),
|
||||
},
|
||||
{
|
||||
path: "meeting_face_auth",
|
||||
name: "meeting_face_auth",
|
||||
meta: { title: "人脸认证", navbarLeft: false },
|
||||
component: () => import("@/views/mine/meeting_face_auth"),
|
||||
},
|
||||
{
|
||||
path: "survey_list",
|
||||
name: "survey_list",
|
||||
meta: { title: "调查问卷" },
|
||||
component: () => import("@/views/mine/survey_list"),
|
||||
},
|
||||
{
|
||||
path: "survey",
|
||||
name: "survey",
|
||||
meta: { title: "调查问卷" },
|
||||
component: () => import("@/views/mine/survey"),
|
||||
},
|
||||
{
|
||||
path: "about_us",
|
||||
name: "about_us",
|
||||
meta: { title: "关于我们" },
|
||||
component: () => import("@/views/mine/about_us"),
|
||||
},
|
||||
{
|
||||
path: "version",
|
||||
name: "version",
|
||||
meta: { title: "当前版本" },
|
||||
component: () => import("@/views/mine/version"),
|
||||
},
|
||||
{
|
||||
path: "agreement",
|
||||
name: "agreement",
|
||||
meta: { title: "用户服务协议" },
|
||||
component: () => import("@/views/mine/agreement"),
|
||||
},
|
||||
{
|
||||
path: "privacy",
|
||||
name: "privacy",
|
||||
meta: { title: "隐私协议" },
|
||||
component: () => import("@/views/mine/privacy"),
|
||||
},
|
||||
{
|
||||
path: "enterprise_list",
|
||||
name: "enterprise_list",
|
||||
meta: { title: "单位列表" },
|
||||
component: () => import("@/views/mine/enterprise_list"),
|
||||
},
|
||||
{
|
||||
path: "registration_form",
|
||||
name: "registration_form",
|
||||
meta: { title: "个人登记表" },
|
||||
component: () => import("@/views/mine/registration_form"),
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
const router = createRouter({
|
||||
history: createWebHashHistory(import.meta.env.BASE_URL),
|
||||
routes,
|
||||
scrollBehavior(to, from, savedPosition) {
|
||||
if (savedPosition) {
|
||||
return savedPosition;
|
||||
} else {
|
||||
return { left: 0, top: 0 };
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
router.beforeEach((to, from, next) => {
|
||||
useMiscellaneousStore().setLoading(false);
|
||||
if (to.name !== "noWeChat") {
|
||||
if (navigator.userAgent.indexOf("MicroMessenger") === -1) {
|
||||
if (import.meta.env.MODE === "development") {
|
||||
next();
|
||||
} else {
|
||||
next({ name: "noWeChat" });
|
||||
}
|
||||
} else {
|
||||
if (to?.meta.isLogin === false) {
|
||||
next();
|
||||
} else {
|
||||
if (useUserStore().getUserInfo.USER_ID) {
|
||||
next();
|
||||
} else {
|
||||
next({ name: "login" });
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
});
|
||||
|
||||
export default router;
|
||||
|
|
@ -1,261 +0,0 @@
|
|||
<template>
|
||||
<div>
|
||||
<div class="fixed">
|
||||
<div v-if="data.videoSrc">
|
||||
<video
|
||||
id="player"
|
||||
playsinline
|
||||
controls
|
||||
:data-poster="data.videoPoster"
|
||||
style="width: 100vw"
|
||||
>
|
||||
<source :src="data.videoSrc" type="video/mp4" />
|
||||
</video>
|
||||
</div>
|
||||
<img
|
||||
v-if="!data.videoSrc"
|
||||
:src="FILE_PATH + data.info.COVERPATH"
|
||||
alt=""
|
||||
class="video_bg"
|
||||
/>
|
||||
<div class="title">
|
||||
<span>{{ data.info.CURRICULUMNAME }}</span>
|
||||
</div>
|
||||
<div class="tabs">
|
||||
<van-tabs v-model:active="data.tabsCurrent">
|
||||
<van-tab title="视频目录" />
|
||||
<van-tab title="详情" />
|
||||
</van-tabs>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="show_container scroll"
|
||||
:style="{ height: data.scrollHeight + 'px' }"
|
||||
v-show="data.tabsCurrent === 1"
|
||||
>
|
||||
<span>{{ data.info.CURRICULUMINTRODUCE }}</span>
|
||||
<div class="image">
|
||||
<img :src="FILE_PATH + data.info.COVERPATH" alt="" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="scroll" v-show="data.tabsCurrent === 0">
|
||||
<div class="video_container">
|
||||
<div
|
||||
class="video_list"
|
||||
v-for="(item, index) in data.chapterList"
|
||||
:key="index"
|
||||
>
|
||||
<div class="video_list_title">
|
||||
<van-icon class="ml5" name="column" color="#9fa7bc" size="24" />
|
||||
<div>{{ item.NAME }}</div>
|
||||
</div>
|
||||
<div class="video_list_main">
|
||||
<div v-if="item.nodes && item.nodes.length > 0">
|
||||
<div
|
||||
class="video_list_main_wrap"
|
||||
v-for="(item1, index1) in item.nodes"
|
||||
:key="index1"
|
||||
@click="fnGetPlayInfo(item1.VIDEOCOURSEWARE_ID)"
|
||||
>
|
||||
<div class="video_list_main_wrap_tit">
|
||||
{{ item1.COURSEWARENAME }}
|
||||
</div>
|
||||
<div class="video_list_main_wrap_info">
|
||||
<span class="ml5">{{ secondsCount(item1.VIDEOTIME) }}</span>
|
||||
<van-icon class="ml5" name="play-circle" color="#3377ff" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else>
|
||||
<div
|
||||
class="video_list_main_wrap"
|
||||
@click="fnGetPlayInfo(item.VIDEOCOURSEWARE_ID)"
|
||||
>
|
||||
<div class="video_list_main_wrap_tit">
|
||||
{{ item.COURSEWARENAME }}
|
||||
</div>
|
||||
<div class="video_list_main_wrap_info">
|
||||
<span class="ml5">{{ secondsCount(item.VIDEOTIME) }}</span>
|
||||
<van-icon class="ml5" name="play-circle" color="#3377ff" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { useRoute } from "vue-router";
|
||||
import { getCourseDetails, getCoursePlayInfo } from "@/request/api.js";
|
||||
import { nextTick, onBeforeUnmount, reactive } from "vue";
|
||||
import { showToast } from "vant";
|
||||
import Plyr from "plyr";
|
||||
import "plyr/dist/plyr.css";
|
||||
import Hls from "hls.js";
|
||||
|
||||
let player = null;
|
||||
let hls = null;
|
||||
const route = useRoute();
|
||||
const { CURRICULUM_ID } = route.query;
|
||||
const FILE_PATH = import.meta.env.VITE_FILE_PATH;
|
||||
const data = reactive({
|
||||
info: {},
|
||||
chapterList: [],
|
||||
tabsCurrent: 0,
|
||||
videoSrc: "",
|
||||
videoPoster: "",
|
||||
scrollHeight: "",
|
||||
});
|
||||
const fnGetData = async () => {
|
||||
const resData = await getCourseDetails({
|
||||
CURRICULUM_ID,
|
||||
});
|
||||
data.info = resData.pd;
|
||||
data.chapterList = resData.chapterList;
|
||||
await nextTick();
|
||||
data.scrollHeight = document.querySelector(".fixed").offsetHeight + "px";
|
||||
};
|
||||
fnGetData();
|
||||
const secondsCount = (second) => {
|
||||
if (!second) return 0;
|
||||
const h = parseInt((second / 60 / 60) % 24, 10);
|
||||
const m = parseInt((second / 60) % 60, 10);
|
||||
const s = parseInt(second % 60, 10);
|
||||
return (
|
||||
(h < 10 ? "0" + h : h) +
|
||||
":" +
|
||||
(m < 10 ? "0" + m : m) +
|
||||
":" +
|
||||
(s < 10 ? "0" + s : s)
|
||||
);
|
||||
};
|
||||
const fnGetPlayInfo = async (VIDEOCOURSEWARE_ID) => {
|
||||
fnDestroyVideo();
|
||||
const resData = await getCoursePlayInfo({
|
||||
VIDEOCOURSEWARE_ID,
|
||||
CURRICULUM_ID,
|
||||
DOMAIN_NAME: window.location.hostname,
|
||||
});
|
||||
if (resData.PLAYURL) {
|
||||
data.videoSrc = resData.PLAYURL;
|
||||
data.videoPoster = resData.COVERURL;
|
||||
await nextTick();
|
||||
fnCreateVideo();
|
||||
} else {
|
||||
showToast(resData.msg);
|
||||
}
|
||||
};
|
||||
const fnCreateVideo = () => {
|
||||
const video = document.querySelector("#player");
|
||||
player = new Plyr(video, {
|
||||
controls: [
|
||||
"play-large",
|
||||
"play",
|
||||
"progress",
|
||||
"current-time",
|
||||
"duration",
|
||||
"mute",
|
||||
"volume",
|
||||
"captions",
|
||||
"fullscreen",
|
||||
],
|
||||
seekTime: 0,
|
||||
ratio: "16:9",
|
||||
speed: { selected: 1, options: [1] },
|
||||
});
|
||||
hls = new Hls();
|
||||
hls.loadSource(data.videoSrc);
|
||||
hls.attachMedia(video);
|
||||
player.play();
|
||||
};
|
||||
const fnDestroyVideo = () => {
|
||||
player && player.destroy();
|
||||
player = null;
|
||||
hls && hls.destroy();
|
||||
hls = null;
|
||||
};
|
||||
onBeforeUnmount(() => {
|
||||
fnDestroyVideo();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.video_bg {
|
||||
width: 100%;
|
||||
height: 450px;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-weight: bold;
|
||||
background: #ffffff;
|
||||
padding: 20px;
|
||||
font-size: 34px;
|
||||
}
|
||||
|
||||
.tabs {
|
||||
background: #ffffff;
|
||||
padding: 0 40px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.video_container {
|
||||
width: 100%;
|
||||
background: #ffffff;
|
||||
padding: 40px;
|
||||
margin-top: 20px;
|
||||
box-sizing: border-box;
|
||||
|
||||
.video_list_title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-left: -10px;
|
||||
}
|
||||
|
||||
.video_list_main {
|
||||
margin-top: 20px;
|
||||
|
||||
.video_list_main_wrap {
|
||||
width: 100%;
|
||||
background: #fafafa;
|
||||
padding: 20px;
|
||||
border-radius: 10px;
|
||||
font-size: 26px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
box-sizing: border-box;
|
||||
color: #666666;
|
||||
margin-bottom: 20px;
|
||||
|
||||
.video_list_main_wrap_info {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ml5 {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.show_container {
|
||||
background: #ffffff;
|
||||
padding: 40px;
|
||||
|
||||
.image {
|
||||
margin-top: 20px;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 400px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.scroll {
|
||||
overflow-y: auto;
|
||||
height: calc(100vh - v-bind("data.scrollHeight") - var(--van-nav-bar-height));
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,51 +0,0 @@
|
|||
<template>
|
||||
<div>
|
||||
<face-authentication
|
||||
@face-pictures="fnSubmit"
|
||||
ref="faceAuthenticationRef"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import FaceAuthentication from "@/components/face_authentication/index.vue";
|
||||
import {
|
||||
setScanCodeToVerifyFace,
|
||||
setScanCodeToVerifyVideoFace,
|
||||
} from "@/request/api.js";
|
||||
import { showToast } from "vant";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
import { ref } from "vue";
|
||||
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
const { USER_ID, STUDENT_ID, VIDEOCOURSEWARE_ID } = route.query;
|
||||
const faceAuthenticationRef = ref(null);
|
||||
const fnSubmit = async ({ image, type }) => {
|
||||
try {
|
||||
if (type === "image") {
|
||||
await setScanCodeToVerifyFace({
|
||||
USERAVATARPREFIX: image.substring(0, image.indexOf("base64,") + 7),
|
||||
USERAVATARURL: image.substring(image.indexOf("base64,") + 7),
|
||||
USER_ID,
|
||||
STUDENT_ID,
|
||||
VIDEOCOURSEWARE_ID,
|
||||
});
|
||||
}
|
||||
if (type === "video") {
|
||||
const formData = new FormData();
|
||||
formData.append("FFILE", image);
|
||||
formData.append("USER_ID", USER_ID);
|
||||
formData.append("STUDENT_ID", STUDENT_ID);
|
||||
formData.append("VIDEOCOURSEWARE_ID", VIDEOCOURSEWARE_ID);
|
||||
await setScanCodeToVerifyVideoFace(formData);
|
||||
}
|
||||
showToast("认证成功");
|
||||
router.back();
|
||||
} catch {
|
||||
type === "image" && faceAuthenticationRef.value.fnCaptureFaceImage();
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
|
|
@ -1,98 +0,0 @@
|
|||
<template>
|
||||
<div class="tuijian-container">
|
||||
<div class="tuijian-main">
|
||||
<div>
|
||||
<div
|
||||
class="tuijian-wrap"
|
||||
v-for="item in data.list"
|
||||
:key="item.CURRICULUM_ID"
|
||||
@click="
|
||||
router.push({
|
||||
name: 'course',
|
||||
query: { CURRICULUM_ID: item.CURRICULUM_ID },
|
||||
})
|
||||
"
|
||||
>
|
||||
<div class="tuijian-wrap-img">
|
||||
<img :src="FILE_PATH + item.COVERPATH" alt="" />
|
||||
</div>
|
||||
<div class="tuijian-wrap-info">
|
||||
<div class="tuijian-wrap-info-title line-2">
|
||||
{{ item.CURRICULUMNAME }}
|
||||
</div>
|
||||
<div class="tuijian-wrap-info-text line-2">
|
||||
{{ item.CURRICULUMINTRODUCE }}
|
||||
</div>
|
||||
<div class="tuijian-wrap-info-num">
|
||||
共{{ item.COURSEWARECOUNT }}节
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { reactive } from "vue";
|
||||
import { getRecommendedCourseList } from "@/request/api.js";
|
||||
import { useRouter } from "vue-router";
|
||||
|
||||
const router = useRouter();
|
||||
const FILE_PATH = import.meta.env.VITE_FILE_PATH;
|
||||
const data = reactive({
|
||||
list: [],
|
||||
});
|
||||
const fnGetData = async () => {
|
||||
const resData = await getRecommendedCourseList();
|
||||
data.list = resData.varList;
|
||||
};
|
||||
fnGetData();
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.tuijian-container {
|
||||
padding: 20px;
|
||||
|
||||
.tuijian-main {
|
||||
.tuijian-wrap {
|
||||
border-bottom: 1px solid #eee;
|
||||
padding: 20px 0;
|
||||
display: flex;
|
||||
|
||||
.tuijian-wrap-img {
|
||||
flex-basis: 250px;
|
||||
//width: 250px;
|
||||
height: 152px;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.tuijian-wrap-info {
|
||||
flex: 1;
|
||||
padding-left: 10px;
|
||||
|
||||
.tuijian-wrap-info-title {
|
||||
font-size: 32px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.tuijian-wrap-info-text {
|
||||
font-size: 28px;
|
||||
color: #999999;
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.tuijian-wrap-info-num {
|
||||
margin-top: 10px;
|
||||
color: #999999;
|
||||
font-size: 26px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,351 +0,0 @@
|
|||
<template>
|
||||
<div>
|
||||
<qr-stream
|
||||
v-if="data.vifKey"
|
||||
v-show="data.vshowKey"
|
||||
style="height: 100vh"
|
||||
/>
|
||||
<div class="bnaner">
|
||||
<div class="sousuoer">
|
||||
<div class="sousuoinput">
|
||||
<van-field
|
||||
class="sousuomain"
|
||||
:border="false"
|
||||
left-icon="search"
|
||||
placeholder="搜索教程"
|
||||
/>
|
||||
</div>
|
||||
<div @click="goScanQR()">
|
||||
<img class="sao" src="/src/assets/images/index/sao.png" alt="" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="banner_num">
|
||||
<div class="banner_num-list" @click="router.push('')">
|
||||
<img src="/src/assets/images/index/ico1.png" alt="" />
|
||||
<div class="banner_num-list_test">新人礼包</div>
|
||||
</div>
|
||||
<div class="banner_num-list" @click="router.push({ name: 'hot' })">
|
||||
<img src="/src/assets/images/index/ico2.png" alt="" />
|
||||
<div class="banner_num-list_test">热门课程</div>
|
||||
</div>
|
||||
<div
|
||||
class="banner_num-list"
|
||||
@click="router.push({ name: 'recommend' })"
|
||||
>
|
||||
<img src="/src/assets/images/index/ico3.png" alt="" />
|
||||
<div class="banner_num-list_test">精品课程</div>
|
||||
</div>
|
||||
<div class="banner_num-list" @click="router.push({ name: 'feedback' })">
|
||||
<img src="/src/assets/images/index/ico4.png" alt="" />
|
||||
<div class="banner_num-list_test">问题反馈</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="container">
|
||||
<div class="bannerbottom_lister">
|
||||
<div
|
||||
class="bannerbottom_list"
|
||||
@click="router.push({ name: 'study', params: { tabsCurrent: 0 } })"
|
||||
>
|
||||
<div class="bannerbottom_list_info">
|
||||
<div class="bannerbottom_list_info_tit">我的学习</div>
|
||||
<div class="bannerbottom_list_info_test">提升自己</div>
|
||||
</div>
|
||||
<div class="bannerbottom_list_img" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="tuijian-container">
|
||||
<div class="tuijian-contain-top">
|
||||
<div class="tuijian-contain-top-left">
|
||||
<img src="/src/assets/images/index/hot.png" alt="" />
|
||||
<div class="jiangpin-title">精品推荐</div>
|
||||
</div>
|
||||
<div class="more" @click="router.push({ name: 'recommend' })">
|
||||
更多
|
||||
</div>
|
||||
</div>
|
||||
<div class="tuijian-main">
|
||||
<div>
|
||||
<div
|
||||
class="tuijian-wrap"
|
||||
v-for="item in data.list"
|
||||
:key="item.CURRICULUM_ID"
|
||||
@click="
|
||||
router.push({
|
||||
name: 'course',
|
||||
query: { CURRICULUM_ID: item.CURRICULUM_ID },
|
||||
})
|
||||
"
|
||||
>
|
||||
<div class="tuijian-wrap-img">
|
||||
<img :src="FILE_PATH + item.COVERPATH" alt="" />
|
||||
</div>
|
||||
<div class="tuijian-wrap-info">
|
||||
<div class="tuijian-wrap-info-title line-2">
|
||||
{{ item.CURRICULUMNAME }}
|
||||
</div>
|
||||
<div class="tuijian-wrap-info-text line-2">
|
||||
{{ item.CURRICULUMINTRODUCE }}
|
||||
</div>
|
||||
<div class="tuijian-wrap-info-num">
|
||||
共{{ item.COURSEWARECOUNT }}节
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<tab-bar />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import TabBar from "@/components/layout/TabBar.vue";
|
||||
import { QrStream } from "vue3-qr-reader";
|
||||
import { reactive } from "vue";
|
||||
import { getRecommendedCourseList, getCount } from "@/request/api.js";
|
||||
import { useRouter } from "vue-router";
|
||||
import { useUserStore } from "@/pinia/user.js";
|
||||
import { showConfirmDialog } from "vant";
|
||||
|
||||
const userStore = useUserStore();
|
||||
const FILE_PATH = import.meta.env.VITE_FILE_PATH;
|
||||
const router = useRouter();
|
||||
const data = reactive({
|
||||
list: [],
|
||||
vifKey: false,
|
||||
vshowKey: false,
|
||||
meetingShow: true,
|
||||
});
|
||||
const fnGetData = async () => {
|
||||
const resData = await getRecommendedCourseList();
|
||||
data.list = resData.varList;
|
||||
};
|
||||
fnGetData();
|
||||
|
||||
const fnGetCount = async () => {
|
||||
const resData = await getCount({
|
||||
USER_ID: userStore.getUserInfo.USER_ID,
|
||||
});
|
||||
if (resData.meetingCount && resData.meetingCount !== 0) {
|
||||
showConfirmDialog({
|
||||
title: "会议提醒",
|
||||
message: "会议已开始,请前往签到!",
|
||||
confirmButtonText: "前往签到",
|
||||
})
|
||||
.then(() => {
|
||||
router.push({ name: "meeting_list" });
|
||||
})
|
||||
.catch(() => {
|
||||
// on cancel
|
||||
});
|
||||
}
|
||||
};
|
||||
fnGetCount();
|
||||
|
||||
function goScanQR() {
|
||||
// data.vifKey = true;
|
||||
// setTimeout(() => {
|
||||
router.push({ name: "scanQRCode" });
|
||||
// }, 2000);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.bnaner {
|
||||
position: relative;
|
||||
height: 460px;
|
||||
background: url("/src/assets/images/index/banner.png") no-repeat bottom center;
|
||||
background-size: 100% 100%;
|
||||
padding: 0;
|
||||
|
||||
.sousuoer {
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding: 20px;
|
||||
|
||||
.sousuoinput {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
width: 80%;
|
||||
height: 70px;
|
||||
line-height: 70px;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
border-radius: 50px;
|
||||
padding-left: 70px;
|
||||
font-size: 28px;
|
||||
position: relative;
|
||||
|
||||
.van-cell {
|
||||
background: transparent;
|
||||
color: #fff;
|
||||
padding: 11px var(--van-cell-horizontal-padding);
|
||||
|
||||
:deep {
|
||||
.van-field__control {
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.sousuomain {
|
||||
position: absolute;
|
||||
left: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.sao {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
margin-left: 25px;
|
||||
}
|
||||
}
|
||||
|
||||
.banner_num {
|
||||
width: 90%;
|
||||
background: #fff;
|
||||
padding: 30px 20px;
|
||||
position: absolute;
|
||||
bottom: -65px;
|
||||
border-radius: 20px;
|
||||
box-sizing: border-box;
|
||||
left: 5%;
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
box-shadow: 0 0 20px #eee;
|
||||
|
||||
.banner_num-list {
|
||||
text-align: center;
|
||||
color: #333;
|
||||
font-size: 30px;
|
||||
|
||||
img {
|
||||
width: 62px;
|
||||
height: 62px;
|
||||
}
|
||||
|
||||
.banner_num-list_test {
|
||||
margin-top: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.container {
|
||||
padding: 40px;
|
||||
box-sizing: border-box;
|
||||
margin-top: 50px;
|
||||
|
||||
.bannerbottom_lister {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
height: 200px;
|
||||
|
||||
.bannerbottom_list {
|
||||
width: 100%;
|
||||
height: 150px;
|
||||
border-radius: 10px;
|
||||
padding: 20px;
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
background: url("/src/assets/images/index/my-study.png") no-repeat bottom
|
||||
center;
|
||||
background-size: 100% 100%;
|
||||
|
||||
.bannerbottom_list_img {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.bannerbottom_list_info_tit {
|
||||
font-size: 32px;
|
||||
font-weight: bold;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.bannerbottom_list_info_test {
|
||||
font-size: 28px;
|
||||
color: #999999;
|
||||
margin-top: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tuijian-container {
|
||||
.tuijian-contain-top {
|
||||
margin-top: -40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
.tuijian-contain-top-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
img {
|
||||
width: 40px;
|
||||
height: 56px;
|
||||
}
|
||||
|
||||
.jiangpin-title {
|
||||
font-size: 32px;
|
||||
font-weight: 600;
|
||||
margin-left: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.more {
|
||||
color: #999999;
|
||||
font-size: 28px;
|
||||
}
|
||||
}
|
||||
|
||||
.tuijian-main {
|
||||
margin-top: 20px;
|
||||
|
||||
.tuijian-wrap {
|
||||
border-bottom: 1px solid #eee;
|
||||
padding: 20px 0;
|
||||
display: flex;
|
||||
|
||||
.tuijian-wrap-img {
|
||||
flex-basis: 250px;
|
||||
//width: 250px;
|
||||
height: 152px;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.tuijian-wrap-info {
|
||||
flex: 1;
|
||||
padding-left: 10px;
|
||||
|
||||
.tuijian-wrap-info-title {
|
||||
font-size: 32px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.tuijian-wrap-info-text {
|
||||
font-size: 28px;
|
||||
color: #999999;
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.tuijian-wrap-info-num {
|
||||
margin-top: 10px;
|
||||
color: #999999;
|
||||
font-size: 26px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
<template>
|
||||
<div class="no_we_chat">
|
||||
已禁止本次访问:您必须使用微信内置浏览器访问本页面!
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script></script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.no_we_chat {
|
||||
padding: 50px;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
font-size: 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
color: #7e7d7d;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,98 +0,0 @@
|
|||
<template>
|
||||
<div class="tuijian-container">
|
||||
<div class="tuijian-main">
|
||||
<div>
|
||||
<div
|
||||
class="tuijian-wrap"
|
||||
v-for="item in data.list"
|
||||
:key="item.CURRICULUM_ID"
|
||||
@click="
|
||||
router.push({
|
||||
name: 'course',
|
||||
query: { CURRICULUM_ID: item.CURRICULUM_ID },
|
||||
})
|
||||
"
|
||||
>
|
||||
<div class="tuijian-wrap-img">
|
||||
<img :src="FILE_PATH + item.COVERPATH" alt="" />
|
||||
</div>
|
||||
<div class="tuijian-wrap-info">
|
||||
<div class="tuijian-wrap-info-title line-2">
|
||||
{{ item.CURRICULUMNAME }}
|
||||
</div>
|
||||
<div class="tuijian-wrap-info-text line-2">
|
||||
{{ item.CURRICULUMINTRODUCE }}
|
||||
</div>
|
||||
<div class="tuijian-wrap-info-num">
|
||||
共{{ item.COURSEWARECOUNT }}节
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { reactive } from "vue";
|
||||
import { getRecommendedCourseList } from "@/request/api.js";
|
||||
import { useRouter } from "vue-router";
|
||||
|
||||
const router = useRouter();
|
||||
const FILE_PATH = import.meta.env.VITE_FILE_PATH;
|
||||
const data = reactive({
|
||||
list: [],
|
||||
});
|
||||
const fnGetData = async () => {
|
||||
const resData = await getRecommendedCourseList();
|
||||
data.list = resData.varList;
|
||||
};
|
||||
fnGetData();
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.tuijian-container {
|
||||
padding: 20px;
|
||||
|
||||
.tuijian-main {
|
||||
.tuijian-wrap {
|
||||
border-bottom: 1px solid #eee;
|
||||
padding: 20px 0;
|
||||
display: flex;
|
||||
|
||||
.tuijian-wrap-img {
|
||||
flex-basis: 250px;
|
||||
//width: 250px;
|
||||
height: 152px;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.tuijian-wrap-info {
|
||||
flex: 1;
|
||||
padding-left: 10px;
|
||||
|
||||
.tuijian-wrap-info-title {
|
||||
font-size: 32px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.tuijian-wrap-info-text {
|
||||
font-size: 28px;
|
||||
color: #999999;
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.tuijian-wrap-info-num {
|
||||
margin-top: 10px;
|
||||
color: #999999;
|
||||
font-size: 26px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,311 +0,0 @@
|
|||
<template>
|
||||
<div className="page-scan">
|
||||
<!--返回-->
|
||||
<!-- <van-nav-bar title="扫描二维码/条形码" fixed @click-left="clickIndexLeft()" class="scan-index-bar">
|
||||
<template #left>
|
||||
<van-icon name="arrow-left" size="18" color="#fff" />
|
||||
<span style="color: #fff"> 取消 </span>
|
||||
</template>
|
||||
</van-nav-bar> -->
|
||||
<!-- <van-nav-bar-->
|
||||
<!-- title="扫描二维码/条形码"-->
|
||||
<!-- right-text="按钮"-->
|
||||
<!-- left-arrow-->
|
||||
<!-- fixed-->
|
||||
<!-- @click-right="onClickRight"-->
|
||||
<!-- @click-left="clickIndexLeft()"-->
|
||||
<!-- />-->
|
||||
<!-- 扫码区域 -->
|
||||
<!-- <video ref="video" id="video" class="scan-video" autoplay></video> -->
|
||||
|
||||
<div className="QrCode">
|
||||
<video ref="video" height="100%" width="100%" id="video" autoPlay></video>
|
||||
</div>
|
||||
<!-- 扫码样式一 -->
|
||||
<div className="Qr_scanner">
|
||||
<div className="box">
|
||||
<div className="line_row">
|
||||
<div className="line"></div>
|
||||
</div>
|
||||
<div className="angle"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 提示语 -->
|
||||
<div>{{ scanText }}</div>
|
||||
<div v-show="tipShow" className="scan-tip">{{ tipMsg }}</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import { reactive, toRefs } from "vue";
|
||||
import { BrowserMultiFormatReader } from "@zxing/library";
|
||||
import { showToast } from "vant";
|
||||
import { useRouter } from "vue-router";
|
||||
import { useUserStore } from "@/pinia/user.js";
|
||||
|
||||
const userStore = useUserStore();
|
||||
const router = useRouter();
|
||||
const data = reactive({
|
||||
loadingShow: false,
|
||||
codeReader: null,
|
||||
scanText: "",
|
||||
vin: null,
|
||||
tipMsg: "正在尝试识别....",
|
||||
tipShow: false,
|
||||
});
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const { loadingShow, codeReader, scanText, vin, tipMsg, tipShow } =
|
||||
toRefs(data);
|
||||
codeReader.value = new BrowserMultiFormatReader();
|
||||
|
||||
openScan();
|
||||
|
||||
function openScan() {
|
||||
codeReader.value
|
||||
.getVideoInputDevices()
|
||||
.then((videoInputDevices) => {
|
||||
tipShow.value = true;
|
||||
tipMsg.value = "正在调用摄像头...";
|
||||
// console.log("videoInputDevices", videoInputDevices);
|
||||
// 默认获取第一个摄像头设备id
|
||||
console.log("videoInputDevices", videoInputDevices);
|
||||
let firstDeviceId = videoInputDevices[0].deviceId;
|
||||
// 获取第一个摄像头设备的名称
|
||||
const videoInputDeviceslablestr = JSON.stringify(
|
||||
videoInputDevices[0].label
|
||||
);
|
||||
if (videoInputDevices.length > 1) {
|
||||
// 判断是否后置摄像头
|
||||
if (videoInputDeviceslablestr.indexOf("back") > -1) {
|
||||
firstDeviceId = videoInputDevices[0].deviceId;
|
||||
} else {
|
||||
firstDeviceId = videoInputDevices[1].deviceId;
|
||||
}
|
||||
}
|
||||
decodeFromInputVideoFunc(firstDeviceId);
|
||||
})
|
||||
.catch((err) => {
|
||||
tipShow.value = false;
|
||||
console.error(err);
|
||||
});
|
||||
}
|
||||
|
||||
function decodeFromInputVideoFunc(firstDeviceId) {
|
||||
codeReader.value.reset(); // 重置
|
||||
scanText.value = "";
|
||||
codeReader.value.decodeFromInputVideoDeviceContinuously(
|
||||
firstDeviceId,
|
||||
"video",
|
||||
(result, err) => {
|
||||
tipMsg.value = "正在尝试识别...";
|
||||
scanText.value = "";
|
||||
if (result) {
|
||||
// console.log("扫描结果", result);
|
||||
scanText.value = result.text;
|
||||
if (scanText.value) {
|
||||
tipShow.value = false;
|
||||
clickIndexLeft();
|
||||
// 这部分接下去的代码根据需要,读者自行编写了
|
||||
// this.$store.commit('app/SET_SCANTEXT', result.text);
|
||||
// console.log('已扫描的小票列表', this.$store.getters.scanTextArr);
|
||||
}
|
||||
}
|
||||
if (err && !err) {
|
||||
tipMsg.value = "识别失败";
|
||||
setTimeout(() => {
|
||||
tipShow.value = false;
|
||||
}, 2000);
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function clickIndexLeft() {
|
||||
// console.log("我要传参了", scanText.value);
|
||||
if (scanText.value.indexOf("%_face") > 0) {
|
||||
const idArrayStr = scanText.value.substring(
|
||||
0,
|
||||
scanText.value.indexOf("%_face")
|
||||
);
|
||||
const idArray = idArrayStr.split(",");
|
||||
|
||||
if (idArray.length !== 3) {
|
||||
showToast("数据错误,请联系管理员");
|
||||
} else if (idArray[1] === userStore.getUserInfo.USER_ID) {
|
||||
router.replace({
|
||||
name: "face_auth",
|
||||
query: {
|
||||
USER_ID: idArray[1],
|
||||
STUDENT_ID: idArray[0],
|
||||
VIDEOCOURSEWARE_ID: idArray[2],
|
||||
},
|
||||
});
|
||||
} else {
|
||||
showToast("请扫描自己的二维码");
|
||||
}
|
||||
}
|
||||
codeReader.value.reset(); // 重置
|
||||
}
|
||||
|
||||
// const onClickRight = () => showToast("按钮");
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
/* .scan-video {
|
||||
height: 80vh;
|
||||
width: 100%
|
||||
}
|
||||
|
||||
.scan-tip {
|
||||
width: 50vw;
|
||||
text-align: center;
|
||||
margin-bottom: 10vh;
|
||||
color: white;
|
||||
font-size: 5vw;
|
||||
}
|
||||
|
||||
.page-scan {
|
||||
overflow-y: hidden;
|
||||
background-color: #363636;
|
||||
} */
|
||||
|
||||
/**
|
||||
扫码样式一
|
||||
*/
|
||||
.QrCode {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
position: relative;
|
||||
|
||||
#video {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
|
||||
.Qr_scanner {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.Qr_scanner .box {
|
||||
width: 75vw;
|
||||
height: 75vw;
|
||||
max-height: 75vh;
|
||||
max-width: 75vh;
|
||||
position: relative;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
border: 1px solid rgb(43, 113, 254);
|
||||
|
||||
.line_row {
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
background-image: linear-gradient(
|
||||
0deg,
|
||||
transparent 24%,
|
||||
rgba(136, 176, 255, 0.1) 25%,
|
||||
rgba(136, 176, 255, 0.1) 26%,
|
||||
transparent 27%,
|
||||
transparent 74%,
|
||||
rgba(136, 176, 255, 0.1) 75%,
|
||||
rgba(136, 176, 255, 0.1) 76%,
|
||||
transparent 77%,
|
||||
transparent
|
||||
),
|
||||
linear-gradient(
|
||||
90deg,
|
||||
transparent 24%,
|
||||
rgba(136, 176, 255, 0.1) 25%,
|
||||
rgba(136, 176, 255, 0.1) 26%,
|
||||
transparent 27%,
|
||||
transparent 74%,
|
||||
rgba(136, 176, 255, 0.1) 75%,
|
||||
rgba(136, 176, 255, 0.1) 76%,
|
||||
transparent 77%,
|
||||
transparent
|
||||
);
|
||||
background-size: 3rem 3rem;
|
||||
background-position: -1rem -1rem;
|
||||
animation: Heightchange 2s infinite;
|
||||
animation-timing-function: cubic-bezier(0.53, 0, 0.43, 0.99);
|
||||
animation-delay: 1.4s;
|
||||
border-bottom: 1px solid rgba(136, 176, 255, 0.1);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: flex-end;
|
||||
}
|
||||
}
|
||||
|
||||
.Qr_scanner .line {
|
||||
width: 100%;
|
||||
height: 3px;
|
||||
background: #2b71fe;
|
||||
// opacity: 0.58;
|
||||
filter: blur(4px);
|
||||
}
|
||||
|
||||
.Qr_scanner .box:after,
|
||||
.Qr_scanner .box:before,
|
||||
.Qr_scanner .angle:after,
|
||||
.Qr_scanner .angle:before {
|
||||
content: "";
|
||||
display: block;
|
||||
position: absolute;
|
||||
width: 78px;
|
||||
height: 78px;
|
||||
border: 0.3rem solid transparent;
|
||||
}
|
||||
|
||||
.Qr_scanner .box:after,
|
||||
.Qr_scanner .box:before {
|
||||
top: -7px;
|
||||
border-top-color: #2b71fe;
|
||||
}
|
||||
|
||||
.Qr_scanner .angle:after,
|
||||
.Qr_scanner .angle:before {
|
||||
bottom: -7px;
|
||||
border-bottom-color: #2b71fe;
|
||||
}
|
||||
|
||||
.Qr_scanner .box:before,
|
||||
.Qr_scanner .angle:before {
|
||||
left: -7px;
|
||||
border-left-color: #2b71fe;
|
||||
}
|
||||
|
||||
.Qr_scanner .box:after,
|
||||
.Qr_scanner .angle:after {
|
||||
right: -7px;
|
||||
border-right-color: #2b71fe;
|
||||
}
|
||||
|
||||
@keyframes radar-beam {
|
||||
0% {
|
||||
transform: translateY(-100%);
|
||||
}
|
||||
100% {
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes Heightchange {
|
||||
0% {
|
||||
height: 0;
|
||||
}
|
||||
100% {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,278 +0,0 @@
|
|||
<template>
|
||||
<div class="bg">
|
||||
<div class="logo">
|
||||
<div v-if="!data.HAS_LOGO">
|
||||
<img src="~@/assets/images/lucency.png" alt="" />
|
||||
</div>
|
||||
<div v-if="data.HAS_LOGO === 'N'">
|
||||
<img
|
||||
src="https://img.zcool.cn/community/013a415fd17a7711013fdcc71843a2.jpg?x-oss-process=image/auto-orient,1/resize,m_lfit,w_1280,limit_1/sharpen,100"
|
||||
alt=""
|
||||
/>
|
||||
</div>
|
||||
<div v-if="data.HAS_LOGO === 'Y'">
|
||||
<img :src="FILE_PATH + data.oemForm.APP_LOGO_PATH" alt="" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form">
|
||||
<div class="title">
|
||||
<div>欢迎登录</div>
|
||||
<div v-if="!data.oemForm.CORP_NAME">联安云全员培训学员APP</div>
|
||||
<div v-else>
|
||||
{{ data.oemForm.CORP_NAME }}
|
||||
</div>
|
||||
</div>
|
||||
<van-form label-align="top">
|
||||
<van-cell-group :border="false">
|
||||
<van-field
|
||||
v-model="data.form.userName"
|
||||
label="身份证号"
|
||||
placeholder="请输入您的身份证号"
|
||||
/>
|
||||
<van-field
|
||||
v-model="data.form.userPwd"
|
||||
label="密码"
|
||||
placeholder="请输入您的密码"
|
||||
type="password"
|
||||
/>
|
||||
<van-cell>
|
||||
<van-checkbox v-model="data.checked" shape="square">
|
||||
30天内免登陆
|
||||
</van-checkbox>
|
||||
</van-cell>
|
||||
</van-cell-group>
|
||||
</van-form>
|
||||
<div class="loginbtn" @click="fnLogin">登录</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { reactive } from "vue";
|
||||
import { debounce } from "throttle-debounce";
|
||||
import { showToast, showDialog } from "vant";
|
||||
import { Login, getOemInfo, getAppVersion } from "@/request/api.js";
|
||||
import { useUserStore } from "@/pinia/user.js";
|
||||
import { useSavedUsers } from "@/pinia/savedUsers.js";
|
||||
import { useMiscellaneousStore } from "@/pinia/miscellaneous.js";
|
||||
import { useRouter } from "vue-router";
|
||||
import dayjs from "dayjs";
|
||||
import { useEventListener } from "@vueuse/core";
|
||||
|
||||
const userStore = useUserStore();
|
||||
const miscellaneousStore = useMiscellaneousStore();
|
||||
const savedUsersStore = useSavedUsers();
|
||||
const router = useRouter();
|
||||
const FILE_PATH = import.meta.env.VITE_FILE_PATH;
|
||||
const data = reactive({
|
||||
checked: false,
|
||||
form: {
|
||||
userName: "",
|
||||
userPwd: "",
|
||||
},
|
||||
oemForm: {
|
||||
CORP_NAME: "",
|
||||
APP_LOGO_PATH: "",
|
||||
},
|
||||
HAS_LOGO: "",
|
||||
APP_DOMAIN_NAME: "",
|
||||
appVersionNew: "",
|
||||
});
|
||||
const fnGetAppVersion = async () => {
|
||||
const resData = await getAppVersion();
|
||||
data.appVersionNew = resData.pd.VERSION;
|
||||
};
|
||||
useEventListener("resize", () => {
|
||||
if (document.activeElement.tagName === "INPUT") {
|
||||
window.setTimeout(() => {
|
||||
document.activeElement.scrollIntoViewIfNeeded();
|
||||
}, 100);
|
||||
}
|
||||
});
|
||||
const fnIdCard = (value) => {
|
||||
return /^[1-9]\d{5}[1-9]\d{3}((0\d)|(1[0-2]))(([0|1|2]\d)|3[0-1])\d{3}([0-9]|X)$/.test(
|
||||
value
|
||||
);
|
||||
};
|
||||
const fnLogin = debounce(
|
||||
1000,
|
||||
async () => {
|
||||
if (miscellaneousStore.getAppVersion >= data.appVersionNew) {
|
||||
if (!data.form.userName) {
|
||||
showToast("请输入用户名");
|
||||
return;
|
||||
}
|
||||
if (!fnIdCard(data.form.userName)) {
|
||||
showToast("身份证号格式不正确");
|
||||
return;
|
||||
}
|
||||
if (!data.form.userPwd) {
|
||||
showToast("密码不能为空");
|
||||
return;
|
||||
}
|
||||
if (data.checked) {
|
||||
savedUsersStore.setSavedUsers({
|
||||
userName: data.form.userName,
|
||||
userPwd: data.form.userPwd,
|
||||
startTime:
|
||||
savedUsersStore.getSavedUsers.startTime ||
|
||||
dayjs().format("YYYY-MM-DD"),
|
||||
endTime:
|
||||
savedUsersStore.getSavedUsers.endTime ||
|
||||
dayjs().add(30, "days").format("YYYY-MM-DD"),
|
||||
});
|
||||
} else {
|
||||
savedUsersStore.$reset();
|
||||
}
|
||||
const resData = await Login({
|
||||
KEYDATA: "qdkjchina" + data.form.userName + ",qd," + data.form.userPwd,
|
||||
PERSONNEL_TYPE: "6",
|
||||
});
|
||||
userStore.setUserInfo({
|
||||
USER_ID: resData.USER_ID, // 用户id
|
||||
NAME: resData.NAME, // 用户名
|
||||
USERNAME: resData.USERNAME, // 用户手机号
|
||||
CORPINFO_ID: resData.CORPINFO_ID, // 创建机构id
|
||||
// TOKEN: resData.token, // 创建机构id
|
||||
USER_SIGN_FILE_PATH: resData.USER_SIGN_FILE_PATH
|
||||
? FILE_PATH + resData.USER_SIGN_FILE_PATH
|
||||
: "", // 学员证照
|
||||
});
|
||||
|
||||
if (resData.AUTHENTICATION === "0") {
|
||||
await showDialog({
|
||||
title: "提示",
|
||||
message: "未人脸验证,点击确认前往人脸验证!",
|
||||
});
|
||||
await router.replace({
|
||||
name: "blankJump",
|
||||
query: { isFirst: "1", routerName: "face_authentication" },
|
||||
});
|
||||
} else {
|
||||
await router.replace({ name: "index" });
|
||||
}
|
||||
} else {
|
||||
showToast("当前版本过低,请点击微信右上角的刷新再试。");
|
||||
}
|
||||
},
|
||||
{ atBegin: true }
|
||||
);
|
||||
const fnInitUser = () => {
|
||||
const { userName, userPwd, startTime, endTime } =
|
||||
savedUsersStore.getSavedUsers;
|
||||
if (userName && userPwd) {
|
||||
if (dayjs(startTime).diff(dayjs(endTime), "days") <= 30) {
|
||||
data.form.userName = userName;
|
||||
data.form.userPwd = userPwd;
|
||||
data.checked = true;
|
||||
fnLogin();
|
||||
}
|
||||
}
|
||||
};
|
||||
await fnGetAppVersion();
|
||||
fnInitUser();
|
||||
const fnGetData = async () => {
|
||||
const resData = await getOemInfo({
|
||||
APP_DOMAIN_NAME: window.location.hostname,
|
||||
});
|
||||
if (resData.pd) {
|
||||
data.oemForm.CORP_NAME = resData.pd.CORP_NAME;
|
||||
data.oemForm.APP_LOGO_PATH = resData.pd.APP_LOGO_PATH;
|
||||
data.HAS_LOGO = "Y";
|
||||
} else {
|
||||
data.HAS_LOGO = "N";
|
||||
}
|
||||
};
|
||||
import.meta.env.PROD && fnGetData();
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.bg {
|
||||
background-color: #fff;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
background-image: url("/src/assets/images/login/login_bg.jpg");
|
||||
background-size: 750px 522px;
|
||||
background-repeat: no-repeat;
|
||||
background-position: top center;
|
||||
position: relative;
|
||||
padding-top: 300px;
|
||||
|
||||
.logo {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
width: 238px;
|
||||
height: 238px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
border: 1px solid rgba(255, 255, 255, 0.43);
|
||||
box-shadow: 1px 1px 1px rgba(0, 0, 0, 0.18);
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%) rotate(45deg);
|
||||
top: 300px;
|
||||
|
||||
div {
|
||||
width: 185px;
|
||||
height: 185px;
|
||||
border-radius: 50%;
|
||||
background-color: #fff;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
transform: rotate(-45deg);
|
||||
box-shadow: 0 0 27px rgba(0, 0, 0, 0.17);
|
||||
|
||||
img {
|
||||
width: 130px;
|
||||
height: 130px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.form {
|
||||
background: #fff;
|
||||
border-radius: 66px;
|
||||
padding: 60px;
|
||||
|
||||
.title {
|
||||
margin-top: 150px;
|
||||
margin-bottom: 50px;
|
||||
text-align: center;
|
||||
|
||||
div {
|
||||
&:nth-child(1) {
|
||||
font-weight: bold;
|
||||
font-size: 50px;
|
||||
}
|
||||
|
||||
&:nth-child(2) {
|
||||
color: #656565;
|
||||
font-size: 30px;
|
||||
margin-top: 30px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:deep {
|
||||
.van-field__label {
|
||||
color: #333;
|
||||
font-size: 36px;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.loginbtn {
|
||||
margin: var(--van-cell-group-inset-padding);
|
||||
background: #1082ff;
|
||||
color: #fff;
|
||||
font-size: 40px;
|
||||
text-align: center;
|
||||
padding: 20px;
|
||||
box-sizing: border-box;
|
||||
border-radius: 50px;
|
||||
margin-top: 100px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
<template>
|
||||
<div class="about-us">
|
||||
<van-cell-group :border="false">
|
||||
<van-cell
|
||||
isLink
|
||||
:to="{ name: 'agreement' }"
|
||||
title="用户服务协议"
|
||||
icon="/src/assets/images/mine/menu_yhxy.svg"
|
||||
/>
|
||||
<van-cell
|
||||
isLink
|
||||
:to="{ name: 'privacy' }"
|
||||
title="隐私协议"
|
||||
icon="/src/assets/images/mine/menu_yszc.svg"
|
||||
/>
|
||||
</van-cell-group>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup></script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.about-us {
|
||||
background-color: #fff;
|
||||
box-sizing: border-box;
|
||||
line-height: 60px;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
<template>
|
||||
<iframe
|
||||
src="http://192.168.192.201:8080/xieyi/privacy.html"
|
||||
style="width: 100vw; height: 100vh; border: none"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {};
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
<template>
|
||||
<div />
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { useRouter, useRoute } from "vue-router";
|
||||
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
router.replace({
|
||||
name: route.query.routerName,
|
||||
query: route.query,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
|
|
@ -1,93 +0,0 @@
|
|||
<template>
|
||||
<div>
|
||||
<div class="form">
|
||||
<van-form label-align="top" @submit="fnSubmit" @failed="fnFailed">
|
||||
<van-cell-group :border="false">
|
||||
<van-field
|
||||
v-model="data.form.oldPwd"
|
||||
label="原密码"
|
||||
type="password"
|
||||
:rules="[{ required: true, message: '请填写原密码' }]"
|
||||
/>
|
||||
<van-field
|
||||
v-model="data.form.nowPwd"
|
||||
label="新密码"
|
||||
type="password"
|
||||
:rules="[{ validator: validatorNowPwd }]"
|
||||
/>
|
||||
<van-field
|
||||
v-model="data.form.confirmPwd"
|
||||
label="确认新密码"
|
||||
type="password"
|
||||
:rules="[{ validator: validatorConfirmPwd }]"
|
||||
/>
|
||||
<van-cell>
|
||||
<van-button type="primary" round block native-type="submit">
|
||||
登录
|
||||
</van-button>
|
||||
</van-cell>
|
||||
</van-cell-group>
|
||||
</van-form>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { reactive } from "vue";
|
||||
import { showToast } from "vant";
|
||||
import { debounce } from "throttle-debounce";
|
||||
import { setChangePassword } from "@/request/api.js";
|
||||
import { useUserStore } from "@/pinia/user.js";
|
||||
import { useRouter } from "vue-router";
|
||||
|
||||
const userStore = useUserStore();
|
||||
const router = useRouter();
|
||||
const data = reactive({
|
||||
form: {
|
||||
oldPwd: "",
|
||||
nowPwd: "",
|
||||
confirmPwd: "",
|
||||
},
|
||||
});
|
||||
const validatorNowPwd = (val) => {
|
||||
if (!val) return "请填写新密码";
|
||||
if (val.length < 6 || val.length > 20) return "长度最少6个字符最多20个字符";
|
||||
};
|
||||
const validatorConfirmPwd = (val) => {
|
||||
if (!val) return "请再次填写新密码";
|
||||
if (val.length < 6 || val.length > 20) return "长度最少6个字符最多20个字符";
|
||||
if (val !== data.form.nowPwd) return "两次输入密码不一致";
|
||||
};
|
||||
const fnSubmit = debounce(
|
||||
1000,
|
||||
async () => {
|
||||
await setChangePassword({
|
||||
PASSWORD: data.form.oldPwd,
|
||||
NOWPASSWORD: data.form.nowPwd,
|
||||
USERNAME: userStore.getUserInfo.USERNAME,
|
||||
});
|
||||
showToast("修改成功");
|
||||
userStore.$reset();
|
||||
await router.replace({ name: "login" });
|
||||
},
|
||||
{ atBegin: true }
|
||||
);
|
||||
const fnFailed = (value) => {
|
||||
showToast(value.errors[0].message);
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.form {
|
||||
background: #fff;
|
||||
padding-top: 20px;
|
||||
|
||||
:deep {
|
||||
.van-field__label {
|
||||
color: #333;
|
||||
font-size: 32px;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||