初始化
@ -1 +1,10 @@
|
||||
VITE_SERVICE_ENV=dev
|
||||
###
|
||||
# @Author: error: error: git config user.name & please set dead value or install git && error: git config user.email & please set dead value or install git & please set dead value or install git
|
||||
# @Date: 2023-07-28 16:10:52
|
||||
# @LastEditors: error: error: git config user.name & please set dead value or install git && error: git config user.email & please set dead value or install git & please set dead value or install git
|
||||
# @LastEditTime: 2023-08-08 18:10:57
|
||||
# @FilePath: \byhl-zt-app\.env.development
|
||||
# @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
|
||||
###
|
||||
VITE_SERVICE_ENV = dev
|
||||
VITE_APP_BASE_API = '/api'
|
||||
|
@ -1 +1,2 @@
|
||||
VITE_SERVICE_ENV=prod
|
||||
VITE_SERVICE_ENV = prod
|
||||
VITE_APP_BASE_API = '/api'
|
||||
|
@ -1,18 +1,28 @@
|
||||
{
|
||||
"extends": "@antfu",
|
||||
"root": true,
|
||||
"env": {
|
||||
"es2021": true,
|
||||
"node": true,
|
||||
"browser": false
|
||||
},
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
/** @see https://github.com/typescript-eslint/typescript-eslint/tree/master/packages/eslint-plugin#recommended-configs */
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"prettier"
|
||||
],
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 12,
|
||||
"sourceType": "module"
|
||||
},
|
||||
"plugins": ["@typescript-eslint"],
|
||||
"ignorePatterns": ["types/env.d.ts", "node_modules/**", "**/dist/**"],
|
||||
"rules": {
|
||||
// if else try catch 风格
|
||||
// ```
|
||||
// try { try {
|
||||
// loading() loading()
|
||||
// } ==> } catch (e) {
|
||||
// catch (e) { hideLoading()
|
||||
// hideLoading() }
|
||||
// }
|
||||
// ```
|
||||
"@typescript-eslint/brace-style": ["error", "1tbs"],
|
||||
|
||||
// if else 必须添加{} if()do() => if(){ do() }
|
||||
"curly":["error", "multi-line"]
|
||||
"@typescript-eslint/no-unused-vars": "error",
|
||||
"@typescript-eslint/no-var-requires": "off",
|
||||
"@typescript-eslint/consistent-type-imports": "error",
|
||||
"vue/multi-word-component-names": "off",
|
||||
"vue/no-v-html": "off"
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
/*
|
||||
* @Author: error: error: git config user.name & please set dead value or install git && error: git config user.email & please set dead value or install git & please set dead value or install git
|
||||
* @Date: 2023-08-07 16:15:41
|
||||
* @LastEditors: error: error: git config user.name & please set dead value or install git && error: git config user.email & please set dead value or install git & please set dead value or install git
|
||||
* @LastEditTime: 2023-08-08 17:33:54
|
||||
* @FilePath: \byhl-zt-app\src\api\captcha.ts
|
||||
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
|
||||
*/
|
||||
import type { CaptchaConfig } from '@/components/Captcha/types'
|
||||
import { LoginResult } from '@/types/user'
|
||||
import request from '@/utils/request'
|
||||
|
||||
export interface CaptchaData {
|
||||
id: string
|
||||
captcha: {
|
||||
backgroundImage: string
|
||||
templateImage: string
|
||||
backgroundImageWidth: number
|
||||
backgroundImageHeight: number
|
||||
sliderImageWidth: number
|
||||
sliderImageHeight: number
|
||||
data: {
|
||||
randomY?: string
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取验证码图片
|
||||
* @param type 图像验证码类型
|
||||
* @returns
|
||||
*/
|
||||
export function captchaGen(type?: string) {
|
||||
return request.get<CaptchaData>('/captcha/tianai/gen', { params: { type: type } })
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证码校验
|
||||
* @param id 验证码id
|
||||
* @param data true:校验通过/false:校验失败
|
||||
* @returns
|
||||
*/
|
||||
export function captchaCheck(id: string, data: CaptchaConfig) {
|
||||
return request.post<boolean>('/captcha/tianai/check', data, { params: { id } })
|
||||
}
|
@ -1,8 +1,32 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
/*
|
||||
* @Author: error: error: git config user.name & please set dead value or install git && error: git config user.email & please set dead value or install git & please set dead value or install git
|
||||
* @Date: 2023-07-28 16:10:52
|
||||
* @LastEditors: error: error: git config user.name & please set dead value or install git && error: git config user.email & please set dead value or install git & please set dead value or install git
|
||||
* @LastEditTime: 2023-08-08 16:36:08
|
||||
* @FilePath: \byhl-zt-app\src\api\login.ts
|
||||
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
|
||||
*/
|
||||
import request from '@/utils/request'
|
||||
import type { OAuth2LoginParam } from '@/components/Captcha/types'
|
||||
import { LoginResult } from '@/types/user'
|
||||
const BASIC_AUTHORIZATION = 'Basic dWk6dWk='
|
||||
export const loginAPI = (mobile: string, password: string) => {
|
||||
return request.post('login/password', {
|
||||
mobile,
|
||||
password
|
||||
})
|
||||
}
|
||||
/**
|
||||
* 账号密码登录
|
||||
* @param parameter
|
||||
*/
|
||||
export function accountLogin(parameter: OAuth2LoginParam) {
|
||||
return request<LoginResult>({
|
||||
url: '/oauth2/token',
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: BASIC_AUTHORIZATION
|
||||
},
|
||||
params: parameter
|
||||
})
|
||||
}
|
@ -0,0 +1,157 @@
|
||||
<template>
|
||||
<div v-show="visible" class="captcha-mask">
|
||||
<div class="slider concat">
|
||||
<div class="content">
|
||||
<div ref="concatImgDivRef" :style="concatImgDivStyle" class="concat-img-div"></div>
|
||||
<div ref="bgImgDivRef" :style="bgImgDivStyle" class="bg-img-div"></div>
|
||||
</div>
|
||||
<div class="slider-move">
|
||||
<div class="slider-move-track">拖动滑块完成拼图</div>
|
||||
<div
|
||||
class="slider-move-btn"
|
||||
:style="sliderMoveBtnStyle"
|
||||
@mousedown="down"
|
||||
@touchstart.prevent="down"
|
||||
></div>
|
||||
</div>
|
||||
<div class="bottom">
|
||||
<div class="close-btn" style="margin-right: 6px" @click="closeCaptcha"></div>
|
||||
<div class="refresh-btn" @click="refreshCaptcha"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, nextTick } from 'vue'
|
||||
import { captchaCheck, type CaptchaData, captchaGen } from '@/api/captcha'
|
||||
import type { CaptchaConfig } from '@/components/Captcha/types'
|
||||
import { useCaptcha } from '@/components/Captcha/useCaptcha'
|
||||
import type { CSSProperties } from 'vue'
|
||||
|
||||
const props = defineProps<{
|
||||
onSuccess?: (id: string) => void
|
||||
}>()
|
||||
|
||||
// 是否显示验证码
|
||||
const visible = ref(false)
|
||||
|
||||
function showCaptcha() {
|
||||
visible.value = true
|
||||
}
|
||||
|
||||
function closeCaptcha() {
|
||||
visible.value = false
|
||||
}
|
||||
|
||||
const bgImgDivRef = ref()
|
||||
const concatImgDivRef = ref()
|
||||
const captchaData = ref<CaptchaData>()
|
||||
|
||||
// 验证码 X 轴移动位置
|
||||
const moveX = ref(0)
|
||||
// 滑动按钮是否被激活
|
||||
const sliderMoveBtnActive = ref(false)
|
||||
// 背景图 div 样式
|
||||
const bgImgDivStyle = ref<CSSProperties>({})
|
||||
// 拼接图 div 样式
|
||||
const concatImgDivStyle = ref<CSSProperties>({ transform: 'translateX(0px)' })
|
||||
// 滑动按钮的样式
|
||||
const sliderMoveBtnStyle = computed<CSSProperties>(() => ({
|
||||
transform: `translateX(${moveX.value}px)`,
|
||||
backgroundPosition: sliderMoveBtnActive.value ? '-5px 31.0092%' : '-5px 11.79625%'
|
||||
}))
|
||||
|
||||
function reset() {
|
||||
moveX.value = 0
|
||||
sliderMoveBtnActive.value = false
|
||||
concatImgDivStyle.value.transform = 'translateX(0px)'
|
||||
}
|
||||
|
||||
/* 刷新验证码 */
|
||||
function refreshCaptcha() {
|
||||
reset()
|
||||
captchaGen('CONCAT').then(res => {
|
||||
console.log("🚀 ~ file: ConcatCaptcha.vue:73 ~ captchaGen ~ res:", res)
|
||||
captchaData.value = res
|
||||
bgImgDivStyle.value.backgroundImage = `url(${res.captcha.backgroundImage})`
|
||||
concatImgDivStyle.value.backgroundImage = `url(${res.captcha.backgroundImage})`
|
||||
concatImgDivStyle.value.backgroundPosition = '0px 0px'
|
||||
const backgroundImageHeight = res.captcha.backgroundImageHeight
|
||||
const height =
|
||||
((backgroundImageHeight - res.captcha.data.randomY!) / backgroundImageHeight) * 159
|
||||
concatImgDivStyle.value.height = `${height}px`
|
||||
|
||||
nextTick(() => {
|
||||
initConfig(
|
||||
bgImgDivRef.value.clientWidth,
|
||||
bgImgDivRef.value.clientHeight,
|
||||
concatImgDivRef.value.width,
|
||||
concatImgDivRef.value.height,
|
||||
206
|
||||
)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function doDown() {
|
||||
sliderMoveBtnActive.value = true
|
||||
}
|
||||
|
||||
function doMove(config: CaptchaConfig) {
|
||||
moveX.value = config.moveX!
|
||||
concatImgDivStyle.value.backgroundPositionX = `${moveX.value}pX`
|
||||
}
|
||||
|
||||
function valid(config: CaptchaConfig) {
|
||||
const data = {
|
||||
bgImageWidth: config.bgImageWidth,
|
||||
bgImageHeight: config.bgImageHeight,
|
||||
templateImageWidth: config.templateImageWidth,
|
||||
templateImageHeight: config.templateImageHeight,
|
||||
startSlidingTime: config.startTime,
|
||||
entSlidingTime: config.stopTime,
|
||||
trackList: config.trackArr
|
||||
}
|
||||
const { id } = captchaData.value!
|
||||
captchaCheck(id, data).then(res => {
|
||||
if (res) {
|
||||
props.onSuccess?.(id)
|
||||
closeCaptcha()
|
||||
} else {
|
||||
refreshCaptcha()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const { initConfig, down } = useCaptcha({ doDown, doMove, valid })
|
||||
|
||||
defineExpose({
|
||||
show() {
|
||||
refreshCaptcha()
|
||||
showCaptcha()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import './index.scss';
|
||||
|
||||
.concat {
|
||||
.bg-img-div {
|
||||
background-size: 100% 159px;
|
||||
background-image: none;
|
||||
background-position: 0 0;
|
||||
z-index: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.concat-img-div {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
background-size: 100% 159px;
|
||||
position: absolute;
|
||||
transform: translate(0px, 0px);
|
||||
z-index: 1;
|
||||
}
|
||||
</style>
|
@ -0,0 +1,145 @@
|
||||
<template>
|
||||
<div v-show="visible" class="captcha-mask">
|
||||
<div class="slider rotate">
|
||||
<div class="content">
|
||||
<div class="bg-img-div">
|
||||
<img ref="bgImgRef" alt="" :src="captchaData?.captcha.backgroundImage" />
|
||||
</div>
|
||||
<div class="rotate-img-div">
|
||||
<img
|
||||
ref="rotateImgRef"
|
||||
:style="rotateImgStyle"
|
||||
:src="captchaData?.captcha.templateImage"
|
||||
alt=""
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="slider-move">
|
||||
<div class="slider-move-track">拖动滑块旋转正确位置</div>
|
||||
<div
|
||||
class="slider-move-btn"
|
||||
:style="sliderMoveBtnStyle"
|
||||
@mousedown="down"
|
||||
@touchstart.prevent="down"
|
||||
></div>
|
||||
</div>
|
||||
<div class="bottom">
|
||||
<div class="close-btn" style="margin-right: 6px" @click="closeCaptcha"></div>
|
||||
<div class="refresh-btn" @click="refreshCaptcha"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { captchaGen, captchaCheck, type CaptchaData } from '@/api/captcha'
|
||||
import type { CaptchaConfig } from '@/components/Captcha/types'
|
||||
import { useCaptcha } from '@/components/Captcha/useCaptcha'
|
||||
import type { CSSProperties } from 'vue'
|
||||
|
||||
const props = defineProps<{
|
||||
onSuccess?: (id: string) => void
|
||||
}>()
|
||||
|
||||
// 是否显示验证码
|
||||
const visible = ref(false)
|
||||
function showCaptcha() {
|
||||
visible.value = true
|
||||
}
|
||||
function closeCaptcha() {
|
||||
visible.value = false
|
||||
}
|
||||
|
||||
const bgImgRef = ref()
|
||||
const rotateImgRef = ref()
|
||||
const captchaData = ref<CaptchaData>()
|
||||
|
||||
// 验证码 X 轴移动位置
|
||||
const moveX = ref(0)
|
||||
// 滑动按钮是否被激活
|
||||
const sliderMoveBtnActive = ref(false)
|
||||
// 旋转图块的样式
|
||||
const rotateImgStyle = ref<CSSProperties>({ transform: 'rotate(0deg)' })
|
||||
// 滑动按钮的样式
|
||||
const sliderMoveBtnStyle = computed<CSSProperties>(() => ({
|
||||
transform: `translateX(${moveX.value}px)`,
|
||||
backgroundPosition: sliderMoveBtnActive.value ? '-5px 31.0092%' : '-5px 11.79625%'
|
||||
}))
|
||||
|
||||
function reset() {
|
||||
moveX.value = 0
|
||||
sliderMoveBtnActive.value = false
|
||||
rotateImgStyle.value = { transform: 'rotate(0deg)' }
|
||||
}
|
||||
|
||||
/* 刷新验证码 */
|
||||
function refreshCaptcha() {
|
||||
reset()
|
||||
captchaGen('ROTATE').then(res => {
|
||||
captchaData.value = res
|
||||
nextTick(() => {
|
||||
initConfig(
|
||||
206,
|
||||
bgImgRef.value.height,
|
||||
rotateImgRef.value.width,
|
||||
rotateImgRef.value.height,
|
||||
206
|
||||
)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function doDown() {
|
||||
sliderMoveBtnActive.value = true
|
||||
}
|
||||
|
||||
function doMove(config: CaptchaConfig) {
|
||||
moveX.value = config.moveX!
|
||||
rotateImgStyle.value.transform = 'rotate(' + moveX.value / (config.end! / 360) + 'deg)'
|
||||
}
|
||||
|
||||
function valid(config: CaptchaConfig) {
|
||||
const data = {
|
||||
bgImageWidth: config.bgImageWidth,
|
||||
bgImageHeight: config.bgImageHeight,
|
||||
templateImageWidth: config.templateImageWidth,
|
||||
templateImageHeight: config.templateImageHeight,
|
||||
startSlidingTime: config.startTime,
|
||||
entSlidingTime: config.stopTime,
|
||||
trackList: config.trackArr
|
||||
}
|
||||
const { id } = captchaData.value!
|
||||
captchaCheck(id, data).then(res => {
|
||||
if (res) {
|
||||
props.onSuccess?.(id)
|
||||
closeCaptcha()
|
||||
} else {
|
||||
refreshCaptcha()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const { initConfig, down } = useCaptcha({ doDown, doMove, valid })
|
||||
|
||||
defineExpose({
|
||||
show() {
|
||||
refreshCaptcha()
|
||||
showCaptcha()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import './index.scss';
|
||||
|
||||
.rotate-img-div {
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
transform: rotate(0deg);
|
||||
margin-left: 50px;
|
||||
}
|
||||
|
||||
.rotate-img-div img {
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
@ -0,0 +1,144 @@
|
||||
<template>
|
||||
<div v-show="visible" class="captcha-mask">
|
||||
<div class="slider">
|
||||
<div class="content">
|
||||
<div class="bg-img-div">
|
||||
<img ref="bgImgRef" alt="" :src="captchaData?.captcha.backgroundImage" />
|
||||
</div>
|
||||
<div class="slider-img-div">
|
||||
<img
|
||||
ref="sliderImgRef"
|
||||
:style="sliderImgStyle"
|
||||
:src="captchaData?.captcha.templateImage"
|
||||
alt=""
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="slider-move">
|
||||
<div class="slider-move-track">拖动滑块完成拼图</div>
|
||||
<div
|
||||
class="slider-move-btn"
|
||||
:style="sliderMoveBtnStyle"
|
||||
@mousedown="down"
|
||||
@touchstart.prevent="down"
|
||||
></div>
|
||||
</div>
|
||||
<div class="bottom">
|
||||
<div class="close-btn" style="margin-right: 6px" @click="closeCaptcha"></div>
|
||||
<div class="refresh-btn" @click="refreshCaptcha"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { captchaGen, captchaCheck, type CaptchaData } from '@/api/captcha'
|
||||
import type { CaptchaConfig } from '@/components/Captcha/types'
|
||||
import { useCaptcha } from '@/components/Captcha/useCaptcha'
|
||||
import type { CSSProperties } from 'vue'
|
||||
|
||||
const props = defineProps<{
|
||||
onSuccess?: (id: string) => void
|
||||
}>()
|
||||
|
||||
// 是否显示验证码
|
||||
const visible = ref(false)
|
||||
function showCaptcha() {
|
||||
visible.value = true
|
||||
}
|
||||
function closeCaptcha() {
|
||||
visible.value = false
|
||||
}
|
||||
|
||||
const bgImgRef = ref()
|
||||
const sliderImgRef = ref()
|
||||
const captchaData = ref<CaptchaData>()
|
||||
|
||||
// 验证码 X 轴移动位置
|
||||
const moveX = ref(0)
|
||||
// 滑动按钮是否被激活
|
||||
const sliderMoveBtnActive = ref(false)
|
||||
// 滑动图块的样式
|
||||
const sliderImgStyle = computed<CSSProperties>(() => ({
|
||||
transform: `translateX(${moveX.value}px)`
|
||||
}))
|
||||
// 滑动按钮的样式
|
||||
const sliderMoveBtnStyle = computed<CSSProperties>(() => ({
|
||||
transform: `translateX(${moveX.value}px)`,
|
||||
backgroundPosition: sliderMoveBtnActive.value ? '-5px 31.0092%' : '-5px 11.79625%'
|
||||
}))
|
||||
|
||||
function reset() {
|
||||
moveX.value = 0
|
||||
sliderMoveBtnActive.value = false
|
||||
}
|
||||
|
||||
/* 刷新验证码 */
|
||||
function refreshCaptcha() {
|
||||
reset()
|
||||
captchaGen().then(res => {
|
||||
console.log("🚀 ~ file: SliderCaptcha.vue:80 ~ captchaGen ~ res:", res)
|
||||
captchaData.value = res
|
||||
nextTick(() => {
|
||||
initConfig(
|
||||
bgImgRef.value.width,
|
||||
bgImgRef.value.height,
|
||||
sliderImgRef.value.width,
|
||||
sliderImgRef.value.height,
|
||||
206
|
||||
)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function doDown() {
|
||||
sliderMoveBtnActive.value = true
|
||||
}
|
||||
|
||||
function doMove(config: CaptchaConfig) {
|
||||
moveX.value = config.moveX!
|
||||
}
|
||||
|
||||
function valid(config: CaptchaConfig) {
|
||||
const {
|
||||
bgImageWidth,
|
||||
bgImageHeight,
|
||||
templateImageWidth,
|
||||
templateImageHeight,
|
||||
startTime: startSlidingTime,
|
||||
stopTime: endSlidingTime,
|
||||
trackArr: trackList
|
||||
} = config
|
||||
const captchaCheckConfig = {
|
||||
bgImageWidth,
|
||||
bgImageHeight,
|
||||
templateImageWidth,
|
||||
templateImageHeight,
|
||||
startSlidingTime,
|
||||
endSlidingTime,
|
||||
trackList
|
||||
}
|
||||
const { id } = captchaData.value!
|
||||
captchaCheck(id, captchaCheckConfig).then(res => {
|
||||
if (res) {
|
||||
props.onSuccess?.(id)
|
||||
closeCaptcha()
|
||||
} else {
|
||||
refreshCaptcha()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const { initConfig, down } = useCaptcha({ doDown, doMove, valid })
|
||||
|
||||
defineExpose({
|
||||
show() {
|
||||
refreshCaptcha()
|
||||
showCaptcha()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import './index.scss';
|
||||
</style>
|
@ -0,0 +1,221 @@
|
||||
<template>
|
||||
<view v-show="visible" class="captcha-mask">
|
||||
<view class="slider word-click">
|
||||
<view class="slider-move">
|
||||
<text class="slider-move-span">请依次点击:</text>
|
||||
<img :src="sliderImageUrl" class="tip-img" alt="" />
|
||||
</view>
|
||||
<view ref="contentRef" class="content" @click="wordClick">
|
||||
<view ref="bgImgDivRef" class="bg-img-div">
|
||||
<img ref="bgImgRef" alt="" :src="backgroundImageUrl" />
|
||||
</view>
|
||||
<view class="bg-click-div">
|
||||
<text
|
||||
v-for="item in clickPositions"
|
||||
:key="item.number"
|
||||
class="click-span"
|
||||
:style="`left:${item.left}px;top:${item.top}px`"
|
||||
>
|
||||
{{ item.number }}
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="bottom">
|
||||
<view class="close-btn" style="margin-right: 6px" @click="closeCaptcha"></view>
|
||||
<view class="refresh-btn" @click="refreshCaptcha"></view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { captchaGen, captchaCheck, type CaptchaData } from '@/api/captcha'
|
||||
import type { CaptchaConfig, Track } from '@/components/Captcha/types'
|
||||
|
||||
const props = defineProps<{
|
||||
onSuccess?: (id: string) => void
|
||||
}>()
|
||||
|
||||
// 是否显示验证码
|
||||
const visible = ref(false)
|
||||
function showCaptcha() {
|
||||
visible.value = true
|
||||
}
|
||||
function closeCaptcha() {
|
||||
visible.value = false
|
||||
}
|
||||
|
||||
const contentRef = ref()
|
||||
const bgImgRef = ref()
|
||||
const bgImgDivRef = ref()
|
||||
const captchaData = ref<CaptchaData>()
|
||||
|
||||
const backgroundImageUrl = ref()
|
||||
const sliderImageUrl = ref()
|
||||
const tpImageUrl = ref()
|
||||
|
||||
let startSlidingTime: Date
|
||||
let entSlidingTime: Date
|
||||
const trackArr: Track[] = []
|
||||
// 选中文字次数
|
||||
const clickCount = ref(0)
|
||||
|
||||
type ClickPosition = {
|
||||
left: number
|
||||
top: number
|
||||
number: number
|
||||
}
|
||||
const clickPositions = ref<ClickPosition[]>([])
|
||||
|
||||
function wordClick(event: MouseEvent) {
|
||||
if (clickCount.value >= 4) {
|
||||
return
|
||||
}
|
||||
clickCount.value++
|
||||
if (clickCount.value === 1) {
|
||||
startSlidingTime = new Date()
|
||||
// move 轨迹
|
||||
window.addEventListener('mousemove', move)
|
||||
}
|
||||
trackArr.push({
|
||||
x: event.offsetX,
|
||||
y: event.offsetY,
|
||||
type: 'click',
|
||||
t: new Date().getTime() - startSlidingTime.getTime()
|
||||
})
|
||||
const left = event.offsetX - 10
|
||||
const top = event.offsetY - 10
|
||||
|
||||
clickPositions.value.push({ left: left, top: top, number: clickCount.value })
|
||||
if (clickCount.value === 4) {
|
||||
// 校验
|
||||
entSlidingTime = new Date()
|
||||
window.removeEventListener('mousemove', move)
|
||||
valid({
|
||||
bgImageWidth: bgImgDivRef.value.clientWidth,
|
||||
bgImageHeight: contentRef.value.clientHeight,
|
||||
sliderImageWidth: -1,
|
||||
sliderImageHeight: -1,
|
||||
startTime: startSlidingTime,
|
||||
stopTime: entSlidingTime,
|
||||
trackArr: trackArr
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function move(event: MouseEvent) {
|
||||
trackArr.push({
|
||||
x: event.offsetX,
|
||||
y: event.offsetY,
|
||||
t: new Date().getTime() - startSlidingTime.getTime(),
|
||||
type: 'move'
|
||||
})
|
||||
}
|
||||
|
||||
function reset() {
|
||||
startSlidingTime = new Date()
|
||||
entSlidingTime = new Date()
|
||||
trackArr.length = 0
|
||||
clickPositions.value = []
|
||||
clickCount.value = 0
|
||||
window.removeEventListener('mousemove', move)
|
||||
}
|
||||
|
||||
/* 刷新验证码 */
|
||||
function refreshCaptcha() {
|
||||
reset()
|
||||
captchaGen('WORD_IMAGE_CLICK').then(res => {
|
||||
console.log("🚀 ~ file: WordClickCaptcha.vue:128 ~ captchaGen ~ res:", res)
|
||||
captchaData.value = res
|
||||
backgroundImageUrl.value = res.captcha.backgroundImage
|
||||
sliderImageUrl.value = res.captcha.templateImage
|
||||
tpImageUrl.value = res.captcha.templateImage
|
||||
})
|
||||
}
|
||||
|
||||
function valid(config: CaptchaConfig) {
|
||||
const data = {
|
||||
bgImageWidth: config.bgImageWidth,
|
||||
bgImageHeight: config.bgImageHeight,
|
||||
templateImageWidth: config.templateImageWidth,
|
||||
templateImageHeight: config.templateImageHeight,
|
||||
startSlidingTime: config.startTime,
|
||||
entSlidingTime: config.stopTime,
|
||||
trackList: config.trackArr
|
||||
}
|
||||
const { id } = captchaData.value!
|
||||
captchaCheck(id, data).then(res => {
|
||||
if (res) {
|
||||
props.onSuccess?.(id)
|
||||
closeCaptcha()
|
||||
} else {
|
||||
refreshCaptcha()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
show() {
|
||||
refreshCaptcha()
|
||||
showCaptcha()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import './index.scss';
|
||||
|
||||
.slider.word-click {
|
||||
height: 250px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.word-click {
|
||||
.bg-img-div {
|
||||
z-index: 0;
|
||||
}
|
||||
.slider-move {
|
||||
height: 40px;
|
||||
margin: 0;
|
||||
}
|
||||
.bottom {
|
||||
margin-top: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.bg-click-div {
|
||||
z-index: 1;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.tip-img {
|
||||
width: 130px;
|
||||
position: absolute;
|
||||
right: 5px;
|
||||
}
|
||||
|
||||
.slider-move-span {
|
||||
font-size: 18px;
|
||||
display: inline-block;
|
||||
height: 40px;
|
||||
line-height: 40px;
|
||||
}
|
||||
|
||||
.click-span {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
border-radius: 50px;
|
||||
background-color: #409eff;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
text-align: center;
|
||||
line-height: 20px;
|
||||
color: #fff;
|
||||
border: 2px solid #fff;
|
||||
}
|
||||
</style>
|
@ -0,0 +1,118 @@
|
||||
.slider {
|
||||
background-color: #fff;
|
||||
width: 278px;
|
||||
height: 285px;
|
||||
z-index: 999;
|
||||
box-sizing: border-box;
|
||||
padding: 9px;
|
||||
border-radius: 6px;
|
||||
box-shadow: 0 0 11px 0 #999999;
|
||||
}
|
||||
|
||||
.slider .content {
|
||||
width: 100%;
|
||||
height: 159px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.bg-img-div {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
transform: translate(0px, 0px);
|
||||
}
|
||||
|
||||
.slider-img-div {
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
transform: translate(0px, 0px);
|
||||
}
|
||||
|
||||
.bg-img-div img {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.slider-img-div img {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.slider .slider-move {
|
||||
height: 60px;
|
||||
width: 100%;
|
||||
margin: 11px 0;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.slider .bottom {
|
||||
height: 19px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.refresh-btn,
|
||||
.close-btn,
|
||||
.slider-move-track,
|
||||
.slider-move-btn {
|
||||
background: url(@/static/sprite.1.2.4.png) no-repeat;
|
||||
}
|
||||
|
||||
.refresh-btn,
|
||||
.close-btn {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.slider-move .slider-move-track {
|
||||
line-height: 38px;
|
||||
font-size: 14px;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
color: #88949d;
|
||||
-moz-user-select: none;
|
||||
-webkit-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.slider {
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.slider-move .slider-move-btn {
|
||||
transform: translate(0px, 0px);
|
||||
background-position: -5px 11.79625%;
|
||||
position: absolute;
|
||||
top: -12px;
|
||||
left: 0;
|
||||
width: 66px;
|
||||
height: 66px;
|
||||
}
|
||||
|
||||
.slider-move-btn:hover,
|
||||
.close-btn:hover,
|
||||
.refresh-btn:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.bottom .close-btn {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
background-position: 0 44.86874%;
|
||||
}
|
||||
|
||||
.bottom .refresh-btn {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
background-position: 0 81.38425%;
|
||||
}
|
||||
|
||||
.captcha-mask {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
z-index: 1009;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
@ -0,0 +1,4 @@
|
||||
export { default as SliderCaptcha } from '@/components/Captcha/SliderCaptcha.vue'
|
||||
export { default as RotateCaptcha } from '@/components/Captcha/RotateCaptcha.vue'
|
||||
export { default as ConcatCaptcha } from '@/components/Captcha/ConcatCaptcha.vue'
|
||||
export { default as WordClickCaptcha } from '@/components/Captcha/WordClickCaptcha.vue'
|
@ -0,0 +1,49 @@
|
||||
import { LoginResult } from '@/types/user'
|
||||
export interface CaptchaConfig {
|
||||
startX?: number
|
||||
startY?: number
|
||||
type?: string
|
||||
startTime?: Date
|
||||
stopTime?: Date
|
||||
trackArr?: Track[]
|
||||
end?: number
|
||||
bgImageWidth?: number
|
||||
bgImageHeight?: number
|
||||
templateImageWidth?: number
|
||||
templateImageHeight?: number
|
||||
movePercent?: number
|
||||
moveX?: number
|
||||
t?: number
|
||||
}
|
||||
|
||||
export interface Track {
|
||||
x: number
|
||||
y: number
|
||||
type: string
|
||||
t: number
|
||||
}
|
||||
|
||||
export interface CaptchaProps {
|
||||
onSuccess?: (id: string) => void
|
||||
}
|
||||
|
||||
export interface LoginFormInstance {
|
||||
doLogin: (captchaId?: string) => Promise<LoginResult>
|
||||
}
|
||||
|
||||
export interface AccountLoginParam {
|
||||
username: string
|
||||
password: string
|
||||
grant_type: string
|
||||
captchaId?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* 手机号登录所需参数
|
||||
*/
|
||||
export interface MobileLoginParam {
|
||||
mobile?: string
|
||||
captcha?: string
|
||||
}
|
||||
|
||||
export type OAuth2LoginParam = (AccountLoginParam | MobileLoginParam)
|
@ -0,0 +1,127 @@
|
||||
import type { Track, CaptchaConfig } from '@/components/Captcha/types'
|
||||
|
||||
export const useCaptcha = (
|
||||
options: {
|
||||
doDown?: (captchaConfig: CaptchaConfig) => void
|
||||
doMove?: (captchaConfig: CaptchaConfig) => void
|
||||
valid?: (captchaConfig: CaptchaConfig) => void
|
||||
} = {}
|
||||
) => {
|
||||
// 验证码配置
|
||||
let currentCaptchaConfig: CaptchaConfig = {}
|
||||
|
||||
function initConfig(
|
||||
bgImageWidth: number,
|
||||
bgImageHeight: number,
|
||||
sliderImageWidth: number,
|
||||
sliderImageHeight: number,
|
||||
end: number
|
||||
) {
|
||||
currentCaptchaConfig = {
|
||||
startTime: new Date(),
|
||||
trackArr: [],
|
||||
movePercent: 0,
|
||||
bgImageWidth,
|
||||
bgImageHeight,
|
||||
sliderImageWidth,
|
||||
sliderImageHeight,
|
||||
end
|
||||
}
|
||||
}
|
||||
|
||||
const down = (event: MouseEvent | TouchEvent) => {
|
||||
const targetTouches = (event as TouchEvent)?.targetTouches
|
||||
let startX = (event as MouseEvent)?.pageX
|
||||
let startY = (event as MouseEvent)?.pageY
|
||||
if (startX === undefined) {
|
||||
startX = Math.round(targetTouches[0].pageX)
|
||||
startY = Math.round(targetTouches[0].pageY)
|
||||
}
|
||||
currentCaptchaConfig.startX = startX
|
||||
currentCaptchaConfig.startY = startY
|
||||
|
||||
const pageX = currentCaptchaConfig.startX
|
||||
const pageY = currentCaptchaConfig.startY
|
||||
const startTime = currentCaptchaConfig.startTime
|
||||
const trackArr = currentCaptchaConfig.trackArr
|
||||
trackArr!.push({
|
||||
x: pageX - startX,
|
||||
y: pageY - startY,
|
||||
type: 'down',
|
||||
t: new Date().getTime() - startTime!.getTime()
|
||||
})
|
||||
// pc
|
||||
window.addEventListener('mousemove', move)
|
||||
window.addEventListener('mouseup', up)
|
||||
// 手机端
|
||||
window.addEventListener('touchmove', move, false)
|
||||
window.addEventListener('touchend', up, false)
|
||||
|
||||
options.doDown?.(currentCaptchaConfig)
|
||||
}
|
||||
|
||||
function move(event: MouseEvent | TouchEvent) {
|
||||
let touchMouseEvent: MouseEvent | Touch = event as MouseEvent
|
||||
if (window.TouchEvent && event instanceof TouchEvent) {
|
||||
touchMouseEvent = event.touches[0]
|
||||
}
|
||||
const pageX = Math.round(touchMouseEvent.pageX)
|
||||
const pageY = Math.round(touchMouseEvent.pageY)
|
||||
const startX = currentCaptchaConfig.startX as number
|
||||
const startY = currentCaptchaConfig.startY as number
|
||||
const startTime = currentCaptchaConfig.startTime as Date
|
||||
const end = currentCaptchaConfig.end as number
|
||||
const bgImageWidth = currentCaptchaConfig.bgImageWidth as number
|
||||
const trackArr = currentCaptchaConfig.trackArr as unknown[]
|
||||
let moveX = pageX - startX
|
||||
const track = {
|
||||
x: pageX - startX,
|
||||
y: pageY - startY,
|
||||
type: 'move',
|
||||
t: new Date().getTime() - startTime.getTime()
|
||||
}
|
||||
trackArr.push(track)
|
||||
if (moveX < 0) {
|
||||
moveX = 0
|
||||
} else if (moveX > end) {
|
||||
moveX = end
|
||||
}
|
||||
currentCaptchaConfig.moveX = moveX
|
||||
currentCaptchaConfig.movePercent = moveX / bgImageWidth
|
||||
|
||||
options.doMove?.(currentCaptchaConfig)
|
||||
}
|
||||
|
||||
function up(event: MouseEvent | TouchEvent) {
|
||||
let touchMouseEvent: MouseEvent | Touch = event as MouseEvent
|
||||
window.removeEventListener('mousemove', move)
|
||||
window.removeEventListener('mouseup', up)
|
||||
window.removeEventListener('touchmove', move)
|
||||
window.removeEventListener('touchend', up)
|
||||
if (window.TouchEvent && event instanceof TouchEvent) {
|
||||
touchMouseEvent = event.changedTouches[0]
|
||||
}
|
||||
currentCaptchaConfig.stopTime = new Date()
|
||||
const pageX = Math.round(touchMouseEvent.pageX)
|
||||
const pageY = Math.round(touchMouseEvent.pageY)
|
||||
const startX = currentCaptchaConfig.startX as number
|
||||
const startY = currentCaptchaConfig.startY as number
|
||||
const startTime = currentCaptchaConfig.startTime as Date
|
||||
const trackArr = currentCaptchaConfig.trackArr as Track[]
|
||||
|
||||
const track: Track = {
|
||||
x: pageX - startX,
|
||||
y: pageY - startY,
|
||||
type: 'up',
|
||||
t: new Date().getTime() - startTime.getTime()
|
||||
}
|
||||
trackArr.push(track)
|
||||
|
||||
options.valid?.(currentCaptchaConfig)
|
||||
}
|
||||
|
||||
return {
|
||||
initConfig,
|
||||
down
|
||||
}
|
||||
}
|
@ -1,62 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import type {
|
||||
Doctor
|
||||
} from '@/types/consult'
|
||||
|
||||
defineProps < {
|
||||
item: Doctor
|
||||
} > ()
|
||||
</script>
|
||||
<template>
|
||||
<view class="doctor-card">
|
||||
<van-image round :src="item.avatar" />
|
||||
<p class="name">{{ item.name }}</p>
|
||||
<p class="van-ellipsis">{{ item.hospitalName }} {{ item.depName }}</p>
|
||||
<p>{{ item.positionalTitles }}</p>
|
||||
<van-button round size="small" type="primary">
|
||||
{{ item.likeFlag === 1 ? '已关注' : '+ 关注' }}
|
||||
</van-button>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.doctor-card {
|
||||
width: 135px;
|
||||
height: 190px;
|
||||
background: #fff;
|
||||
border-radius: 20px;
|
||||
box-shadow: 0px 0px 11px 0px rgba(229, 229, 229, 0.2);
|
||||
text-align: center;
|
||||
padding: 15px;
|
||||
margin-left: 15px;
|
||||
display: inline-block;
|
||||
box-sizing: border-box;
|
||||
|
||||
>.van-image {
|
||||
width: 58px;
|
||||
height: 58px;
|
||||
vertical-align: top;
|
||||
border-radius: 50%;
|
||||
margin: 0 auto 8px;
|
||||
}
|
||||
|
||||
>p {
|
||||
margin-bottom: 0;
|
||||
font-size: 11px;
|
||||
color: var(--cp-tip);
|
||||
|
||||
&.name {
|
||||
font-size: 13px;
|
||||
color: var(--cp-text1);
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
>.van-button {
|
||||
padding: 0 12px;
|
||||
height: 28px;
|
||||
margin-top: 8px;
|
||||
width: 72px;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -1,86 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
ref
|
||||
} from 'vue'
|
||||
import DoctorCard from './DoctorCard.vue'
|
||||
import {
|
||||
getDoctorPage
|
||||
} from '@/api/consult'
|
||||
import type {
|
||||
DoctorList
|
||||
} from '@/types/consult'
|
||||
|
||||
|
||||
//获取关注医生列表数据
|
||||
const list = ref < DoctorList > ([])
|
||||
const getList = async () => {
|
||||
const {
|
||||
data
|
||||
} = await getDoctorPage(({
|
||||
current: 1,
|
||||
pageSize: 5
|
||||
}))
|
||||
//默认展示5个医生
|
||||
list.value = data.rows
|
||||
}
|
||||
//1、原生js方式
|
||||
const width = ref(375)
|
||||
onLoad(() => {
|
||||
//获取手机屏幕的宽度
|
||||
const windowResizeCallback = (res) => {
|
||||
console.log('变化后的窗口宽度=' + res.size.windowWidth)
|
||||
console.log('变化后的窗口高度=' + res.size.windowHeight)
|
||||
width.value = res.size.windowWidth
|
||||
}
|
||||
uni.onWindowResize(windowResizeCallback)
|
||||
getList()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view class="follow-doctor">
|
||||
<!-- 1、头部 -->
|
||||
<view className="head">
|
||||
<p>推荐关注</p>
|
||||
<a href="javascript:;"> 查看更多<i class="van-icon van-icon-arrow" /></a>
|
||||
</view>
|
||||
<!-- 2、医生列表 -->
|
||||
<view class="body">
|
||||
<!-- swipe 组件 -->
|
||||
<!-- width是用来指定单个item的宽度 -->
|
||||
<!-- 问题:150是写死的 -->
|
||||
<!-- 解决:基准比例(150/375)*当前屏幕宽度 做一个动态计算 -->
|
||||
<van-swipe :autoplay="3000" :width="(150/375)*width" :loop="false" :show-indicators="false">
|
||||
<van-swipe-item v-for="item in list" :key="item.id">
|
||||
<!-- 单个医生的信息介绍 -->
|
||||
<doctor-card :item="item" />
|
||||
</van-swipe-item>
|
||||
</van-swipe>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.follow-doctor {
|
||||
background-color: var(--cp-bg);
|
||||
height: 250px;
|
||||
|
||||
.head {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
height: 45px;
|
||||
align-items: center;
|
||||
padding: 0 15px;
|
||||
font-size: 13px;
|
||||
|
||||
>a {
|
||||
color: var(--cp-tip);
|
||||
}
|
||||
}
|
||||
|
||||
.body {
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -1,138 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import type {
|
||||
Knowledge
|
||||
} from '@/types/consult'
|
||||
|
||||
defineProps < {
|
||||
item: Knowledge
|
||||
} > ()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view class="knowledge-card van-hairline--bottom">
|
||||
<view class="head">
|
||||
<van-image round class="avatar" :src="item.creatorAvatar"></van-image>
|
||||
<view class="info">
|
||||
<p class="name">{{ item.creatorName }}</p>
|
||||
<p class="dep van-ellipsis">
|
||||
{{ item.creatorHospatalName }} {{ item.creatorDep }} {{ item.creatorTitles }}
|
||||
</p>
|
||||
</view>
|
||||
<van-button class="btn" size="small" round>
|
||||
{{ item.likeFlag === 1 ? '已关注' : '+ 关注' }}
|
||||
</van-button>
|
||||
</view>
|
||||
<view class="body">
|
||||
<h3 class="title van-ellipsis">{{ item.title }}</h3>
|
||||
<p class="tag">
|
||||
<span v-for="(tag, i) in item.topics" :key="i"># {{ tag }}</span>
|
||||
</p>
|
||||
<p class="intro van-multi-ellipsis--l2">{{ item.content.replace(/<[^>]+>/g, '') }}</p>
|
||||
<view class="imgs" :class="{ large: item.coverUrl.length === 1 }">
|
||||
<van-image fit="cover" v-for="(url, i) in item.coverUrl" :key="i" :src="url" />
|
||||
</view>
|
||||
<p class="logs">
|
||||
<span>{{ item.collectionNumber }} 收藏</span>
|
||||
<span>{{ item.commentNumber }} 评论</span>
|
||||
</p>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.knowledge-card {
|
||||
padding: 20px 0 16px;
|
||||
|
||||
.head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.avatar {
|
||||
width: 38px;
|
||||
height: 38px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.info {
|
||||
width: 200px;
|
||||
padding-right: 10px;
|
||||
|
||||
.name {
|
||||
color: var(--cp-text2);
|
||||
}
|
||||
|
||||
.dep {
|
||||
color: var(--cp-tip);
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 0 12px;
|
||||
border-color: var(--cp-primary);
|
||||
color: var(--cp-primary);
|
||||
height: 28px;
|
||||
width: 72px;
|
||||
}
|
||||
}
|
||||
|
||||
.body {
|
||||
.title {
|
||||
font-size: 16px;
|
||||
margin-top: 8px;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.tag {
|
||||
margin-top: 6px;
|
||||
|
||||
>span {
|
||||
color: var(--cp-primary);
|
||||
margin-right: 20px;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.intro {
|
||||
margin-top: 7px;
|
||||
line-height: 2;
|
||||
color: var(--cp-text3);
|
||||
}
|
||||
|
||||
.imgs {
|
||||
margin-top: 7px;
|
||||
display: flex;
|
||||
|
||||
.van-image {
|
||||
width: 106px;
|
||||
height: 106px;
|
||||
margin-right: 12px;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
|
||||
&:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&.large {
|
||||
.van-image {
|
||||
width: 185px;
|
||||
height: 125px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.logs {
|
||||
margin-top: 10px;
|
||||
|
||||
>span {
|
||||
color: var(--cp-tip);
|
||||
margin-right: 16px;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
@ -1,72 +1,73 @@
|
||||
{
|
||||
"name": "",
|
||||
"appid": "",
|
||||
"description": "",
|
||||
"versionName": "1.0.0",
|
||||
"versionCode": "100",
|
||||
"transformPx": false,
|
||||
/* 5+App特有相关 */
|
||||
"app-plus": {
|
||||
"usingComponents": true,
|
||||
"nvueStyleCompiler": "uni-app",
|
||||
"compilerVersion": 3,
|
||||
"splashscreen": {
|
||||
"alwaysShowBeforeRender": true,
|
||||
"waiting": true,
|
||||
"autoclose": true,
|
||||
"delay": 0
|
||||
"name" : "cmd",
|
||||
"appid" : "__UNI__F6B11EE",
|
||||
"description" : "",
|
||||
"versionName" : "1.0.0",
|
||||
"versionCode" : "100",
|
||||
"transformPx" : false,
|
||||
/* 5+App特有相关 */
|
||||
"app-plus" : {
|
||||
"usingComponents" : true,
|
||||
"nvueStyleCompiler" : "uni-app",
|
||||
"compilerVersion" : 3,
|
||||
"splashscreen" : {
|
||||
"alwaysShowBeforeRender" : true,
|
||||
"waiting" : true,
|
||||
"autoclose" : true,
|
||||
"delay" : 0
|
||||
},
|
||||
/* 模块配置 */
|
||||
"modules" : {},
|
||||
/* 应用发布信息 */
|
||||
"distribute" : {
|
||||
/* android打包配置 */
|
||||
"android" : {
|
||||
"permissions" : [
|
||||
"<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
|
||||
"<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>",
|
||||
"<uses-permission android:name=\"android.permission.VIBRATE\"/>",
|
||||
"<uses-permission android:name=\"android.permission.READ_LOGS\"/>",
|
||||
"<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>",
|
||||
"<uses-feature android:name=\"android.hardware.camera.autofocus\"/>",
|
||||
"<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>",
|
||||
"<uses-permission android:name=\"android.permission.CAMERA\"/>",
|
||||
"<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>",
|
||||
"<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>",
|
||||
"<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>",
|
||||
"<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>",
|
||||
"<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>",
|
||||
"<uses-feature android:name=\"android.hardware.camera\"/>",
|
||||
"<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>",
|
||||
"<uses-permission android:name=\"android.permission.CALL_PHONE\"/>"
|
||||
]
|
||||
},
|
||||
/* ios打包配置 */
|
||||
"ios" : {},
|
||||
/* SDK配置 */
|
||||
"sdkConfigs" : {}
|
||||
}
|
||||
},
|
||||
/* 模块配置 */
|
||||
"modules": {},
|
||||
/* 应用发布信息 */
|
||||
"distribute": {
|
||||
/* android打包配置 */
|
||||
"android": {
|
||||
"permissions": [
|
||||
"<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
|
||||
"<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>",
|
||||
"<uses-permission android:name=\"android.permission.VIBRATE\"/>",
|
||||
"<uses-permission android:name=\"android.permission.READ_LOGS\"/>",
|
||||
"<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>",
|
||||
"<uses-feature android:name=\"android.hardware.camera.autofocus\"/>",
|
||||
"<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>",
|
||||
"<uses-permission android:name=\"android.permission.CAMERA\"/>",
|
||||
"<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>",
|
||||
"<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>",
|
||||
"<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>",
|
||||
"<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>",
|
||||
"<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>",
|
||||
"<uses-feature android:name=\"android.hardware.camera\"/>",
|
||||
"<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>"
|
||||
]
|
||||
},
|
||||
/* ios打包配置 */
|
||||
"ios": {},
|
||||
/* SDK配置 */
|
||||
"sdkConfigs": {}
|
||||
}
|
||||
},
|
||||
/* 快应用特有相关 */
|
||||
"quickapp": {},
|
||||
/* 小程序特有相关 */
|
||||
"mp-weixin": {
|
||||
"appid": "",
|
||||
"setting": {
|
||||
"urlCheck": false
|
||||
/* 快应用特有相关 */
|
||||
"quickapp" : {},
|
||||
/* 小程序特有相关 */
|
||||
"mp-weixin" : {
|
||||
"appid" : "",
|
||||
"setting" : {
|
||||
"urlCheck" : false
|
||||
},
|
||||
"usingComponents" : true
|
||||
},
|
||||
"usingComponents": true
|
||||
},
|
||||
"mp-alipay": {
|
||||
"usingComponents": true
|
||||
},
|
||||
"mp-baidu": {
|
||||
"usingComponents": true
|
||||
},
|
||||
"mp-toutiao": {
|
||||
"usingComponents": true
|
||||
},
|
||||
"uniStatistics": {
|
||||
"enable": false
|
||||
},
|
||||
"vueVersion": "3"
|
||||
"mp-alipay" : {
|
||||
"usingComponents" : true
|
||||
},
|
||||
"mp-baidu" : {
|
||||
"usingComponents" : true
|
||||
},
|
||||
"mp-toutiao" : {
|
||||
"usingComponents" : true
|
||||
},
|
||||
"uniStatistics" : {
|
||||
"enable" : false
|
||||
},
|
||||
"vueVersion" : "3"
|
||||
}
|
||||
|
@ -1,5 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
</script>
|
||||
<template>
|
||||
<view>mine</view>
|
||||
</template>
|
@ -1,5 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
</script>
|
||||
<template>
|
||||
<view>mine</view>
|
||||
</template>
|
@ -0,0 +1,69 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg width="1361px" height="609px" viewBox="0 0 1361 609" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 46.2 (44496) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>Group 21</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs></defs>
|
||||
<g id="Ant-Design-Pro-3.0" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="账户密码登录-校验" transform="translate(-79.000000, -82.000000)">
|
||||
<g id="Group-21" transform="translate(77.000000, 73.000000)">
|
||||
<g id="Group-18" opacity="0.8" transform="translate(74.901416, 569.699158) rotate(-7.000000) translate(-74.901416, -569.699158) translate(4.901416, 525.199158)">
|
||||
<ellipse id="Oval-11" fill="#CFDAE6" opacity="0.25" cx="63.5748792" cy="32.468367" rx="21.7830479" ry="21.766008"></ellipse>
|
||||
<ellipse id="Oval-3" fill="#CFDAE6" opacity="0.599999964" cx="5.98746479" cy="13.8668601" rx="5.2173913" ry="5.21330997"></ellipse>
|
||||
<path d="M38.1354514,88.3520215 C43.8984227,88.3520215 48.570234,83.6838647 48.570234,77.9254015 C48.570234,72.1669383 43.8984227,67.4987816 38.1354514,67.4987816 C32.3724801,67.4987816 27.7006688,72.1669383 27.7006688,77.9254015 C27.7006688,83.6838647 32.3724801,88.3520215 38.1354514,88.3520215 Z" id="Oval-3-Copy" fill="#CFDAE6" opacity="0.45"></path>
|
||||
<path d="M64.2775582,33.1704963 L119.185836,16.5654915" id="Path-12" stroke="#CFDAE6" stroke-width="1.73913043" stroke-linecap="round" stroke-linejoin="round"></path>
|
||||
<path d="M42.1431708,26.5002681 L7.71190162,14.5640702" id="Path-16" stroke="#E0B4B7" stroke-width="0.702678964" opacity="0.7" stroke-linecap="round" stroke-linejoin="round" stroke-dasharray="1.405357899873153,2.108036953469981"></path>
|
||||
<path d="M63.9262187,33.521561 L43.6721326,69.3250951" id="Path-15" stroke="#BACAD9" stroke-width="0.702678964" stroke-linecap="round" stroke-linejoin="round" stroke-dasharray="1.405357899873153,2.108036953469981"></path>
|
||||
<g id="Group-17" transform="translate(126.850922, 13.543654) rotate(30.000000) translate(-126.850922, -13.543654) translate(117.285705, 4.381889)" fill="#CFDAE6">
|
||||
<ellipse id="Oval-4" opacity="0.45" cx="9.13482653" cy="9.12768076" rx="9.13482653" ry="9.12768076"></ellipse>
|
||||
<path d="M18.2696531,18.2553615 C18.2696531,13.2142826 14.1798519,9.12768076 9.13482653,9.12768076 C4.08980114,9.12768076 0,13.2142826 0,18.2553615 L18.2696531,18.2553615 Z" id="Oval-4" transform="translate(9.134827, 13.691521) scale(-1, -1) translate(-9.134827, -13.691521) "></path>
|
||||
</g>
|
||||
</g>
|
||||
<g id="Group-14" transform="translate(216.294700, 123.725600) rotate(-5.000000) translate(-216.294700, -123.725600) translate(106.294700, 35.225600)">
|
||||
<ellipse id="Oval-2" fill="#CFDAE6" opacity="0.25" cx="29.1176471" cy="29.1402439" rx="29.1176471" ry="29.1402439"></ellipse>
|
||||
<ellipse id="Oval-2" fill="#CFDAE6" opacity="0.3" cx="29.1176471" cy="29.1402439" rx="21.5686275" ry="21.5853659"></ellipse>
|
||||
<ellipse id="Oval-2-Copy" stroke="#CFDAE6" opacity="0.4" cx="179.019608" cy="138.146341" rx="23.7254902" ry="23.7439024"></ellipse>
|
||||
<ellipse id="Oval-2" fill="#BACAD9" opacity="0.5" cx="29.1176471" cy="29.1402439" rx="10.7843137" ry="10.7926829"></ellipse>
|
||||
<path d="M29.1176471,39.9329268 L29.1176471,18.347561 C23.1616351,18.347561 18.3333333,23.1796097 18.3333333,29.1402439 C18.3333333,35.1008781 23.1616351,39.9329268 29.1176471,39.9329268 Z" id="Oval-2" fill="#BACAD9"></path>
|
||||
<g id="Group-9" opacity="0.45" transform="translate(172.000000, 131.000000)" fill="#E6A1A6">
|
||||
<ellipse id="Oval-2-Copy-2" cx="7.01960784" cy="7.14634146" rx="6.47058824" ry="6.47560976"></ellipse>
|
||||
<path d="M0.549019608,13.6219512 C4.12262681,13.6219512 7.01960784,10.722722 7.01960784,7.14634146 C7.01960784,3.56996095 4.12262681,0.670731707 0.549019608,0.670731707 L0.549019608,13.6219512 Z" id="Oval-2-Copy-2" transform="translate(3.784314, 7.146341) scale(-1, 1) translate(-3.784314, -7.146341) "></path>
|
||||
</g>
|
||||
<ellipse id="Oval-10" fill="#CFDAE6" cx="218.382353" cy="138.685976" rx="1.61764706" ry="1.61890244"></ellipse>
|
||||
<ellipse id="Oval-10-Copy-2" fill="#E0B4B7" opacity="0.35" cx="179.558824" cy="175.381098" rx="1.61764706" ry="1.61890244"></ellipse>
|
||||
<ellipse id="Oval-10-Copy" fill="#E0B4B7" opacity="0.35" cx="180.098039" cy="102.530488" rx="2.15686275" ry="2.15853659"></ellipse>
|
||||
<path d="M28.9985381,29.9671598 L171.151018,132.876024" id="Path-11" stroke="#CFDAE6" opacity="0.8"></path>
|
||||
</g>
|
||||
<g id="Group-10" opacity="0.799999952" transform="translate(1054.100635, 36.659317) rotate(-11.000000) translate(-1054.100635, -36.659317) translate(1026.600635, 4.659317)">
|
||||
<ellipse id="Oval-7" stroke="#CFDAE6" stroke-width="0.941176471" cx="43.8135593" cy="32" rx="11.1864407" ry="11.2941176"></ellipse>
|
||||
<g id="Group-12" transform="translate(34.596774, 23.111111)" fill="#BACAD9">
|
||||
<ellipse id="Oval-7" opacity="0.45" cx="9.18534718" cy="8.88888889" rx="8.47457627" ry="8.55614973"></ellipse>
|
||||
<path d="M9.18534718,17.4450386 C13.8657264,17.4450386 17.6599235,13.6143199 17.6599235,8.88888889 C17.6599235,4.16345787 13.8657264,0.332739156 9.18534718,0.332739156 L9.18534718,17.4450386 Z" id="Oval-7"></path>
|
||||
</g>
|
||||
<path d="M34.6597385,24.809694 L5.71666084,4.76878945" id="Path-2" stroke="#CFDAE6" stroke-width="0.941176471"></path>
|
||||
<ellipse id="Oval" stroke="#CFDAE6" stroke-width="0.941176471" cx="3.26271186" cy="3.29411765" rx="3.26271186" ry="3.29411765"></ellipse>
|
||||
<ellipse id="Oval-Copy" fill="#F7E1AD" cx="2.79661017" cy="61.1764706" rx="2.79661017" ry="2.82352941"></ellipse>
|
||||
<path d="M34.6312443,39.2922712 L5.06366663,59.785082" id="Path-10" stroke="#CFDAE6" stroke-width="0.941176471"></path>
|
||||
</g>
|
||||
<g id="Group-19" opacity="0.33" transform="translate(1282.537219, 446.502867) rotate(-10.000000) translate(-1282.537219, -446.502867) translate(1142.537219, 327.502867)">
|
||||
<g id="Group-17" transform="translate(141.333539, 104.502742) rotate(275.000000) translate(-141.333539, -104.502742) translate(129.333539, 92.502742)" fill="#BACAD9">
|
||||
<circle id="Oval-4" opacity="0.45" cx="11.6666667" cy="11.6666667" r="11.6666667"></circle>
|
||||
<path d="M23.3333333,23.3333333 C23.3333333,16.8900113 18.1099887,11.6666667 11.6666667,11.6666667 C5.22334459,11.6666667 0,16.8900113 0,23.3333333 L23.3333333,23.3333333 Z" id="Oval-4" transform="translate(11.666667, 17.500000) scale(-1, -1) translate(-11.666667, -17.500000) "></path>
|
||||
</g>
|
||||
<circle id="Oval-5-Copy-6" fill="#CFDAE6" cx="201.833333" cy="87.5" r="5.83333333"></circle>
|
||||
<path d="M143.5,88.8126685 L155.070501,17.6038544" id="Path-17" stroke="#BACAD9" stroke-width="1.16666667"></path>
|
||||
<path d="M17.5,37.3333333 L127.466252,97.6449735" id="Path-18" stroke="#BACAD9" stroke-width="1.16666667"></path>
|
||||
<polyline id="Path-19" stroke="#CFDAE6" stroke-width="1.16666667" points="143.902597 120.302281 174.935455 231.571342 38.5 147.510847 126.366941 110.833333"></polyline>
|
||||
<path d="M159.833333,99.7453842 L195.416667,89.25" id="Path-20" stroke="#E0B4B7" stroke-width="1.16666667" opacity="0.6"></path>
|
||||
<path d="M205.333333,82.1372105 L238.719406,36.1666667" id="Path-24" stroke="#BACAD9" stroke-width="1.16666667"></path>
|
||||
<path d="M266.723424,132.231988 L207.083333,90.4166667" id="Path-25" stroke="#CFDAE6" stroke-width="1.16666667"></path>
|
||||
<circle id="Oval-5" fill="#C1D1E0" cx="156.916667" cy="8.75" r="8.75"></circle>
|
||||
<circle id="Oval-5-Copy-3" fill="#C1D1E0" cx="39.0833333" cy="148.75" r="5.25"></circle>
|
||||
<circle id="Oval-5-Copy-2" fill-opacity="0.6" fill="#D1DEED" cx="8.75" cy="33.25" r="8.75"></circle>
|
||||
<circle id="Oval-5-Copy-4" fill-opacity="0.6" fill="#D1DEED" cx="243.833333" cy="30.3333333" r="5.83333333"></circle>
|
||||
<circle id="Oval-5-Copy-5" fill="#E0B4B7" cx="175.583333" cy="232.75" r="5.25"></circle>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 8.7 KiB |
@ -0,0 +1,17 @@
|
||||
@font-face {
|
||||
font-family: "iconfont"; /* Project id 2761882 */
|
||||
src: url('./iconfont.ttf') format('truetype');
|
||||
}
|
||||
|
||||
.iconfont {
|
||||
font-family: "iconfont" !important;
|
||||
font-size: 16px;
|
||||
font-style: normal;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.icon-dianhua:before {
|
||||
content: "\e6a9";
|
||||
}
|
||||
|
Before Width: | Height: | Size: 272 KiB |
Before Width: | Height: | Size: 39 KiB |
Before Width: | Height: | Size: 36 KiB |
Before Width: | Height: | Size: 37 KiB |
After Width: | Height: | Size: 45 KiB |
Before Width: | Height: | Size: 79 KiB |
After Width: | Height: | Size: 15 KiB |