自主产品,供应链食堂系统。将两个端拆开了。
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.

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