This commit is contained in:
Teng 2025-08-13 17:19:40 +08:00
parent 7e3211d192
commit 22f8525586
1299 changed files with 11452 additions and 3360 deletions

View File

@ -31,7 +31,8 @@
</view>
</view>
<view class="array-father">
<view v-for="(item,index) in iconsArray.slice(5,8)" :key="index" class="item" @click="jumpToTarget(index+5)">
<view v-for="(item,index) in iconsArray.slice(5,8)" :key="index" class="item"
@click="jumpToTarget(index+5)">
<view class="left-item">
<image class="left-icon" :src="`/static/index/settings/${index+5}.png`" />
<view class="left-font">
@ -49,7 +50,7 @@
<exit :show="exitshow" @close="exitshow=false" />
<reset :show="resetshow" @close="resetshow=false" />
<zy-update ref="zyupgrade" :noticeflag="true" theme="blue" :h5preview="false" oldversion="1.0.0"
:appstoreflag="true" :autocheckupdate="true" @showupdateTips="noNeed" ></zy-update>
:appstoreflag="true" :autocheckupdate="true" @showupdateTips="noNeed"></zy-update>
<!-- 弹出层 -->
<view v-if="openany" class="popup-any" :style="animation?{opacity:1}:{opacity:0}">
<view class="mask" @click="openany=false"></view>
@ -76,6 +77,7 @@
import twoseven from '@/pages/login/twoseven.vue'
const zyupgrade = ref(null);
const issay = ref(false)
const openany = ref(false);
const opentype = ref(false);
const exitshow = ref(false);
@ -117,36 +119,37 @@
break
case 5:
issay.value = true;
zyupgrade.value?.check_update()
break
case 6:
openany.value = true;
opentype.value = true;
animation.value = false;
setTimeout(()=>{
setTimeout(() => {
animation.value = true;
},50)
}, 50)
break
case 7:
openany.value = true;
opentype.value = false;
animation.value = false;
setTimeout(()=>{
setTimeout(() => {
animation.value = true;
},50)
}, 50)
break
}
}
const noNeed = () => {
if(props.isShow){
if (props.isShow && issay.value) {
uni.showToast({
title: '已经是最新版了',
icon: 'none', //
duration: 2000 //
title: '已经是最新版了',
icon: 'none', //
duration: 2000 //
});
}
}
</script>
@ -219,6 +222,7 @@
height: 100rpx;
color: #0174D3;
}
.popup-any {
position: fixed;
inset: 0;
@ -229,11 +233,12 @@
transition: opacity 0.5s ease;
background-color: rgba(0, 0, 0, 0.3);
}
.mask {
position: absolute;
inset: 0;
}
.box-any {
position: absolute;
top: 50%;
@ -248,16 +253,17 @@
flex-direction: column;
padding: 65rpx 40rpx;
}
.title-left {
display: flex;
align-items: center;
.back-img {
width: 60rpx;
height: 60rpx;
margin-right: 30rpx;
}
.back-font {
font-size: 35rpx;
font-weight: 600;

View File

@ -23,11 +23,11 @@
},
width: {
type: String,
default: '60rpx'
default: '65rpx'
},
height: {
type: String,
default: '60rpx'
default: '65rpx'
},
objectFit: {
type: String,

View File

@ -1,37 +1,64 @@
<template>
<view class="move-circle">
<image src="/static/index/keyimg/lunpan.png" class="move-circle-all" />
<view class="click-box-top" @click="handleClick(0)">
</view>
<view class="click-box-left" @click="handleClick(3)">
</view>
<view class="click-box-bottom" @click="handleClick(2)">
</view>
<view class="click-box-right" @click="handleClick(1)">
</view>
</view>
<view class="move-circle" :style="containerStyle">
<image src="/static/index/keyimg/lunpan.png" class="move-circle-all" />
<view class="click-box-top" @click="handleClick(0)" />
<view class="click-box-left" @click="handleClick(3)" />
<view class="click-box-bottom" @click="handleClick(2)" />
<view class="click-box-right" @click="handleClick(1)" />
</view>
</template>
<script setup lang="ts">
import { reactive, ref } from 'vue'
const emit = defineEmits(['movecard'])
import { ref, computed } from 'vue'
const props = defineProps({
/**
* 水平位置单位 rpx
*/
left: {
type: Number,
default: 0
},
/**
* 垂直位置单位 rpx
* 优先使用 bottom如果同时传入 top bottom则以 top 为准
*/
bottom: {
type: Number,
default: 0
},
top: {
type: Number,
default: undefined
}
})
const emit = defineEmits<{
(e: 'movecard', dir: number): void
}>()
const key = ref(-1)
let resetTimer: number | null = null
// ID
let resetTimer = null
//
const containerStyle = computed(() => {
const style: Record<string, string> = {
left: `${props.left}rpx`
}
if (props.top !== undefined) {
style.top = `${props.top}rpx`
} else {
style.bottom = `${props.bottom}rpx`
}
return style
})
function handleClick(dir: number) {
//
if (resetTimer !== null) {
clearTimeout(resetTimer)
}
//
key.value = dir
emit('movecard', dir)
// 800ms
// 500ms
resetTimer = setTimeout(() => {
key.value = -1
resetTimer = null
@ -39,52 +66,47 @@ function handleClick(dir: number) {
}
</script>
<style lang="less" scoped>
.move-circle {
position: absolute;
bottom: 100rpx;
left: 100rpx;
width: 330rpx;
height: 330rpx;
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
z-index: 9999;
.click-box-top{
position: absolute;
top: 0;
left: 100rpx;
width: 130rpx;
height: 120rpx;
}
.click-box-bottom{
position: absolute;
bottom: 0;
left: 100rpx;
width: 130rpx;
height: 120rpx;
}
.click-box-left{
position: absolute;
bottom: 100rpx;
left: 0;
width: 98rpx;
height: 120rpx;
}
.click-box-right{
position: absolute;
bottom: 100rpx;
right: 0;
width: 98rpx;
height: 120rpx;
}
}
.move-circle-all{
width: 300rpx;
height: 300rpx;
}
</style>
.move-circle {
position: absolute;
width: 330rpx;
height: 330rpx;
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
z-index: 9999;
}
.move-circle-all {
width: 300rpx;
height: 300rpx;
}
.click-box-top {
position: absolute;
top: 0;
left: 100rpx;
width: 130rpx;
height: 120rpx;
}
.click-box-bottom {
position: absolute;
bottom: 0;
left: 100rpx;
width: 130rpx;
height: 120rpx;
}
.click-box-left {
position: absolute;
bottom: 100rpx;
left: 0;
width: 98rpx;
height: 120rpx;
}
.click-box-right {
position: absolute;
bottom: 100rpx;
right: 0;
width: 98rpx;
height: 120rpx;
}
</style>

View File

@ -0,0 +1,227 @@
<template>
<view class="move-circle" :style="{ bottom: `${movebottom}rpx`, left: `${moveleft}rpx` }" @touchend="onLongPressEnd"
@touchcancel="onLongPressEnd">
<!-- 返回 -->
<view :class="beblue === 5 ? 'click-box-target' : 'click-box'" @tap="onTap(5)"
@longpress="(e) => onLongPressStart(5, e)">
<span :class="beblue === 5 ? 'grad-text' : ''">返回</span>
</view>
<!-- -->
<view :class="beblue === 0 ? 'click-box-target' : 'click-box'" @tap="onTap(0)"
@longpress="(e) => onLongPressStart(0, e)">
<image :src="`/static/index/newruler/arrow_1${beblue===0 ? '_1' : ''}.png`" class="image-photo" />
</view>
<!-- 确定 -->
<view :class="beblue === 4 ? 'click-box-target' : 'click-box'" @tap="onTap(4)"
@longpress="(e) => onLongPressStart(4, e)">
<span :class="beblue === 4 ? 'grad-text' : ''">确定</span>
</view>
<!-- -->
<view :class="beblue === 3 ? 'click-box-target' : 'click-box'" @tap="onTap(3)"
@longpress="(e) => onLongPressStart(3, e)">
<image :src="`/static/index/newruler/arrow_3${beblue===3 ? '_3' : ''}.png`" class="image-photo" />
</view>
<!-- -->
<view :class="beblue === 2 ? 'click-box-target' : 'click-box'" @tap="onTap(2)"
@longpress="(e) => onLongPressStart(2, e)">
<image :src="`/static/index/newruler/arrow_2${beblue===2 ? '_2' : ''}.png`" class="image-photo" />
</view>
<!-- -->
<view :class="beblue === 1 ? 'click-box-target' : 'click-box'" @tap="onTap(1)"
@longpress="(e) => onLongPressStart(1, e)">
<image :src="`/static/index/newruler/arrow_4${beblue===1 ? '_4' : ''}.png`" class="image-photo" />
</view>
</view>
</template>
<script setup lang="ts">
import { ref, onBeforeUnmount } from 'vue'
const emit = defineEmits<{ (e : 'movecard', dir : number) : void }>()
let clickResetTimer : ReturnType<typeof setTimeout> | null = null
let longPressInterval : ReturnType<typeof setInterval> | null = null
let isLongPress = false
const beblue = ref<number>(-1)
let activeLongPressDir : number | null = null
const props = defineProps({
movebottom: {
type: Number,
default: 0,
},
moveleft: {
type: Number,
default: 0,
},
})
function clearClickResetTimer() {
if (clickResetTimer) {
clearTimeout(clickResetTimer)
clickResetTimer = null
}
}
function clearLongPressInterval() {
if (longPressInterval) {
clearInterval(longPressInterval)
longPressInterval = null
}
isLongPress = false
activeLongPressDir = null
}
//
function onTap(dir : number) {
//
clearLongPressInterval()
clearClickResetTimer()
beblue.value = dir
emit('movecard', dir)
// 800ms
clickResetTimer = setTimeout(() => {
beblue.value = -1
clickResetTimer = null
}, 500)
}
// longpress
function onLongPressStart(dir : number, e ?: any) {
//
clearClickResetTimer()
clearLongPressInterval()
beblue.value = dir
emit('movecard', dir)
// 500ms
activeLongPressDir = dir
isLongPress = true
longPressInterval = setInterval(() => {
if (activeLongPressDir !== null) emit('movecard', activeLongPressDir)
}, 500)
}
// touchend / touchcancel
function onLongPressEnd() {
//
if (!isLongPress) return
// interval
clearLongPressInterval()
// 0.8s
clearClickResetTimer()
clickResetTimer = setTimeout(() => {
beblue.value = -1
clickResetTimer = null
}, 500)
}
onBeforeUnmount(() => {
clearClickResetTimer()
clearLongPressInterval()
})
</script>
<style lang="less" scoped>
.move-circle {
position: absolute;
bottom: 0rpx;
left: 0rpx;
width: 400rpx;
display: flex;
flex-wrap: wrap;
z-index: 99;
touch-action: none;
}
.click-box,
.click-box-target {
width: 100rpx;
height: 100rpx;
margin-left: 15rpx;
margin-bottom: 15rpx;
display: flex;
justify-content: center;
align-items: center;
border-radius: 30rpx;
font-size: 28rpx;
transition: transform 0.18s ease, box-shadow 0.18s ease, background 0.25s ease;
-webkit-tap-highlight-color: transparent;
}
.click-box {
background-color: #f6f6f9;
box-shadow: 0 4px 12px #e5e7ea;
border: 2rpx solid #e6e7eb;
color: #888d99;
}
/* 选中态:背景径向渐变 + 中心缩放动画(从中间放大再回到原样) */
.click-box-target {
background: radial-gradient(circle at 30% 30%, #f2f7fd 0%, #deeaf9 100%);
box-shadow: 0 8px 18px rgba(0, 0, 0, 0.08);
border: 2rpx solid #c3ccd9;
color: transparent;
/* 文字使用渐变填充 */
animation: scalePulse 360ms cubic-bezier(.2, .8, .2, 1);
transform-origin: center center;
}
@keyframes scalePulse {
0% {
transform: scale(1);
}
25% {
/* 先收缩一点点 */
transform: scale(0.94);
}
65% {
/* 再放大到略超出的感觉 */
transform: scale(1.08);
}
100% {
transform: scale(1);
}
}
/* 文本渐变(用于返回/确定文字) */
.grad-text {
background-image: linear-gradient(90deg, #5b8bb3, #87a1bd);
background-size: 200% 100%;
background-position: 0% 50%;
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
transition: background-position 0.8s linear;
}
/* 选中时文字渐变滚动效果 */
.click-box-target .grad-text {
background-position: 100% 50%;
}
.image-photo {
width: 30%;
height: 30%;
transition: transform 0.18s ease, filter 0.18s ease;
}
/* 选中时图片略微放大 */
.click-box-target .image-photo {
/* 让图片跟随父元素缩放,不额外放大,保留平滑过渡 */
transform: none;
transition: transform 0.18s ease, filter 0.18s ease;
filter: none;
}
</style>

View File

@ -1,94 +1,383 @@
<template>
<view class="move-circle">
<image src="/static/index/keyimg/yaogan.png" class="move-circle-all" />
<view class="click-box-top" @click="handleClick(0)">
</view>
<view class="click-box-left" @click="handleClick(3)">
</view>
<view class="click-box-bottom" @click="handleClick(2)">
</view>
<view class="click-box-right" @click="handleClick(1)">
<view class="move-circle" :style="{ bottom: `${movebottom}rpx`, left: `${moveleft}rpx` }" @touchmove="onTouchMove"
@touchend="onPressEnd" @touchcancel="onPressEnd">
<!-- 长按持续波纹动画 -->
<view v-if="showShadow && pao" class="light-shadow ripple-loop" :style="{
left: shadow.x + 'px',
top: shadow.y + 'px',
transform: 'translate(-50%, -50%)'
}"></view>
<!-- 点击单次波纹动画 key 刷新 -->
<view v-if="showRippleOnce && pao" :key="rippleKey" class="light-shadow ripple-once" :style="{
left: shadow.x + 'px',
top: shadow.y + 'px',
transform: 'translate(-50%, -50%)'
}" @animationend="onRippleAnimationEnd"></view>
<image :src="!notext?`/static/index/newruler/direction_1.png`: `/static/index/newruler/suere.png`" v-show="type==-1 || type==4" class="move-circle-all" />
<!-- <image src="/static/index/newruler/direction_2.png" v-show="type==-2" class="move-circle-all" />
<image src="/static/index/newruler/direction_3.png" v-show="type==-3" class="move-circle-all" /> -->
<image :src="!notext?`/static/index/newruler/direction_3.png`: `/static/index/newruler/sure_2.png`" v-show="type==3" class="move-circle-all" />
<image :src="!notext?`/static/index/newruler/direction_5.png`: `/static/index/newruler/sure_4.png`" v-show="type==2" class="move-circle-all" />
<image :src="!notext?`/static/index/newruler/direction_4.png`: `/static/index/newruler/sure_3.png`" v-show="type==0" class="move-circle-all" />
<image :src="!notext?`/static/index/newruler/direction_2.png`: `/static/index/newruler/sure_1.png`" v-show="type==1" class="move-circle-all" />
<view class="pulse-circle" v-if="getblue" :key="pulseKey">
</view>
<!-- 四个方向按钮 -->
<view class="click-box-top" @tap="onTap(0, $event)" @longpress="(e) => onLongPressStart(0, e)" />
<view class="click-box-left" @tap="onTap(3, $event)" @longpress="(e) => onLongPressStart(3, e)" />
<view class="click-box-bottom" @tap="onTap(2, $event)" @longpress="(e) => onLongPressStart(2, e)" />
<view class="click-box-right" @tap="onTap(1, $event)" @longpress="(e) => onLongPressStart(1, e)" />
<view class="click-box-center" @tap="onTap(4, $event)" @longpress="(e) => onLongPressStart(4, e)" />
</view>
</template>
<script setup lang="ts">
import { reactive, ref } from 'vue'
const emit = defineEmits(['movecard'])
import { ref, reactive, watch, nextTick, onBeforeUnmount } from 'vue'
const key = ref(-1)
const emit = defineEmits<{
(e : 'movecard', dir : number) : void
}>()
// ID
let resetTimer = null
const key = ref(-1)
let clickResetTimer : ReturnType<typeof setTimeout> | null = null
let longPressInterval : ReturnType<typeof setInterval> | null = null
let isLongPress = false
function handleClick(dir: number) {
//
if (resetTimer !== null) {
clearTimeout(resetTimer)
}
const type = ref(-1)
const shadow = reactive({ x: 0, y: 0 })
const showShadow = ref(false) //
const showRippleOnce = ref(false) //
const rippleKey = ref(0) // key
//
key.value = dir
emit('movecard', dir)
const RADIUS = uni.upx2px(175)
const DIAMETER = uni.upx2px(350)
const LEFT = uni.upx2px(-50)
const BOTTOM = uni.upx2px(100)
const windowHeight = uni.getSystemInfoSync().windowHeight
const TOP = windowHeight - BOTTOM - DIAMETER
// 800ms
resetTimer = setTimeout(() => {
key.value = -1
resetTimer = null
}, 500)
}
const props = defineProps({
getblue: {
type: Boolean,
default: false
},
movebottom: {
type: Number,
default: 0
},
moveleft: {
type: Number,
default: 0
},
pao:{
type: Boolean,
default: true
},
notext:{
type: Boolean,
default: false
}
});
const getblue = ref(false)
const pulseKey = ref(0)
let timeout1 : ReturnType<typeof setTimeout> | null = null
let timeout2 : ReturnType<typeof setTimeout> | null = null
let timeout3 : ReturnType<typeof setTimeout> | null = null
const types = [-1, -2, -3]
const index = ref(0)
let timer = null;
const switchType = () => {
index.value = (index.value + 1) % types.length
type.value = types[index.value]
}
watch(() => props.getblue, (val) => {
// if (timer) clearInterval(timer)
// timer = setInterval(() => {
// switchType()
// }, 100)
//
// if (timeout1) clearTimeout(timeout1)
// if (timeout2) clearTimeout(timeout2)
// if (timeout3) clearTimeout(timeout3)
// //
// getblue.value = false
// nextTick(() => {
// pulseKey.value++
// getblue.value = true
// //
// timeout1 = setTimeout(() => {
// getblue.value = false
// }, 3000)
// //
// timeout2 = setTimeout(() => {
// getblue.value = true
// }, 3010)
// //
// timeout3 = setTimeout(() => {
// getblue.value = false
// }, 6500)
// })
// }
})
onBeforeUnmount(() => {
if (timer) clearInterval(timer)
})
function onTap(dir : number, e : any) {
if (timer) clearInterval(timer)
if (isLongPress) return
clearClickTimer()
key.value = dir
emit('movecard', dir)
// console.log("?????",e.touches)
if (e?.touches && e.touches.length) {
const touch = e.touches[0]
updateShadowPosition(touch.pageX, touch.pageY)
}
showRippleOnce.value = false
rippleKey.value++
type.value = dir
setTimeout(() => {
type.value = -1
}, 300)
setTimeout(() => {
showRippleOnce.value = true
}, 16)
clickResetTimer = setTimeout(() => {
key.value = -1
clickResetTimer = null
}, 300)
}
function onRippleAnimationEnd() {
showRippleOnce.value = false
}
function onLongPressStart(dir : number, e : TouchEvent) {
if (timer) clearInterval(timer)
clearLongPressInterval()
isLongPress = true
showShadow.value = true
type.value = dir
const touch = (e?.touches || [])[0]
if (touch) updateShadowPosition(touch.pageX, touch.pageY)
key.value = dir
emit('movecard', dir)
longPressInterval = setInterval(() => {
key.value = dir
emit('movecard', dir)
}, 500)
}
function onPressEnd() {
clearClickTimer()
clearLongPressInterval()
isLongPress = false
showShadow.value = false
key.value = -1;
type.value = -1
}
function clearClickTimer() {
if (clickResetTimer) {
clearTimeout(clickResetTimer)
clickResetTimer = null
}
}
function clearLongPressInterval() {
if (longPressInterval) {
clearInterval(longPressInterval)
longPressInterval = null
}
}
function updateShadowPosition(pageX : number, pageY : number) {
let x = pageX
let y = pageY - TOP - 50 + props.movebottom/2
const dx = x - RADIUS
const dy = y - RADIUS
const dist = Math.sqrt(dx * dx + dy * dy)
if (dist > RADIUS) {
const angle = Math.atan2(dy, dx)
x = RADIUS + RADIUS * Math.cos(angle)
y = RADIUS + RADIUS * Math.sin(angle)
}
shadow.x = x
shadow.y = y
}
function onTouchMove(e : any) {
if (!isLongPress) return
const touch = (e.detail?.touches || e.touches || [])[0]
if (!touch) return
updateShadowPosition(touch.pageX, touch.pageY)
}
</script>
<style lang="less" scoped>
.move-circle {
position: absolute;
bottom: 93rpx;
left: 130rpx;
width: 250rpx;
height: 250rpx;
// border-radius: 50%;
bottom: 0rpx;
left: 0rpx;
width: 350rpx;
height: 350rpx;
display: flex;
justify-content: center;
align-items: center;
z-index: 9999;
.click-box-top{
z-index: 99;
touch-action: none;
.click-box-top {
position: absolute;
top: 0;
top: 20rpx;
left: 70rpx;
width: 110rpx;
height: 70rpx;
width: 220rpx;
height: 80rpx;
}
.click-box-bottom {
position: absolute;
bottom: 20rpx;
left: 70rpx;
width: 220rpx;
height: 80rpx;
}
.click-box-left {
position: absolute;
bottom: 100rpx;
left: 0;
width: 90rpx;
height: 150rpx;
}
.click-box-right {
position: absolute;
bottom: 100rpx;
right: 0;
width: 90rpx;
height: 150rpx;
}
.click-box-center {
position: absolute;
bottom: 130rpx;
right: 130rpx;
width: 90rpx;
height: 90rpx;
// background-color: red;
}
.click-box-bottom{
position: absolute;
bottom: 0;
left: 70rpx;
width: 110rpx;
height: 70rpx;
// background-color: blue;
}
.move-circle-all {
width: 350rpx;
height: 350rpx;
}
.light-shadow {
position: absolute;
width: 40rpx;
height: 40rpx;
background-color: transparent;
border: 60rpx solid #3da6ff;
border-radius: 50%;
pointer-events: none;
opacity: 1;
}
/* 无限循环波纹动画,长按时用 */
.ripple-loop {
animation: rippleLoop 1.2s ease-out infinite;
}
/* 点击一次的波纹动画 */
.ripple-once {
animation: rippleLoop 1.2s ease-out forwards;
}
@keyframes rippleLoop {
0% {
transform: translate(-50%, -50%) scale(0.5);
opacity: 0.6;
}
.click-box-left{
position: absolute;
bottom: 75rpx;
left: 0;
width: 70rpx;
height: 100rpx;
// background-color: yellow;
}
.click-box-right{
position: absolute;
bottom: 75rpx;
right: 0;
width: 70rpx;
height: 100rpx;
// background-color: green;
100% {
transform: translate(-50%, -50%) scale(2.5);
opacity: 0;
}
}
.move-circle-all{
width: 250rpx;
height: 250rpx;
.light-circle {
position: relative;
width: 150px;
height: 150px;
border-radius: 50%;
background: #111;
/* 你背景色自己改 */
overflow: visible;
}
.circle {
position: relative;
width: 150px;
height: 150px;
border-radius: 50%;
background: #222;
margin: 50px;
}
.pulse-circle {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
border-radius: 50%;
background: radial-gradient(circle, #03a4ff 0%, transparent 70%);
animation: pulse 3s forwards;
}
@keyframes pulse {
0% {
width: 0;
height: 0;
opacity: 0.8;
}
50% {
width: 350rpx;
height: 350rpx;
opacity: 0.4;
}
100% {
width: 0;
height: 0;
opacity: 0;
}
}
</style>

View File

@ -0,0 +1,172 @@
<template>
<view>
<view :class="['drawer', { 'drawer-open': isVisible }]" :style="drawerStyle">
<view class="drawer-content" @touchstart.passive="onTouchStart" @touchmove.passive="onTouchMove"
@touchend="onTouchEnd" @touchcancel="onTouchEnd">
<!-- 抽屉中间的半圆 -->
<view class="drawer-content-circle" :style="isVisible?{}:{background:`linear-gradient(to bottom,#62E8FF,#0097FF)`}" @click="whiteDrawer">
<image class="drawer-img" :src="isVisible?'/static/index/watch/whitearrow.png':'/static/index/watch/arrow.png' " />
</view>
<slot />
</view>
</view>
</view>
</template>
<script setup>
import {
ref,
defineProps,
computed,
onMounted
} from 'vue'
//
const isVisible = ref(false)
//
const props = defineProps({
widNumber: {
type: Number,
default: 26
}
})
//
const screenWidth = ref(0)
onMounted(() => {
const sys = uni.getSystemInfoSync()
screenWidth.value = sys.screenWidth
})
//
const startX = ref(0)
const dragging = ref(false)
const currentOffset = ref(0)
// + transform
const drawerStyle = computed(() => {
const widthPct = `${props.widNumber}%`
if (dragging.value) {
// X
const offset = currentOffset.value
return {
width: widthPct,
transform: `translateX(${offset}px)`,
transition: 'none'
}
} else {
// class
return {
width: widthPct
}
}
})
// /
function openDrawer() {
isVisible.value = true
}
function closeDrawer() {
isVisible.value = false
}
function whiteDrawer() {
// &
isVisible.value = !isVisible.value
rotate180()
}
defineExpose({
openDrawer,
closeDrawer
})
//
function onTouchStart(e) {
if (!isVisible.value) return
dragging.value = true
currentOffset.value = 0
startX.value = e.touches[0].pageX
}
function onTouchMove(e) {
if (!dragging.value) return
const delta = e.touches[0].pageX - startX.value
currentOffset.value = delta > 0 ? delta : 0
}
function onTouchEnd() {
if (!dragging.value) return
dragging.value = false
const halfPx = screenWidth.value * (props.widNumber / 100) / 2
if (currentOffset.value > halfPx) {
closeDrawer()
rotate180()
}
currentOffset.value = 0
}
//
const angle = ref(0)
const boxStyle = computed(() => ({
transform: `rotate(${angle.value}deg)`,
transition: 'transform 0.6s ease'
}))
function rotate180() {
angle.value += 180
}
</script>
<style lang="less" scoped>
.drawer {
position: fixed;
bottom: 0;
right: 0;
height: 85vh;
background: #eff0f4;
z-index: 1000;
border-top-left-radius: 80rpx;
border-bottom-left-radius: 80rpx;
/* 初始隐藏 */
transform: translateX(100%);
transition: transform 0.4s ease;
}
.drawer-open {
transform: translateX(0);
}
.drawer-content {
position: relative;
width: 100%;
height: 100%;
}
.drawer-content-circle {
position: absolute;
bottom: 270rpx;
left: -60rpx;
width: 150rpx;
height: 160rpx;
border-radius: 50%;
z-index: -1;
// background: #fff;
background: linear-gradient(to right,
#fff 0rpx,
#eff0f4 60rpx,
#eff0f4 100%);
display: flex;
align-items: center;
clip-path: inset(0 60% 0 0);
}
.drawer-img {
width: 20rpx;
height: 20rpx;
margin-left: 25rpx;
transform: rotate(180deg);
}
</style>

View File

@ -194,7 +194,7 @@
margin-top: 10rpx;
.white-circle-click {
width: 170rpx;
width: 150rpx;
height: 70rpx;
}
@ -230,7 +230,7 @@
overflow: hidden;
/* 小容器 */
.bad-circle-up {
/* .bad-circle-up {
position: absolute;
top: -20rpx;
left: 15rpx;
@ -250,7 +250,7 @@
background-color: rgb(229, 233, 249);
-webkit-mask-image: radial-gradient(circle farthest-side at 100% 100%, transparent calc(100% - 1px), black 100%);
mask-image: radial-gradient(circle farthest-side at 100% 100%, transparent calc(100% - 1px), black 100%);
}
} */
.middle-left-box {
margin-left: 10rpx;

View File

@ -297,10 +297,7 @@
:class="(index === upmenuIndex) ? `doctorsay-container-card-font-dark`:`doctorsay-container-card-font`">
{{ item.name }}
</view>
<view class="bad-circle-up" v-show="index === upmenuIndex && index">
</view>
<view class="bad-circle-down" v-show="index === upmenuIndex && index!==5">
</view>
</view>
</view>
</view>
@ -308,7 +305,7 @@
<view class="under-button">
<view class="under-button-black" :style="isop?{opacity: 1}:{opacity: 0}">
不能重复添加相同的即时服务指令
<view class="under-button-three"></view>
<!-- <view class="under-button-three"></view> -->
</view>
<view class="white-circle" @click="changecard">
<image class="white-circle-img" src="/static/index/keyimg/movebutton.png" />

View File

@ -2,8 +2,8 @@
"name" : "护理单元",
"appid" : "__UNI__FB2D473",
"description" : "护理单元",
"versionName" : "1.3.4",
"versionCode" : 134,
"versionName" : "1.4.5",
"versionCode" : 145,
"transformPx" : false,
/* 5+App */
"app-plus" : {

View File

@ -10,6 +10,14 @@
}
},
//
// {
// "path": "pages/ceshi",
// "style": {
// "navigationStyle": "custom"
// }
// },
{
"path": "pages/index/index",
"style": {
@ -24,6 +32,14 @@
"navigationStyle": "custom"
}
},
//
{
"path": "pages/watch/index",
"style": {
"navigationStyle": "custom"
}
},
{
"path": "pages/NursingNew/index",

View File

@ -28,7 +28,7 @@
</view>
<!-- 首页 -->
<fuwu :isShow="menuIndex == 0" />
<settings :isShow="menuIndex == 5" @jump="jumpToIndex" />
<settings :isShow="menuIndex == 4" @jump="jumpToIndex" />
<storeroomindex :isShow="menuIndex == -1" @nav="navMenu" />
<leidaindex :isShow="menuIndex==-2" />
<saoma :isShow="menuIndex==-3" />
@ -61,8 +61,9 @@
{ url: '/static/index/relindex/fuwu.png', targetUrl: '/static/index/relindex/fuwublue.png', name: '服务考核', pao: 0 },
{ url: '/static/index/relindex/jigou.png', targetUrl: '/static/index/relindex/jigoublue.png', name: '机构功能1', pao: 0 },
{ url: '/static/index/relindex/jigou.png', targetUrl: '/static/index/relindex/jigoublue.png', name: '机构功能2', pao: 0 },
{ url: '/static/index/relindex/jigou.png', targetUrl: '/static/index/relindex/jigoublue.png', name: '机构功能3', pao: 0 },
{ url: '/static/index/relindex/shezhi.png', targetUrl: '/static/index/relindex/shezhiblue.png', name: '设置', pao: 0 },
{ url: '/static/index/newindex/curve/return_8.png', targetUrl: '/static/index/newindex/curve/return_1.png', name: '返回', pao: 0 },
// { url: '/static/index/relindex/shezhi.png', targetUrl: '/static/index/relindex/shezhiblue.png', name: '', pao: 0 },
]);
const iconTop = ref<iconTopLink[]>([
@ -86,6 +87,9 @@
}
//
const changeMenu = (index : number) => {
if(index === 5 ){
uni.navigateBack()
}
menuIndex.value = index;
};
const clicktopright = (index : number) => {
@ -127,7 +131,9 @@
}
const name = ref("");
//
onLoad(() => {
onLoad((options) => {
console.log("???",options.menu)
options.menu ? menuIndex.value = options.menu : menuIndex.value = 0;
name.value = uni.getStorageSync('realname')
});
</script>

View File

@ -1,5 +1,5 @@
<template>
<view class="index-content-other" v-show="isShow" :style="transition?{opacity: `1`}:{opacity: `0`}">
<view class="index-content-other" :style="transition?{opacity: `1`}:{opacity: `0`}">
<view class="right-container-title-nav">
<text style="margin-left: 30rpx;">
NUID2508000001
@ -142,6 +142,8 @@
</scroll-view>
<image class="top-img" :src="`/static/index/newindex/rightmenu/bottom.png`" @click="scrollTop > (55* rightMenu.length) ? scrollTop = (55* rightMenu.length) : scrollTop += 115 "/>
</view>
<!-- 轮盘 -->
</view>
@ -149,6 +151,7 @@
<script setup lang="ts">
import { ref, onMounted, onBeforeUnmount, computed, nextTick, defineProps, watch } from 'vue';
const props = defineProps({
isShow: {
@ -333,7 +336,7 @@
])
// 使watchisShow
const transition = ref(true);
const transition = ref(false);
watch(
@ -342,14 +345,14 @@
// falsetrue0.2
if (!oldVal && newVal) {
transition.value = false;
// photoplay.value = false;
// console.log("????",transition.value)
setTimeout(() => {
transition.value = true;
// photoplay.value = true;
}, 50)
} else {
// photoplay.value = false;
transition.value = false;
}
}
)
const name = ref("");
@ -391,7 +394,7 @@
<style scoped lang="less">
.index-content-other {
width: calc(100% - 310rpx);
width: calc(100% - 330rpx);
height: 100%;
transition: opacity 1s ease;
position: relative;
@ -427,7 +430,7 @@
.right-icons {
position: absolute;
right: 0;
right: 30rpx;
top: 0;
// transform: translateY(-50%);
display: flex;
@ -453,8 +456,8 @@
.menu {
flex: 0 0 auto; // 👈
height: 100rpx;
width: 220rpx;
height: 90rpx;
width: 240rpx;
border-radius: 50rpx;
// margin: 45rpx auto;
margin-right: 20rpx;
@ -487,9 +490,6 @@
.right-container-tem {
display: flex;
// justify-content: space-around;
// margin-top: 30rpx;
// margin-bottom: 20rpx;
.right-container-tem-text {
font-size: 30rpx;

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -38,18 +38,14 @@
</text>
</view>
<view class="menus-father">
<view v-for="(item,index) in leftMenuArray" :key="index" class="menu" :style="index === menuIndex?{backgroundColor:`#fff`}:{} " @click="changeMenu(index)">
<view v-for="(item,index) in leftMenuArray" :key="index" class="menu"
:style="index === menuIndex?{backgroundColor:`#fff`}:{} " @click="changeMenu(index)">
<!-- <image class="menu-img" :src="item.url" /> -->
<donghua :width="`65rpx`" :height="`65rpx`" :links="item.url" :playing="index === menuIndex" />
<text style="font-size: 33rpx;margin-left: 15rpx;">
<text style="font-size: 31rpx;margin-left: 15rpx;">
{{item.name}}
</text>
<!-- <view class="blue-circle" v-show="index === menuIndex">
<image class="blue-circle-size" :src="`/static/index/ray.png`" />
</view>
<image class="left-img" :src="index === menuIndex ? item.targetUrl : item.url"
@click="changeMenu(index)" /> -->
</view>
</view>
<!-- <view class="left-img-container">
@ -63,9 +59,10 @@
</view> -->
</view>
<!-- 主页 -->
<index :isShow="menuIndexshow" v-show="!menuIndex" />
<index :isShow="menuIndexshow" v-show="!menuIndex" />
<nurse :isold="isOld===2" :liang="indexNumber" :isshow="menuIndexshowsecond" v-show="menuIndex==1&&isOld===2" />
<!-- <arrowkeys @movecard="" /> -->
<!-- 超凶表格 -->
<!-- 旧表格 -->
<!-- <rightItemssecond ref="ruler" :liang="indexNumber" :isshow="menuIndexshowsecond" :canmove="canmove"
@ -83,17 +80,17 @@
</template>
<script setup lang="ts">
import { ref, onMounted, onBeforeUnmount } from 'vue';
import { ref, onMounted, onBeforeUnmount, nextTick } from 'vue';
import type { Link } from "./index";
import index from "./component/index.vue"
import nurse from "./component/nurse/index.vue"
// import rightItemssecond from "../../component/rightItemssecond/index.vue"
// import specialruler from "../../component/specialruler/index.vue"
// import rightItemssecondrelnew from "../../component/rightItemssecondrelnew/index.vue"
import arrowkeys from '@/component/public/newgame/arrowkeys.vue';
import { onShow } from '@dcloudio/uni-app';
onMounted(() => {
menuIndex.value = 0
menuIndex.value = -1;
nextTick(() => menuIndex.value = 0)
isOld.value = 2;
uni.getSystemInfoSync(); // global
uni.pageScrollTo; //
@ -120,7 +117,7 @@
'png',
1, // 1
false //
),name:'首页'
), name: '首页'
},
{
url: genPaths(
@ -130,7 +127,7 @@
'png',
1, // 1
false //
),name:'护嘱'
), name: '护嘱'
},
{
url: genPaths(
@ -140,7 +137,7 @@
'png',
1, // 1
false //
),name:'医嘱'
), name: '医嘱'
},
{
url: genPaths(
@ -150,7 +147,7 @@
'png',
1, // 1
false //
),name:'请领'
), name: '请领'
},
{
url: genPaths(
@ -160,7 +157,7 @@
'png',
1, // 1
false //
),name:'设备'
), name: '设备'
},
{
url: genPaths(
@ -170,18 +167,18 @@
'png',
1, // 1
false //
),name:'返回'
), name: '返回'
},
])
const menuArray = ref([
{ url: '/static/index/lefticon/index.png',name:'首页' },
{ url: '/static/index/lefticon/nurse.png',name:'护嘱' },
{ url: '/static/index/lefticon/doctor.png',name:'医嘱' },
{ url: '/static/index/lefticon/give.png',name:'请领' },
{ url: '/static/index/lefticon/wifi.png' ,name:'设备'},
{ url: '/static/index/lefticon/back.png',name:'返回' }
{ url: '/static/index/lefticon/index.png', name: '首页' },
{ url: '/static/index/lefticon/nurse.png', name: '护嘱' },
{ url: '/static/index/lefticon/doctor.png', name: '医嘱' },
{ url: '/static/index/lefticon/give.png', name: '请领' },
{ url: '/static/index/lefticon/wifi.png', name: '设备' },
{ url: '/static/index/lefticon/back.png', name: '返回' }
])
//
const iconList = ref<Link[]>([
{ url: '/static/index/lefticon/index.png', targetUrl: '/static/index/lefticontarget/blueindex.png' },
@ -232,7 +229,7 @@
// uni.navigateBack()
// return
// }
if(menuIndex.value===index){
if (menuIndex.value === index) {
return
}
menuIndex.value = index;
@ -368,7 +365,7 @@
} else {
}
} else if (saveruler.value.typeName) {
if (isOld.value===0) {
if (isOld.value === 0) {
ruler.value?.rulerMoveEnd(saveruler.value);
} else {
rulernew.value?.rulerMoveEnd(saveruler.value);
@ -430,23 +427,29 @@
setTimeout(() => {
changeMenu(menuIndex.value)
}, 50)
menuIndexshow.value = false
setTimeout(() => {
menuIndexshow.value = true
}, 50)
});
</script>
<style lang="less" scoped>
.menus-father{
.menus-father {
margin-top: 50rpx;
width: 100%;
.menu{
.menu {
height: 110rpx;
width: 230rpx;
// background-color: #fff;
width: 280rpx;
border-radius: 60rpx;
margin: 45rpx auto;
margin: 45rpx 0;
display: flex;
justify-content: center;
align-items: center;
margin-left: 30rpx;
// .menu-img{
// width: 65rpx;
// height: 65rpx;
@ -454,10 +457,10 @@
// }
}
}
.backgroundContainer {
display: flex;
position: relative;
@ -503,7 +506,7 @@
}
.left-container {
width: 280rpx;
width: 320rpx;
height: 100%;
.blue-circle-pos {

View File

@ -245,7 +245,7 @@
uni.setStorageSync('username', form.username);
uni.setStorageSync('realname', res.result.userInfo.realname);
setTimeout(() => {
jumpTo(`/pages/Initialization/index`)
jumpTo(`/pages/watch/index`)
}, 500)
} else {
uni.showToast({
@ -266,7 +266,7 @@
onShow(() => {
zyupgrade.value?.check_update();
if(uni.getStorageSync('token') && uni.getStorageSync('token')!==1){
jumpTo(`/pages/Initialization/index`)
jumpTo(`/pages/watch/index`)
}
});
</script>

View File

@ -495,7 +495,7 @@
height: 40rpx;
border-radius: 50%;
display: flex;
background-color: #0184db;
background-color: #A9ACB1;
justify-content: center;
align-items: center;
position: absolute;
@ -698,7 +698,7 @@
height: 80rpx;
width: 247rpx;
border-right: 1rpx solid transparent;
border-image: repeating-linear-gradient(180deg, #0184db 0px, #0184db 6rpx, transparent 6rpx, transparent 12rpx) 1;
border-image: repeating-linear-gradient(180deg, #A9ACB1 0px,#A9ACB1 6rpx, transparent 6rpx, transparent 12rpx) 1;
font-weight: 700;
}
@ -710,7 +710,7 @@
flex-direction: column;
border-right: 1rpx solid transparent;
border-image: repeating-linear-gradient(180deg, #0184db 0px, #0184db 6rpx, transparent 6rpx, transparent 12rpx) 1;
border-image: repeating-linear-gradient(180deg, #A9ACB1 0px,#A9ACB1 6rpx, transparent 6rpx, transparent 12rpx) 1;
}
.super-card-time-card {
@ -720,11 +720,9 @@
height: 153rpx;
width: calc(100%);
border-top: 1rpx solid transparent;
border-image: repeating-linear-gradient(90deg, #0184db 0px, #0184db 6rpx, transparent 6rpx, transparent 12rpx) 1;
border-image: repeating-linear-gradient(90deg, #A9ACB1 0px, #A9ACB1 6rpx, transparent 6rpx, transparent 12rpx) 1;
flex-direction: column;
position: relative;
}
}
@ -732,24 +730,24 @@
z-index: 999;
background:
/* 左上角水平 */
linear-gradient(to right, #0184db, #0184db) top left / 60rpx 8rpx no-repeat,
linear-gradient(to right, #A9ACB1, #A9ACB1) top left / 60rpx 8rpx no-repeat,
/* 左上角垂直 */
linear-gradient(to bottom, #0184db, #0184db) top left / 8rpx 60rpx no-repeat,
linear-gradient(to bottom, #A9ACB1, #A9ACB1) top left / 8rpx 60rpx no-repeat,
/* 右上角水平 */
linear-gradient(to left, #0184db, #0184db) top right / 60rpx 8rpx no-repeat,
linear-gradient(to left, #A9ACB1, #A9ACB1) top right / 60rpx 8rpx no-repeat,
/* 右上角垂直 */
linear-gradient(to bottom, #0184db, #0184db) top right / 8rpx 60rpx no-repeat,
linear-gradient(to bottom, #A9ACB1, #A9ACB1) top right / 8rpx 60rpx no-repeat,
/* 左下角水平 */
linear-gradient(to right, #0184db, #0184db) bottom left / 60rpx 8rpx no-repeat,
linear-gradient(to right, #A9ACB1, #A9ACB1) bottom left / 60rpx 8rpx no-repeat,
/* 左下角垂直 */
linear-gradient(to top, #0184db, #0184db) bottom left / 8rpx 60rpx no-repeat,
linear-gradient(to top, #A9ACB1, #A9ACB1) bottom left / 8rpx 60rpx no-repeat,
/* 右下角水平 */
linear-gradient(to left, #0184db, #0184db) bottom right / 60rpx 8rpx no-repeat,
linear-gradient(to left, #A9ACB1, #A9ACB1) bottom right / 60rpx 8rpx no-repeat,
/* 右下角垂直 */
linear-gradient(to top, #0184db, #0184db) bottom right / 8rpx 60rpx no-repeat;
linear-gradient(to top, #A9ACB1, #A9ACB1) bottom right / 8rpx 60rpx no-repeat;
}
.title-time-border-yellow {

View File

@ -2,8 +2,8 @@
<view class="doctorsay-container-container">
<view class="doctorsay-container-title">
<view class="doctorsay-container-left">
<view class="doctorsay-container-left-gun"></view>
<view class="doctorsay-container-left-font">时间矩阵</view>
<!-- <view class="doctorsay-container-left-gun"></view>
<view class="doctorsay-container-left-font">时间矩阵</view> -->
</view>
<view class="doctorsay-container-right">
@ -36,7 +36,7 @@
<scroll-view style="height: 100%;width: 100%;" :scroll-left="scrollLeft" scroll-x
:show-scrollbar="false">
<view :style="{width:widthType * 24 + `rpx`}"
style="display: flex; box-shadow: 10rpx 10rpx 20rpx rgba(0, 0, 0, 0.1);background: linear-gradient(to right, #c4dbf4,#c9c2ef, #c6dcf3);">
style="display: flex;">
<view v-for="(item0,index0) in changetimearr" :key="index0">
<view class="super-card-time" :style="{width:widthType + `rpx`}">
{{(item0.positioning.length == 1 ? ('0' + item0.positioning) : item0.positioning) + ":00"}}
@ -52,12 +52,13 @@
<view v-for="(item1,index1) in item0.children" style="width: 100%;"
:key="index1">
<view
:class="(clickX === index0 && clickY ===index1) ? `title-time-border-blue` : ``"
:style="{height:heightType + `rpx`}"
class="super-card-time-card" style="position: relative;"
@touchend="handleTap(item1,index0,index1,$event)" :data-index0="index0"
:data-index1="index1">
<view class="title-time-blue"
v-show="clickX == index0 && clickY == index1">
</view>
<view :class="getClass(item1,index0,index1)"
style="font-size: 30rpx;overflow: hidden;">
<view class="title-time" v-if="item1.startTime"
@ -658,10 +659,11 @@
width: calc(100% - 4rpx);
height: calc(100vh - 4rpx);
/* 设置背景图和白色背景 */
background: url("/static/index/lightbgcnew.png") center/cover, rgba(255, 255, 255, 0.3);
backdrop-filter: blur(20rpx);
// background: url("/static/index/lightbgcnew.png") center/cover, rgba(255, 255, 255, 0.3);
// backdrop-filter: blur(20rpx);
/* 使用 screen 混合模式,让图像与白色混合变淡 */
background-blend-mode: screen;
// background-blend-mode: screen;
background-color: #eff0f4;
border-radius: 30rpx;
box-shadow: 5px 5px 10px rgba(0, 0, 0, 0.1);
/* 右下角阴影 */
@ -710,7 +712,7 @@
height: 40rpx;
border-radius: 50%;
display: flex;
background-color: #0184db;
background-color: #A9ACB1;
justify-content: center;
align-items: center;
position: absolute;
@ -894,16 +896,17 @@
.super-card-container {
/* 设置背景图和白色背景 */
background: url("/static/index/clearmountain.png") center/cover, rgba(255, 255, 255, 0.5);
// background: url("/static/index/clearmountain.png") center/cover, rgba(255, 255, 255, 0.5);
/* 使用 screen 混合模式,让图像与白色混合变淡 */
background-blend-mode: screen;
isolation: isolate;
// background-blend-mode: screen;
// isolation: isolate;
overflow: hidden;
width: calc(100% - 100rpx);
height: 100%;
border-radius: 20rpx;
border: 2rpx solid #fff;
box-shadow: 10rpx 10rpx 20px rgba(0, 0, 0, 0.1);
// border-radius: 20rpx;
// border: 2rpx solid #fff;
// box-shadow: 10rpx 10rpx 20px rgba(0, 0, 0, 0.1);
color: #A9ACB1;
position: relative;
}
@ -913,9 +916,9 @@
align-items: center;
height: 80rpx;
width: 380rpx;
border-right: 1rpx solid transparent;
border-image: repeating-linear-gradient(180deg, #0184db 0px, #0184db 6rpx, transparent 6rpx, transparent 12rpx) 1;
font-weight: 700;
// border-right: 1rpx solid transparent;
// border-image: repeating-linear-gradient(180deg, #A9ACB1 0px, #A9ACB1 6rpx, transparent 6rpx, transparent 12rpx) 1;
// font-weight: 700;
}
.super-card-time-und {
@ -926,7 +929,7 @@
flex-direction: column;
border-right: 1rpx solid transparent;
border-image: repeating-linear-gradient(180deg, #0184db 0px, #0184db 6rpx, transparent 6rpx, transparent 12rpx) 1;
border-image: repeating-linear-gradient(180deg, #A9ACB1 0px, #A9ACB1 6rpx, transparent 6rpx, transparent 12rpx) 1;
}
.super-card-time-card {
@ -936,7 +939,7 @@
// height: 180rpx;
width: calc(100%);
border-top: 1rpx solid transparent;
border-image: repeating-linear-gradient(90deg, #0184db 0px, #0184db 6rpx, transparent 6rpx, transparent 12rpx) 1;
border-image: repeating-linear-gradient(90deg, #A9ACB1 0px, #A9ACB1 6rpx, transparent 6rpx, transparent 12rpx) 1;
flex-direction: column;
position: relative;
@ -948,24 +951,24 @@
z-index: 999;
background:
/* 左上角水平 */
linear-gradient(to right, #0184db, #0184db) top left / 60rpx 8rpx no-repeat,
linear-gradient(to right, #A9ACB1, #A9ACB1) top left / 60rpx 8rpx no-repeat,
/* 左上角垂直 */
linear-gradient(to bottom, #0184db, #0184db) top left / 8rpx 60rpx no-repeat,
linear-gradient(to bottom, #A9ACB1, #A9ACB1) top left / 8rpx 60rpx no-repeat,
/* 右上角水平 */
linear-gradient(to left, #0184db, #0184db) top right / 60rpx 8rpx no-repeat,
linear-gradient(to left, #A9ACB1, #A9ACB1) top right / 60rpx 8rpx no-repeat,
/* 右上角垂直 */
linear-gradient(to bottom, #0184db, #0184db) top right / 8rpx 60rpx no-repeat,
linear-gradient(to bottom, #A9ACB1, #A9ACB1) top right / 8rpx 60rpx no-repeat,
/* 左下角水平 */
linear-gradient(to right, #0184db, #0184db) bottom left / 60rpx 8rpx no-repeat,
linear-gradient(to right, #A9ACB1, #A9ACB1) bottom left / 60rpx 8rpx no-repeat,
/* 左下角垂直 */
linear-gradient(to top, #0184db, #0184db) bottom left / 8rpx 60rpx no-repeat,
linear-gradient(to top, #A9ACB1, #A9ACB1) bottom left / 8rpx 60rpx no-repeat,
/* 右下角水平 */
linear-gradient(to left, #0184db, #0184db) bottom right / 60rpx 8rpx no-repeat,
linear-gradient(to left, #A9ACB1, #A9ACB1) bottom right / 60rpx 8rpx no-repeat,
/* 右下角垂直 */
linear-gradient(to top, #0184db, #0184db) bottom right / 8rpx 60rpx no-repeat;
linear-gradient(to top, #A9ACB1, #A9ACB1) bottom right / 8rpx 60rpx no-repeat;
}
.title-time-border-yellow {
@ -1255,4 +1258,31 @@
border-top-right-radius: 40rpx;
}
}
.title-time-blue {
/* 你的定位与尺寸保持不变 */
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 90%;
height: 88%;
z-index: 10;
--color: #99C9FD;
--thick: 2px;
--radius: 16px;
border: var(--thick) dashed var(--color);
border-radius: var(--radius);
background:
/* 上边 */
repeating-linear-gradient(90deg, var(--color) 0 var(--dash), transparent 0 calc(var(--dash) + var(--gap))) top left / 100% var(--thick) no-repeat,
/* 下边 */
repeating-linear-gradient(90deg, var(--color) 0 var(--dash), transparent 0 calc(var(--dash) + var(--gap))) bottom left / 100% var(--thick) no-repeat,
/* 左边 */
repeating-linear-gradient(0deg, var(--color) 0 var(--dash), transparent 0 calc(var(--dash) + var(--gap))) top left / var(--thick) 100% no-repeat,
/* 右边 */
repeating-linear-gradient(0deg, var(--color) 0 var(--dash), transparent 0 calc(var(--dash) + var(--gap))) top right / var(--thick) 100% no-repeat;
}
</style>

View File

@ -0,0 +1,522 @@
```vue
<!-- 轮盘一级圆盘 + 二级左半弧滚动盘-->
<template>
<view class="draw-all">
<!-- 摄像头 -->
<view class="carmera">
<image class="all-size" src="/static/index/watch/camera.png" />
</view>
<!-- 轮子背景 -->
<view class="roll">
<image class="all-size" src="/static/index/watch/panzi.png" />
</view>
<!-- 一级转盘完整圆盘可旋转自动校正选中最左侧 -->
<view ref="compass" class="compass-container" @touchstart.stop.prevent="onTouchStart"
@touchmove.prevent.stop="onTouchMove" @touchend.stop.prevent="onTouchEnd"
@touchcancel.stop.prevent="onTouchEnd" :style="wrapperStyle">
<view v-for="(item, i) in items" :key="i" class="compass-item" :style="itemStyle(item.baseAngle)">
<text :class="i===target?`item-label-target`: `item-label`" :style="labelStyle">
<view
style="z-index: 2;display: flex;flex-direction: column;justify-content: center;align-items: center;">
<image style="width: 50rpx;height: 50rpx;margin-bottom: 0rpx;"
:src="`/static/index/watch/Wheel/${i}${i===target?1:0}.png`" />
<view :style="i===target?{color:'#fff'}:{}">
{{ item.label }}
</view>
</view>
<image class="targetimge" src="/static/index/watch/bluetarget.png"
:style="{opacity:i===target?1:0}" />
</text>
</view>
</view>
<!-- 二级转盘只占左半球90°~270°竖向滚动有边界吸附最多 5 -->
<view v-if="items2.length" ref="compass2" class="compass-container second"
@touchstart.stop.prevent="onTouchStart2" @touchmove.prevent.stop="onTouchMove2"
@touchend.stop.prevent="onTouchEnd2" @touchcancel.stop.prevent="onTouchEnd2" :style="wrapperStyle2"
v-show="target">
<view v-for="(item, i) in items2" :key="i" class="compass-item" :style="itemStyle2(item.baseAngle)">
<text :class="i===target2?`item-label-second-target`: `item-label-second`" :style="labelStyle2">
<view
style="z-index: 2;display: flex;flex-direction: column;justify-content: center;align-items: center;">
<image style="width: 70rpx;height: 70rpx;margin-bottom: 0rpx;"
:src="`/static/index/watch/Wheel/${target}${i}${i===target2?1:0}.png`" />
<view v-show="target!==-1" :style="i===target2?{color:'#0E86EA'}:{}">
{{ item.label }}
</view>
</view>
</text>
</view>
</view>
<view class="" v-show="target===5">
<joystick @movecard="" :movebottom="44" :moveleft="-5" :pao="false" :notext="true" />
</view>
<!-- 一级手势蒙层 -->
<view v-if="dragging==='first'" class="gesture-mask" @touchmove.stop.prevent="onTouchMove"
@touchend.stop.prevent="onTouchEnd" @touchcancel.stop.prevent="onTouchEnd" />
<!-- 二级手势蒙层 -->
<view v-if="dragging==='second'" class="gesture-mask" @touchmove.stop.prevent="onTouchMove2"
@touchend.stop.prevent="onTouchEnd2" @touchcancel.stop.prevent="onTouchEnd2" />
</view>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted, computed, nextTick, watch, onBeforeUnmount } from 'vue'
import joystick from '@/component/public/newgame/joysticknew.vue';
const emit = defineEmits([])
const props = defineProps({})
/* ===================== 一级:完整圆盘 ===================== */
// 10
const labels = ['开机', '静音', '对讲', '截屏', '录制', '方位', '清晰度', '分屏', '翻转', '告警']
const count = labels.length
// 360
const items = reactive(
labels.map((label, idx) => ({
label,
baseAngle: (360 / count) * idx
}))
)
//
let restoreTimer : number | null = null
let restoreTimer2 : number | null = null
const RESTORE_MS = 300 // 0.3s
//
const currentAngle = ref(0)
let startAngle = 0
const dragging = ref<null | 'first' | 'second'>(null)
let idleTimer : number | null = null
function armIdleFinish(handler : () => void, ms = 160) {
if (idleTimer) clearTimeout(idleTimer)
idleTimer = setTimeout(() => {
if (dragging.value) handler()
}, ms) as unknown as number
}
function clearIdle() {
if (idleTimer) {
clearTimeout(idleTimer)
idleTimer = null
}
}
// atan2
const center = reactive({ x: 0, y: 0 })
//
onMounted(async () => {
await nextTick()
uni
.createSelectorQuery()
.select('.compass-container')
.boundingClientRect(rect => {
if (rect) {
center.x = rect.left + rect.width / 2
center.y = rect.top + rect.height / 2
}
})
.exec()
})
//
const transitioning = ref(false)
const wrapperStyle = computed(() => ({
transform: `rotate(${currentAngle.value}deg)`,
transition: transitioning.value ? 'transform 0.3s ease-out' : 'none'
}))
//
function itemStyle(baseAngle : number) {
const radius = 190
const rad = (baseAngle * Math.PI) / 180
const x = radius * Math.cos(rad)
const y = radius * Math.sin(rad)
return { transform: `translate(${x}px, ${y}px)` }
}
//
const labelStyle = computed(() => ({
transform: `rotate(${-currentAngle.value}deg)`
}))
// atan2
function getTouchAngle(e : TouchEvent) {
const t = e.touches[0]
const dx = t.clientX - center.x
const dy = t.clientY - center.y
return ((Math.atan2(dy, dx) * 180) / Math.PI) * 2 // ×2
}
function clearRestoreTimer() {
if (restoreTimer !== null) {
clearTimeout(restoreTimer)
restoreTimer = null
}
}
//
const target = ref(5)
const saveindex = ref(-1)
function startRestoreTimer() {
clearTimeout(restoreTimer)
restoreTimer = setTimeout(() => {
if (target.value === -1) {
target.value = saveindex.value
}
}, 300)
}
function startRestoreTimer2() {
clearTimeout(restoreTimer2)
restoreTimer2 = setTimeout(() => {
if (target2.value === -1) {
target2.value = saveindex2.value
}
}, 300)
}
function onTouchStart(e : TouchEvent) {
saveindex.value = target.value
target.value = -1
transitioning.value = false
startAngle = getTouchAngle(e) - currentAngle.value
dragging.value = 'first'
startRestoreTimer()
}
function onTouchMove(e : TouchEvent) {
const angle = getTouchAngle(e)
currentAngle.value = angle - startAngle
armIdleFinish(onTouchEnd)
// 0.3s move saveindex
startRestoreTimer()
}
function getLeftmostIndex() {
let minDiff = Infinity
let idx = 0
items.forEach((item, i) => {
let real = (item.baseAngle + currentAngle.value) % 360
if (real < 0) real += 360
const diff = Math.abs(real - 180)
if (diff < minDiff) {
minDiff = diff
idx = i
}
})
return idx
}
function onTouchEnd() {
//
clearRestoreTimer()
const step = 360 / count
const raw = currentAngle.value
const nearest = Math.round(raw / step) * step
transitioning.value = true
currentAngle.value = nearest
const leftIndex = getLeftmostIndex()
target.value = leftIndex
// console.log("???", target.value)
setTimeout(() => (transitioning.value = false), 300)
dragging.value = null
}
onBeforeUnmount(() => {
if (restoreTimer !== null) clearTimeout(restoreTimer)
if (restoreTimer2 !== null) clearTimeout(restoreTimer2)
clearIdle()
})
/* ===================== 二级:左半弧竖向滚动盘 ===================== */
// 5
const secondMap : Record<number, string[]> = {
0: [],
1: [],
2: [],
3: [],
4: [],
5: [],
6: ['超清', '流畅', '自动'],
7: ['180°全景', '四分屏', '360°全景', '全景拉伸', '原图'],
8: ['上下翻转', '关闭', '左右翻转'],
9: []
}
//
const items2 = reactive<{ label : string; baseAngle : number }[]>([])
const target2 = ref(0)
// [-Δ, +Δ]
const currentOffset2 = ref(0)
const step2 = ref(0) // Δ = 180 / (n + 1)
const transitioning2 = ref(false)
// /
const DEG_PER_PX = 0.5
// currentOffset2
const wrapperStyle2 = computed(() => ({
transform: `rotate(${currentOffset2.value}deg)`,
transition: transitioning2.value ? 'transform 0.25s ease-out' : 'none'
}))
//
const labelStyle2 = computed(() => ({
transform: `rotate(${-currentOffset2.value}deg)`
}))
// 90°~270°
function itemStyle2(baseAngle : number) {
const radius = 240
const rad = (baseAngle * Math.PI) / 180
const x = radius * Math.cos(rad)
const y = radius * Math.sin(rad)
return { transform: `translate(${x}px, ${y}px)` }
}
//
const presetAngles = [135, 157.5, 180, 202.5, 225]
//
function getBalancedAngles(n : number) : number[] {
const order = [2, 1, 3, 0, 4]
return order.slice(0, n).map(i => presetAngles[i])
}
const minOffset2 = ref(0);
const maxOffset2 = ref(0);
// rebuildSecondByFirstIndex
function rebuildSecondByFirstIndex(firstIdx : number) {
const list = (secondMap[firstIdx] || []).slice(0, 5)
const angles = getBalancedAngles(list.length)
minOffset2.value = 180 - Math.max(...angles)
maxOffset2.value = 180 - Math.min(...angles)
items2.splice(0, items2.length)
if (!list.length) {
currentOffset2.value = 0
step2.value = 0
target2.value = -1
return
}
for (let i = 0; i < list.length; i++) {
items2.push({
label: list[i],
baseAngle: angles[i]
})
}
currentOffset2.value = 0
step2.value = 22.5 //
nextTick(() => {
target2.value = getLeftmostIndex2()
})
}
// 180°
function getLeftmostIndex2() {
if (!items2.length) return -1
let minDiff = Infinity
let idx = 0
items2.forEach((item, i) => {
let real = (item.baseAngle + currentOffset2.value) % 360
if (real < 0) real += 360
const diff = Math.abs(real - 180)
if (diff < minDiff) {
minDiff = diff
idx = i
}
})
return idx
}
// target
watch(
() => target.value,
(idx) => {
if (idx >= 0) rebuildSecondByFirstIndex(idx)
},
{ immediate: true }
)
/* ---- 二级手势:竖向滚动到极限距离([-Δ, +Δ]),松手吸附到 Δ 的整数倍 ---- */
let startY2 = 0
let startOffset2 = 0
function clamp2(val : number) {
return Math.max(minOffset2.value, Math.min(maxOffset2.value, val))
}
const saveindex2 =ref(-1)
function onTouchStart2(e : TouchEvent) {
saveindex2.value = target2.value
const t = e.touches[0]
startY2 = t.clientY
startOffset2 = currentOffset2.value
transitioning2.value = false
target2.value = -1
dragging.value = 'second'
startRestoreTimer2()
}
function onTouchMove2(e : TouchEvent) {
if (!items2.length) return
const t = e.touches[0]
const dy = t.clientY - startY2
const Δ = step2.value || 1
// 使 -dy
const raw = startOffset2 + (-dy) * DEG_PER_PX
currentOffset2.value = clamp2(raw, -Δ, +Δ)
armIdleFinish(onTouchEnd2)
startRestoreTimer2()
}
function onTouchEnd2() {
if (!items2.length) return
const Δ = step2.value || 1
// Δ [-Δ, +Δ]
const snapped = Math.round(currentOffset2.value / Δ) * Δ
transitioning2.value = true
currentOffset2.value = clamp2(snapped, -Δ, +Δ)
// 180°
target2.value = getLeftmostIndex2()
setTimeout(() => (transitioning2.value = false), 250)
dragging.value = null
}
</script>
<style lang="less" scoped>
.draw-all {
width: 100%;
height: 100%;
background-color: #eff0f4;
overflow: hidden;
position: relative;
.carmera {
position: absolute;
right: 0;
bottom: 250rpx;
height: 600rpx;
width: 200rpx;
z-index: 1;
}
.roll {
position: absolute;
right: 0;
bottom: 0rpx;
height: 1300rpx;
width: 650rpx;
}
}
.compass-container {
width: 380px;
height: 380px;
border-radius: 50%;
position: absolute;
right: -60%;
transform: translateY(-60%);
bottom: 270rpx;
margin: auto;
touch-action: none;
z-index: 9999;
}
.compass-item {
position: absolute;
top: 50%;
left: 50%;
width: 100px;
height: 100px;
margin: -50px 0 0 -50px;
display: flex;
align-items: center;
justify-content: center;
}
.item-label {
font-size: 25rpx;
width: 130rpx;
height: 130rpx;
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
background: linear-gradient(to bottom, #e6e7ed, #f4f5f7);
border: 2rpx solid #fff;
position: relative;
}
.item-label-target {
font-size: 25rpx;
width: 130rpx;
height: 130rpx;
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
background: linear-gradient(to bottom, #e6e7ed, #f4f5f7);
position: relative;
}
.item-label-second {
font-size: 25rpx;
width: 130rpx;
height: 130rpx;
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
position: relative;
}
.item-label-second-target {
font-size: 25rpx;
width: 130rpx;
height: 130rpx;
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
position: relative;
}
.all-size {
width: 100%;
height: 100%;
}
.targetimge {
width: 150rpx;
height: 130rpx;
position: absolute;
right: 0;
top: 0;
z-index: 1;
transition: opacity 0.8s ease;
}
/* 二级容器放在一级之下,尺寸更大一圈(视觉在后面) */
.compass-container.second {
width: 480px;
height: 480px;
border-radius: 50%;
position: absolute;
right: -70%;
transform: translateY(-70%);
bottom: 172rpx;
margin: auto;
touch-action: none;
z-index: 1; //
}
.gesture-mask {
position: fixed;
left: 0;
top: 0;
right: 0;
bottom: 0;
z-index: 9999;
background: transparent;
/* 或 rgba(0,0,0,0.001) */
}
</style>
```

0
pages/watch/index.css Normal file
View File

4
pages/watch/index.ts Normal file
View File

@ -0,0 +1,4 @@
export type Link = {
url : string;
targetUrl : string;
};

1124
pages/watch/index.vue Normal file

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

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