自主产品,供应链食堂系统。将两个端拆开了。
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

315 lines
7.9 KiB

  1. <template>
  2. <view>
  3. <swiper class="swiper" :animation="animationData"
  4. :style="{ 'opacity': animation ? '0' : '1',
  5. 'background-color': perspective ? 'rgba(0,0,0,0.5)' : '#000000'}"
  6. :current="swiper_current" :circular="circular"
  7. v-if="show_assembly" @click="hideAssembly"
  8. @transition="transition"
  9. @touchmove.stop.prevent="touchmove"
  10. @change="e => switchItem(e.detail.current)">
  11. <swiper-item v-for="(item, index) in images" :key="index"
  12. class="swiper-item">
  13. <!-- 可移动缩放操作的容器 -->
  14. <movable-area class="movable-area" :scale-area="true">
  15. <movable-view class="movable-view" direction="all" :scale="true" :inertia="true" @scale="scale" :scale-value="scale_values[swiper_current]">
  16. <image :src="item" class="swiper-image" mode="aspectFit" @mousewheel.stop="mousewheel" @longpress="longpress"></image>
  17. </movable-view>
  18. </movable-area>
  19. <!-- 控制器 -->
  20. <view class="controls" v-if="controls">
  21. <view>
  22. <view @click.stop="switchItem(swiper_current - 1)" v-if="swiper_current != 0">
  23. <uni-icons type="arrowleft" size="36" color="#fff"></uni-icons>
  24. </view>
  25. <view v-else></view>
  26. </view>
  27. <view>
  28. <view @click.stop="switchItem(swiper_current + 1)" v-if="swiper_current != images.length - 1">
  29. <uni-icons type="arrowright" size="36" color="#fff"></uni-icons>
  30. </view>
  31. <view v-else></view>
  32. </view>
  33. </view>
  34. <!-- 指示器 -->
  35. <view class="indicators" v-if="indicators && images.length > 1">
  36. <view class="number" v-if="indicatorType == 'number'">{{ swiper_current + 1 }} / {{ images.length }}</view>
  37. <view class="square" v-else-if="indicatorType == 'square'">
  38. <view v-for="(item, index) in images" :key="index" class="indicators-icon" :style="swiper_current == index ? 'background-color:'+ themeColor : ''"></view>
  39. </view>
  40. <view class="dot" v-else-if="indicatorType == 'dot'">
  41. <view v-for="(item, index) in images" :key="index" class="indicators-icon" :style="swiper_current == index ? 'background-color:'+ themeColor : ''"></view>
  42. </view>
  43. </view>
  44. <!-- 底部菜单弹出层 -->
  45. <view class="menu-popup" v-if="menu && show_menu" @click.stop>
  46. <button @click="download">保存图片</button>
  47. </view>
  48. </swiper-item>
  49. </swiper>
  50. </view>
  51. </template>
  52. <script>
  53. export default {
  54. props: {
  55. controls: {
  56. type: Boolean, // 是否显示左右箭头控制
  57. default: true
  58. },
  59. indicators: {
  60. type: Boolean,
  61. default: true // 是否显示指示器
  62. },
  63. indicatorType: {
  64. type: String, // 指示器类型,dot圆点,square方形,number数字
  65. default: 'number'
  66. },
  67. perspective: {
  68. type: Boolean, // 是否开启背景透视,遮罩透明可以看见底下其他元素
  69. default: true
  70. },
  71. circular: {
  72. type: Boolean, // 是否可以衔接滑动
  73. default: false
  74. },
  75. themeColor: {
  76. type: String, // 主题色
  77. default: '#1833F2'
  78. },
  79. animation: {
  80. type: Boolean, // 是否开启动画
  81. default: false
  82. },
  83. menu: {
  84. type: Boolean, // 长按图片时是否显示菜单按钮,H5不支持,传参无效果
  85. default: true
  86. }
  87. },
  88. data(){
  89. return {
  90. swiper_current: 0, // 当前显示的图片下标
  91. images: [],
  92. show_assembly: false, // 显示预览图片
  93. duration: 500, // 动画持续时间
  94. animationObj: {}, // 动画配置对象
  95. animationData: {}, // 动画导出执行对象
  96. scale_values: [], // 每张图的缩放比例
  97. clientY: 0,
  98. show_menu: false
  99. }
  100. },
  101. created(){
  102. // TODO PC端屏蔽遮罩滚动穿透
  103. // TODO PC端滚轮放大缩小
  104. // TODO 双击时会被关掉
  105. },
  106. methods: {
  107. // 初始化加载动画
  108. initAnimation(){
  109. let animationObj = uni.createAnimation({
  110. duration: this.duration
  111. });
  112. this.animationObj = animationObj;
  113. animationObj.opacity(0).step();
  114. animationObj.opacity(1).step();
  115. this.animationData = animationObj.export();
  116. },
  117. // swiper touchmove事件
  118. touchmove(){
  119. return false;
  120. },
  121. // swiper 位置发生变化
  122. transition(){
  123. if(this.show_menu){
  124. this.show_menu = false;
  125. }
  126. },
  127. // swiper被改变
  128. switchItem(current){
  129. this.swiper_current = current;
  130. // this.clientY = 0; // 切换时需要复位
  131. },
  132. // movable 缩放事件
  133. scale(event){
  134. let scale = parseInt(event.detail.scale * 100) +'%';
  135. uni.showToast({
  136. title: scale,
  137. icon: 'none',
  138. position: 'bottom'
  139. })
  140. },
  141. // 鼠标滚动缩放事件,目前会触发浏览器页面滚动事件,暂时先不要这个功能
  142. mousewheel(event){
  143. return;
  144. let scale = this.scale_values[this.swiper_current];
  145. if(this.clientY > event.clientY){
  146. if(scale < 10){
  147. scale += 0.5;
  148. }
  149. }else{
  150. if(scale > 0.5){
  151. scale -= 0.5;
  152. }
  153. }
  154. this.clientY = event.clientY;
  155. this.scale_values.splice(this.swiper_current, 1, scale);
  156. },
  157. // 显示图片
  158. show(options){
  159. this.images = options.images || [];
  160. this.swiper_current = options.current || 0;
  161. this.scale_values = this.images.map(item => 1);
  162. this.show_assembly = true;
  163. if(this.$props.animation){
  164. this.initAnimation();
  165. }
  166. },
  167. // 关闭隐藏图片
  168. hideAssembly(){
  169. if(this.show_menu){
  170. this.show_menu = false;
  171. return;
  172. }
  173. if(this.$props.animation){
  174. this.animationObj.opacity(0).step();
  175. this.animationData = this.animationObj.export();
  176. setTimeout(() => {
  177. this.show_assembly = false;
  178. }, this.duration);
  179. }else{
  180. this.show_assembly = false;
  181. }
  182. },
  183. // 长按事件
  184. longpress(event){
  185. // #ifndef H5
  186. if(this.$props.menu){
  187. this.show_menu = true;
  188. }
  189. // #endif
  190. },
  191. // 保存图片
  192. download(){
  193. uni.showLoading({
  194. title: '正在保存'
  195. })
  196. this.show_menu = false;
  197. uni.downloadFile({
  198. url: this.images[this.swiper_current],
  199. success: res => {
  200. let tempFilePath = res.tempFilePath;
  201. uni.saveFile({
  202. tempFilePath,
  203. success: result => {
  204. uni.showToast({
  205. title: '保存成功',
  206. icon: 'success'
  207. })
  208. },
  209. fail: err => {
  210. uni.showToast({
  211. title: '保存失败',
  212. icon: 'none'
  213. })
  214. },
  215. complete: () => uni.hideLoading()
  216. })
  217. },
  218. complete: () => uni.hideLoading()
  219. })
  220. }
  221. }
  222. }
  223. </script>
  224. <style lang="scss" scoped="scoped">
  225. .swiper{
  226. width: 100vw;
  227. height: 100vh;
  228. position: fixed;
  229. top: 0;
  230. left: 0;
  231. z-index: 1000;
  232. .swiper-item{
  233. position: relative;
  234. .movable-area, .movable-view, .swiper-image{
  235. width: 100%;
  236. height: 100%;
  237. }
  238. .controls{
  239. position: absolute;
  240. padding: 0 16rpx;
  241. box-sizing: border-box;
  242. display: flex;
  243. justify-content: space-between;
  244. top: 47vh;
  245. left: 0;
  246. font-size: 40rpx;
  247. width: 100%;
  248. z-index: 1002;
  249. }
  250. .indicators{
  251. width: 100%;
  252. position: absolute;
  253. left: 0;
  254. bottom: 10vh;
  255. z-index: 1002;
  256. display: flex;
  257. justify-content: center;
  258. .number, .square, .dot{
  259. width: max-content;
  260. height: max-content;
  261. background-color: #dfe4ea;
  262. }
  263. .number{
  264. padding: 4rpx 26rpx;
  265. border-radius: 40rpx;
  266. color: #555555;
  267. }
  268. .square{
  269. border-radius: 40rpx;
  270. display: flex;
  271. padding: 2rpx 4rpx;
  272. view{
  273. border-radius: 50%;
  274. }
  275. }
  276. .dot{
  277. display: flex;
  278. padding: 4rpx 4rpx;
  279. }
  280. .indicators-icon{
  281. width: 20rpx;
  282. height: 20rpx;
  283. background-color: #bdc5bd;
  284. margin: 2rpx;
  285. }
  286. }
  287. }
  288. }
  289. .menu-popup{
  290. position: fixed;
  291. bottom: 0;
  292. left: 0;
  293. height: max-content;
  294. width: 100%;
  295. background-color: #FFFFFF;
  296. z-index: 1004;
  297. padding-bottom: constant(safe-area-inset-bottom);
  298. padding-bottom: env(safe-area-inset-bottom);
  299. button{
  300. height: 100rpx;
  301. line-height: 100rpx;
  302. background-color: transparent;
  303. border-bottom: 1rpx solid #e5e5e5;
  304. &:last-child{
  305. border-bottom: none;
  306. }
  307. }
  308. }
  309. // 轻提示框样式
  310. /deep/.uni-sample-toast{
  311. z-index: 1002;
  312. }
  313. </style>