This commit is contained in:
haomingming 2025-08-25 14:24:59 +08:00
parent 979c332454
commit ad85572062
53 changed files with 7076 additions and 30 deletions

53
common/nvue.css Normal file
View File

@ -0,0 +1,53 @@
.uni-pd {
padding-left: 30upx;
padding-top: 20upx;
padding-bottom: 20upx;
background-color: #EEEEEE;
}
.uni-padding-wrap{
padding:0 30upx;
}
.uni-list-cell {
position: relative;
display: flex;
margin-top: 5upx;
flex-direction: row;
justify-content: space-between;
align-items: center;
background-color: #FBFBFB;
}
.uni-label {
width: 210upx;
word-wrap: break-word;
word-break: break-all;
text-indent:20upx;
}
.uni-list-cell-db{
flex: 1;
padding-left: 20upx;
}
.uni-input {
height: 50upx;
flex: 1;
}
.uni-common-mt{
margin-top:30upx;
}
.uni-btn-v{
padding:10upx 0;
}
.btn {
height: 100upx;
border-width: 2upx;
border-style: solid;
border-color: rgb(162, 217, 192);
background-color: rgba(162, 217, 192, 0.2);
border-radius: 5upx;
display:inline-block;
margin-top: 20upx;
text-align: center;
line-height: 100upx;
}
.uni-center{
text-align:center;
}

181
common/uni-nvue.css Normal file
View File

@ -0,0 +1,181 @@
/* #ifndef APP-PLUS-NVUE */
page {
min-height: 100%;
height: auto;
}
/* #endif */
/* 解决头条小程序字体图标不显示问题因为头条运行时自动插入了span标签且有全局字体 */
/* #ifdef MP-TOUTIAO */
text :not(view) {
font-family: uniicons;
}
/* #endif */
.uni-icon {
font-family: uniicons;
font-weight: normal;
}
.uni-bg-red {
background-color: #F76260;
color: #FFF;
}
.uni-bg-green {
background-color: #09BB07;
color: #FFF;
}
.uni-bg-blue {
background-color: #007AFF;
color: #FFF;
}
.uni-container {
flex: 1;
padding: 15px;
background-color: #f8f8f8;
}
.uni-padding-lr {
padding-left: 15px;
padding-right: 15px;
}
.uni-padding-tb {
padding-top: 15px;
padding-bottom: 15px;
}
.uni-header-logo {
padding: 15px 15px;
flex-direction: column;
justify-content: center;
align-items: center;
margin-top: 10upx;
}
.uni-header-image {
width: 80px;
height: 80px;
}
.uni-hello-text {
margin-bottom: 20px;
}
.hello-text {
color: #7A7E83;
font-size: 14px;
line-height: 20px;
}
.hello-link {
color: #7A7E83;
font-size: 14px;
line-height: 20px;
}
.uni-panel {
margin-bottom: 12px;
}
.uni-panel-h {
background-color: #ffffff;
flex-direction: row;
align-items: center;
padding: 12px;
}
/*
.uni-panel-h:active {
background-color: #f8f8f8;
}
*/
.uni-panel-h-on {
background-color: #f0f0f0;
}
.uni-panel-text {
flex: 1;
color: #000000;
font-size: 14px;
font-weight: normal;
}
.uni-panel-icon {
margin-left: 15px;
color: #999999;
font-size: 14px;
font-weight: normal;
transform: rotate(0deg);
transition-duration: 0s;
transition-property: transform;
}
.uni-panel-icon-on {
transform: rotate(180deg);
}
.uni-navigate-item {
flex-direction: row;
align-items: center;
background-color: #FFFFFF;
border-top-style: solid;
border-top-color: #f0f0f0;
border-top-width: 1px;
padding: 12px;
}
.uni-navigate-item:active {
background-color: #f8f8f8;
}
.uni-navigate-text {
flex: 1;
color: #000000;
font-size: 14px;
font-weight: normal;
}
.uni-navigate-icon {
margin-left: 15px;
color: #999999;
font-size: 14px;
font-weight: normal;
}
.uni-list-cell {
position: relative;
flex-direction: row;
justify-content: flex-start;
align-items: center;
}
.uni-list-cell-pd {
padding: 22upx 30upx;
}
.flex-r {
flex-direction: row;
}
.flex-c {
flex-direction: column;
}
.a-i-c {
align-items: center;
}
.j-c-c {
justify-content: center;
}
.list-item {
flex-direction: row;
padding: 10px;
}

1706
common/uni.css Normal file

File diff suppressed because it is too large Load Diff

108
common/util.js Normal file
View File

@ -0,0 +1,108 @@
function formatTime(time) {
if (typeof time !== 'number' || time < 0) {
return time
}
var hour = parseInt(time / 3600)
time = time % 3600
var minute = parseInt(time / 60)
time = time % 60
var second = time
return ([hour, minute, second]).map(function (n) {
n = n.toString()
return n[1] ? n : '0' + n
}).join(':')
}
function formatTimeByDuration(result, timeShowBlock = 1) {
let h = Math.floor(result / 3600) < 10 ? '0' + Math.floor(result / 3600) : Math.floor(result / 3600);
let m = Math.floor((result / 60 % 60)) < 10 ? '0' + Math.floor((result / 60 % 60)) : Math.floor((result / 60 % 60));
let s = Math.floor((result % 60)) < 10 ? '0' + Math.floor((result % 60)) : Math.floor((result % 60));
if (timeShowBlock === 2)
return result = h + ':' + m + ':' + s;
else
return result = m + ':' + s;
}
function formatLocation(longitude, latitude) {
if (typeof longitude === 'string' && typeof latitude === 'string') {
longitude = parseFloat(longitude)
latitude = parseFloat(latitude)
}
longitude = longitude.toFixed(2)
latitude = latitude.toFixed(2)
return {
longitude: longitude.toString().split('.'),
latitude: latitude.toString().split('.')
}
}
var dateUtils = {
UNITS: {
'年': 31557600000,
'月': 2629800000,
'天': 86400000,
'小时': 3600000,
'分钟': 60000,
'秒': 1000
},
humanize: function (milliseconds) {
var humanize = '';
for (var key in this.UNITS) {
if (milliseconds >= this.UNITS[key]) {
humanize = Math.floor(milliseconds / this.UNITS[key]) + key + '前';
break;
}
}
return humanize || '刚刚';
},
format: function (dateStr) {
var date = this.parse(dateStr)
var diff = Date.now() - date.getTime();
if (diff < this.UNITS['天']) {
return this.humanize(diff);
}
var _format = function (number) {
return (number < 10 ? ('0' + number) : number);
};
return date.getFullYear() + '/' + _format(date.getMonth() + 1) + '/' + _format(date.getDay()) + '-' +
_format(date.getHours()) + ':' + _format(date.getMinutes());
},
parse: function (str) { //将"yyyy-mm-dd HH:MM:ss"格式的字符串转化为一个Date对象
var a = str.split(/[^0-9]/);
return new Date(a[0], a[1] - 1, a[2], a[3], a[4], a[5]);
}
};
function debounce(func, wait, immediate) {
let timeout, result;
return function() {
let context = this;
let args = arguments;
if (timeout) clearTimeout(timeout);
let later = function() {
timeout = null;
if (!immediate) result = func.apply(context, args);
};
let callNow = immediate && !timeout;
timeout = setTimeout(later, wait);
if (callNow) result = func.apply(this, args);
return result;
};
}
export default {
formatTime: formatTime,
formatLocation: formatLocation,
dateUtils: dateUtils,
debounce: debounce,
formatTimeByDuration: formatTimeByDuration
}

View File

@ -0,0 +1,95 @@
<template>
<view class="plv-player-drag-tips">
<view class="plv-player-drag-tips__box">
<image class="plv-player-drag-tips__direction-img" :src="imgSrc"
@error="imageError">
</image>
<text class="plv-player-drag-tips__time">
{{changeTimeFormat}} / {{durationFormat}}
</text>
</view>
</view>
</template>
<script>
import utils from '@/common/util.js';
import backImg from '@/static/skin/back.png';
import forwardImg from '@/static/skin/forward.png'
export default {
props: {
currentTime: {
type: Number
},
duration: {
type: Number
},
changeTime: {
type: Number
}
},
data() {
return {
isForward: false
}
},
watch: {
changeTime(newValue, oldValue){
this.isForward = newValue > oldValue;
}
},
computed: {
timeFormatType() {
this.duration >= 3600 ? 2 : 1;
},
changeTimeFormat() {
return utils.formatTimeByDuration(this.changeTime, this.timeFormatType);
},
durationFormat() {
return utils.formatTimeByDuration(this.duration, this.timeFormatType)
},
imgSrc() {
return this.isForward ? forwardImg : backImg;
}
}
}
</script>
<style>
.plv-player-drag-tips {
width: 250rpx;
height: 160rpx;
background: rgba(0,0,0,.7);
border-radius: 10rpx;
}
.plv-player-drag-tips__box {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
justify-content: center;
align-items: center;
}
.plv-player-drag-tips__direction-img {
width: 100rpx;
height: 100rpx;
}
.plv-player-drag-tips__time {
font-size: 24rpx;
color: #FFFFFF;
}
</style>

View File

@ -0,0 +1,271 @@
<template>
<view class="plv-player-gesture"
@touchstart="touchstart"
@touchmove="touchmove"
@longpress="handleLongPress"
@touchcancel="handleTouchCancel"
@touchend="touchend">
</view>
</template>
<script>
export default {
props: {
// 屏幕宽度(物理像素)
holeWidth: Number,
// 视频总时长
duration: Number,
// 视频当前播放时间
currentTime: Number
},
computed: {
halfWidth() {
return parseInt(this.holeWidth / 2);
}
},
data() {
return {
// 是否在左区域按下手势
downLeft: false,
// 滑动方向
swipeDir: null
}
},
mounted() {
this.addHandler();
},
methods:{
addHandler() {
// 增加父容器左右滑动监听
let finalTime = -1;
// 计算左右滑动seek进度
const countTime = (position) => {
const { holeWidth, duration, currentTime } = this;
const precent = Math.abs(position) / holeWidth;
const nowTime = currentTime || 0;
const constSecond = duration < 60 ? duration : 60;
let plusTime = parseInt(Math.abs(precent * constSecond));
if (position < 0) plusTime = plusTime * -1;
finalTime = nowTime + plusTime;
finalTime = this.limit(finalTime, 0, duration);
return finalTime;
};
this.addGesture((type, position) => {
switch (type) {
// 下滑
case -1:
// console.log('//下滑');
this.$emit('onGestureEvent', {
type: this.downLeft ? 'LEFT_DOWN' : 'RIGHT_DOWN',
position
});
break;
// 上滑
case -2:
// console.log('//上滑');
this.$emit('onGestureEvent', {
type: this.downLeft ? 'LEFT_UP' : 'RIGHT_UP',
position
});
break;
// 左滑
case 0:
// console.log('//左滑');
this.swipeDir = type;
countTime(position);
this.$emit('onGestureEvent', {
type: 'SEEK_TIME_UPDATE',
finalTime
});
break;
// 右滑
case 1:
// console.log('//右滑');
this.swipeDir = type;
countTime(position);
this.$emit('onGestureEvent', {
type: 'SEEK_TIME_UPDATE',
finalTime
});
break;
// touchend for swipe
case 3:
if (this.swipeDir > -1) {
this.$emit('onGestureEvent', {
type: this.swipeDir ? 'SWIPE_RIGHT' : 'SWIPE_LEFT'
});
this.swipeDir = -1;
}
if (finalTime > -1) {
this.$emit('onGestureSeekTo', finalTime);
finalTime = -1;
}
break;
// doubleTap
case 4:
// console.log('>>>> 双击');
this.$emit('onGestureEvent', {
type: 'DOUBLE_CLICK'
});
break;
// tap
case 5:
// console.log('>>>> 单击');
this.handleClick();
break;
// longTap
case 6:
break;
}
});
},
addGesture(callback) {
const { options } = this;
let startX, startY, moveEndX, moveEndY, X, Y, position, hasType;
let T, lastTap, isJustTouch = true;
const excuteCb = (type) => {
// type 手势类型 -2 上滑 -1 下滑 0 左滑 1 右滑 2 just touch 3 touchend 4 doubleTap 5 tap 6 longTap
if (callback && typeof callback === 'function')
callback(type, position);
};
const YPlus = 20;
const YReduce = YPlus * -1;
const XPlus = 50;
const XReduce = XPlus * -1;
const isXchange = () => {
if (X > XPlus || X < XReduce) {
position = X;
hasType = 'X';
isJustTouch = false;
}
if (X > XPlus) excuteCb(1);
// 左滑
else if (X < XReduce) excuteCb(0);
};
const isYchange = () => {
if (Y > YPlus || Y < YReduce) {
position = Y;
hasType = 'Y';
isJustTouch = false;
}
if (Y > YPlus) excuteCb(-1);
// 上滑
else if (Y < YReduce) excuteCb(-2);
};
const countDirect = () => {
if (hasType) {
hasType === 'X' ? isXchange() : isYchange();
return;
}
isXchange();
isYchange();
};
this.handleTouch = (type, e) => {
switch(type) {
case 'start':
startX = e.changedTouches[0].pageX;
startY = e.changedTouches[0].pageY;
T = Date.now();
hasType = '';
isJustTouch = true;
this.downLeft = startX < this.halfWidth ? true : false;
break;
case 'move':
moveEndX = e.changedTouches[0].pageX;
moveEndY = e.changedTouches[0].pageY;
X = moveEndX - startX;
Y = moveEndY - startY;
countDirect();
break;
case 'end':
if (isJustTouch)
if (lastTap && Date.now() - lastTap <= 300)
excuteCb(4);
else if (Date.now() - T < 1000)
excuteCb(5);
else
excuteCb(6);
else
excuteCb(3);
lastTap = Date.now();
isJustTouch = true;
break;
}
};
},
handleClick() {
this.$emit('onGestureClick');
},
touchstart(e) {
this.handleTouch('start', e);
},
touchmove(e) {
this.handleTouch('move', e);
},
touchend(e) {
this.handleTouch('end', e);
this.$emit('onTouchEnd');
},
handleTouchCancel() {
this.$emit('onTouchCancel');
},
handleLongPress() {
this.$emit('onLongPress');
},
/**
* 控制最小值,最大值
* @param num
* @param min
* @param max
* @returns {*}
*/
limit(num, min, max) {
if (num < min) return min;
if (num > max) return max;
return num;
}
}
}
</script>
<style>
.plv-player-gesture {
flex: 1;
flex-direction: row;
}
.plv-player-gesture__left {
flex: 5;
}
.plv-player-gesture__right {
flex: 5;
}
</style>

View File

@ -0,0 +1,68 @@
<template>
<view v-if="isShow" class="plv-player-icon">
<image class="plv-player-icon__img" :src="imgUrl"></image>
<text class="plv-player-icon__text">{{ precent + '%' }}</text>
</view>
</template>
<script>
export default {
props: {
imgUrl: {
type: 'string',
required: true
},
precent: {
type: 'Number',
default: 100,
required: true
}
},
watch: {
precent(num) {
if (!this.isShow) this.isShow = true;
this.startClock();
}
},
data() {
return {
isShow: false
}
},
methods: {
startClock() {
this.clearClock();
this.clock = setTimeout(() => {
this.isShow = false;
}, 1500);
},
clearClock() {
if (this.clock) clearTimeout(this.clock);
this.clock = null;
}
}
}
</script>
<style>
.plv-player-icon {
align-items: center;
justify-content: center;
}
.plv-player-icon__img {
width: 80rpx;
height: 80rpx;
}
.plv-player-icon__text {
color: #FFFFFF;
text-align: center;
}
</style>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,92 @@
<template>
<view v-if="isShow" class="plv-player-volume-wrap">
<image class="plv-player-volume__img" :src="value === 0 ? muteImgUrl : imgUrl "></image>
<view class="plv-player-volume__progress">
<view class="plv-player-volume__progress__played" :style="'flex:' + value"></view>
<view class="plv-player-volume__progress_surplus" :style="'flex:' + valueLeft"></view>
</view>
</view>
</template>
<script>
export default {
props: {
imgUrl: {
type: 'string',
required: true
},
muteImgUrl: {
type: 'string',
required: true
},
value: {
type: 'Number',
default: 0.5,
required: true
}
},
watch: {
value(num) {
if (!this.isShow) this.isShow = true;
this.startClock();
}
},
computed: {
valueLeft() {
return 1 - this.value;
}
},
data() {
return {
isShow: false
}
},
methods: {
startClock() {
this.clearClock();
this.clock = setTimeout(() => {
this.isShow = false;
}, 1500);
},
clearClock() {
if (this.clock) clearTimeout(this.clock);
this.clock = null;
}
}
}
</script>
<style>
.plv-player-volume-wrap {
flex-direction: row;
align-items: center;
justify-content: center;
}
.plv-player-volume__img {
width: 40rpx;
height: 40rpx;
margin-right: 10rpx;
}
.plv-player-volume__progress {
width: 260rpx;
height: 8rpx;
flex-direction: row;
background-color: #09BB07;
}
.plv-player-volume__progress__played {
background-color: #007AFF;
}
.plv-player-volume__progress_surplus {
background-color: #FFFFFF;
}
</style>

View File

@ -0,0 +1,315 @@
<template>
<view class="wrap">
<view :class="isFull ? 'player-full' : 'player'">
<plv-player
ref="vod"
class="vod-player"
seekType=1
autoPlay=true
disableScreenCAP=false
rememberLastPosition=false
@onPlayStatus="onPlayStatus"
@onPlayError="onPlayError"
@positionChange="positionChange"
@onEnableSubtitle="onEnableSubtitle"
@onSRTTextConfig="onSRTTextConfig"
@onSRTTitle="onSRTTitle">
</plv-player>
<skin ref="skin"
class="skin-control"
:defaultVolume="defaultVolume"
@onPlayBtnClick="onPlayBtnClick"
@onToSeek="onToSeek"
@onFullBtnClick="onFullBtnClick"
@onHdBtnClick="onHdBtnClick"
@onRateBtnClick="onRateBtnClick"
@onVolumeChanged="onVolumeChanged"
@onScalingBtnClick="onScalingBtnClick"
@onScreenShot="onScreenShot"
@onChangeSRTBtnClick="onChangeSRTBtnClick"
@onChangeSRTSingleMode="onChangeSRTSingleMode"
@onOpenSRT="onOpenSRT">
</skin>
</view>
<view v-if="!isFull" style="padding: 20rpx;">
<input class="uni-input" style="min-height: 35px; height: 35px; border: 1px solid #cccccc;font-size: 14px;" :value="videoVid"
@input="onVidInput" placeholder="请输入视频vid" />
<button type="primary" @click="setVid()">播放</button>
</view>
</view>
</template>
<script>
import Skin from '@/components/plv-player-skin/skin.nvue';
import DragTips from '@/components/plv-player-skin/drag-tips.nvue';
const dom = weex.requireModule('dom');
export default {
components: {
Skin
},
onReady() {
this.vodPlayer = this.$refs.vod;
this.skin = this.$refs.skin;
// plus.screen.lockOrientation('portrait-primary');
},
data() {
return {
title: '播放器Demo',
//
vodPlayer: null,
//
skin: null,
//
isFull: false,
//
defaultVolume: 0.5,
videoVid: 'cfb7a69a75b5004ddb137d7bd96aa7d7_c'
};
},
methods: {
//vid
onVidInput: function(event) {
this.videoVid = event.detail.value;
},
setVid() {
if(!this.videoVid) {
uni.showToast({
title: "请输入视频Vid",
icon: "none"
})
return;
}
const { vodPlayer } = this;
vodPlayer.setVid({
vid:this.videoVid,
level:0
}, (ret) => {
this.text = JSON.stringify(ret);
if (ret.errMsg != null) {
uni.showToast({
title: ret.errMsg,
icon: "none"
})
}
});
},
onPlayError(e){
if (e.detail.errEvent != null) {
uni.showToast({
title:'playErrorEvent - '+e.detail.errEvent,
icon: "none"
})
}
},
positionChange(e){
this.skin.timeUpdate(e.detail.currentPosition);
},
onEnableSubtitle(e){
console.log("===1" + e.detail);
this.skin.setEnableSubtitle(e.detail.isEnableSubtitle, e.detail.isEnableDoubleSubtitle);
},
onSRTTextConfig(e) {
console.log("===1 fontColor: " + e.detail.fontColor);
this.skit.setSRTTextConfig(e.detail);
},
onSRTTitle(e) {
console.log("===1 onSRTTitle" + e.detail);
},
onPlayStatus(e){
const { skin, vodPlayer } = this;
const state = e.detail.playbackState;
const preparedToPlay = e.detail.preparedToPlay;
if (state != null) {
this.skin.changePlayStatus(state === 'start');
} else if (preparedToPlay != null) {
this.updateDuration();
this.updateLevels();
this.updateScaling();
}
},
updateDuration() {
const { vodPlayer } = this;
if (!vodPlayer) return;
vodPlayer.getDuration(null, ret => {
this.skin.updateDuration(ret.duration);
});
},
updateLevels() {
const { vodPlayer } = this;
if (!vodPlayer) return;
vodPlayer.getLevelNum(null, ret => {
this.skin.updateLevels(ret.levelNum);
});
vodPlayer.getCurrentLevel(null, ret => {
this.skin.updateCurrentLevel(ret.currentLevel);
});
},
updateScaling() {
const { vodPlayer } = this;
if (!vodPlayer) return;
vodPlayer.getScalingMode({}, ret => {
this.skin.updateScaling(ret.scalingMode);
});
},
onPlayBtnClick(isPlaying) {
const { vodPlayer } = this;
if (!vodPlayer) return;
isPlaying ? vodPlayer.start() : vodPlayer.pause();
},
onHdBtnClick(level) {
const { vodPlayer } = this;
if (!vodPlayer) return;
vodPlayer.changeLevel({level});
},
onRateBtnClick(speed) {
const { vodPlayer } = this;
if (!vodPlayer) return;
vodPlayer.setSpeed({speed});
},
onScalingBtnClick(scalingMode) {
const { vodPlayer } = this;
if (!vodPlayer) return;
vodPlayer.setScalingMode({scalingMode});
},
onChangeSRTBtnClick(srt) {
console.log("===1 " + srt);
const { vodPlayer } = this;
if (!vodPlayer) return;
vodPlayer.changeSRT(srt);
},
onChangeSRTSingleMode(isSingle) {
const { vodPlayer } = this;
if (!vodPlayer) return;
vodPlayer.changeSRTSingleMode(isSingle);
},
onOpenSRT(isOpen) {
const { vodPlayer } = this;
if (!vodPlayer) return;
vodPlayer.setOpenSRT({
openSrt: isOpen
});
},
onFullBtnClick(isFull) {
// plus.screen.unlockOrientation();
plus.navigator.setFullscreen(isFull);
//h5+
// isFull ? plus.screen.lockOrientation('landscape-primary') : plus.screen.lockOrientation('portrait-primary');
//
const { vodPlayer } = this;
if (!vodPlayer) return;
isFull ? vodPlayer.changeToLandscape() : vodPlayer.changeToPortrait();
this.isFull = isFull;
},
onToSeek(time) {
const { vodPlayer } = this;
if (!vodPlayer) return;
vodPlayer.seekTo({seconds: time});
},
onVolumeChanged(value) {
const { vodPlayer, skin } = this;
if (!vodPlayer) return;
vodPlayer.getVolume(null, ret => {
const changedValue = ret.volume + value;
const realValue = this.limit(changedValue, 0, 1);
vodPlayer.setVolume({volume: realValue});
skin.updateVolumeValue(realValue);
});
},
onScreenShot() {
const { vodPlayer } = this;
if (!vodPlayer) return;
vodPlayer.snapshot(null, result =>{
if(result.errMsg != null){
console.log(result.errMsg)
}
});
},
/**
* 控制最小值最大值
* @param num
* @param min
* @param max
* @returns {*}
*/
limit(num, min, max) {
if (num < min) return min;
if (num > max) return max;
return num;
}
}
}
</script>
<style>
.wrap {
flex: 1;
}
.title {
height: 140rpx;
}
.player {
height: 400rpx;
}
.player-full {
flex: 1;
}
.vod-player {
flex: 1;
}
.skin-control {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 1;
}
.hide {
height: 0;
}
</style>

View File

@ -54,6 +54,22 @@
}
}
}
},
"nativePlugins" : {
"PLV-MediaPlayer" : {
"__plugin_info__" : {
"name" : "Polyv播放器插件",
"description" : "该插件封装了保利威底层播放器,是云直播和云点播插件必须的附属插件",
"platforms" : "Android,iOS",
"url" : "https://ext.dcloud.net.cn/plugin?id=4798",
"android_package_name" : "",
"ios_bundle_id" : "com.domainname.apppolyvios",
"isCloud" : true,
"bought" : 1,
"pid" : "4798",
"parameters" : {}
}
}
}
},
/* SDK */

View File

@ -3,9 +3,7 @@
"version": "1.0.0",
"main": "main.js",
"scripts": {
"dev": "cross-env NODE_ENV=development uniapp-cli build --watch",
"test": "cross-env NODE_ENV=test uniapp-cli build",
"prod": "cross-env NODE_ENV=production uniapp-cli build"
},
"keywords": [],
"author": "",

View File

@ -178,6 +178,16 @@
}
}
},
{
"path": "myPatient/myPatient",
"style": {
"navigationStyle": "custom",
"navigationBarTitleText": "uni-app分页",
"app": {
"bounce": "none"
}
}
},
{
"path": "videoDetail/videoDetail",
"style": {
@ -319,6 +329,109 @@
}
}
},
{
"path": "patientGroup/patientGroup",
"style": {
"navigationStyle": "custom",
"navigationBarTitleText": "uni-app分页",
"app": {
"bounce": "none"
}
}
},
{
"path": "groupMsg/groupMsg",
"style": {
"navigationStyle": "custom",
"navigationBarTitleText": "uni-app分页",
"app": {
"bounce": "none"
}
}
},
{
"path": "myCode/myCode",
"style": {
"navigationStyle": "custom",
"navigationBarTitleText": "uni-app分页",
"app": {
"bounce": "none"
}
}
},
{
"path": "myDownLoad/myDownLoad",
"style": {
"navigationStyle": "custom",
"navigationBarTitleText": "uni-app分页",
"app": {
"bounce": "none"
}
}
},
{
"path": "myCollect/myCollect",
"style": {
"navigationStyle": "custom",
"navigationBarTitleText": "uni-app分页",
"app": {
"bounce": "none"
}
}
},
{
"path": "myCourseware/myCourseware",
"style": {
"navigationStyle": "custom",
"navigationBarTitleText": "uni-app分页",
"app": {
"bounce": "none"
}
}
},
{
"path": "changeMobile/changeMobile",
"style": {
"navigationStyle": "custom",
"navigationBarTitleText": "uni-app分页",
"app": {
"bounce": "none"
}
}
},
{
"path": "wechatContact/wechatContact",
"style": {
"navigationStyle": "custom",
"navigationBarTitleText": "uni-app分页",
"app": {
"bounce": "none"
}
}
},
{
"path": "idcardAuth/idcardAuth",
"style": {
"navigationStyle": "custom",
"navigationBarTitleText": "uni-app分页",
"app": {
"bounce": "none"
}
}
},
{
"path": "videoHistroy/videoHistroy",
"style": {
"navigationStyle": "custom",
"navigationBarTitleText": "uni-app分页",
"app": {
"bounce": "none"
}
}
},
{
"path": "pwdLogin/pwdLogin",
"style": {
@ -346,7 +459,7 @@
"list": [
{
"name": "", //
"path": "pages_app/login/login", //
"path": "pages_app/videoDetail/videoDetail", //
"query": "" //onLoad
}
]

View File

@ -92,7 +92,7 @@
</template>
<script setup>
import CustomTabbar from '@/compoents/tabBar/tabBar.vue';
import CustomTabbar from '@/components/tabBar/tabBar.vue';
import { ref } from 'vue';
import { onShow } from "@dcloudio/uni-app";
import course from "@/static/jingpinkecheng.png"

View File

@ -257,7 +257,7 @@
<script setup>
import { ref, reactive, onMounted, nextTick, computed } from 'vue';
import { onShow, onLoad, onPageScroll } from "@dcloudio/uni-app";
import CustomTabbar from '@/compoents/tabBar/tabBar.vue';
import CustomTabbar from '@/components/tabBar/tabBar.vue';
import navTo from "@/utils/navTo.js";
import bg from "@/static/more_bg.png"
import patient from "@/static/icon_home_my_patient.png"

View File

@ -137,7 +137,7 @@
<script setup>
import { ref,nextTick} from 'vue';
import { onShow } from "@dcloudio/uni-app";
import CustomTabbar from '@/compoents/tabBar/tabBar.vue';
import CustomTabbar from '@/components/tabBar/tabBar.vue';
import select from "@/static/triangle_normal.png"
import selectOn from "@/static/triangle_normal.png"
import playImg from "@/static/bofang.png"

View File

@ -215,7 +215,7 @@
</template>
<script setup>
import CustomTabbar from '@/compoents/tabBar/tabBar.vue';
import CustomTabbar from '@/components/tabBar/tabBar.vue';
import { ref } from 'vue';
import { onShow } from "@dcloudio/uni-app";
import hzshImg from "@/static/hzsh.png"

View File

@ -95,7 +95,7 @@
<script setup>
import { ref } from 'vue';
import { onShow } from "@dcloudio/uni-app";
import CustomTabbar from '@/compoents/tabBar/tabBar.vue';
import CustomTabbar from '@/components/tabBar/tabBar.vue';
import upImg from "@/static/cb_up.png"
import downImg from "@/static/cb_up.png"
import filter from "@/static/cb_screen_no.png"

View File

@ -0,0 +1,193 @@
<template>
<view class="change-mobile-page">
<!-- 顶部导航栏 -->
<uni-nav-bar
left-icon="left"
title="更换手机号"
@clickLeft="goBack"
fixed
color="#8B2316"
height="140rpx"
:border="false"
backgroundColor="#eeeeee"
></uni-nav-bar>
<!-- 表单区域 -->
<view class="form-wrapper">
<!-- 新手机号 -->
<view class="input-row">
<view class="left-icon">
<up-image :src="phoneImg" width="36rpx" height="36rpx" ></up-image>
</view>
<input
class="text-input"
type="number"
maxlength="11"
v-model="mobile"
placeholder="请输入新手机号码"
placeholder-style="color:#c7c7c7"
/>
</view>
<!-- 验证码 -->
<view class="input-row">
<view class="left-icon">
<up-image :src="smsImg" width="36rpx" height="36rpx" ></up-image>
</view>
<input
class="text-input"
type="number"
maxlength="6"
v-model="code"
placeholder="请输入验证码"
placeholder-style="color:#c7c7c7"
/>
<button
class="code-btn"
:disabled="sending || !canSend"
@click="sendCode"
>
{{ sending ? countdown + 's' : '获取验证码' }}
</button>
</view>
</view>
<!-- 底部确定按钮 -->
<view class="bottom-bar">
<button class="confirm-btn" @click="onConfirm">确定</button>
</view>
</view>
</template>
<script setup>
import { ref, onUnmounted,computed } from 'vue';
import phoneImg from "@/static/phone.png"
import smsImg from "@/static/sms.png"
const mobile = ref('');
const code = ref('');
const sending = ref(false);
const countdown = ref(60);
let timer = null;
const canSend = computed(() => /^1\d{10}$/.test(mobile.value));
const goBack = () => {
uni.navigateBack();
};
const startTimer = () => {
sending.value = true;
countdown.value = 60;
timer = setInterval(() => {
countdown.value -= 1;
if (countdown.value <= 0) {
clearInterval(timer);
timer = null;
sending.value = false;
}
}, 1000);
};
const sendCode = () => {
if (!canSend.value) {
uni.showToast({ title: '请先输入正确的手机号', icon: 'none' });
return;
}
if (sending.value) return;
// TODO:
uni.showToast({ title: '验证码已发送', icon: 'success' });
startTimer();
};
const onConfirm = () => {
if (!canSend.value) {
uni.showToast({ title: '手机号格式不正确', icon: 'none' });
return;
}
if (!/^\d{4,6}$/.test(code.value)) {
uni.showToast({ title: '请输入正确的验证码', icon: 'none' });
return;
}
// TODO:
uni.showToast({ title: '已提交', icon: 'success' });
};
onUnmounted(() => {
if (timer) clearInterval(timer);
});
</script>
<style lang="scss" scoped>
.change-mobile-page {
min-height: 100vh;
background: #ffffff;
}
.form-wrapper {
padding: 40rpx 30rpx 0; //
}
.input-row {
display: flex;
align-items: center;
background: #ffffff;
border: 2rpx solid #eeeeee;
border-radius: 16rpx;
height: 90rpx;
padding: 0 20rpx;
margin-bottom: 30rpx;
}
.left-icon {
width: 50rpx;
display: flex;
align-items: center;
justify-content: center;
margin-right: 10rpx;
}
.text-input {
flex: 1;
height: 100%;
font-size: 30rpx;
color: #333333;
}
.code-btn {
width: 200rpx;
height: 44rpx;
background-color: #fff;
border-radius: 50rpx;
margin-right: 10px;
display: flex;
font-size: 25rpx;
color:#8B2316;
align-items: center;
justify-content: center;
cursor: pointer;
border: 2rpx solid #8B2316;
transition: background-color 0.2s ease;
}
.code-btn:disabled {
opacity: 0.5;
color:#8B2316;
background-color: #ccc;
}
.bottom-bar {
position: fixed;
left: 0;
right: 0;
bottom: 40rpx;
}
.confirm-btn {
margin: 0 30rpx;
height: 100rpx;
border-radius: 20rpx;
border: 2rpx solid #8B2316;
background: #ffffff;
color: #8B2316;
font-size: 34rpx;
}
</style>

View File

@ -0,0 +1,153 @@
<template>
<view class="group-msg-page">
<!-- 顶部导航栏 -->
<uni-nav-bar
left-icon="left"
title="群发消息"
@clickLeft="goBack"
fixed
color="#8B2316"
height="140rpx"
:border="false"
backgroundColor="#eeeeee"
/>
<!-- 内容区域 -->
<view class="content-area">
<view class="emptybox">
<up-image :src="emptyImg" width="176rpx" height="204rpx" ></up-image>
<text class="empty-text">暂无数据</text>
</view>
</view>
<!-- 底部固定按钮 -->
<view class="bottom-bar">
<button class="send-btn" @click="openModal">群发消息</button>
</view>
<!-- 弹窗与遮罩 -->
<view v-if="showModal" class="mask" @click="closeModal"></view>
<view v-if="showModal" class="center-modal">
<view class="modal-title">温馨提示</view>
<view class="modal-divider"></view>
<view class="modal-item" @click="onSelect('single')">
<text>单独选择</text>
</view>
<view class="modal-divider"></view>
<view class="modal-item" @click="onSelect('group')">
<text>分组选择</text>
</view>
</view>
</view>
</template>
<script setup>
import { ref } from 'vue';
import emptyImg from "@/static/icon_empty.png"
const showModal = ref(true);
const goBack = () => {
uni.navigateBack();
};
const openModal = () => {
showModal.value = true;
};
const closeModal = () => {
showModal.value = false;
};
const onSelect = (type) => {
showModal.value = false;
if (type === 'single') {
uni.showToast({ title: '单独选择', icon: 'none' });
} else if (type === 'group') {
uni.showToast({ title: '分组选择', icon: 'none' });
}
};
</script>
<style lang="scss" scoped>
.group-msg-page {
min-height: 100vh;
background: #f5f5f5;
}
.content-area {
// nav-bar
padding-bottom: 120rpx; //
display: flex;
align-items: center;
justify-content: center;
height: calc(100vh - 280rpx);
}
.empty-text {
font-size: 28rpx;
color: #999999;
}
.bottom-bar {
position: fixed;
left: 0;
right: 0;
bottom: 0;
background: #ffffff;
border-top: 1rpx solid #eaeaea;
padding: 0; //
.send-btn {
width: 100%;
height: 100rpx;
background: #00cac1;
color: #ffffff;
border: none;
border-radius: 0;
font-size: 32rpx;
font-weight: normal;
}
}
/* 遮罩与弹窗 */
.mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0,0,0,0.6);
z-index: 10;
}
.center-modal {
position: fixed;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
width: 650rpx;
background: #ffffff;
border-radius: 16rpx;
z-index: 11;
overflow: hidden;
box-shadow: 0 10rpx 40rpx rgba(0,0,0,0.15);
}
.modal-title {
text-align: center;
font-size: 34rpx;
color: #8B2316;
padding: 28rpx 20rpx;
}
.modal-item {
padding: 36rpx 28rpx;
font-size: 30rpx;
color: #333333;
background: #ffffff;
}
.modal-divider {
height: 2rpx;
background: #eeeeee;
}
</style>

View File

@ -0,0 +1,303 @@
<template>
<view class="idcard-auth-page">
<!-- 顶部导航栏 -->
<uni-nav-bar
left-icon="left"
title="身份验证"
@cviewckLeft="goBack"
fixed
color="#8B2316"
height="140rpx"
:border="false"
backgroundColor="#eeeeee"
>
</uni-nav-bar>
<!-- 内容区域 -->
<view class="content-area">
<!-- 进度指示器 -->
<view class="progress-bar">
<view class="barbox">
<view class="imgbox">
<up-image :src="stepImg" width="46rpx" height="46rpx" ></up-image>
<view class="desc ">身份信息</view>
</view>
<view class="line"></view>
<view class="imgbox">
<up-image :src="stepImg" width="46rpx" height="46rpx" ></up-image>
<view class="desc">添加银行卡</view>
</view>
<view class="line"></view>
<view class="imgbox">
<up-image :src="stepImg" width="46rpx" height="46rpx" ></up-image>
<view class="desc">完成</view>
</view>
</view>
<!-- <view class="progress-step active">
<view class="step-icon">
<uni-icons type="checkmarkempty" color="#ff0000" size="20"></uni-icons>
</view>
<text class="step-text">身份信息</text>
</view>
<view class="progress-line active"></view>
<view class="progress-step">
<view class="step-icon">
<uni-icons type="checkmarkempty" color="#cccccc" size="20"></uni-icons>
</view>
<text class="step-text">添加银行卡</text>
</view>
<view class="progress-line"></view>
<view class="progress-step">
<view class="step-icon">
<uni-icons type="checkmarkempty" color="#cccccc" size="20"></uni-icons>
</view>
<text class="step-text">完成</text>
</view> -->
</view>
<!-- 输入表单 -->
<view class="form-section">
<view class="form-item">
<text class="form-label">姓名</text>
<input
class="form-input"
placeholder="请输入您的姓名"
v-model="formData.name"
placeholder-style="color: #cccccc"
/>
</view>
<view class="form-item">
<text class="form-label">身份证号</text>
<input
class="form-input"
placeholder="请输入您的身份证号"
v-model="formData.idNumber"
placeholder-style="color: #cccccc"
/>
</view>
</view>
<!-- 提示文字 -->
<view class="warning-text">
平台不支持绑定多人银行卡,请务必绑定本人卡,实名认证信息,谨慎填写!
</view>
<!-- 下一步按钮 -->
<view class="bottom-actions">
<button class="next-btn" @click="onNextStep">下一步</button>
</view>
</view>
</view>
</template>
<script setup>
import { ref } from 'vue';
import stepImg from "@/static/add_card_no.png"
import stepActiveImg from "@/static/add_card_yes.png"
const formData = ref({
name: '',
idNumber: ''
});
const goBack = () => {
uni.navigateBack();
};
const onNextStep = () => {
if (!formData.value.name.trim()) {
uni.showToast({ title: '请输入姓名', icon: 'none' });
return;
}
if (!formData.value.idNumber.trim()) {
uni.showToast({ title: '请输入身份证号', icon: 'none' });
return;
}
uni.showToast({ title: '验证通过,跳转下一步', icon: 'success' });
//
};
</script>
<style lang="scss" scoped>
.idcard-auth-page {
min-height: 100vh;
background: #f5f5f5;
}
.content-area {
padding-top: 160rpx;
padding: 0rpx 0rpx 0;
}
.progress-bar {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background: #ffffff;
padding: 80rpx 20rpx 120rpx;
border-radius: 16rpx;
margin-bottom: 30rpx;
.barbox{
display: flex;
align-items: center;
justify-content: center;
.imgbox{
flex:1;
position: relative;
.desc{
position: absolute;
left:-50%;
color:#d0d0d0;
top:80rpx;
font-size: 28rpx;
margin-left: -10rpx;
white-space: nowrap;
transform: translateY(-50%);
&.active{
color:#8B2316;
}
}
}
.imgbox:last-child{
.desc{
margin-left: 15rpx;
}
}
.line{
width:240rpx;
margin:0 -2rpx;
height: 16rpx;
background:#cccccc;
&.active{
background:#8B2316;
}
}
}
.steptext{
margin:0 30rpx;
display: flex;
width:900rpx;
justify-content: space-between;
align-items:center ;
}
.progress-step {
display: flex;
flex-direction: column;
align-items: center;
flex: 1;
.step-icon {
width: 60rpx;
height: 60rpx;
border-radius: 50%;
background: #f0f0f0;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 16rpx;
&.active {
background: #ff0000;
}
}
.step-text {
font-size: 24rpx;
color: #666666;
&.active {
color: #000000;
}
}
&.active {
.step-icon {
background: #ff0000;
}
.step-text {
color: #000000;
}
}
}
.progress-line {
flex: 1;
height: 4rpx;
background: #f0f0f0;
margin: 0 20rpx;
&.active {
background: #ff0000;
}
}
}
.form-section {
background: #ffffff;
border-radius: 16rpx;
padding: 30rpx;
.form-item {
margin-bottom: 30rpx;
display: flex;
align-items: center;
border-bottom: 2rpx solid #eee;
&:last-child {
margin-bottom: 0;
}
.form-label {
display: block;
font-size: 28rpx;
color: #000000;
width:120rpx;
}
.form-input {
flex:1;
height: 80rpx;
padding: 0 20rpx;
font-size: 28rpx;
background: #ffffff;
&:focus {
border-color: #ff0000;
}
}
}
}
.warning-text {
background: #ffffff;
border-radius: 16rpx;
padding: 30rpx;
margin-bottom: 60rpx;
font-size: 26rpx;
color: #000000;
line-height: 1.6;
}
.bottom-actions {
padding: 0 30rox 40rpx;
.next-btn {
margin:0 30rpx;
height: 88rpx;
background: #cccccc;
color: #ffffff;
border: none;
border-radius: 8rpx;
font-size: 32rpx;
&:active {
background: #8B2316;
}
}
}
</style>

350
pages_app/myCode/myCode.vue Normal file
View File

@ -0,0 +1,350 @@
<template>
<view class="my-code-page">
<!-- 顶部导航栏 -->
<uni-nav-bar
left-icon="left"
title="我的二维码"
@cviewckLeft="goBack"
fixed
color="#8B2316"
height="140rpx"
:border="false"
backgroundColor="#eeeeee"
>
<template v-slot:right>
<uni-icons type="redo" color="#8B2316" size="22"></uni-icons>
</template>
</uni-nav-bar>
<!-- 内容 -->
<scroll-view scroll-y class="page-scroll">
<!-- 顶部蓝色横幅 -->
<view class="blue-banner">
<up-image :src="bgImg" width="100%" mode="widthFix" ></up-image>
</view>
<!-- 白色信息卡 -->
<view class="qrbox">
<view class="qr-card">
<view class="leftCircle"></view>
<view class="rightCircle"></view>
<view class="halfCircle"></view>
<view class="avatar-wrapper">
<image class="avatar" :src="avatarImg" mode="aspectFill" />
</view>
<view class="name-viewne">邹建东 主任医师</view>
<view class="org-viewne">北京肝胆相照公益基金会</view>
<view class="dash-viewne"></view>
<view class="slogan">
<text class="h1">不方便到医院就诊</text>
<text >我在<text class="hl">肝胆相照</text>线上帮助您</text>
</view>
<view class="contact-qr">
<view class="contact-btn">
<up-image :src="viewnkImg" width="430rpx" height="131rpx" ></up-image>
</view>
<image class="qr-img" :src="qrImg" mode="aspectFit" />
</view>
</view>
</view>
<view class="squrebox">
<view class="square">
<view class="s-top"></view>
<view class="s-middle">
<view id="head">如何添加我为随访医生</view>
</view>
<view class="s-bottom">
<view>
<view class="descrow"><text>1</text>微信扫一扫上方二维码关注肝胆相照一家人公众号</view>
<view class="descrow"><text>2</text>点击邹建东专家工作室选择微信登录注册后直接发送随访申请</view>
<view class="descrow"><text>3</text>若未弹出随访申请发送成功提示请再次点击邹建东专家工作室发送随访申请</view>
<view class="descrow"><text>4</text>审核通过后点击就医服务-随访交流与专家进行图文交流</view>
</view>
</view>
<view class="s-half-circle"></view>
<view class="s-half-circle-left"></view>
</view>
</view>
</scroll-view>
<!-- 底部保存按钮 -->
<view class="save-bar">
<button class="save-btn" @cviewck="onSave">保存二维码到手机</button>
</view>
</view>
</template>
<script setup>
import { ref } from 'vue';
const avatarImg = '/static/xxtx.png';
const qrImg = '/static/sfewm.png';
import bgImg from "@/static/background.jpg"
import viewnkImg from "@/static/arr.png"
const goBack = () => {
uni.navigateBack();
};
const onSave = () => {
uni.showToast({ title: '已保存(示例)', icon: 'none' });
};
</script>
<style lang="scss" scoped>
.my-code-page {
min-height: 100vh;
background-color: #0d7dfd;
}
.squrebox{
margin:0 30rpx;
overflow: hidden;
}
.square{
background-color: #3492FC;
border-radius: 30rpx;
padding-bottom: 40rpx;
border: 2rpx solid #fff;
position: relative;
.s-half-circle-left{
border: 2rpx solid #fff;
position: absolute;
width: 60rpx;
height: 60rpx;
z-index:1;
border-radius:50%;
top:53rpx;
background-color: #0d7dfd;
left: -30rpx;
}
.s-half-circle{
border: 2rpx solid #fff;
position: absolute;
width: 60rpx;
height: 60rpx;
z-index:1;
border-radius:50%;
top:53rpx;
background-color: #0d7dfd;
right: -30rpx;
}
text{
display: inline-flex;
width: 40rpx;
background-color: #6FB3FE;
height: 40rpx;
margin-right: 10rpx;
border-radius:50%;
align-items: center;
justify-content: center;
}
#head {
width: 75%;
height: 60rpx;
line-height: 60rpx;
color: white;
margin: 0 auto;
text-align: center;
border-radius: 30rpx;
border: 2rpx solid white;
letter-spacing: 8rpx;
}
.s-top{
margin-top: 50rpx;
}
.s-bottom{
padding:0 40rpx;
font-size: 26rpx;
margin-top: 30rpx;
line-height: 40rpx;
color:#fff;
.descrow{
margin-bottom: 10rpx;
}
}
}
.page-scroll {
height: calc(100vh - 100rpx);
padding-bottom: 140rpx;
}
.blue-banner {
height: 520rpx;
width:100%;
color: #ffffff;
text-aviewgn: center;
.banner-title-small {
font-size: 26rpx;
opacity: .9;
}
.banner-title {
margin-top: 40rpx;
font-size: 44rpx;
letter-spacing: 2rpx;
font-weight: 600;
}
}
.qr-card {
position: relative;
margin: -120rpx 30rpx 30rpx;
background: #ffffff;
border-radius: 20rpx;
padding: 140rpx 30rpx 30rpx;
box-shadow: 0 12rpx 30rpx rgba(0,0,0,.08);
.leftCircle{
position: absolute;
top:251rpx;
left:-20rpx;
width: 40rpx;
height: 40rpx;
border-radius: 50%;
background-color: #0d7dfd;
}
.rightCircle{
position: absolute;
top:251rpx;
right:-20rpx;
width: 40rpx;
height: 40rpx;
border-radius: 50%;
background-color: #0d7dfd;
}
.halfCircle{
border-radius: 50%;
position: absolute;
border: 10rpx solid #6DB7FD;
border-width: 10rpx 10rpx 0 10rpx;
width: 160rpx;
padding:10rpx;
height: 80rpx;
left: 50%;
border-radius: 100rpx 100rpx 0 0;
top: -110rpx;
transform: translateX(-50%);
overflow: hidden;
}
.avatar-wrapper {
position: absolute;
left: 50%;
top: -100rpx;
transform: translateX(-50%);
width: 160rpx;
height: 160rpx;
border-radius: 50%;
background: #ffffff;
padding: 8rpx;
box-shadow: 0 6rpx 20rpx rgba(0,0,0,.1);
.avatar {
width: 100%;
height: 100%;
border-radius: 50%;
}
}
.name-viewne {
text-align: center;
font-size: 34rpx;
color: #1a76d2;
margin-top: 10rpx;
}
.org-viewne {
text-align: center;
font-size: 28rpx;
color: #4a90e2;
margin-top: 16rpx;
}
.dash-viewne {
margin: 24rpx auto;
height: 0;
border-bottom: 2rpx dashed #1c90fd;
}
.slogan {
display: flex;
font-weight:bold;
letter-spacing: 8rpx;
flex-direction: column;
text-aviewgn: center;
font-size: 40rpx;
color: #1e88e5;
viewne-height: 1.6;
text{
text-align: center;
}
.hl {
color: #ff9f1a;
margin-left: 8rpx;
}
}
.contact-qr {
display: flex;
aviewgn-items: center;
justify-content: space-between;
gap: 20rpx;
margin-top: 30rpx;
.contact-btn {
flex: 1;
position: relative;
color: #ffffff;
display: flex;
aviewgn-items: center;
justify-content: space-between;
.contact-text {
white-space: pre-viewne;
font-size: 26rpx;
viewne-height: 1.5;
}
.arrow {
font-size: 36rpx;
opacity: .8;
}
}
.qr-img {
width: 220rpx;
height: 220rpx;
border-radius: 12rpx;
border: 2rpx soviewd #e0e0e0;
}
}
}
.bottom-gap { height: 120rpx; }
.save-bar {
position: fixed;
left: 0;
right: 0;
bottom: 0;
z-index:10;
background: #ffffff;
border-top: 1rpx soviewd #eaeaea;
.save-btn {
width: 100%;
height: 100rpx;
background: #00cac1;
color: #ffffff;
border: none;
border-radius: 0;
font-size: 32rpx;
font-weight: normal;
}
}
</style>

View File

@ -0,0 +1,109 @@
<template>
<view class="my-collect-page">
<!-- 顶部导航栏 -->
<!-- 顶部导航栏 -->
<uni-nav-bar
left-icon="left"
title="我的收藏"
@cviewckLeft="goBack"
fixed
color="#8B2316"
height="140rpx"
:border="false"
backgroundColor="#eeeeee"
>
</uni-nav-bar>
<!-- 内容区域 -->
<view class="content-area">
<!-- 观看历史 -->
<view class="list-item" @click="goToHistory">
<text class="item-text">英文期刊</text>
<uni-icons type="right" color="#999999" size="16"></uni-icons>
</view>
<!-- 分隔线 -->
<view class="divider"></view>
<!-- 离线缓存 -->
<view class="list-item" @click="goToCache">
<text class="item-text">病例荟萃</text>
<uni-icons type="right" color="#999999" size="16"></uni-icons>
</view>
<!-- 分隔线 -->
<view class="divider"></view>
<view class="list-item" @click="goToCache">
<text class="item-text">病例讨论</text>
<uni-icons type="right" color="#999999" size="16"></uni-icons>
</view>
<view class="divider"></view>
<view class="list-item" @click="goToCache">
<text class="item-text">肝胆视频</text>
<uni-icons type="right" color="#999999" size="16"></uni-icons>
</view>
<view class="divider"></view>
<view class="list-item" @click="goToCache">
<text class="item-text">课件文档</text>
<uni-icons type="right" color="#999999" size="16"></uni-icons>
</view>
<view class="divider"></view>
<view class="list-item" @click="goToCache">
<text class="item-text">肝胆新闻</text>
<uni-icons type="right" color="#999999" size="16"></uni-icons>
</view>
<view class="divider"></view>
<view class="list-item" @click="goToCache">
<text class="item-text">患教学堂</text>
<uni-icons type="right" color="#999999" size="16"></uni-icons>
</view>
<view class="divider"></view>
</view>
</view>
</template>
<script setup>
import { ref } from 'vue';
const goBack = () => {
uni.navigateBack();
};
const goToHistory = () => {
uni.showToast({ title: '观看历史', icon: 'none' });
};
const goToCache = () => {
uni.showToast({ title: '离线缓存', icon: 'none' });
};
</script>
<style lang="scss" scoped>
.my-collect-page {
min-height: 100vh;
background: #f9f9f9;
}
.content-area {
}
.list-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 30rpx 30rpx;
background: #ffffff;
.item-text {
font-size: 32rpx;
color: #000000;
}
}
.divider {
height: 2rpx;
background: #f0f0f0;
}
</style>

View File

@ -0,0 +1,278 @@
<template>
<view class="my-courseware-page">
<!-- 顶部导航栏 -->
<uni-nav-bar
left-icon="left"
title="我的课件"
@cviewckLeft="goBack"
fixed
color="#8B2316"
height="140rpx"
:border="false"
backgroundColor="#eeeeee"
>
</uni-nav-bar>
<!-- 标签页导航 -->
<view class="tab-nav">
<view
class="tab-item"
:class="{ active: activeTab === 'download' }"
@click="switchTab('download')"
>
我的下载
</view>
<view class="divder"></view>
<view
class="tab-item"
:class="{ active: activeTab === 'share' }"
@click="switchTab('share')"
>
我的分享
</view>
</view>
<!-- 摘要栏 -->
<view class="summary-bar">
<view class="summary-item">
<up-image :src="downLoadImg" width="36rpx" height="36rpx" ></up-image>
<text class="summary-text">: {{ downloadCount }}</text>
</view>
<view class="summary-item">
<up-image :src="moneyImg" width="36rpx" height="36rpx" ></up-image>
<text class="summary-text">: {{ totalAmount }}</text>
</view>
</view>
<!-- 课件列表 -->
<scroll-view
scroll-y
class="courseware-list"
refresher-enabled
@refresherrefresh="onRefresh"
:refresher-triggered="refreshing"
@scrolltolower="onLoadMore"
:lower-threshold="100"
>
<view class="courseware-item" v-for="(item, index) in coursewareList" :key="index" @click="onItemClick(item)">
<view class="item-content">
<view class="courseware-name">
<text class="label">课件名称:</text>
<text class="value">{{ item.name }}</text>
</view>
<view class="courseware-time">
<text class="label">时间:</text>
<text class="value">{{ item.time }}</text>
</view>
<view class="courseware-status">
<text class="label">状态:</text>
<text class="value status-paid">{{ item.status }}</text>
</view>
</view>
</view>
<!-- 加载更多提示 -->
<view v-if="loading" class="loading-more">
<text>加载中...</text>
</view>
<!-- 没有更多数据提示 -->
<view v-if="noMore" class="no-more">
<text>没有更多数据了</text>
</view>
</scroll-view>
</view>
</template>
<script setup>
import { ref } from 'vue';
import downLoadImg from "@/static/course_download.png"
import moneyImg from "@/static/course_yuan.png"
const activeTab = ref('download');
const refreshing = ref(false);
const loading = ref(false);
const noMore = ref(false);
const page = ref(1);
const pageSize = ref(10);
const downloadCount = ref(4);
const totalAmount = ref('20.00');
const coursewareList = ref([
{
name: '慢性病毒性肝炎患者干扰素治疗不良反应临床处理专家共识',
time: '2025-02-21',
status: '已支付'
},
{
name: '慢乙肝抗病毒治疗-把握时机正确选择',
time: '2024-11-27',
status: '已支付'
},
{
name: '肝病相关血小板减少症临床管理中国专家共识(2023)解读',
time: '2024-10-06',
status: '已支付'
},
{
name: '俞云松:耐药阳性菌感染诊疗思路(CHINET数据云)',
time: '2022-10-24',
status: '已支付'
}
]);
const goBack = () => {
uni.navigateBack();
};
const switchTab = (tab) => {
activeTab.value = tab;
//
page.value = 1;
noMore.value = false;
//
};
const onItemClick = (item) => {
uni.showToast({ title: `点击了: ${item.name}`, icon: 'none' });
};
const onRefresh = () => {
refreshing.value = true;
page.value = 1;
noMore.value = false;
setTimeout(() => {
refreshing.value = false;
uni.showToast({ title: '刷新完成', icon: 'success' });
}, 1000);
};
const onLoadMore = () => {
if (loading.value || noMore.value) return;
loading.value = true;
setTimeout(() => {
if (page.value < 3) {
page.value++;
uni.showToast({ title: '加载完成', icon: 'success' });
} else {
noMore.value = true;
}
loading.value = false;
}, 1000);
};
</script>
<style lang="scss" scoped>
.my-courseware-page {
min-height: 100vh;
}
.tab-nav {
display: flex;
background: #ffffff;
border-bottom: 2rpx solid #f0f0f0;
position: fixed;
top: 140rpx;
left: 0;
right: 0;
z-index: 10;
.divder{
width: 2rpx;
height:100rpx;
background:#eee;
}
.tab-item {
flex: 1;
text-align: center;
padding: 30rpx 0;
font-size: 32rpx;
color: #666666;
position: relative;
&.active {
color: #8B2316;
}
}
}
.summary-bar {
display: flex;
justify-content: space-between;
align-items: center;
background: #8B2316;
padding: 20rpx 30rpx;
position: fixed;
top: 260rpx; //
left: 0;
right: 0;
z-index: 10;
.summary-item {
display: flex;
align-items: center;
.summary-text {
color: #ffffff;
font-size: 28rpx;
margin-left: 10rpx;
}
}
}
.courseware-list {
height: calc(100vh - 140rpx - 116rpx - 80rpx);
position: fixed;
top: 336rpx; //
bottom: 0;
width: 100%;
}
.courseware-item {
background: #ffffff;
margin-bottom: 2rpx;
padding: 30rpx;
border-bottom:2rpx solid #eee;
.item-content {
.courseware-name,
.courseware-time,
.courseware-status {
display: flex;
margin-bottom: 20rpx;
&:last-child {
margin-bottom: 0;
}
.label {
width: 120rpx;
color: #8B2316;
font-size: 28rpx;
}
.value {
flex: 1;
color: #666;
font-size: 28rpx;
line-height: 1.4;
&.status-paid {
color: #666;
}
}
}
}
}
.loading-more, .no-more {
text-align: center;
padding: 30rpx;
color: #999999;
font-size: 26rpx;
}
</style>

View File

@ -0,0 +1,90 @@
<template>
<view class="my-download-page">
<!-- 顶部导航栏 -->
<!-- 顶部导航栏 -->
<uni-nav-bar
left-icon="left"
title="我的下载"
@cviewckLeft="goBack"
fixed
color="#8B2316"
height="140rpx"
:border="false"
backgroundColor="#eeeeee"
>
</uni-nav-bar>
<!-- 内容区域 -->
<view class="content-area">
<!-- 观看历史 -->
<view class="list-item" @click="goToHistory">
<text class="item-text">诊疗指南</text>
<uni-icons type="right" color="#999999" size="16"></uni-icons>
</view>
<!-- 分隔线 -->
<view class="divider"></view>
<!-- 离线缓存 -->
<view class="list-item" @click="goToCache">
<text class="item-text">期刊杂志</text>
<uni-icons type="right" color="#999999" size="16"></uni-icons>
</view>
<!-- 分隔线 -->
<view class="divider"></view>
<view class="list-item" @click="goToCache">
<text class="item-text">课件文档</text>
<uni-icons type="right" color="#999999" size="16"></uni-icons>
</view>
<view class="divider"></view>
</view>
</view>
</template>
<script setup>
import { ref } from 'vue';
const goBack = () => {
uni.navigateBack();
};
const goToHistory = () => {
uni.showToast({ title: '观看历史', icon: 'none' });
};
const goToCache = () => {
uni.showToast({ title: '离线缓存', icon: 'none' });
};
</script>
<style lang="scss" scoped>
.my-download-page {
min-height: 100vh;
background: #f9f9f9;
}
.content-area {
}
.list-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 30rpx 30rpx;
background: #ffffff;
.item-text {
font-size: 32rpx;
color: #000000;
}
}
.divider {
height: 2rpx;
background: #f0f0f0;
}
</style>

View File

@ -1,24 +1,14 @@
<template>
<uni-nav-bar
left-icon="left"
title="我的鲜花"
@clickLeft="goBack"
fixed
color="#8B2316"
height="140rpx"
:border="false"
backgroundColor="#eeeeee"
/>
<view class="flower-page">
<!-- 顶部统计栏与截图一致两项 -->
<view class="stats-bar">
<view class="stat">
<text class="icon">🌸</text>
<up-image :src="flowerImg" width="36rpx" height="36rpx" ></up-image>
<text class="num">{{ stat.totalCount }}</text>
</view>
<view class="stat">
<text class="icon"></text>
<up-image :src="moneyImg" width="36rpx" height="36rpx" ></up-image>
<text class="num">{{ stat.totalAmount.toFixed(2) }}</text>
</view>
</view>
@ -42,7 +32,7 @@
lower-threshold="80"
>
<view v-if="records.length === 0" class="empty-wrap">
<image class="empty-icon" src="/static/wdfl.png" mode="aspectFit" />
<up-image :src="emptyImg" width="176rpx" height="204rpx" ></up-image>
<text class="empty-text">您暂未收到鲜花</text>
</view>
@ -60,7 +50,9 @@
<script setup>
import { ref } from 'vue';
import flowerImg from "@/static/flowers.png"
import moneyImg from "@/static/mind_totle_money.png"
import emptyImg from "@/static/icon_empty.png"
//
const stat = ref({ totalCount: 0, totalAmount: 0.0 });
@ -132,13 +124,16 @@ $card: #ffffff;
.stats-bar {
display: flex;
justify-content: space-around;
align-items: center;
padding: 24rpx 0;
padding: 24rpx 30rpx;
background: #fff;
border-bottom: 2rpx solid #eee;
.stat { display: flex; align-items: center; gap: 16rpx; }
.stat:last-child{
margin-left: 120px;
}
.icon { font-size: 34rpx; }
.num { font-size: 30rpx; color: #333; }
}

View File

@ -0,0 +1,482 @@
<template>
<view class="new-patient-page">
<uni-nav-bar
left-icon="left"
title="我的患者"
@clickLeft="goBack"
fixed
color="#8B2316"
height="140rpx"
:border="false"
backgroundColor="#eeeeee"
/>
<!-- 提醒区域 -->
<view class="reminder-section">
<view class="reminder-icon">
<uni-icons type="notification" size="16" color="#ff9500"></uni-icons>
</view>
<text class="reminder-text">提醒: 为了避免不必要的纠纷,请您务必选择线下就诊过的患者</text>
</view>
<!-- 随访申请 -->
<view class="follow-up-section">
<view class="section-title">随访申请</view>
<view class="pending-request" v-if="pendingRequest">
<view class="request-item">
<view class="avatar">
<view class="avatar-icon"></view>
</view>
<view class="request-content">
<view class="request-time">2025-08-18 15:55:03</view>
<view class="request-text">我是陈新华,在线上和您沟通过,请您同意我作为您的随访患者</view>
<view class="action-buttons">
<button class="reject-btn" @click="rejectRequest">拒绝</button>
<button class="agree-btn" @click="agreeRequest">同意</button>
</view>
</view>
</view>
</view>
</view>
<!-- 申请记录 -->
<view class="history-section">
<view class="section-title">申请记录(近一月)</view>
<view class="history-list">
<view class="history-item" v-for="(item, index) in historyList" :key="index">
<view class="avatar">
<view class="avatar-icon"></view>
</view>
<view class="history-content">
<view class="history-time">{{ item.time }}</view>
<view class="nickname">昵称: {{ item.nickname }}</view>
<view class="history-text">{{ item.message }}</view>
<view class="status-info">
<up-image :src="goImg" width="30rpx" height="30rpx" ></up-image>
<text class="status-text">已同意</text>
</view>
</view>
</view>
</view>
</view>
<!-- 底部按钮 -->
<view class="bottom-button">
<button class="add-patient-btn" @click="addPatient">加患者</button>
</view>
</view>
</template>
<script setup>
import { ref } from 'vue';
import goImg from "@/static/go_big.png"
//
const pendingRequest = ref({
name: '陈新华',
message: '我是陈新华,在线上和您沟通过,请您同意我作为您的随访患者',
time: '2025-08-18 15:55:03'
});
const historyList = ref([
{
nickname: '韩夫臣',
message: '我是韩夫臣,在线上和您沟通过,请您...',
time: '2025-08-19 22:41:35'
},
{
nickname: '鲁保山',
message: '我是鲁保山,在线上和您沟通过,请您...',
time: '2025-08-19 22:41:27'
},
{
nickname: '蒋宁宁',
message: '我是蒋宁宁,在线上和您沟通过,请您...',
time: '2025-08-19 11:25:46'
},
{
nickname: '蒋宁',
message: '我是蒋宁,在线上和您沟通过,请您同...',
time: '2025-08-19 11:25:39'
}
]);
//
const goBack = () => {
uni.navigateBack();
};
const rejectRequest = () => {
uni.showModal({
title: '确认拒绝',
content: '确定要拒绝陈新华的随访申请吗?',
success: (res) => {
if (res.confirm) {
uni.showToast({
title: '已拒绝申请',
icon: 'success'
});
// API
}
}
});
};
const agreeRequest = () => {
uni.showModal({
title: '确认同意',
content: '确定要同意陈新华的随访申请吗?',
success: (res) => {
if (res.confirm) {
uni.showToast({
title: '已同意申请',
icon: 'success'
});
// API
}
}
});
};
const addPatient = () => {
uni.showToast({
title: '跳转到添加患者页面',
icon: 'none'
});
//
};
</script>
<style lang="scss" scoped>
.new-patient-page {
min-height: 100vh;
background-color: #f5f5f5;
}
.status-bar {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10rpx 30rpx;
background-color: #ffffff;
font-size: 24rpx;
color: #333;
.status-left {
display: flex;
align-items: center;
gap: 20rpx;
.time {
font-weight: 500;
}
.status-icons {
display: flex;
gap: 10rpx;
.status-icon {
width: 32rpx;
height: 32rpx;
background-color: #ff0000;
color: #ffffff;
border-radius: 6rpx;
display: flex;
align-items: center;
justify-content: center;
font-size: 20rpx;
font-weight: normal;
}
}
}
.status-right {
display: flex;
align-items: center;
gap: 20rpx;
.network {
color: #666;
}
.wifi-icon {
width: 32rpx;
height: 24rpx;
background-color: #333;
border-radius: 2rpx;
}
.battery {
width: 40rpx;
height: 20rpx;
border: 2rpx solid #333;
border-radius: 4rpx;
background-color: #ff0000;
display: flex;
align-items: center;
justify-content: center;
.battery-text {
font-size: 20rpx;
color: #ffffff;
}
}
}
}
.header {
display: flex;
align-items: center;
padding: 20rpx 30rpx;
background-color: #ffffff;
border-bottom: 1rpx solid #f0f0f0;
.back-btn {
padding: 10rpx;
}
.title {
flex: 1;
text-align: center;
font-size: 36rpx;
font-weight: normal;
color: #ff0000;
margin-right: 60rpx; //
}
}
.reminder-section {
display: flex;
align-items: flex-start;
padding: 30rpx;
background-color: #ffffff;
margin-bottom: 20rpx;
.reminder-icon {
margin-right: 20rpx;
margin-top: 4rpx;
}
.reminder-text {
flex: 1;
font-size: 28rpx;
color: #333;
line-height: 1.5;
}
}
.follow-up-section {
background-color: #ffffff;
margin-bottom: 20rpx;
.section-title {
padding: 30rpx 30rpx 20rpx;
font-size: 32rpx;
font-weight: normal;
color: #333;
border-bottom: 1rpx solid #f0f0f0;
}
.pending-request {
padding: 30rpx;
.request-item {
display: flex;
gap: 20rpx;
.avatar {
width: 80rpx;
height: 80rpx;
background-color: #ffb6c1;
border-radius: 12rpx;
display: flex;
align-items: center;
justify-content: center;
.avatar-icon {
width: 40rpx;
height: 40rpx;
background-color: #ffffff;
border-radius: 50%;
}
}
.request-content {
flex: 1;
.request-text {
font-size: 28rpx;
color: #333;
line-height: 1.4;
margin-bottom: 20rpx;
}
.request-time {
font-size: 24rpx;
display: flex;
justify-content: flex-end;
color: #333;
margin-bottom: 10rpx;
}
.action-buttons {
display: flex;
justify-content: center;
gap:60rpx;
.reject-btn {
width:180rpx;
height: 70rpx;
border: 2rpx solid #8B2316;
background-color: #ffffff;
color: #8B2316;
padding:0;
border-radius: 10rpx;
font-size: 28rpx;
display: flex;
align-items: center;
justify-content: center;
margin: 0;
}
.agree-btn {
margin: 0;
width:180rpx;
padding:0;
height: 70rpx;
background-color: #8B2316;
color: #ffffff;
border: none;
border-radius: 10rpx;
font-size: 28rpx;
display: flex;
align-items: center;
justify-content: center;
}
}
}
}
}
}
.history-section {
background-color: #ffffff;
margin-bottom: 120rpx; //
.section-title {
padding: 30rpx 30rpx 20rpx;
font-size: 32rpx;
font-weight: normal;
color: #333;
border-bottom: 1rpx solid #f0f0f0;
}
.history-list {
padding-bottom: 100rpx;
.history-item {
display: flex;
gap: 20rpx;
padding: 30rpx;
border-bottom: 1rpx solid #f0f0f0;
&:last-child {
border-bottom: none;
}
.avatar {
width: 80rpx;
height: 80rpx;
background-color: #ffb6c1;
border-radius: 12rpx;
display: flex;
align-items: center;
justify-content: center;
.avatar-icon {
width: 40rpx;
height: 40rpx;
background-color: #ffffff;
border-radius: 50%;
}
}
.history-content {
flex: 1;
.nickname {
font-size: 28rpx;
font-weight: normal;
color: #8B2316;
margin-bottom: 10rpx;
}
.history-text {
font-size: 28rpx;
color: #333;
line-height: 1.4;
margin-bottom: 20rpx;
}
.status-info {
display: flex;
align-items: center;
justify-content: flex-end;
gap: 10rpx;
margin-bottom: 10rpx;
.status-arrow {
width: 0;
height: 0;
border-left: 8rpx solid transparent;
border-right: 8rpx solid transparent;
border-bottom: 12rpx solid #999;
transform: rotate(45deg);
}
.status-text {
font-size: 30rpx;
color: #999;
}
}
.history-time {
display: flex;
justify-content: flex-end;
font-size: 24rpx;
color: #333;
}
}
}
}
}
.bottom-button {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background-color: #ffffff;
border-top: 1rpx solid #f0f0f0;
.add-patient-btn {
width: 100%;
height: 100rpx;
background-color: #00cac1;
color: #ffffff;
border: none;
border-radius: 0rpx;
font-size: 32rpx;
font-weight: normal;
display: flex;
align-items: center;
justify-content: center;
}
}
</style>

View File

@ -0,0 +1,82 @@
<template>
<view class="my-video-page">
<!-- 顶部导航栏 -->
<!-- 顶部导航栏 -->
<uni-nav-bar
left-icon="left"
title="我的视频"
@cviewckLeft="goBack"
fixed
color="#8B2316"
height="140rpx"
:border="false"
backgroundColor="#eeeeee"
>
</uni-nav-bar>
<!-- 内容区域 -->
<view class="content-area">
<!-- 观看历史 -->
<view class="list-item" @click="goToHistory">
<text class="item-text">观看历史</text>
<uni-icons type="right" color="#999999" size="16"></uni-icons>
</view>
<!-- 分隔线 -->
<view class="divider"></view>
<!-- 离线缓存 -->
<view class="list-item" @click="goToCache">
<text class="item-text">离线缓存</text>
<uni-icons type="right" color="#999999" size="16"></uni-icons>
</view>
</view>
</view>
</template>
<script setup>
import { ref } from 'vue';
const goBack = () => {
uni.navigateBack();
};
const goToHistory = () => {
uni.showToast({ title: '观看历史', icon: 'none' });
};
const goToCache = () => {
uni.showToast({ title: '离线缓存', icon: 'none' });
};
</script>
<style lang="scss" scoped>
.my-video-page {
min-height: 100vh;
background: #f9f9f9;
}
.content-area {
}
.list-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 30rpx 30rpx;
background: #ffffff;
.item-text {
font-size: 32rpx;
color: #000000;
}
}
.divider {
height: 2rpx;
background: #f0f0f0;
}
</style>

View File

@ -0,0 +1,474 @@
<template>
<view class="patient-group-page">
<!-- 头部 -->
<uni-nav-bar
left-icon="left"
title="患者分组"
@clickLeft="goBack"
fixed
color="#8B2316"
height="140rpx"
:border="false"
backgroundColor="#eeeeee"
/>
<!-- 筛选排序栏 -->
<view class="filter-sort-bar">
<view class="sort-section" @click="toggleGroupSort">
<text class="sort-label">分组排序</text>
<view class="imgbox">
<up-image :src="upImg" width="26rpx" height="26rpx" ></up-image>
</view>
</view>
<view class="divider"></view>
<view class="current-sort" @click="toggleInnerSort">
<text class="sort-text">按首字母</text>
<view class="imgbox">
<up-image :src="upImg" width="26rpx" height="26rpx" ></up-image>
</view>
</view>
</view>
<!-- 分组排序弹窗 -->
<view v-if="showGroupSort" class="popup-panel">
<view class="popup-item" :class="{ active: selectedGroupSort==='letter' }" @click.stop="chooseGroupSort('letter')">
<text class="item-text">按首字母</text>
<uni-icons v-if="selectedGroupSort==='letter'" type="checkmarkempty" color="#8B2316" size="22"></uni-icons>
</view>
<view class="popup-divider"></view>
<view class="popup-item" :class="{ active: selectedGroupSort==='count' }" @click.stop="chooseGroupSort('count')">
<text class="item-text">分组人数</text>
<uni-icons v-if="selectedGroupSort==='count'" type="checkmarkempty" color="#8B2316" size="22"></uni-icons>
</view>
</view>
<view v-if="showGroupSort" class="popup-mask" @click="closeGroupSort"></view>
<!-- 组内排序弹窗 -->
<view v-if="showInnerSort" class="popup-panel">
<view class="popup-item" :class="{ active: selectedInnerSort==='letter' }" @click.stop="chooseInnerSort('letter')">
<text class="item-text">按首字母</text>
<uni-icons v-if="selectedInnerSort==='letter'" type="checkmarkempty" color="#8B2316" size="22"></uni-icons>
</view>
<view class="popup-divider"></view>
<view class="popup-item" :class="{ active: selectedInnerSort==='count' }" @click.stop="chooseInnerSort('count')">
<text class="item-text">分组人数</text>
<uni-icons v-if="selectedInnerSort==='count'" type="checkmarkempty" color="#8B2316" size="22"></uni-icons>
</view>
</view>
<view v-if="showInnerSort" class="popup-mask" @click="closeInnerSort"></view>
<!-- 患者列表 -->
<scroll-view class="patient-list-section" scroll-y="true" :style="{ height: scrollViewHeight }">
<view class="groupcell">
<view class="section-title">
<view class="imgbox">
<up-image :src="groupRightImg" width="19rpx" height="32rpx" ></up-image>
</view>
<view class="title">待分组患者 | 5</view>
</view>
<view class="patient-list" >
<view class="patient-item" v-for="(patient, index) in patientList" :key="index">
<view class="patient-avatar">
<up-image :src="patient.avatar" width="80rpx" height="80rpx" mode="aspectFill"></up-image>
</view>
<view class="patient-info">
<view class="patient-name">{{ patient.name }}</view>
<view class="follow-up-time">随访于{{ patient.lastFollowUp }}</view>
</view>
</view>
</view>
</view>
</scroll-view>
</view>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue';
import upImg from "@/static/triangle_green_theme.png"
import downImg from "@/static/triangle_normal.png"
import groupRightImg from "@/static/groupright_big.png"
import groupDownImg from "@/static/groupup_big.png"
//
const patientList = ref([
{
name: 'aa',
lastFollowUp: '2020-12-02',
avatar: '/static/avatar1.png' //
},
{
name: '测试',
lastFollowUp: '2023-10-08',
avatar: '/static/avatar2.png' //
},
{
name: '刘三多',
lastFollowUp: '2021-12-16',
avatar: '/static/avatar3.png' // 绿
},
{
name: '路测试',
lastFollowUp: '2019-10-28',
avatar: '/static/avatar4.png' //
},
{
name: '哦哦哦',
lastFollowUp: '2023-10-08',
avatar: '/static/avatar5.png' //
}
]);
//
const scrollViewHeight = computed(() => {
//
// (140rpx) + (80rpx) + (80rpx) = 300rpx
// px
const systemInfo = uni.getSystemInfoSync();
const windowHeight = systemInfo.windowHeight;
const navHeight = 140 / 2; // rpxpx
const filterHeight = 80 / 2;
const titleHeight = 80 / 2;
const availableHeight = windowHeight - navHeight - filterHeight - titleHeight;
return `${availableHeight}px`;
});
//
const showGroupSort = ref(false);
const selectedGroupSort = ref('letter'); // letter | count
//
const showInnerSort = ref(false);
const selectedInnerSort = ref('letter');
const toggleGroupSort = () => {
showGroupSort.value = !showGroupSort.value;
if (showGroupSort.value) showInnerSort.value = false;
};
const closeGroupSort = () => {
showGroupSort.value = false;
};
const chooseGroupSort = (type) => {
selectedGroupSort.value = type;
closeGroupSort();
};
const toggleInnerSort = () => {
showInnerSort.value = !showInnerSort.value;
if (showInnerSort.value) showGroupSort.value = false;
};
const closeInnerSort = () => {
showInnerSort.value = false;
};
const chooseInnerSort = (type) => {
selectedInnerSort.value = type;
closeInnerSort();
};
//
const goBack = () => {
uni.navigateBack();
};
const createNew = () => {
uni.showToast({
title: '跳转到新建分组页面',
icon: 'none'
});
//
};
</script>
<style lang="scss" scoped>
.patient-group-page {
min-height: 100vh;
background-color: #f5f5f5;
}
/* 弹窗样式 */
.popup-mask {
position: fixed;
top: 220rpx; /* 位于筛选栏下方 */
left: 0;
right: 0;
bottom: 0;
background: rgba(0,0,0,0.3);
z-index: 8;
}
.popup-panel {
position: fixed;
top: 220rpx; /* 紧贴筛选栏 */
left: 0;
right: 0;
background: #fff;
z-index: 10;
box-shadow: 0 6rpx 20rpx rgba(0,0,0,0.08);
}
.popup-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 28rpx 30rpx;
font-size: 30rpx;
color: #333;
}
.popup-divider {
height: 2rpx;
background: #eaeaea;
}
.popup-item.active .item-text {
color: #8B2316;
}
.status-bar {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10rpx 30rpx;
background-color: #ffffff;
font-size: 24rpx;
color: #333;
.status-left {
display: flex;
align-items: center;
gap: 20rpx;
.time {
font-weight: 500;
}
.app-icons {
display: flex;
gap: 10rpx;
.app-icon {
width: 24rpx;
height: 24rpx;
border-radius: 4rpx;
&.weibo {
background-color: #ff8200;
}
&.taobao {
background-color: #ff6a00;
}
&.xiaohongshu {
background-color: #ff2442;
}
}
}
}
.status-right {
display: flex;
align-items: center;
gap: 15rpx;
.network {
color: #666;
}
.signal-icon {
width: 32rpx;
height: 20rpx;
background-color: #333;
border-radius: 2rpx;
}
.battery-text {
font-size: 20rpx;
color: #333;
}
.battery {
width: 40rpx;
height: 20rpx;
border: 2rpx solid #333;
border-radius: 4rpx;
background-color: #4caf50;
position: relative;
&::after {
content: '';
position: absolute;
right: -6rpx;
top: 6rpx;
width: 4rpx;
height: 8rpx;
background-color: #333;
border-radius: 0 2rpx 2rpx 0;
}
}
}
}
.header {
display: flex;
align-items: center;
padding: 20rpx 30rpx;
background-color: #ffffff;
border-bottom: 1rpx solid #f0f0f0;
.back-btn {
padding: 10rpx;
}
.title {
flex: 1;
text-align: center;
font-size: 36rpx;
font-weight: normal;
color: #ff0000;
margin-right: 60rpx; //
}
.new-btn {
padding: 10rpx;
.new-text {
font-size: 28rpx;
color: #ff0000;
}
}
}
.filter-sort-bar {
display: flex;
align-items: center;
justify-content:center;
padding: 20rpx 30rpx;
background-color: #ffff;
position: fixed;
top: 140rpx;
left: 0;
right: 0;
z-index: 9;
border-bottom: 1rpx solid #f0f0f0;
.imgbox{
margin-top: -10rpx;
}
.sort-section {
display: flex;
align-items: center;
gap: 10rpx;
.sort-label {
font-size: 28rpx;
color: #333;
}
.sort-icon.down {
width: 0;
height: 0;
border-left: 8rpx solid transparent;
border-right: 8rpx solid transparent;
border-top: 12rpx solid #999;
}
}
.divider {
width: 2rpx;
height: 40rpx;
background-color: #e0e0e0;
margin: 0 80rpx;
}
.current-sort {
display: flex;
align-items: center;
gap: 10rpx;
.sort-text {
font-size: 28rpx;
color: #333;
}
.sort-icon.up {
width: 0;
height: 0;
border-left: 8rpx solid transparent;
border-right: 8rpx solid transparent;
border-bottom: 12rpx solid #999;
&.active {
border-bottom-color: #ff0000;
}
}
}
}
.patient-list-section {
top: 240rpx;
width:100%;
bottom:0;
position: fixed;
background-color: #ffffff;
.section-title {
padding: 30rpx 30rpx 20rpx;
font-size: 32rpx;
font-weight: normal;
display: flex;
color: #333;
border-bottom: 1rpx solid #f0f0f0;
.imgbox{
margin-top: 4rpx;
margin-right: 8rpx;
}
}
.patient-list {
//
&::-webkit-scrollbar {
display: none;
}
.patient-item {
display: flex;
align-items: center;
gap: 20rpx;
padding: 30rpx;
border-bottom: 1rpx solid #f0f0f0;
&:last-child {
border-bottom: none;
}
.patient-avatar {
width: 80rpx;
height: 80rpx;
border-radius: 12rpx;
overflow: hidden;
}
.patient-info {
flex: 1;
.patient-name {
font-size: 32rpx;
color: #333;
margin-top: 24rpx;
}
.follow-up-time {
font-size: 24rpx;
color: #999;
display: flex;
justify-content: flex-end;
}
}
}
}
}
</style>

View File

@ -21,7 +21,12 @@
<scroll-view class="content-scroll" scroll-y :show-scrollbar="false" @scrolltolower="onScrollToLower" lower-threshold="60">
<!-- 视频区域 -->
<view class="player-wrapper">
<video class="player" :src="videoSrc" :poster="poster" controls objectFit="contain"></video>
<!-- #ifdef APP -->
<uniVideo></uniVideo>
<!-- #endif -->
<!-- #ifdef MP-WEIXIN -->
<video class="player" :src="videoSrc" :poster="poster" controls objectFit="contain"></video>
<!-- #endif -->
</view>
<!-- 标签切换 -->
@ -70,7 +75,7 @@
<script setup>
import { ref } from 'vue';
import uniVideo from '@/components/uniVideo/uniVideo.vue';
const videoSrc = ref('');
const poster = ref('/static/livebg.png');
const activeTab = ref('info');

View File

@ -0,0 +1,214 @@
<template>
<view class="video-history-page">
<!-- 顶部导航栏 -->
<uni-nav-bar
left-icon="left"
title="观看历史"
@clickLeft="goBack"
fixed
color="#8B2316"
height="140rpx"
:border="false"
backgroundColor="#eeeeee"
>
<template v-slot:right>
<text class="edit-btn" @click="onEdit">编辑</text>
</template>
</uni-nav-bar>
<!-- 观看历史列表 -->
<scroll-view
scroll-y
class="history-list"
refresher-enabled
@refresherrefresh="onRefresh"
:refresher-triggered="refreshing"
@scrolltolower="onLoadMore"
:lower-threshold="100"
>
<view class="history-item" v-for="(item, index) in historyList" :key="index" @click="onItemClick(item)">
<view class="thumbnail">
<up-image :src="item.thumbnail" width="200rpx" height="120rpx" mode="aspectFill"></up-image>
</view>
<view class="content">
<view class="title">{{ item.title }}</view>
<view class="author">{{ item.author }}</view>
</view>
</view>
<!-- 加载更多提示 -->
<view v-if="loading" class="loading-more">
<text>加载中...</text>
</view>
<!-- 没有更多数据提示 -->
<view v-if="noMore" class="no-more">
<text>没有更多数据了</text>
</view>
</scroll-view>
</view>
</template>
<script setup>
import { ref } from 'vue';
const refreshing = ref(false);
const loading = ref(false);
const noMore = ref(false);
const page = ref(1);
const pageSize = ref(10);
const historyList = ref([
{
thumbnail: '/static/bo_bg.png',
title: '隐球菌性脑膜(脑)炎的诊治',
author: '蒋荣猛'
},
{
thumbnail: '/static/bo_bg.png',
title: '南京市第二医院疑难肝病病理读片会暨疑难肝病MDT 第135期',
author: '南京市第二医院'
},
{
thumbnail: '/static/bo_bg.png',
title: '徐医感染: 硬化出血发热路, 关关难过关关过',
author: '徐州医科大学附属医院感染科'
},
{
thumbnail: '/static/bo_bg.png',
title: '自身免疫性肝病专栏|免疫治疗的双刃剑——1例自身免疫肝炎患者的…',
author: '首都医科大学附属北京佑安医院…'
},
{
thumbnail: '/static/bo_bg.png',
title: '慢性乙肝功能性治愈讨论',
author: '庄辉'
},
{
thumbnail: '/static/bo_bg.png',
title: '《2025年版慢加急性肝衰竭指南》解读',
author: '陈煜'
},
{
thumbnail: '/static/bo_bg.png',
title: '《2025年版慢加急性肝衰竭指南》解读',
author: '陈煜'
},
{
thumbnail: '/static/bo_bg.png',
title: '慢性乙肝功能性治愈讨论',
author: '庄辉'
}
]);
const goBack = () => {
uni.navigateBack();
};
const onEdit = () => {
uni.showToast({ title: '编辑功能', icon: 'none' });
};
const onItemClick = (item) => {
uni.showToast({ title: `点击了: ${item.title}`, icon: 'none' });
};
const onRefresh = () => {
refreshing.value = true;
page.value = 1;
noMore.value = false;
//
setTimeout(() => {
refreshing.value = false;
uni.showToast({ title: '刷新完成', icon: 'success' });
}, 1000);
};
const onLoadMore = () => {
if (loading.value || noMore.value) return;
loading.value = true;
//
setTimeout(() => {
// API
//
if (page.value < 3) { // 3
page.value++;
// historyList
uni.showToast({ title: '加载完成', icon: 'success' });
} else {
noMore.value = true;
}
loading.value = false;
}, 1000);
};
</script>
<style lang="scss" scoped>
.video-history-page {
min-height: 100vh;
background: #f9f9f9;
}
.history-list {
height: calc(100vh - 140rpx);
position: fixed;
top:140rpx;
bottom:0;
width:100%;
}
.edit-btn {
color: #8B2316;
font-size: 28rpx;
padding: 10rpx;
}
.history-item {
display: flex;
align-items: center;
padding: 30rpx;
background: #ffffff;
margin-bottom: 2rpx;
.thumbnail {
margin-right: 20rpx;
border-radius: 8rpx;
overflow: hidden;
}
.content {
flex: 1;
display: flex;
height: 120rpx;
flex-direction: column;
justify-content: space-between;
margin-right: 20rpx;
.title {
font-size: 28rpx;
color: #333333;
line-height: 1.4;
margin-bottom: 10rpx;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
}
.author {
font-size: 24rpx;
color: #666666;
}
}
}
.loading-more, .no-more {
text-align: center;
padding: 30rpx;
color: #999999;
font-size: 26rpx;
}
</style>

View File

@ -0,0 +1,96 @@
<template>
<view class="wechat-contact-page">
<!-- 顶部导航栏 -->
<uni-nav-bar
left-icon="left"
title="微信关联"
@clickLeft="goBack"
fixed
color="#8B2316"
height="140rpx"
:border="false"
backgroundColor="#eeeeee"
></uni-nav-bar>
<!-- 微信卡片 -->
<view class="card">
<view class="wx-left">
<up-image :src="wxIcon" width="120rpx" height="120rpx" mode="aspectFill"></up-image>
</view>
<view class="wx-center">
<view class="wx-title">微信</view>
<view class="wx-sub">未绑定微信</view>
</view>
<view class="wx-right">
<button class="bind-btn" @click="onBind">绑定</button>
</view>
</view>
<!-- 操作说明 -->
<view class="tips">
<view class="tips-title">操作说明:</view>
<view class="tips-line">1. 肝胆相照注册账号与微信绑定肝胆相照相关直播视频无忧随心看</view>
<view class="tips-line">2. 仅需操作一次后续通过微信观看直播视频无需额外操作立即进入</view>
<view class="tips-line">
若您有任何疑问或需要我们协助请与您的小助手联系或直接微信联系
<text class="highlight">igandan1000</text>
</view>
</view>
</view>
</template>
<script setup>
import { ref } from 'vue';
import wxIcon from '@/static/wechat_icon.png';
const goBack = () => {
uni.navigateBack();
};
const onBind = () => {
uni.showToast({ title: '去绑定', icon: 'none' });
};
</script>
<style lang="scss" scoped>
.wechat-contact-page {
min-height: 100vh;
background: #ffffff;
padding-top: 160rpx;
}
.card {
display: flex;
align-items: center;
background: #ffffff;
margin: 20rpx 0;
padding: 30rpx;
border-bottom: 2rpx solid #f0f0f0;
.wx-left { margin-right: 24rpx; }
.wx-center {
flex: 1;
.wx-title { font-size: 34rpx; color: #222222; }
.wx-sub { margin-top: 10rpx; font-size: 26rpx; color: #9e9e9e; }
}
.wx-right {
.bind-btn {
min-width: 140rpx;
height: 64rpx;
background: #2ecc71;
color: #ffffff;
border: none;
border-radius: 12rpx;
font-size: 28rpx;
padding: 0 24rpx;
}
}
}
.tips {
padding: 30rpx;
background: #f7f7f7;
.tips-title { font-size: 30rpx; color: #222222; margin-bottom: 16rpx; }
.tips-line { font-size: 28rpx; color: #666666; line-height: 1.8; margin-bottom: 10rpx; }
.highlight { color: #e53935; }
}
</style>

BIN
static/add_card_no.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

BIN
static/add_card_yes.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

BIN
static/arr.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
static/background.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

BIN
static/course_download.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
static/course_yuan.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
static/fapaio_tip.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
static/flowers.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
static/go_big.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 775 B

BIN
static/groupright_big.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 346 B

BIN
static/groupup_big.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 411 B

BIN
static/mind_totle_money.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

BIN
static/skin/back.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 999 B

BIN
static/skin/forward.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 962 B

BIN
static/skin/lock.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 948 B

BIN
static/skin/skinLock.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 780 B

BIN
static/skin/unlock.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 826 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -1,10 +1,12 @@
let client_type=''
// #ifdef MP-WEIXIN
const client_type="M"
client_type="M"
// #endif
// #ifdef APP-IOS
const client_type="I"
client_type="I"
// #endif
// #ifdef APP-ANDROID
const client_type="A"
client_type="A"
// #endif
export default client_type