diff --git a/src/pages/me/index.vue b/src/pages/me/index.vue index e3bf2b8..c4f0b23 100644 --- a/src/pages/me/index.vue +++ b/src/pages/me/index.vue @@ -81,11 +81,16 @@ <img src="/static/image/mine/wdtj.png" style="width: 24px;height: 24px;"> </template> </u-cell> - <u-cell size="large" title="我的题库" :value="carName" isLink @tap="toChangeCarType"> + <u-cell size="large" title="我的题库" :value="carName" @tap="toChangeCarType"> <template #icon> <img src="/static/image/mine/wdtk.png" style="width: 24px;height: 24px;"> </template> </u-cell> + <u-cell size="large" title="联系我们" @tap="callPhoneNumber"> + <template #icon> + <img src="/static/image/mine/callme.png" style="width: 24px;height: 24px;"> + </template> + </u-cell> </u-cell-group> </view> <view v-if="isLogin" class="flex ai-c jc-c mt12 br8 bc-fff" style="height: 50px;" @tap="handleLogout"> @@ -146,6 +151,12 @@ export default { this.carName = storage.get('carName') || '小车C1/C2/C3' }, methods: { + // 拨打电话 + callPhoneNumber() { + uni.makePhoneCall({ + phoneNumber: '15105693067' + }); + }, toChangeCarType() { uni.navigateTo({ url: "/pages/me/changeCarType" diff --git a/src/pages/questionBank/components/Question.vue b/src/pages/questionBank/components/Question.vue index 08331a4..4777f26 100644 --- a/src/pages/questionBank/components/Question.vue +++ b/src/pages/questionBank/components/Question.vue @@ -7,9 +7,8 @@ :class="tCurrent==item.value?'checked':'unchecked'" @tap="sectionChange(item.value)">{{item.label}}</view> </view> </view> - <swiper class="swiper mt20" :current="swiperIndex" :duration="duration" :autoplay="false" - @change="onChange" @animationfinish="onAnimationfinish" - @touchend="touchEnd"> + <swiper class="swiper mt20" :current="swiperIndex" :duration="duration" :autoplay="false" @change="onChange" + @animationfinish="onAnimationfinish" @touchend="touchEnd"> <swiper-item v-for="(quesItem,quesIndex) in swiperList" :key="quesIndex.questionId"> <scroll-view scroll-y="true" class="swiper-scroll"> <view> @@ -17,8 +16,10 @@ <text class="tag_box">{{getQuestType(quesItem.type)}}</text> <text class="fs18" style="line-height: 42rpx;vertical-align: middle;">{{quesItem.question}}</text> </view> - <view class="p14" v-if="quesItem.imageUrl"> - <image v-show="quesItem.imageUrl" style="width: 100%;height: auto;" mode="widthFix" :lazy-load="true" @load="onoff='1'" :src="quesItem.imageUrl"></image> + <view class="p14 flex jc-c ai-c" v-if="quesItem.imageUrl"> + <image v-show="quesItem.imageUrl" style="width: auto;max-height:40vh;" mode="heightFix" + :lazy-load="true" @load="onoff='1'" :src="quesItem.imageUrl" @click="preview(quesItem.imageUrl)"> + </image> </view> <template v-if="quesItem.type!='3'"> <view class="flex m14lr ai-c mt20" v-for="(item,index) in quesItem.optionList" :key="item.op" @@ -41,7 +42,8 @@ v-if="quesItem.clickAnswer&&!quesItem.trueAnswer.includes(quesItem.clickAnswer) || showBestAnswer"> <view class="answer_box"> <text class="fs18 fw600 cor-000">答案:{{getRightOp(quesItem.trueAnswer)}}</text> - <view v-if="showSkillInfo==='show'&&quesItem.skillInfo" class="fs18 cor-000 mt5">答题技巧:{{quesItem.skillInfo}}</view> + <view v-if="showSkillInfo==='show'&&quesItem.skillInfo" class="fs18 cor-000 mt5"> + 答题技巧:{{quesItem.skillInfo}}</view> </view> <view class="flex ai-c jc-c mt10"> <view style="height: 6rpx;width: 120rpx;background-color: rgb(232, 232, 232);"></view> @@ -50,7 +52,10 @@ </view> <view class="mt10"> <view class="fw600 cor-000 mb10 flex ai-c"> - <view style="background: linear-gradient(90deg, #11DF20 0%, #00B74F 100%);height: 36rpx;width: 8rpx;" class="mr5"></view>题目解析</view> + <view + style="background: linear-gradient(90deg, #11DF20 0%, #00B74F 100%);height: 36rpx;width: 8rpx;" + class="mr5"></view>题目解析 + </view> <view style="text-indent:2em;">{{quesItem.bestAnswer}}</view> </view> </view> @@ -92,24 +97,29 @@ <view class="m14lr mt30" v-if="isShowAnswer"> <view class="answer_box"> <text class="fs18 fw600 cor-000">答案:{{getRightOp(quesItem.trueAnswer)}}</text> - <view v-if="showSkillInfo==='show'&&quesItem.skillInfo" class="fs18 cor-000"> 答题技巧:{{quesItem.skillInfo}}</view> - </view> - <view class="flex ai-c jc-c mt10"> - <view style="height: 6rpx;width: 120rpx;background-color: rgb(232, 232, 232);"></view> - <view class="fs18 fw600 cor-000 p15lr">试题详解</view> - <view style="height: 6rpx;width: 120rpx;background-color: rgb(232, 232, 232);"></view> - </view> - <view class="mt10"> - <view class="fw600 cor-000 mb10 flex ai-c"> - <view style="background: linear-gradient(90deg, #11DF20 0%, #00B74F 100%);height: 36rpx;width: 8rpx;" class="mr5"></view>题目解析</view> - <view style="text-indent:2em;">{{quesItem.bestAnswer}}</view> + <view v-if="showSkillInfo==='show'&&quesItem.skillInfo" class="fs18 cor-000"> + 答题技巧:{{quesItem.skillInfo}}</view> + </view> + <view class="flex ai-c jc-c mt10"> + <view style="height: 6rpx;width: 120rpx;background-color: rgb(232, 232, 232);"></view> + <view class="fs18 fw600 cor-000 p15lr">试题详解</view> + <view style="height: 6rpx;width: 120rpx;background-color: rgb(232, 232, 232);"></view> + </view> + <view class="mt10"> + <view class="fw600 cor-000 mb10 flex ai-c"> + <view + style="background: linear-gradient(90deg, #11DF20 0%, #00B74F 100%);height: 36rpx;width: 8rpx;" + class="mr5"></view>题目解析 </view> + <view style="text-indent:2em;">{{quesItem.bestAnswer}}</view> + </view> </view> </template> </view> </scroll-view> </swiper-item> </swiper> + <q-previewImage ref="previewRef" :urls="imgs" @open="open"></q-previewImage> <view class="wp100 flex jc-sb ai-c p14 bc-fff" v-if="isShowAll" style="position: fixed;bottom: 0;left: 0;"> <view style="width: 220rpx;"> <view v-if="type==='practice'" style="width: 220rpx;height: 80rpx;"></view> @@ -232,8 +242,8 @@ </view> </view> <view class="flex ai-c jc-fs p14" style="flex-wrap: wrap;max-height: 400px;overflow-y: scroll;"> - <view v-for="(item,index) of questionList" :key="item.questionId" style="width:20%;position: relative;" class="flex ai-c jc-c" - @tap="chooseQueston(index)"> + <view v-for="(item,index) of questionList" :key="item.questionId" style="width:20%;position: relative;" + class="flex ai-c jc-c" @tap="chooseQueston(index)"> <view class="tCircle mb10" :class="{ 'active':index == topicIndex, 'success':type=='exam'?rightList.includes(item.questionId):storageRightList.includes(item.questionId), @@ -241,8 +251,8 @@ }"> {{index+1}} </view> - <u-icon name="star-fill" v-if="collectList.includes(item.questionId)" style="position: absolute;right: 5px;top:-3px" - color="rgb(249,236,141)" size="24"></u-icon> + <u-icon name="star-fill" v-if="collectList.includes(item.questionId)" + style="position: absolute;right: 5px;top:-3px" color="rgb(249,236,141)" size="24"></u-icon> </view> </view> </view> @@ -317,10 +327,11 @@ }, data() { return { - subject:'1', - showSkillInfo:'hidden', - currentType:storage.get('carType') || '1001', - onoff:'0', + imgs: [], + subject: '1', + showSkillInfo: 'hidden', + currentType: storage.get('carType') || '1001', + onoff: '0', navTitle: '', originArray: '', showBestAnswer: false, @@ -333,7 +344,7 @@ tCurrent: 0, index: 0, qIndex: 0, - storageRightList:storage.get(`rightList_subject${this.subject}`) || [], + storageRightList: storage.get(`rightList_subject${this.subject}`) || [], storageWrongList: storage.get(`wrongList_subject${this.subject}`) || [], rightList: [], wrongList: [], @@ -348,9 +359,9 @@ } }, created() { - const carType=storage.get('carType') || '1001' - querySysConfig(carType, 'NeedSkillInfo').then(resp=>{ - if(resp.code === '0000'){ + const carType = storage.get('carType') || '1001' + querySysConfig(carType, 'NeedSkillInfo').then(resp => { + if (resp.code === '0000') { this.showSkillInfo = resp.data.configValue } }) @@ -377,12 +388,27 @@ }, timeCount() { - const time = 45 * 60 * 1000 + const time = 45 * 60 * 1000 return time } }, methods: { ...mapActions(useQuestionStore, ['getCurrentIndex']), + open() { + + }, + preview(url) { + this.imgs = [url] //设置图片数组 + // #ifdef MP-WEIXIN + this.$nextTick(() => { + this.imgs = [url] + setTimeout(()=>{ + this.$refs.previewRef.open(url); + },500) + }) + // #endif + + }, getOriginArr(val) { const arr = JSON.parse(val) let arr1 = [] @@ -536,11 +562,11 @@ toSubmit() { const restTime = this.time.hours * 60 * 60 + this.time.minutes * 60 + this.time.seconds const score = (this.rightList.length / this.questionList.length * 100).toFixed(0) - if(this.rightList.length+this.wrongList.length==0){ + if (this.rightList.length + this.wrongList.length == 0) { uni.navigateBack({ - delta:1 + delta: 1 }) - }else{ + } else { submitTest({ "carTypeId": storage.get('carType') || '1001', "score": score, @@ -769,12 +795,12 @@ if (title) { this.navTitle = title } - if(subject){ + if (subject) { this.subject = subject console.log(this.subject); - this.storageRightList=storage.get(`rightList_subject${subject}`) || [] - this.storageWrongList=storage.get(`wrongList_subject${subject}`) || [] - this.collectList=storage.get(`collectList_subject${subject}`) || [] + this.storageRightList = storage.get(`rightList_subject${subject}`) || [] + this.storageWrongList = storage.get(`wrongList_subject${subject}`) || [] + this.collectList = storage.get(`collectList_subject${subject}`) || [] } if (val && val.length) { this.questionList = JSON.parse(val) @@ -783,12 +809,12 @@ } console.log(this.questionList); if (this.navTitle === '顺序答题') { - if(subject){ + if (subject) { this.pickerTopic(this[`currentIndex_subject${subject}`]) - }else{ + } else { this.pickerTopic(this[`currentIndex_subject${this.subject}`]) } - + } else { this.pickerTopic(this.topicIndex) } diff --git a/src/static/image/mine/callme.png b/src/static/image/mine/callme.png new file mode 100644 index 0000000..fce9119 Binary files /dev/null and b/src/static/image/mine/callme.png differ diff --git a/src/uni_modules/g-preview-img/changelog.md b/src/uni_modules/g-preview-img/changelog.md new file mode 100644 index 0000000..a688afc --- /dev/null +++ b/src/uni_modules/g-preview-img/changelog.md @@ -0,0 +1,11 @@ +## 1.0.4(2022-12-07) +修改:判断在APP端不监听document的滚动 +## 1.0.3(2022-12-05) +新增:支持视频预览,视频图片混用 +新增:支持预览单张,handlePreviewImg方法直接传入图片或视频地址即可单张预览 +## 1.0.2(2022-12-05) + +## 1.0.1(2022-12-05) +文档错误修改 +## 1.0.0(2022-11-29) +初始化插件 diff --git a/src/uni_modules/g-preview-img/components/g-preview-img/g-preview-img.vue b/src/uni_modules/g-preview-img/components/g-preview-img/g-preview-img.vue new file mode 100644 index 0000000..f235e7f --- /dev/null +++ b/src/uni_modules/g-preview-img/components/g-preview-img/g-preview-img.vue @@ -0,0 +1,235 @@ +<template> + <view class="pos"> + <uni-transition :mode-class="modeClass" :show="show"> + <!-- 多张图片预览 --> + <view class="content" @tap="closedPreview"> + <swiper + class="swiper" + circular + :current="curDot" + @change="swiperChange" + :indicator-dots="false" + > + <swiper-item v-for="(item, idx) in selfImgList" :key="idx"> + <movable-area scale-area> + <movable-view + :scale="!disabledScale" + direction="all" + scale="true" + scale-min="0.5" + scale-max="5" + :scale-value="1" + damping="150" + friction="15" + > + <image v-if="isImg(item)" :src="item" mode="widthFix"></image> + <view class="video-preview" v-else> + <video + :autoplay="true" + :src="item" + :enable-progress-gesture="false" + :show-fullscreen-btn="false" + ></video> + </view> + </movable-view> + </movable-area> + </swiper-item> + </swiper> + </view> + <!-- 指示器 --> + <slot name="indicator" v-if="imgList.length > 1 && !indicatorDotsType"> + <view class="current-dot"> + <view class="change-buttom" @tap.stop="previousImg"> + <uni-icons class="font-white" type="back" size="30"></uni-icons> + </view> + <view class="font-white cur"> + {{ curDot + 1 }}/{{ imgList.length }} + </view> + <view class="change-buttom" @tap.stop="nextImg"> + <uni-icons class="font-white" type="forward" size="30"></uni-icons> + </view> + </view> + </slot> + </uni-transition> + </view> +</template> +<script> +export default { + props: { + // 过渡效果 + modeClass: { + type: Array, + default: ['fade', 'zoom-out'], + }, + // 指示器类型 true 圆点 false 数字 + indicatorDotsType: { + type: Boolean, + default: true, + }, + // 图片列表 + imgList: { + type: Array, + default: () => [], + }, + // 是否禁止放大缩小 禁止后swiper可以滑动切换 + disabledScale: { + type: Boolean, + default: false, + }, + }, + data() { + return { + show: false, + curDot: 0, + selfImgList: [], + }; + }, + + computed: { + isImg() { + return (src) => { + return src.indexOf('.mp4') === -1 ? true : false; + }; + }, + }, + watch: { + //监听打开时阻止下面元素的滚动事件 + /* #ifdef APP-PLUS*/ + show(val) { + if (val) { + document + .getElementsByClassName('pos')[0] + .addEventListener('touchmove', function (e) { + e.preventDefault(); + }); + } + }, + /* #endif */ + imgList: { + handler(val) { + if (val.length) { + this.selfImgList = val.concat(); + } + }, + }, + }, + methods: { + handlePreviewImg(param) { + this.show = !this.show; + if (typeof param === 'string') { + this.selfImgList = [param]; + } else { + if (param) { + this.curDot = param; + } + } + this.$emit('preview', this.show); + }, + closedPreview() { + this.show = !this.show; + this.curDot = 0; + this.$emit('preview', this.show); + }, + swiperChange(e) { + this.curDot = e.detail.current; + this.$emit('changeImg', e.detail.current); + }, + previousImg() { + let num = this.imgList.length - 1; + if (this.curDot <= 0) { + this.curDot = num; + } else { + this.curDot--; + } + }, + nextImg() { + let num = this.imgList.length - 1; + if (this.curDot >= num) { + this.curDot = 0; + } else { + this.curDot++; + } + }, + }, +}; +</script> +<style lang="scss" scoped> +movable-view { + display: flex; + align-items: center; + justify-content: center; + width: 100%; + height: 100%; +} + +movable-area { + height: 100%; + width: 100%; + position: fixed; + overflow: hidden; +} + +movable-view image { + width: 100%; +} +.content { + height: 100vh; + width: 100vw; + display: flex; + align-items: center; + background: #00000076; +} +.pos { + position: absolute; + top: 0; + left: 0; + z-index: 9999; +} +.swiper { + height: 100%; + width: 100%; +} +.current-dot { + position: absolute; + bottom: 10%; + left: 25%; + width: 50%; + display: flex; + align-items: center; + justify-content: space-around; + .change-buttom { + padding: 10rpx; + border-radius: 50%; + background: #3f3f3f; + } + .cur { + font-size: 20rpx; + } +} + +.font-white { + color: #fff !important; +} +::v-deep { + .uni-swiper-dots-horizontal { + bottom: 12%; + } + .uni-swiper-dot { + width: 10rpx; + height: 10rpx; + } + + uni-video { + width: 100vw; + height: 100vh; + } +} +.video-preview { + position: relative; + .video-close { + position: absolute; + right: 50rpx; + top: 50rpx; + } +} +</style> diff --git a/src/uni_modules/g-preview-img/package.json b/src/uni_modules/g-preview-img/package.json new file mode 100644 index 0000000..a37a659 --- /dev/null +++ b/src/uni_modules/g-preview-img/package.json @@ -0,0 +1,84 @@ +{ + "id": "g-preview-img", + "displayName": "g-preview-img一款兼容vue2,vue3的图片预览插件,视频预览,支持单张多张,左右滑动,放大缩小", + "version": "1.0.4", + "description": "g-preview-img一款兼容vue2,vue3的图片预览插件,视频预览,支持单张多张,左右滑动,放大缩小", + "keywords": [ + "vue2", + "vue3", + "图片预览", + "视频预览" +], + "repository": "", + "engines": { + "HBuilderX": "^3.1.0" + }, + "dcloudext": { + "type": "component-vue", + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "" + }, + "uni_modules": { + "dependencies": [], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "Vue": { + "vue2": "y", + "vue3": "y" + }, + "App": { + "app-vue": "y", + "app-nvue": "y" + }, + "H5-mobile": { + "Safari": "y", + "Android Browser": "y", + "微信浏览器(Android)": "y", + "QQ浏览器(Android)": "y" + }, + "H5-pc": { + "Chrome": "y", + "IE": "y", + "Edge": "y", + "Firefox": "y", + "Safari": "y" + }, + "小程序": { + "微信": "u", + "阿里": "u", + "百度": "u", + "字节跳动": "u", + "QQ": "u", + "钉钉": "u", + "快手": "u", + "飞书": "u", + "京东": "u" + }, + "快应用": { + "华为": "u", + "联盟": "u" + } + } + } + } +} \ No newline at end of file diff --git a/src/uni_modules/g-preview-img/readme.md b/src/uni_modules/g-preview-img/readme.md new file mode 100644 index 0000000..202bf5e --- /dev/null +++ b/src/uni_modules/g-preview-img/readme.md @@ -0,0 +1,64 @@ + +### 一款兼容vue2,vue3的图片预览插件,视频预览,支持图片视频混用,支持单张多张,左右滑动,放大缩小 + +### 基础使用方法 + +```javascript + <template> + + <image v-for="(item,idx) in imgList" :src="item" :key="idx" @tap="handleClick(idx)"></image> + <g-preview-img :imgList="imgList" ref="preview"><g-preview-img> + </template> + + <script setup> + import { ref } from 'vue' + const preview = ref(null) + const imgList = ['图片路径1','图片路径2'] + const handleClick = (idx)=>{ + // idx为要打开的图片的索引,也可以不传,默认打开第一张 + // handlePreviewImg的参数支持传入单张图片地址或单个视频地址 + preview.value.handlePreviewImg(idx) + } + </script> +``` + +| 属性名/事件 | 类型 | 默认值 | 说明 | +| ----------------- | ------------ | --------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| modeClass | Array/String | \['fade', 'zoom-out'] | uni-transition组件过渡效果,可选值见 <https://uniapp.dcloud.net.cn/component/uniui/uni-transition.html#mode-class-%E5%86%85%E7%BD%AE%E8%BF%87%E6%B8%A1%E5%8A%A8%E7%94%BB%E7%B1%BB%E5%9E%8B%E8%AF%B4%E6%98%8E> | +| indicatorDotsType | Boolean | false | 多张图片的指示器,ture为圆点,false数字,当图片列表只有一张图片时,默认不展示指示器 | +| imgList | Array | | 图片列表 | +| disabledScale | Boolean | false | 是否禁止双指放大缩小 | +| @preview | 打开关闭事件 | | 接受一个参数,ture为开启,false为关闭 | +| @changeImg | 图片切换的事件 | | 参数为当前的图片索引 | + +#### 插槽,自定义翻页按钮 + +```js + +<g-preview-img :imgList="imgList" ref="preview"> + <template> + <!--你的翻页按钮--> + <view @tap.stop="previousImg">上一页</view> + <view @tap.stop="nextImg">下一页</view> + </template> +</g-preview-img> + +<script> + const preview = ref(null) + //上一页的方法 + const previousImg = ()=>{ + preview.value.previousImg() + } + //下一页的方法 + const previousImg = ()=>{ + preview.value.nextImg() + } +</script> +``` + +#### 支持uniapp原生swiper的属性 + + 插件内部swiper标签上绑定了$attrs,所以在使用时可以传入一些swiper的属性 + 注意:不要传disable-touch这个属性,会有意想不到的错误 + +**插件bug会及时修复!!** diff --git a/src/uni_modules/q-previewImage/changelog.md b/src/uni_modules/q-previewImage/changelog.md new file mode 100644 index 0000000..cd4026c --- /dev/null +++ b/src/uni_modules/q-previewImage/changelog.md @@ -0,0 +1,24 @@ +## 1.1.1(2023-08-01) +优化文档 +## 1.1.0(2023-08-01) +优化文档 +## 1.0.9(2023-07-10) +优化文档 +## 1.0.8(2023-06-25) +优化文档 +## 1.0.7(2023-06-25) +优化文档 +## 1.0.6(2023-05-26) +优化文档 +## 1.0.5(2023-05-22) +优化文档 +## 1.0.4(2023-04-30) +新增图片放大功能,解决原生组件和tabbar导航栏等无法覆盖的问题 +## 1.0.3(2023-04-28) +优化文档 +## 1.0.2(2023-04-28) +优化文档 +## 1.0.1(2023-04-28) +新增长按事件 +## 1.0.0(2023-04-28) +插件上线 diff --git a/src/uni_modules/q-previewImage/components/q-previewImage/q-previewImage.vue b/src/uni_modules/q-previewImage/components/q-previewImage/q-previewImage.vue new file mode 100644 index 0000000..5386aa1 --- /dev/null +++ b/src/uni_modules/q-previewImage/components/q-previewImage/q-previewImage.vue @@ -0,0 +1,121 @@ +<template> + <view class="previewImage" v-if="show" @tap="close"> + <view class="page" v-if="urls.length > 0"> + <text class="text">{{ current + 1 }} / {{ urls.length }}</text> + </view> + <swiper class="swiper" :current="current" @change="swiperChange" @touchstart="handleTouchStart" @touchend="handleTouchEnd"> + <swiper-item v-for="(item, index) in urls" :key="index"> + <movable-area class="movable-area" scale-area> + <movable-view class="movable-view" direction="all" :inertia="true" damping="100" scale="true" scale-min="1" scale-max="4" :scale-value="scale"> + <scroll-view scroll-y="true" class="uni-scroll-view"> + <view class="scroll-view"><image :key="index" class="image" :src="item" mode="widthFix" @longpress="onLongpress(item)" /></view> + </scroll-view> + </movable-view> + </movable-area> + </swiper-item> + </swiper> + </view> +</template> + +<script> +export default { + props: { + urls: { + type: Array, + required: true, + default: () => { + return []; + } + } + }, + data() { + return { + show: false, + current: 0, //当前页 + scale: 1, + isZooming: false // 是否处于缩放状态 + }; + }, + methods: { + //打开 + open(current) { + this.current = this.urls.findIndex(item => item === current); + this.show = true; + this.$emit('open'); + }, + //关闭 + close() { + if (!this.isZooming) { + this.show = false; + this.current = 0; + this.$emit('close'); + } + }, + //图片改变 + swiperChange(e) { + this.current = e.detail.current; + }, + //监听长按 + onLongpress(e) { + this.$emit('onLongpress', e); + }, + handleTouchStart() { + this.isZooming = true; + }, + handleTouchEnd() { + this.isZooming = false; + } + } +}; +</script> + +<style lang="scss" scoped> +.previewImage { + z-index: 9999; + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: #000000; + .swiper { + width: 100%; + height: 100vh; + swiper-item { + .movable-area { + height: 100%; + width: 100%; + .movable-view { + width: 100%; + min-height: 100%; + .scroll-view { + display: flex; + align-items: center; + justify-content: center; + height: 100vh; + .image { + width: 100%; + height: auto; + } + } + } + } + } + } + .page { + position: absolute; + z-index: 9999; + width: 100%; + top: 60rpx; + text-align: center; + .text { + color: #fff; + font-size: 32rpx; + background-color: rgba(0, 0, 0, 0.5); + padding: 3rpx 16rpx; + border-radius: 20rpx; + user-select: none; + } + } +} +</style> diff --git a/src/uni_modules/q-previewImage/package.json b/src/uni_modules/q-previewImage/package.json new file mode 100644 index 0000000..3550c3f --- /dev/null +++ b/src/uni_modules/q-previewImage/package.json @@ -0,0 +1,81 @@ +{ + "id": "q-previewImage", + "displayName": "图片预览、多图左右滑动、图片放大、支持覆盖原生组件、原生导航栏、tabbar", + "version": "1.1.1", + "description": "最简洁的模拟图片预览,支持长按事件,多图左右滑动,大图上下滑动查看,支持图片放大,支持覆盖原生组件/原生导航栏/tabbar 支持vue2/vue3/app/小程序/h5", + "keywords": [ + "图片预览" +], + "repository": "", + "engines": { + "HBuilderX": "^3.4.14" + }, + "dcloudext": { + "type": "component-vue", + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "" + }, + "uni_modules": { + "dependencies": [], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "Vue": { + "vue2": "y", + "vue3": "y" + }, + "App": { + "app-vue": "y", + "app-nvue": "n" + }, + "H5-mobile": { + "Safari": "y", + "Android Browser": "u", + "微信浏览器(Android)": "u", + "QQ浏览器(Android)": "u" + }, + "H5-pc": { + "Chrome": "u", + "IE": "u", + "Edge": "u", + "Firefox": "u", + "Safari": "u" + }, + "小程序": { + "微信": "y", + "阿里": "u", + "百度": "u", + "字节跳动": "u", + "QQ": "u", + "钉钉": "u", + "快手": "u", + "飞书": "u", + "京东": "u" + }, + "快应用": { + "华为": "u", + "联盟": "u" + } + } + } + } +} \ No newline at end of file diff --git a/src/uni_modules/q-previewImage/readme.md b/src/uni_modules/q-previewImage/readme.md new file mode 100644 index 0000000..76c9508 --- /dev/null +++ b/src/uni_modules/q-previewImage/readme.md @@ -0,0 +1,244 @@ +# 最简洁的模拟图片预览,支持长按事件,多图左右滑动,大图上下滑动查看,支持图片放大,支持覆盖原生组件/原生导航栏/tabbar 支持vue2/vue3/app/小程序/h5 + + - 为了解决项目中因一些特殊原因无法使用uni.previewImage,例如App.onShow或者页面的oShow中写了方法。 + - 如果用uni.previewImage,每次预览图片都会进到onShow的方法里 + - 可以基本实现官方的预览图片功能,但是体验不如uni.previewImage() + - 如没有特殊原因,还是推荐官方的uni.previewImage() + +## 安装指引 + +##1. 在插件市场打开本插件页面,在右侧点击`使用 HBuilderX 导入插件`,选择要导入的项目点击确定 + +##2. 使用方法 vue2写法 + +``` +<template> + <view> + <video v-if="videoShow" id="myVideo" src="https://img.cdn.aliyun.dcloud.net.cn/guide/uniapp/%E7%AC%AC1%E8%AE%B2%EF%BC%88uni-app%E4%BA%A7%E5%93%81%E4%BB%8B%E7%BB%8D%EF%BC%89-%20DCloud%E5%AE%98%E6%96%B9%E8%A7%86%E9%A2%91%E6%95%99%E7%A8%8B@20200317.mp4" controls></video> + <image v-for="(item, index) in imgs" :key="index" :src="item" @click="preview(item)"></image> + <q-previewImage ref="previewImage" :urls="imgs" @onLongpress="onLongpress" @open="open" @close="close"></q-previewImage> + </view> +</template> + +<script> +export default { + data() { + return { + videoShow:true,//video组件是否显示 + imgs: [], + }; + }, + + methods: { + preview(url) { + this.imgs = ['https://web-assets.dcloud.net.cn/unidoc/zh/multiport-20210812.png', 'https://web-assets.dcloud.net.cn/unidoc/zh/uni-function-diagram.png'] //设置图片数组 + // #ifdef MP-WEIXIN + this.$nextTick(()=>{ + this.$refs.previewImage.open(url); // 传入当前选中的图片地址(小程序必须添加$nextTick,解决组件首次加载无图) + }) + // #endif + + // #ifndef MP-WEIXIN + this.$refs.previewImage.open(url); // 传入当前选中的图片地址 + // #endif + }, + onLongpress(e){ //长按事件 + console.log('当前长按的图片是' + e); + uni.showActionSheet({ + itemList: ['转发给朋友', '保存到手机'], + success: function (res) { + console.log('选中了第' + (res.tapIndex + 1) + '个按钮'); + }, + fail: function (res) { + console.log(res.errMsg); + } + }); + }, + + /* open和close方法一般用不到,但是在一些特殊场景会用到, + * 比如预览图片时你需要覆盖 NavigationBar和 TabBar, + * 或者在app中需要预览图片时覆盖住原生组件,比如video或者map等, + * 你可以根据open和close去做一些操作,例如隐藏导航栏或者隐藏一些原生组件等 + */ + open(){ //监听组件显示 (隐藏TabBar和NavigationBar,隐藏video原生组件) + // uni.hideTabBar() + // uni.setNavigationBarColor({ + // frontColor: '#000000', // 设置前景色为黑色 + // backgroundColor: '#000000', // 设置背景色为黑色 + // }) + // this.videoShow = false + }, + close(){ //监听组件隐藏 (显示TabBar和NavigationBar,显示video原生组件) + // uni.showTabBar() + // uni.setNavigationBarColor({ + // frontColor: '#ffffff', // 设置前景色为白色 + // backgroundColor: '#000000', // 设置背景色为黑色 + // }) + // this.videoShow = true + } + } + }; +</script> + + +``` + +##3. vue3 setup写法 + +``` + +<template> + <view> + <video v-if="videoShow" id="myVideo" src="https://img.cdn.aliyun.dcloud.net.cn/guide/uniapp/%E7%AC%AC1%E8%AE%B2%EF%BC%88uni-app%E4%BA%A7%E5%93%81%E4%BB%8B%E7%BB%8D%EF%BC%89-%20DCloud%E5%AE%98%E6%96%B9%E8%A7%86%E9%A2%91%E6%95%99%E7%A8%8B@20200317.mp4" controls></video> + <image v-for="(item, index) in imgs" :key="index" :src="item" @click="preview(item)"></image> + <q-previewImage ref="previewImage" :urls="imgs" @onLongpress="onLongpress" @open="open" @close="close"></q-previewImage> + </view> +</template> + +<script setup> +import { reactive, ref, toRefs,nextTick } from 'vue'; + +const data = reactive({ + videoShow:true,//video组件是否显示 + imgs: [], +}); +const previewImage = ref(null); + +const { imgs,videoShow } = toRefs(data)// 解构 + +const preview = url => { + data.imgs = ['https://web-assets.dcloud.net.cn/unidoc/zh/multiport-20210812.png', 'https://web-assets.dcloud.net.cn/unidoc/zh/uni-function-diagram.png'] //设置图片数组 + + + // #ifdef MP-WEIXIN + nextTick(()=>{ + previewImage.value.open(url); // 传入当前选中的图片地址(小程序必须添加nextTick,解决组件首次加载无图) + }) + // #endif + + // #ifndef MP-WEIXIN + previewImage.value.open(url); // 传入当前选中的图片地址 + // #endif +}; + +const onLongpress = e =>{ + console.log('当前长按的图片是' + e); + uni.showActionSheet({ + itemList: ['转发给朋友', '保存到手机'], + success: function (res) { + console.log('选中了第' + (res.tapIndex + 1) + '个按钮'); + }, + fail: function (res) { + console.log(res.errMsg); + } + }); +} + +/* open和close方法一般用不到,但是在一些特殊场景会用到, +* 比如预览图片时你需要覆盖 NavigationBar和 TabBar, +* 或者在app中需要预览图片时覆盖住原生组件,比如video或者map等, +* 你可以根据open和close去做一些操作,例如隐藏导航栏或者隐藏一些原生组件等 +*/ +const open = () => { //监听组件显示 (隐藏TabBar和NavigationBar,隐藏video原生组件) + // uni.hideTabBar() + // uni.setNavigationBarColor({ + // frontColor: '#000000', // 设置前景色为黑色 + // backgroundColor: '#000000', // 设置背景色为黑色 + // }) + // data.videoShow = false +} + +const close = () => { //监听组件隐藏 (显示TabBar和NavigationBar,显示video原生组件) + // uni.showTabBar() + // uni.setNavigationBarColor({ + // frontColor: '#ffffff', // 设置前景色为白色 + // backgroundColor: '#000000', // 设置背景色为黑色 + // }) + // data.videoShow = true +} + +</script> + +``` + +##4. 项目示例 (一般返回的数据图片是以逗号或特殊字符分割的字符串,点击时就需要传两个参数,一个是图片数组,一个是当前图片的index) +## 注意q-previewImage不要写在循环体中,imgs其实就是用来存放当前图片的数组,每次点击每次赋值就行 + +``` +<template> + <view> + <video v-if="videoShow" id="myVideo" src="https://img.cdn.aliyun.dcloud.net.cn/guide/uniapp/%E7%AC%AC1%E8%AE%B2%EF%BC%88uni-app%E4%BA%A7%E5%93%81%E4%BB%8B%E7%BB%8D%EF%BC%89-%20DCloud%E5%AE%98%E6%96%B9%E8%A7%86%E9%A2%91%E6%95%99%E7%A8%8B@20200317.mp4" controls></video> + <view v-for="(item, index) in list" :key="index" class="list"> + <image :src="i" mode="aspectFill" v-for="(i,imgindex) in item.urls.split(',')" @click.stop="preimg(item.urls.split(','),imgindex)"></image> + <view> + <q-previewImage ref="previewImage" :urls="imgs" @onLongpress="onLongpress" @open="open" @close="close"></q-previewImage> + </view> +</template> + +<script> +export default { + data() { + return { + videoShow:true,//是否显示video组件 + imgs: [],//imgs其实就是用来存放当前图片的数组,每次点击每次赋值就行 + + }; + }, + + methods: { + preimg(urls,index){ + this.imgs = urls //imgs其实就是用来存放当前图片的数组,每次点击每次赋值就行 + // #ifdef MP-WEIXIN + this.$nextTick(()=>{ + this.$refs.previewImage.open(this.imgs[index]); // 传入当前选中的图片地址(小程序必须添加$nextTick,解决组件首次加载无图) + }) + // #endif + + // #ifndef MP-WEIXIN + this.$refs.previewImage.open(this.imgs[index]); // 传入当前选中的图片地址 + // #endif + }, + onLongpress(e){ //长按事件 + console.log('当前长按的图片是' + e); + uni.showActionSheet({ + itemList: ['转发给朋友', '保存到手机'], + success: function (res) { + console.log('选中了第' + (res.tapIndex + 1) + '个按钮'); + }, + fail: function (res) { + console.log(res.errMsg); + } + }); + }, + /* open和close方法一般用不到,但是在一些特殊场景会用到, + * 比如预览图片时你需要覆盖 NavigationBar和 TabBar, + * 或者在app中需要预览图片时覆盖住原生组件,比如video或者map等, + * 你可以根据open和close去做一些操作,例如隐藏导航栏或者隐藏一些原生组件等 + */ + open(){ //监听组件显示 (隐藏TabBar和NavigationBar,隐藏video原生组件) + // uni.hideTabBar() + // uni.setNavigationBarColor({ + // frontColor: '#000000', // 设置前景色为黑色 + // backgroundColor: '#000000', // 设置背景色为黑色 + // }) + // this.videoShow = false + }, + close(){ //监听组件隐藏 (显示TabBar和NavigationBar,显示video原生组件) + // uni.showTabBar() + // uni.setNavigationBarColor({ + // frontColor: '#ffffff', // 设置前景色为白色 + // backgroundColor: '#000000', // 设置背景色为黑色 + // }) + // this.videoShow = true + } + } + }; +</script> + +``` + + +## 如果插件对您有一点帮助,请给个五星好评,感谢支持 + + +## 如有问题,请加qq 965969604 \ No newline at end of file