鲁洪霞 2026-01-28 11:35:14 +08:00
parent c029b39483
commit 40b8c4163b
126 changed files with 14099 additions and 16467 deletions

8
.env
View File

@ -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

View File

@ -1 +0,0 @@
VITE_BASE=/

View File

@ -1 +0,0 @@
VITE_BASE=/app

View File

@ -1,3 +0,0 @@
public
dist
package.json

View File

@ -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",
},
};

24
.gitignore vendored
View File

@ -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?

View File

@ -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).

View File

@ -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>

View File

@ -1,10 +0,0 @@
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": [
"src/*"
]
}
}
}

13
nuxt-demo/.editorconfig Normal file
View File

@ -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

8
nuxt-demo/.gitignore vendored Normal file
View File

@ -0,0 +1,8 @@
/.idea
/.claude
/.nuxt
/dist
/node_modules
*.local
yarn.lock

75
nuxt-demo/README.md Normal file
View File

@ -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.

15
nuxt-demo/app/app.vue Normal file
View File

@ -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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 906 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 350 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@ -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>

View File

@ -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>

View File

@ -0,0 +1,11 @@
<script setup lang="ts">
</script>
<template>
<div>me</div>
</template>
<style scoped>
</style>

View File

@ -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", // 禁止无意义的字符串拼接
},
},
);

11
nuxt-demo/nuxt.config.ts Normal file
View File

@ -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: "河北秦安安全科技股份有限公司",
},
},
});

13232
nuxt-demo/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

30
nuxt-demo/package.json Normal file
View File

@ -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"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

@ -0,0 +1,2 @@
User-Agent: *
Disallow:

18
nuxt-demo/tsconfig.json Normal file
View File

@ -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"
}
]
}

8277
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -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"
}
}

View File

@ -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;
},
},
},
};

View File

@ -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

View File

@ -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>

View File

@ -1,31 +0,0 @@
* {
box-sizing: border-box;
}
// 5
// 使1使flexmin-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;
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 399 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 983 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -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

View File

@ -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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

View File

@ -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);
});
};
}
}

View File

@ -1,2 +0,0 @@
import mitt from "mitt";
export default mitt();

View File

@ -1,3 +0,0 @@
<template>
<router-view></router-view>
</template>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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");

View File

@ -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,
},
});

View File

@ -1,7 +0,0 @@
import { createPinia } from "pinia";
import piniaPersistedstate from "pinia-plugin-persistedstate";
const pinia = createPinia();
pinia.use(piniaPersistedstate);
export default pinia;

View File

@ -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"],
},
});

View File

@ -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,
},
});

View File

@ -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,
},
});

View File

@ -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); // 首页获取代办数

View File

@ -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);
});
});
}

View File

@ -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;

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

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