feat: 初始化项目配置和样式- 添加项目环境变量配置文件 (.env)

- 创建代码忽略文件列表 (.gitignore)
- 设置代码格式化规则 (.prettierrc)- 添加各种样式文件,包括背景、边框、按钮、卡片等组件样式
- 创建暗黑主题样式文件
- 添加表单和代码样式文件
This commit is contained in:
liguigong
2025-09-12 09:53:45 +08:00
commit b91b273686
582 changed files with 90552 additions and 0 deletions
+213
View File
@@ -0,0 +1,213 @@
<!-- eslint-disable -->
<template>
<view
v-html="videoHtml"
id="dom-video"
class="dom-video"
:eventDrive="eventDrive"
:change:eventDrive="domVideo.eventHandle"
:videoSrc="videoSrc"
:change:videoSrc="domVideo.srcChange"
:videoProps="videoProps"
:change:videoProps="domVideo.propsChange"
:randomNum="randomNum"
:change:randomNum="domVideo.randomNumChange"
/>
</template>
<script>
export default {
props: {
src: {
type: String,
default: '',
},
autoplay: {
type: Boolean,
default: false,
},
loop: {
type: Boolean,
default: false,
},
controls: {
type: Boolean,
default: false,
},
objectFit: {
type: String,
default: 'contain',
},
muted: {
type: Boolean,
default: false,
},
poster: {
type: String,
default: '',
},
},
// 数据状态
data() {
return {
videoHtml: '',
videoSrc: '',
eventDrive: null,
videoProps: {},
randomNum: Math.floor(Math.random() * 100000000 + 1),
};
},
watch: {
// 监听视频资源文件更新
src: {
handler(val) {
if (!val) return;
this.initVideoHtml();
setTimeout(() => {
this.videoSrc = val;
}, 0);
},
immediate: true,
},
// 监听首次加载
autoplay: {
handler(val) {
this.videoProps.autoplay = val;
},
immediate: true,
},
},
// 生命周期
mounted() {
this.initVideoHtml();
},
// 方法
methods: {
// 将video的事件传递给父组件
videoEvent(data) {
// console.log('向父组件传递事件 =>', data)
this.$emit(data);
},
// 初始化视频
initVideoHtml() {
this.videoHtml = `<video
src="${this.src}"
id="dom-html-video_${this.randomNum}"
class="dom-html-video"
${this.autoplay ? 'autoplay' : ''}
${this.loop ? 'loop' : ''}
${this.controls ? 'controls' : ''}
${this.muted ? 'muted' : ''}
${this.poster ? 'poster="' + this.poster + '"' : ''}
preload="auto"
playsinline
webkit-playsinline
width="100%"
height="100%"
style="object-fit: ${this.objectFit};padding:0;"
>
<source src="${this.src}" type="video/mp4">
<source src="${this.src}" type="video/ogg">
<source src="${this.src}" type="video/webm">
</video>
`;
// console.log('视频html =>', this.videoHtml)
},
resetEventDrive() {
this.eventDrive = null;
},
// 将service层的事件/数据 => 传递给renderjs层
play() {
this.eventDrive = 'play';
},
pause() {
this.eventDrive = 'pause';
},
stop() {
this.eventDrive = 'stop';
},
},
};
</script>
<script module="domVideo" lang="renderjs">
export default {
data() {
return {
video: null,
num: '',
options: {}
}
},
mounted() {
this.initVideoEvent()
},
methods: {
initVideoEvent() {
setTimeout(() => {
let video = document.getElementById(`dom-html-video_${this.num}`)
this.video = video
// 监听视频事件
video.addEventListener('play', () => {
this.$ownerInstance.callMethod('videoEvent', 'play')
})
video.addEventListener('pause', () => {
this.$ownerInstance.callMethod('videoEvent', 'pause')
})
video.addEventListener('ended', () => {
this.$ownerInstance.callMethod('videoEvent', 'ended')
this.$ownerInstance.callMethod('resetEventDrive')
})
}, 100)
},
eventHandle(eventType) {
if (eventType) {
this.video = document.getElementById(`dom-html-video_${this.num}`)
if (eventType === 'play') {
this.video.play()
} else if (eventType === 'pause') {
this.video.pause()
} else if (eventType === 'stop') {
this.video.stop()
}
}
},
srcChange(val) {
// 实现视频的第一帧作为封面,避免视频展示黑屏
this.initVideoEvent()
setTimeout(() => {
let video = document.getElementById(`dom-html-video_${this.num}`)
video.addEventListener('loadedmetadata', () => {
let { autoplay } = this.options
video.play()
if (!autoplay) {
video.pause()
}
})
}, 0)
},
propsChange(obj) {
this.options = obj
},
randomNumChange(val) {
this.num = val
},
}
}
</script>
<style lang="scss" scoped>
.dom-video {
overflow: hidden;
height: 100%;
padding: 0;
&-height {
height: 100%;
}
}
</style>
+227
View File
@@ -0,0 +1,227 @@
<template>
<view class="ui-video-wrap">
<!-- #ifndef APP-PLUS -->
<video
:id="`sVideo${uid}`"
class="radius"
:style="[{ height: height + 'rpx' }]"
:src="src"
controls
object-fit="contain"
:enable-progress-gesture="state.enableProgressGesture"
:initial-time="initialTime"
x5-video-player-type="h5"
x-webkit-airplay="allow"
webkit-playsinline="true"
@error="videoErrorCallback"
@timeupdate="timeupdate"
@play="play"
@pause="pause"
@ended="end"
:poster="poster"
>
</video>
<!-- #endif -->
<!-- #ifdef APP-PLUS -->
<dom-video
ref="domVideo"
:id="`sVideo${uid}`"
class="radius"
:style="[{ height: height + 'rpx' }]"
object-fit="contain"
:controls="true"
:show-progress="false"
:show-fullscreen-btn="false"
:show-play-btn="false"
:show-bottom-progress="false"
:autoplay="false"
:src="src"
@play="play"
@pause="pause"
@ended="end"
:poster="poster"
/>
<!-- #endif -->
<!-- <view
v-show="!state.isplay"
class="poster-wrap radius"
:style="{ height: height + 'px' }"
@click="beforePlay"
>
<image class="poster-image" mode="aspectFill" :src="poster" v-if="poster" />
<view class="play-icon ss-flex ss-row-center ss-col-center">
<text class="cicon-play-arrow ss-font-40"></text>
</view>
</view> -->
</view>
</template>
<script setup>
/**
* 视频组件
*
* @property {Number} uid = 0 - 当前轮播下标,还用来标记视频Id
* @property {Number} moveX = 0 - app端轮播滑动距离
* @property {String} height = 300 - 高度(rpx)
* @property {String} width = 750 - 宽度(rpx)
* @property {Number} initialTime = 0 - 指定视频播放位置
* @property {String} videoSize - 视频大小
* @property {String} src - 视频播放地址
* @property {String} poster - 视频封面
*
*
*/
import { reactive, nextTick, getCurrentInstance } from 'vue';
import DomVideo from './components/dom-video.vue';
import sheep from '@/sheep';
const vm = getCurrentInstance();
// 数据
const state = reactive({
// #ifdef APP-PLUS
enableProgressGesture: true, // 手势滑动
// #endif
// #ifndef APP-PLUS
enableProgressGesture: false, // 手势滑动
// #endif
showModal: false, // 弹框
});
// 接收参数
const props = defineProps({
moveX: {
type: [Number],
default: 0,
},
// 下标索引
uid: {
type: [Number, String],
default: 0,
},
// 视频高度
height: {
type: Number,
default: 300,
},
// 视频宽度
width: {
type: Number,
default: 750,
},
// 指定视频初始播放位置,单位为秒(s)
initialTime: {
type: Number,
default: 0,
},
src: {
type: String,
default: '',
},
poster: {
type: String,
default: 'https://img1.baidu.com/it/u=1601695551,235775011&fm=26&fmt=auto',
},
});
// 事件
const emits = defineEmits(['videoTimeupdate']);
// 播放进度变化时触发,播放进度传给父组件
const timeupdate = (e) => {
emits('videoTimeupdate', e);
};
const videoErrorCallback = (e) => {
console.log('视频错误信息:', e.target.errMsg);
};
// 当开始/继续播放时触发play事件
const play = () => {
console.log('视频开始');
};
// 当暂停播放时触发 pause 事件
const pause = () => {
console.log('视频暂停');
};
// 视频结束触发end 时间
const end = () => {
console.log('视频结束');
};
// 开始播放
const startPlay = () => {
nextTick(() => {
const video = uni.createVideoContext(`sVideo${props.index}`, vm);
video.play();
});
};
//暂停播放
const pausePlay = () => {
const video = uni.createVideoContext(`sVideo${props.index}`, vm);
video.pause();
};
// 播放前拦截
const beforePlay = () => {
uni.getNetworkType({
success: (res) => {
console.log(res.networkType, 'res.networkType');
const networkType = res.networkType;
// if (networkType === 'wifi' || networkType === 'ethernet') {
// startPlay();
// } else {
// uni.showModal({
// title: '提示',
// content: `当前为移动网络,播放视频需消耗手机流量,是否继续播放?${networkType}`,
// success: (res) => {
// if (res.confirm) {
// startPlay();
// } else {
// state.isplay = false;
// }
// },
// });
// sheep.$helper.toast('正在消耗流量播放');
// startPlay();
// }
startPlay();
},
});
};
// 抛出方法供父组件调用
defineExpose({
pausePlay,
});
</script>
<style lang="scss" scoped>
.radius {
width: 100%;
}
.ui-video-wrap {
display: flex;
align-items: center;
justify-content: center;
.poster-wrap {
position: relative;
width: 100%;
height: 100%;
.poster-image {
width: 100%;
height: 100%;
}
.play-icon {
position: absolute;
left: 50%;
top: 50%;
width: 80rpx;
height: 80rpx;
transform: translate(-50%, -50%);
background-color: rgba($color: #000000, $alpha: 0.1);
border-radius: 50%;
}
}
}
</style>