소스 검색

no message

laosan2382995021@163.com 4 년 전
부모
커밋
102d5f2c7f
81개의 변경된 파일2406개의 추가작업 그리고 294개의 파일을 삭제
  1. 59 0
      package-lock.json
  2. 1 0
      package.json
  3. 2 0
      src/assets/scss/entry/overflow.scss
  4. 9 0
      src/components/index.ts
  5. 42 1
      src/components/scroll-view/index.scss
  6. 157 175
      src/components/scroll-view/mixins/config.ts
  7. 5 0
      src/components/scroll-view/mixins/index.ts
  8. 38 43
      src/components/scroll-view/mixins/on.ts
  9. 22 25
      src/components/scroll-view/mixins/status.ts
  10. 30 0
      src/components/scroll-view/mixins/style.ts
  11. 47 40
      src/components/scroll-view/props.ts
  12. 12 6
      src/components/scroll-view/src/main.vue
  13. 3 0
      src/components/scroll-view/types/scroll-type.d.ts
  14. 3 0
      src/components/swiper-item/index.ts
  15. 5 0
      src/components/swiper-item/props.ts
  16. 11 0
      src/components/swiper-item/src/main.vue
  17. 0 0
      src/components/swiper-item/style.scss
  18. 3 0
      src/components/swiper/index.ts
  19. 7 0
      src/components/swiper/props.ts
  20. 33 0
      src/components/swiper/src/main.vue
  21. 0 0
      src/components/swiper/style.scss
  22. 3 0
      src/components/tab/index.ts
  23. 119 0
      src/components/tab/mixins/handle.ts
  24. 3 0
      src/components/tab/mixins/index.ts
  25. 40 0
      src/components/tab/props.ts
  26. 57 0
      src/components/tab/src/main.vue
  27. 36 0
      src/components/tab/style.scss
  28. 14 0
      src/components/tab/types/tab.d.ts
  29. 28 0
      src/components/v-image/api/calculation.ts
  30. 188 0
      src/components/v-image/api/image.ts
  31. 18 0
      src/components/v-image/api/unit.ts
  32. 4 0
      src/components/v-image/const/container.ts
  33. 8 0
      src/components/v-image/const/status.ts
  34. 3 0
      src/components/v-image/index.ts
  35. 76 0
      src/components/v-image/lib/main.vue
  36. 19 0
      src/components/v-image/mixins/el.ts
  37. 31 0
      src/components/v-image/mixins/icon.ts
  38. 16 0
      src/components/v-image/mixins/index.ts
  39. 183 0
      src/components/v-image/mixins/size.ts
  40. 115 0
      src/components/v-image/mixins/src.ts
  41. 48 0
      src/components/v-image/mixins/status.ts
  42. 113 0
      src/components/v-image/mixins/style.ts
  43. 29 0
      src/components/v-image/mixins/video.ts
  44. 185 0
      src/components/v-image/props.ts
  45. 6 0
      src/components/v-image/style/global.scss
  46. 40 0
      src/components/v-image/style/index.scss
  47. 3 0
      src/components/v-image/types/container-const.d.ts
  48. 27 0
      src/components/v-image/types/image-api.d.ts
  49. 7 0
      src/components/v-image/types/size.d.ts
  50. 7 0
      src/config/config.ts
  51. 3 0
      src/config/index.ts
  52. 12 0
      src/pages/home/data/tabData.ts
  53. 36 3
      src/pages/home/src/main.vue
  54. 11 0
      src/pages/home/style.scss
  55. 28 0
      src/types/config.d.ts
  56. 3 0
      src/types/lib.data.d.ts
  57. 13 0
      src/types/lib.mixins.d.ts
  58. 15 0
      src/utils/tool/url.ts
  59. 9 1
      src/views/index.ts
  60. 42 0
      src/views/view-cate/data/cate.ts
  61. BIN
      src/views/view-cate/images/cate/1.png
  62. BIN
      src/views/view-cate/images/cate/10.png
  63. BIN
      src/views/view-cate/images/cate/2.png
  64. BIN
      src/views/view-cate/images/cate/3.png
  65. BIN
      src/views/view-cate/images/cate/4.png
  66. BIN
      src/views/view-cate/images/cate/5.png
  67. BIN
      src/views/view-cate/images/cate/6.png
  68. BIN
      src/views/view-cate/images/cate/7.png
  69. BIN
      src/views/view-cate/images/cate/8.png
  70. BIN
      src/views/view-cate/images/cate/9.png
  71. 3 0
      src/views/view-cate/index.ts
  72. 79 0
      src/views/view-cate/src/main.vue
  73. 109 0
      src/views/view-cate/style.scss
  74. 3 0
      src/views/view-menu/index.ts
  75. 3 0
      src/views/view-menu/mixins/index.ts
  76. 48 0
      src/views/view-menu/src/main.vue
  77. 14 0
      src/views/view-menu/style.scss
  78. 8 0
      src/views/view-menu/style/variable.scss
  79. 3 0
      src/views/view-play-with/index.ts
  80. 21 0
      src/views/view-play-with/src/main.vue
  81. 28 0
      src/views/view-play-with/style.scss

+ 59 - 0
package-lock.json

@@ -8,6 +8,7 @@
       "version": "0.1.0",
       "dependencies": {
         "core-js": "^3.6.5",
+        "swiper": "^6.6.2",
         "vue": "^3.0.0",
         "vue-class-component": "^8.0.0-0",
         "vue-router": "^4.0.0-0",
@@ -5515,6 +5516,14 @@
       "integrity": "sha1-mgtsJ4LtahxzI9QiZxg9+b2LHVc=",
       "dev": true
     },
+    "node_modules/dom7": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/dom7/-/dom7-3.0.0.tgz",
+      "integrity": "sha512-oNlcUdHsC4zb7Msx7JN3K0Nro1dzJ48knvBOnDPKJ2GV9wl1i5vydJZUSyOfrkKFDZEud/jBsTk92S/VGSAe/g==",
+      "dependencies": {
+        "ssr-window": "^3.0.0-alpha.1"
+      }
+    },
     "node_modules/domain-browser": {
       "version": "1.2.0",
       "resolved": "https://registry.npm.taobao.org/domain-browser/download/domain-browser-1.2.0.tgz",
@@ -13370,6 +13379,11 @@
         "node": ">=0.10.0"
       }
     },
+    "node_modules/ssr-window": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/ssr-window/-/ssr-window-3.0.0.tgz",
+      "integrity": "sha512-q+8UfWDg9Itrg0yWK7oe5p/XRCJpJF9OBtXfOPgSJl+u3Xd5KI328RUEvUqSMVM9CiQUEf1QdBzJMkYGErj9QA=="
+    },
     "node_modules/ssri": {
       "version": "8.0.1",
       "resolved": "https://registry.nlark.com/ssri/download/ssri-8.0.1.tgz?cache=0&sync_timestamp=1621364918494&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fssri%2Fdownload%2Fssri-8.0.1.tgz",
@@ -13767,6 +13781,29 @@
         "node": ">=4.0.0"
       }
     },
+    "node_modules/swiper": {
+      "version": "6.6.2",
+      "resolved": "https://registry.npmjs.org/swiper/-/swiper-6.6.2.tgz",
+      "integrity": "sha512-l9ICRsPtK92fF1nzR/r56bNE9A8ufz99yr810+IgQTfnKWVDIE/DD/uQKtIRpbFAIeuesU/J4F1ziIC/jBug7g==",
+      "funding": [
+        {
+          "type": "patreon",
+          "url": "https://www.patreon.com/vladimirkharlampidi"
+        },
+        {
+          "type": "open_collective",
+          "url": "http://opencollective.com/swiper"
+        }
+      ],
+      "hasInstallScript": true,
+      "dependencies": {
+        "dom7": "^3.0.0",
+        "ssr-window": "^3.0.0"
+      },
+      "engines": {
+        "node": ">= 4.7.0"
+      }
+    },
     "node_modules/tapable": {
       "version": "1.1.3",
       "resolved": "https://registry.npm.taobao.org/tapable/download/tapable-1.1.3.tgz?cache=0&sync_timestamp=1607088891056&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Ftapable%2Fdownload%2Ftapable-1.1.3.tgz",
@@ -20818,6 +20855,14 @@
         }
       }
     },
+    "dom7": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/dom7/-/dom7-3.0.0.tgz",
+      "integrity": "sha512-oNlcUdHsC4zb7Msx7JN3K0Nro1dzJ48knvBOnDPKJ2GV9wl1i5vydJZUSyOfrkKFDZEud/jBsTk92S/VGSAe/g==",
+      "requires": {
+        "ssr-window": "^3.0.0-alpha.1"
+      }
+    },
     "domain-browser": {
       "version": "1.2.0",
       "resolved": "https://registry.npm.taobao.org/domain-browser/download/domain-browser-1.2.0.tgz",
@@ -27184,6 +27229,11 @@
         "tweetnacl": "~0.14.0"
       }
     },
+    "ssr-window": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/ssr-window/-/ssr-window-3.0.0.tgz",
+      "integrity": "sha512-q+8UfWDg9Itrg0yWK7oe5p/XRCJpJF9OBtXfOPgSJl+u3Xd5KI328RUEvUqSMVM9CiQUEf1QdBzJMkYGErj9QA=="
+    },
     "ssri": {
       "version": "8.0.1",
       "resolved": "https://registry.nlark.com/ssri/download/ssri-8.0.1.tgz?cache=0&sync_timestamp=1621364918494&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fssri%2Fdownload%2Fssri-8.0.1.tgz",
@@ -27509,6 +27559,15 @@
         "util.promisify": "~1.0.0"
       }
     },
+    "swiper": {
+      "version": "6.6.2",
+      "resolved": "https://registry.npmjs.org/swiper/-/swiper-6.6.2.tgz",
+      "integrity": "sha512-l9ICRsPtK92fF1nzR/r56bNE9A8ufz99yr810+IgQTfnKWVDIE/DD/uQKtIRpbFAIeuesU/J4F1ziIC/jBug7g==",
+      "requires": {
+        "dom7": "^3.0.0",
+        "ssr-window": "^3.0.0"
+      }
+    },
     "tapable": {
       "version": "1.1.3",
       "resolved": "https://registry.npm.taobao.org/tapable/download/tapable-1.1.3.tgz?cache=0&sync_timestamp=1607088891056&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Ftapable%2Fdownload%2Ftapable-1.1.3.tgz",

+ 1 - 0
package.json

@@ -8,6 +8,7 @@
   },
   "dependencies": {
     "core-js": "^3.6.5",
+    "swiper": "^6.6.2",
     "vue": "^3.0.0",
     "vue-class-component": "^8.0.0-0",
     "vue-router": "^4.0.0-0",

+ 2 - 0
src/assets/scss/entry/overflow.scss

@@ -2,6 +2,7 @@
   overflow: hidden;
   text-overflow: ellipsis;
   white-space: nowrap;
+  width: 100%;
 }
 
 @for $index from 2 through 6 {
@@ -11,5 +12,6 @@
     display: -webkit-box;
     -webkit-line-clamp: $index;
     -webkit-box-orient: vertical;
+
   }
 }

+ 9 - 0
src/components/index.ts

@@ -0,0 +1,9 @@
+export { default as tab } from './tab';
+
+export { default as vImage } from './v-image';
+
+export { default as scrollView } from './scroll-view';
+
+export { default as swiper } from './swiper';
+
+export { default as swiperItem } from './swiper-item';

+ 42 - 1
src/components/scroll-view/index.scss

@@ -16,4 +16,45 @@
   display: table;
   clear: both;
 }
-/* 滚动容器 */
+/* 滚动容器 */
+
+/* 滚动条样式 */
+$scroll-type-main-radius: 10px;
+.scroll-type-main::-webkit-scrollbar {/*滚动条整体样式*/
+  width: 15px;     /*高宽分别对应横竖滚动条的尺寸*/
+  background-color: #fff;
+  border-radius: $scroll-type-main-radius;
+}
+
+.scroll-type-main::-webkit-scrollbar-thumb {/*滚动条里面小方块*/
+  border-radius: $scroll-type-main-radius;
+  background: #CECECE;
+}
+
+.scroll-type-main::-webkit-scrollbar-track {/*滚动条里面轨道*/
+  border-radius: $scroll-type-main-radius;
+  background: #fff;
+}
+/* 滚动条样式 */
+
+/* 滚动条样式 */
+.scroll-type-grey::-webkit-scrollbar {/*滚动条整体样式*/
+  width: 15px;     /*高宽分别对应横竖滚动条的尺寸*/
+  background-color: #E5E5E5;
+}
+
+.scroll-type-grey::-webkit-scrollbar-thumb {/*滚动条里面小方块*/
+  background-color: #CECECE;
+}
+
+.scroll-type-grey::-webkit-scrollbar-track {/*滚动条里面轨道*/
+  background-color: #E5E5E5;
+}
+/* 滚动条样式 */
+
+/* 不显示滚动条 */
+.scroll-type-none::-webkit-scrollbar {/*滚动条整体样式*/
+  width: 0;     /*高宽分别对应横竖滚动条的尺寸*/
+  height: 0;
+}
+/* 不显示滚动条 */

+ 157 - 175
src/components/scroll-view/mixins/config.ts

@@ -1,7 +1,5 @@
-import {Component, Mixins, Ref} from "vue-property-decorator";
 import {emitNames} from '../enum/emit';
 import ScrollApi from '../api/scroll';
-import ScrollStatus from './status';
 import {
     Config,
     EventTypes,
@@ -11,11 +9,20 @@ import {
     ScrollYConfig
 } from "../types/on";
 
-@Component({
+export default <LibMixins>{
 
-    created(this:ScrollViewConfig){
-        this._config = {};
-        this._scrollConfig = {
+    computed:{
+        scrollEl:function (){
+            return this.$refs.scroll
+        },
+        scrollTakeEl:function (){
+            return this.$refs.scrollTake;
+        }
+    },
+
+    created(){
+
+        let _scrollConfig:ScrollConfig = {
             look:false,
             stipitate:undefined,
             x:{
@@ -31,219 +38,194 @@ import {
                 maxScroll:0
             }
         };
+        let _scrollApi:ScrollApi | undefined = undefined;
+        let _config:Config = {};
+        let _listeners = undefined;
+
+        this._config = _config;
+        this._scrollApi = _scrollApi;
+        this._listeners = _listeners;
+        this._scrollConfig = _scrollConfig;
+
     },
 
-    mounted(this:ScrollViewConfig){
+    mounted(){
         // 执行安装
         return this.installScroll();
     },
 
     // 释放组件的时候触发
-    beforeDestroy(this:ScrollViewConfig){
+    beforeUnmount(){
         this._scrollApi && this._scrollApi.destroy();
         this._scrollConfig.stipitate&&clearTimeout(this._scrollConfig.stipitate);
         this._scrollApi = undefined;
         this.scrollEl && this._config.scrollUnique && this.removeEventListener(this.scrollEl,'scroll',this._config.scrollUnique);
-    }
+    },
 
-})
-export default class ScrollViewConfig extends  Mixins(ScrollStatus) {
-
-    // 滚动标签实例
-    @Ref('scroll') scrollEl:HTMLElement | undefined;
-    // 滚动包容标签实例
-    @Ref('scrollTake') scrollTakeEl:HTMLElement | undefined;
-
-    // 运行时的 scrollApi
-    _scrollApi:ScrollApi | undefined;
-    // 运行时参数
-    _config:Config={};
-    // 滚动系列参数配置
-    _scrollConfig:ScrollConfig={
-        look:false,
-        stipitate:undefined,
-        x:{
-            x:0,
-            width:0,
-            actualWidth:0,
-            maxScroll:0
+    methods:{
+        // 配置滚动系列参数
+        setScrollConfig(){
+            if (this._scrollConfig.look) return;
+            // 设置锁
+            this._scrollConfig.look = true;
+            // 配置 x 轴
+            this.scrollX && this.setScrollAxisConfig('x');
+            // 配置 y 轴
+            this.scrollY && this.setScrollAxisConfig('y');
+            // 帧频率不允许重复
+            this._scrollConfig.stipitate = setTimeout(()=>{
+                this._scrollConfig.stipitate = undefined;
+                this._scrollConfig.look = false;
+            },16.66);
         },
-        y:{
-            y:0,
-            height:0,
-            actualHeight:0,
-            maxScroll:0
-        }
-    };
-    // 配置滚动系列参数
-    setScrollConfig(){
-        if (this._scrollConfig.look) return;
-        // 设置锁
-        this._scrollConfig.look = true;
-        // 配置 x 轴
-        this.scrollX && this.setScrollAxisConfig('x');
-        // 配置 y 轴
-        this.scrollY && this.setScrollAxisConfig('y');
-        // 帧频率不允许重复
-        this._scrollConfig.stipitate = setTimeout(()=>{
-            this._scrollConfig.stipitate = undefined;
-            this._scrollConfig.look = false;
-        },16.66);
-    };
-    // 配置单独轴系列参数
-    setScrollAxisConfig(axis:'x' | 'y'){
-
-        if (this.scrollEl === undefined || this.scrollTakeEl === undefined) return;
-
-        let sizeKey: 'width' | 'height' = axis === 'x' ? 'width':'height';
-        let actualKey : 'actualWidth' | 'actualHeight' = axis === 'x' ? 'actualWidth' :'actualHeight';
-        let scrollAxis: 'scrollLeft' | 'scrollTop' = axis === 'x'? 'scrollLeft' :'scrollTop';
-        let elSizeKey:'offsetWidth' | 'offsetHeight' = axis === 'x' ? 'offsetWidth' :'offsetHeight';
-
-        // 设置配置文件
-        // @ts-ignore
-        this._scrollConfig[axis] = {
-            [sizeKey]: this.scrollEl[elSizeKey] || 0,
-            [axis]: this.scrollEl[scrollAxis] || 0,
-            [actualKey]: this.scrollTakeEl[elSizeKey] || 0,
-            maxScroll:0
-        };
-        //
-        // // 设置最大滚动
-        // @ts-ignore
-        (this._scrollConfig[axis] as ScrollYConfig | ScrollXConfig).maxScroll = Math.max(0,this._scrollConfig[axis][actualKey] - this._scrollConfig[axis][sizeKey]);
 
-    };
-    // 安装
-    installScroll(){
+        // 配置单独轴系列参数
+        setScrollAxisConfig(axis:'x' | 'y'){
 
-        // 设置当前的配置文件
-        this.setScrollConfig();
+            if (this.scrollEl === undefined || this.scrollTakeEl === undefined) return;
 
-        // 创建滚动Api
-        if (this.scrollEl !== undefined) {
-            this._scrollApi = new ScrollApi(this._scrollConfig,this.scrollEl);
-        }
+            let sizeKey: 'width' | 'height' = axis === 'x' ? 'width':'height';
+            let actualKey : 'actualWidth' | 'actualHeight' = axis === 'x' ? 'actualWidth' :'actualHeight';
+            let scrollAxis: 'scrollLeft' | 'scrollTop' = axis === 'x'? 'scrollLeft' :'scrollTop';
+            let elSizeKey:'offsetWidth' | 'offsetHeight' = axis === 'x' ? 'offsetWidth' :'offsetHeight';
 
-        // 如果存在此三种的任何一种 事件开启监听
-        if (
-            (this.$listeners[emitNames.onScroll]
-            ||
-            this.$listeners[emitNames.onScrollLower]
-            ||
-            this.$listeners[emitNames.onScrollUpper]) && this.scrollEl !== undefined
-        ) {
-
-            // 获取存储的唯一标识
-            this._config.scrollUnique = this.addEventListener(this.scrollEl,'scroll',()=> this.onScroll());
-        }
+            // 设置配置文件
+            // @ts-ignore
+            this._scrollConfig[axis] = {
+                [sizeKey]: this.scrollEl[elSizeKey] || 0,
+                [axis]: this.scrollEl[scrollAxis] || 0,
+                [actualKey]: this.scrollTakeEl[elSizeKey] || 0,
+                maxScroll:0
+            };
+            //
+            // // 设置最大滚动
+            // @ts-ignore
+            (this._scrollConfig[axis] as ScrollYConfig | ScrollXConfig).maxScroll = Math.max(0,this._scrollConfig[axis][actualKey] - this._scrollConfig[axis][sizeKey]);
 
-    }
-    // 滚动时触发
-    onScroll(){
-        if (this.scrollEl) {
-            // 设置配置
+        },
+        // 安装
+        installScroll(){
+
+            // 设置当前的配置文件
             this.setScrollConfig();
 
-            if (this.$listeners[emitNames.onScrollUpper] || this.$listeners[emitNames.onScrollLower]) {
+            // 创建滚动Api
+            if (this.scrollEl !== undefined) {
+                this._scrollApi = new ScrollApi(this._scrollConfig,this.scrollEl);
+            }
 
-                if (this.scrollY) {
-                    if ( this.$listeners[emitNames.onScrollLower] && this.testScrollDistance('y','lowerThreshold','_lowerThresholdYStatus')) {
-                        this.emitScrollLower('y');
-                    } else if (this.$listeners[emitNames.onScrollUpper] && this.testScrollDistance('y','upperThreshold','_upperThresholdYStatus')) {
-                        this.emitScrollUpper('y');
+            // 如果存在此三种的任何一种 事件开启监听
+            if (
+                (this.$attrs[emitNames.onScroll]
+                    ||
+                    this.$attrs[emitNames.onScrollLower]
+                    ||
+                    this.$attrs[emitNames.onScrollUpper]) && this.scrollEl !== undefined
+            ) {
+
+                // 获取存储的唯一标识
+                this._config.scrollUnique = this.addEventListener(this.scrollEl,'scroll',()=> this.onScroll());
+            }
+
+        },
+        // 滚动时触发
+        onScroll(){
+            if (this.scrollEl) {
+                // 设置配置
+                this.setScrollConfig();
+
+                if (this.$listeners[emitNames.onScrollUpper] || this.$listeners[emitNames.onScrollLower]) {
+
+                    if (this.scrollY) {
+                        if ( this.$listeners[emitNames.onScrollLower] && this.testScrollDistance('y','lowerThreshold','_lowerThresholdYStatus')) {
+                            this.emitScrollLower('y');
+                        } else if (this.$listeners[emitNames.onScrollUpper] && this.testScrollDistance('y','upperThreshold','_upperThresholdYStatus')) {
+                            this.emitScrollUpper('y');
+                        }
                     }
-                }
 
-                if (this.scrollX) {
-                    if ( this.$listeners[emitNames.onScrollLower] && this.testScrollDistance('x','lowerThreshold','_lowerThresholdXStatus')) {
-                        this.emitScrollLower('x');
-                    } else if (this.$listeners[emitNames.onScrollUpper] && this.testScrollDistance('x','upperThreshold','_upperThresholdXStatus')) {
-                        this.emitScrollUpper('x');
+                    if (this.scrollX) {
+                        if ( this.$listeners[emitNames.onScrollLower] && this.testScrollDistance('x','lowerThreshold','_lowerThresholdXStatus')) {
+                            this.emitScrollLower('x');
+                        } else if (this.$listeners[emitNames.onScrollUpper] && this.testScrollDistance('x','upperThreshold','_upperThresholdXStatus')) {
+                            this.emitScrollUpper('x');
+                        }
                     }
+
                 }
 
-            }
+                if(this.$listeners[emitNames.onScroll]) {
+                    return this.emitScroll(this._scrollConfig);
+                }
 
-            if(this.$listeners[emitNames.onScroll]) {
-                return this.emitScroll(this._scrollConfig);
             }
+        },
 
-        }
-    }
+        // 校验是否满足条件
+        testScrollDistance(axis:'x' | 'y',key:'lowerThreshold' | 'upperThreshold',testKey:'_lowerThresholdXStatus'|'_lowerThresholdYStatus'|'_upperThresholdXStatus'|'_upperThresholdYStatus'){
 
-    // 校验是否满足条件
-    testScrollDistance(axis:'x' | 'y',key:'lowerThreshold' | 'upperThreshold',testKey:'_lowerThresholdXStatus'|'_lowerThresholdYStatus'|'_upperThresholdXStatus'|'_upperThresholdYStatus'){
+            if (
+                this[key] !== undefined
+                &&
+                key === 'upperThreshold' ? this._scrollConfig[axis][axis] <= this[key] : this._scrollConfig[axis].maxScroll - this._scrollConfig[axis][axis] <= this[key]
+            ) {
 
-        if (
-        this[key] !== undefined
-        &&
-        // @ts-ignore
-        key === 'upperThreshold' ? this._scrollConfig[axis][axis] <= this[key] : this._scrollConfig[axis].maxScroll - this._scrollConfig[axis][axis] <= this[key]
-        ) {
+                if (!this[testKey]) {
+                    this[testKey] = true;
 
-            if (!this[testKey]) {
-                this[testKey] = true;
+                    return true;
+                }
 
-                return true;
+            } else {
+                this[testKey] = false;
             }
+            return false;
 
-        } else {
-            this[testKey] = false;
-        }
-        return false;
-
-    }
-
-    // 监听器对象
-    _listeners: { [key:string]:ListenerEventTarget } | undefined = undefined;
-
-    // 添加监听
-    addEventListener<T extends EventTypes>(el:HTMLElement,event:T,target:ListenerEventTarget){
-        // 如果没有监听器对象创建
-        if (this._listeners === undefined) {
-            this._listeners = {};
-        }
-
-        let unique:string = this.getUnique(event);
+        },
 
-        this._listeners[unique] = target;
+        // 添加监听
+        addEventListener<T extends EventTypes>(el:HTMLElement,event:T,target:ListenerEventTarget){
+            // 如果没有监听器对象创建
+            if (this._listeners === undefined) {
+                this._listeners = {};
+            }
 
-        if (window.addEventListener !== undefined) {
-            el.addEventListener(event,target,{
-                passive:true
-            });
-        } else {
-            // @ts-ignore 兼容策略
-            el.attachEvent('on'+event,target,{
-                passive:true
-            });
-        }
+            let unique:string = this.getUnique(event);
 
-        return unique;
-    }
+            this._listeners[unique] = target;
 
-    // 移除兼容
-    removeEventListener<K extends EventTypes>(el:HTMLElement,event:K,unique:string){
-        if (this._listeners !== undefined && this._listeners[unique] !== undefined) {
-            if (window.removeEventListener !== undefined) {
-                el.removeEventListener(event, this._listeners[unique]);
+            if (window.addEventListener !== undefined) {
+                el.addEventListener(event,target,{
+                    passive:true
+                });
             } else {
                 // @ts-ignore 兼容策略
-                el.detachEvent('on' + event, this._listeners[unique]);
+                el.attachEvent('on'+event,target,{
+                    passive:true
+                });
             }
-            return  true;
-        } else {
-            return false;
-        }
-
 
+            return unique;
+        },
 
-    }
+        // 移除兼容
+        removeEventListener<K extends EventTypes>(el:HTMLElement,event:K,unique:string){
+            if (this._listeners !== undefined && this._listeners[unique] !== undefined) {
+                if (window.removeEventListener !== undefined) {
+                    el.removeEventListener(event, this._listeners[unique]);
+                } else {
+                    // @ts-ignore 兼容策略
+                    el.detachEvent('on' + event, this._listeners[unique]);
+                }
+                return  true;
+            } else {
+                return false;
+            }
+        },
 
-    // 获取唯一值
-    getUnique(value:string=''):string{
-        return  new Date().getTime() + value  + Math.floor(Math.random() * 10000) ;
+        // 获取唯一值
+        getUnique(value:string=''):string{
+            return  new Date().getTime() + value  + Math.floor(Math.random() * 10000) ;
+        }
     }
-
 }

+ 5 - 0
src/components/scroll-view/mixins/index.ts

@@ -0,0 +1,5 @@
+import config from './config';
+import on from './on';
+import status from "./status";
+import style from './style';
+export default [status,config,on,style];

+ 38 - 43
src/components/scroll-view/mixins/on.ts

@@ -1,68 +1,63 @@
-import {Component,Mixins} from "vue-property-decorator";
-
 import { ScrollRefComponents,scrollParams,scrollAnimateParams,scrollRunningParams } from '../types/scroll';
 
-import Config from './config';
-
 import Unit from '@/utils/unit/unit';
 
-@Component
-export default class ScrollViewOn extends Mixins(Config) implements ScrollRefComponents{
+export default <LibMixins>{
+    methods:<ScrollRefComponents & LibMixins>{
+        scrollTo(scrollParams: scrollParams) {
 
-    scrollTo(scrollParams: scrollParams) {
+            // 获取配置文件
+            this.setScrollConfig();
 
-        // 获取配置文件
-        this.setScrollConfig();
+            if (scrollParams.x) {
+                scrollParams.x = Unit.unit(scrollParams.x,this._scrollConfig.x.maxScroll);
+                scrollParams.x =  scrollParams.x - this._scrollConfig.x.x;
+            }
 
-        if (scrollParams.x) {
-            scrollParams.x = Unit.unit(scrollParams.x,this._scrollConfig.x.maxScroll);
-            scrollParams.x =  scrollParams.x - this._scrollConfig.x.x;
-        }
+            if (scrollParams.y) {
+                scrollParams.y = Unit.unit(scrollParams.y,this._scrollConfig.y.maxScroll);
+                scrollParams.y =  scrollParams.y - this._scrollConfig.y.y;
+            }
 
-        if (scrollParams.y) {
-            scrollParams.y = Unit.unit(scrollParams.y,this._scrollConfig.y.maxScroll);
-            scrollParams.y =  scrollParams.y - this._scrollConfig.y.y;
-        }
+            return this._scrollApi?.animateTo(scrollParams as scrollRunningParams);
 
-        return this._scrollApi?.animateTo(scrollParams as scrollRunningParams);
+        },
 
-    }
+        scrollToEnd(scrollParams: scrollAnimateParams={},all:boolean) {
 
-    scrollToEnd(scrollParams: scrollAnimateParams={},all:boolean) {
+            // 获取配置文件
+            this.setScrollConfig();
 
-        // 获取配置文件
-        this.setScrollConfig();
+            let newScrollParams:scrollParams = scrollParams;
 
-        let newScrollParams:scrollParams = scrollParams;
+            if (this.scrollY && this._scrollConfig.y.y < this._scrollConfig.y.maxScroll) {
+                newScrollParams.y = this._scrollConfig.y.maxScroll;
 
-        if (this.scrollY && this._scrollConfig.y.y < this._scrollConfig.y.maxScroll) {
-            newScrollParams.y = this._scrollConfig.y.maxScroll;
+                return !all && this.scrollTo(newScrollParams);
+            }
 
-            return !all && this.scrollTo(newScrollParams);
-        }
+            if (this.scrollX && this._scrollConfig.x.x < this._scrollConfig.x.maxScroll) {
+                newScrollParams.x = this._scrollConfig.x.maxScroll;
+            }
 
-        if (this.scrollX && this._scrollConfig.x.x < this._scrollConfig.x.maxScroll) {
-            newScrollParams.x = this._scrollConfig.x.maxScroll;
-        }
+            return this.scrollTo(newScrollParams);
 
-        return this.scrollTo(newScrollParams);
+        },
 
-    }
+        scrollToStart(scrollParams: scrollAnimateParams={},all:boolean) {
 
-    scrollToStart(scrollParams: scrollAnimateParams={},all:boolean) {
+            let newScrollParams:scrollParams = scrollParams;
 
-        let newScrollParams:scrollParams = scrollParams;
+            if (this.scrollY && this._scrollConfig.y.y>0) {
+                newScrollParams.y = 0;
+                return !all && this.scrollTo(newScrollParams);
+            }
 
-        if (this.scrollY && this._scrollConfig.y.y>0) {
-            newScrollParams.y = 0;
-            return !all && this.scrollTo(newScrollParams);
-        }
+            if (this.scrollX && this._scrollConfig.x.x>0) {
+                newScrollParams.x = 0;
+            }
 
-        if (this.scrollX && this._scrollConfig.x.x>0) {
-            newScrollParams.x = 0;
+            return this.scrollTo(newScrollParams);
         }
-
-        return this.scrollTo(newScrollParams);
     }
-
 }

+ 22 - 25
src/components/scroll-view/mixins/status.ts

@@ -1,35 +1,32 @@
-import {Emit,Component,Mixins} from 'vue-property-decorator';
-
-import Props from "../props";
-
 import { emitNames } from '../enum/emit';
 import {ScrollConfig} from "@/components/scroll-view/types/on";
 
-@Component
-export default class ScrollViewStatus extends Mixins(Props) {
-
-    @Emit(emitNames.onScrollUpper) emitScrollUpper(axis:'x'|'y'){
-        return axis;
-    }
-
-    @Emit(emitNames.onScrollLower) emitScrollLower(axis:'x'|'y'){
-        return axis;
-    }
+export default <LibMixins>{
 
-    @Emit(emitNames.onScroll) emitScroll(config:ScrollConfig){
-        return config;
-    }
+    methods:{
+        emitScrollUpper:function (axis:'x'|'y') {
+            return this.$emit(emitNames.onScrollUpper,axis);
+        },
+        emitScrollLower:function (axis:'x'|'y') {
+            return this.$emit(emitNames.onScrollLower,axis);
+        },
+        emitScroll:function (config:ScrollConfig) {
+            return this.$emit(emitNames.onScroll,config);
+        }
+    },
 
-    // x是否执行了到达底部
-    protected _lowerThresholdXStatus:boolean = false;
+    created() {
+        // x是否执行了到达底部
+        this._lowerThresholdXStatus = false;
 
-    // y是否执行了到达底部
-    protected _lowerThresholdYStatus:boolean = false;
+        // y是否执行了到达底部
+        this._lowerThresholdYStatus = false;
 
-    // x是否执行了到达顶部
-    protected _upperThresholdXStatus:boolean = false;
+        // x是否执行了到达顶部
+        this._upperThresholdXStatus = false;
 
-    // y是否执行了到达顶部
-    protected _upperThresholdYStatus:boolean = false;
+        // y是否执行了到达顶部
+        this._upperThresholdYStatus = false;
+    }
 
 }

+ 30 - 0
src/components/scroll-view/mixins/style.ts

@@ -0,0 +1,30 @@
+import {ContainerScrollStyle, ContainerStyle, ContainerStyleFlex} from "$components/scroll-view/types/style";
+
+export default <LibMixins>{
+
+    computed:{
+        // 容器
+        containerStyle():ContainerStyle | ContainerStyleFlex{
+            if (this.flex) {
+                return  {
+                    flexGrow:1,
+                    flexShrink:1
+                }
+            } else {
+                return {
+                    width:this.width?this.width.toString():'100%',
+                    height: this.height?this.height.toString():'100%'
+                }
+            }
+
+        },
+        // 滚动容器
+        scrollContainer():ContainerScrollStyle{
+            return {
+                overflowY: this.scrollY ? 'auto':'hidden',
+                overflowX: this.scrollX ? 'auto':'hidden',
+            }
+        }
+    }
+
+}

+ 47 - 40
src/components/scroll-view/props.ts

@@ -1,6 +1,3 @@
-import {Vue, Prop, Component, PropSync} from 'vue-property-decorator';
-import {ContainerStyle,ContainerStyleFlex,ContainerScrollStyle} from "./types/style";
-
 // 是否为 单位
 const numberEmValidator = function (value:string | number):boolean{
     // 转为字符串
@@ -9,60 +6,70 @@ const numberEmValidator = function (value:string | number):boolean{
     return /^[.\d]+[a-z%]*/i.test(value);
 }
 
-@Component({
-    computed:{
-        // 容器
-        containerStyle(this:ScrollViewProps):ContainerStyle | ContainerStyleFlex{
-            if (this.flex) {
-                return  {
-                    flexGrow:1,
-                    flexShrink:1
-                }
-            } else {
-                return {
-                    width:this.width?this.width.toString():'100%',
-                    height: this.height?this.height.toString():'100%'
-                }
-            }
-
-        },
-        // 滚动容器
-        scrollContainer(this:ScrollViewProps):ContainerScrollStyle{
-            return {
-                overflowY: this.scrollY ? 'auto':'hidden',
-                overflowX: this.scrollX ? 'auto':'hidden',
-            }
-        }
-    }
-})
-export default class ScrollViewProps extends Vue {
+export default {
 
     // 高度
-    @Prop({type:[Number,String],default:'100%',validator:numberEmValidator}) protected height: string | number | undefined;
+    height:{
+        type:[Number,String],
+        default:'100%',
+        validator:numberEmValidator
+    },
 
     // 宽度
-    @Prop({type:[Number,String],default:'100%',validator:numberEmValidator}) protected width: string | number | undefined;
+    width:{
+        type:[Number,String],
+        default:'100%',
+        validator:numberEmValidator
+    },
 
     // 是否启用flex布局系列之 flex ,自动通过 flex-group 占领剩余的空间
-    @Prop({type:Boolean,default:false}) protected flex:boolean | undefined;
+    flex:{
+        type:Boolean,
+        default: false
+    },
 
-    // Y轴滚动
-    @Prop({type:Boolean,default:true}) protected scrollY:boolean | undefined;
+    //  Y轴滚动
+    scrollY:{
+        type:Boolean,
+        default:true
+    },
 
     // X轴滚动
-    @Prop({type:Boolean,default:false}) protected scrollX:boolean | undefined;
+    scrollX:{
+        type:Boolean,
+        default:false
+    },
 
     // x轴位置
-    @PropSync('scrollLeft',{type:[Number,String],default:0,validator:numberEmValidator}) protected useScrollLeft:number | string | undefined;
+    scrollLeft:{
+        type:[Number,String],
+        default:0,
+        validator:numberEmValidator
+    },
 
     // y轴位置
-    @PropSync('scrollTop',{type:[Number,String],default:0,validator:numberEmValidator}) protected useScrollTop:number | string | undefined;
+    scrollTop:{
+        type:[Number,String],
+        default:0,
+        validator:numberEmValidator
+    },
 
     // 距离顶部多远触发
-    @Prop({type:Number,default:0}) upperThreshold: number | undefined;
+    upperThreshold:{
+        type:Number,
+        default:0
+    },
 
     // 距离底部多远触发
-    @Prop({type:Number,default:0}) lowerThreshold:number | undefined
+    lowerThreshold:{
+        type:Number,
+        default:0
+    },
 
+    // 滚动条类型
+    scrollType:{
+        type:String,
+        default: 'none'
+    }
 
 }

+ 12 - 6
src/components/scroll-view/src/main.vue

@@ -2,6 +2,7 @@
   <section
     :style="containerStyle"
     class="scroll-container"
+    :class="['scroll-type-'+scrollType]"
   >
     <!--  滚动视图驱动部分   -->
     <div class="scrollContainer"
@@ -17,16 +18,21 @@
 </template>
 
 <script lang="ts">
-import {Mixins,Component} from "vue-property-decorator";
-// 引入 配置参数
-import On from '../mixins/on';
+import props from '../props';
 
-@Component({
+import mixins from '../mixins';
 
-})
-export default class ScrollView extends Mixins(On) {
+export default {
+
+  name:'scroll-view',
+
+  mixins,
+
+  props
 
 }
+
+
 </script>
 
 <style scoped lang="scss" src="../index.scss"></style>

+ 3 - 0
src/components/scroll-view/types/scroll-type.d.ts

@@ -0,0 +1,3 @@
+declare const enum scrollType {
+    none='none'
+}

+ 3 - 0
src/components/swiper-item/index.ts

@@ -0,0 +1,3 @@
+import main from './src/main.vue';
+
+export default main;

+ 5 - 0
src/components/swiper-item/props.ts

@@ -0,0 +1,5 @@
+export default {
+
+
+
+}

+ 11 - 0
src/components/swiper-item/src/main.vue

@@ -0,0 +1,11 @@
+<template>
+  <div class="swiper-slide"><slot></slot></div>
+</template>
+
+<script>
+export default {
+  name: "swiper-item"
+}
+</script>
+
+<style scoped lang="scss" src="../style.scss"></style>

+ 0 - 0
src/components/swiper-item/style.scss


+ 3 - 0
src/components/swiper/index.ts

@@ -0,0 +1,3 @@
+import main from './src/main.vue';
+
+export default main;

+ 7 - 0
src/components/swiper/props.ts

@@ -0,0 +1,7 @@
+export default {
+
+    swiperOption:{
+        type:Object
+    }
+
+}

+ 33 - 0
src/components/swiper/src/main.vue

@@ -0,0 +1,33 @@
+<template>
+  <div class="swiper-container tab-container" ref="swiper">
+    <div class="swiper-wrapper">
+        <slot></slot>
+    </div>
+  </div>
+</template>
+
+<script>
+import 'swiper/swiper.min.css';
+import props from '../props';
+import Swiper from 'swiper';
+export default {
+  name: "swiper",
+  methods:{
+    installSwiper(){
+      if (this.$refs.swiper) {
+        return new Swiper(this.$refs.swiper,this.swiperOption || this.$attrs);
+      } else {
+        return  undefined;
+      }
+    }
+  },
+  mounted() {
+    this.swiper = this.installSwiper();
+  },
+
+  props
+
+}
+</script>
+
+<style scoped lang="scss" src="../style.scss"></style>

+ 0 - 0
src/components/swiper/style.scss


+ 3 - 0
src/components/tab/index.ts

@@ -0,0 +1,3 @@
+import main from './src/main.vue';
+
+export default main;

+ 119 - 0
src/components/tab/mixins/handle.ts

@@ -0,0 +1,119 @@
+export default <LibMixins>{
+
+    data(){
+      return {
+          vUseTabData:undefined,
+          vUseTabSelect:undefined,
+          swipeOption:undefined
+      }
+    },
+
+
+    computed:{
+
+        swiper(){
+            if (this.$refs.swiper) {
+                return this.$refs.swiper.swiper;
+            }
+          return undefined;
+        },
+
+        // 当前索引的下标
+        vTabSelect:{
+            get(this:any):number{
+
+                if (this.vUseTabSelect === undefined) {
+                    this.vUseTabSelect = this.value === undefined ? this.defaultValue : this.value;
+                }
+
+                return this.vUseTabSelect;
+
+            },
+            set(this:any,value:number) {
+                if (this.vUseTabSelect !== value) {
+                    this.vUseTabSelect = value;
+                    if (this.vUseTabData && this.vUseTabData[value].loading !== true) {
+                        this.vUseTabData[value].loading = true;
+                    }
+                    // 查看是否需要变更 swiper
+                    if (this.swiper && this.swiper.realIndex !== value) {
+                        this.swiper.slideTo(value);
+                    }
+                    // 执行change
+                    if (this.value !== value)  {
+                        return this.$emit('input',value);
+                    }
+
+                }
+            }
+        },
+
+        // 当前使用的 data
+        vTabData():Array<tabDataObject> {
+
+            if (this.vUseTabData === undefined) {
+                this.vUseTabData = (this.data as tabData).map((item,index):tabDataObject=>{
+
+                    let result:tabDataObject;
+                    if (typeof item === 'string') {
+                        result = {
+                            label: item,
+                            slot:'',
+                            loading: false
+                        }
+                    } else {
+                        result = item;
+                    }
+
+                    // 设置 loading
+                    if (!result.loading) {
+                        result.loading = !(index !== this.vTabSelect && this.lazy);
+                    }
+
+                    // 设置插槽
+                    result.slot = result.slot || 'tab-'+index;
+
+                    return result;
+
+                });
+            }
+
+            return this.vUseTabData;
+
+        }
+
+    },
+
+    watch:{
+      value:function (){
+          return this.triggerTo(this.value);
+      }
+    },
+
+    methods:{
+
+        triggerTo(index:number){
+            if (this.vUseTabSelect !== index) {
+                this.vTabSelect = index;
+            }
+        }
+
+    },
+
+    created() {
+
+        this.swipeOption = {
+            initialSlide: this.vTabSelect,
+            allowTouchMove: this.touchMove,
+            speed: this.speed,
+            on:{
+                slideChangeTransitionStart: (swiper: any)=>{
+                    // 设置执行下标
+                    this.vTabSelect = swiper.realIndex;
+                }
+            }
+        }
+    }
+
+
+}

+ 3 - 0
src/components/tab/mixins/index.ts

@@ -0,0 +1,3 @@
+import handle from './handle';
+
+export default [handle];

+ 40 - 0
src/components/tab/props.ts

@@ -0,0 +1,40 @@
+export default {
+
+    // 头部数据
+    data:{
+        type:Array,
+        default:function ():tabData {
+            return ['找陪玩','语聊厅']
+        }
+    },
+
+    // 是否使用懒加载
+    lazy:{
+        type:Boolean,
+        default: true
+    },
+
+    // 当前使用的value
+    value:{
+        type: Number
+    },
+
+    // 默认展示
+    defaultValue:{
+        type:Number,
+        default:0
+    },
+
+    // 是否使用滑动
+    touchMove:{
+        type:Boolean,
+        default: true
+    },
+
+    // 过渡时间
+    speed:{
+        type: Number,
+        default:300
+    }
+
+}

+ 57 - 0
src/components/tab/src/main.vue

@@ -0,0 +1,57 @@
+<template>
+  <section class="tab-container flex">
+
+    <!--   头部组件   -->
+    <slot name="header" :data="vTabData" :trigger="triggerTo">
+      <header class="rowACenter tab-header">
+        <slot
+          name="header-item"
+          v-for="(item,index) in vTabData"
+          :item="item"
+          :index="index"
+          :trigger="triggerTo"
+        >
+          <aside
+              :key="'scroll-index-'+index"
+              :class="{'tab-header-margin':index!==0,'tab-header-select': index === vTabSelect}"
+              class="cursor-pointer relative"
+              @click="triggerTo(index)"
+          >
+            <span class="relative">{{item.label}}</span>
+            <div class="absolute tab-header-line" v-if="index === vTabSelect"></div>
+          </aside>
+        </slot>
+
+      </header>
+    </slot>
+    <!--   内容组件   -->
+    <article class="flex-1 overflow">
+      <swiper v-if="swipeOption" ref="swiper" :swiperOption="swipeOption">
+        <swiper-item
+          v-for="item in vTabData"
+          :key="item.slot"
+        >
+          <slot v-if="item.loading" :name="item.slot">{{item.slot}}--{{item.label}}</slot>
+        </swiper-item>
+      </swiper>
+    </article>
+  </section>
+</template>
+
+<script>
+import props from '../props';
+import mixins from '../mixins';
+import swiper from '$components/swiper';
+import swiperItem from '$components/swiper-item';
+export default {
+  name: "tab",
+  props,
+  mixins,
+  components:{
+    swiper,
+    swiperItem
+  }
+}
+</script>
+
+<style scoped lang="scss" src="../style.scss"></style>

+ 36 - 0
src/components/tab/style.scss

@@ -0,0 +1,36 @@
+/* 容器 */
+.tab-container {
+  width: 100%;
+  height: 100%;
+  overflow: hidden;
+}
+/* 容器 */
+
+/* 头部样式 */
+.tab-header{
+  margin-bottom: 20px;
+  font-size: 18px;
+  line-height: 25px;
+  color: rgba(255,255,255,0.4);
+  font-weight: 400;
+}
+.tab-header-margin{
+  margin-left: 25px;
+}
+.tab-header span {
+  z-index: 10;
+}
+.tab-header-select{
+  font-size: 20px;
+  color: #fff;
+  font-weight: 500;
+}
+.tab-header-line{
+  background: $main-linear;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  height: 13px;
+  border-radius: 7px;
+}
+/* 头部样式 */

+ 14 - 0
src/components/tab/types/tab.d.ts

@@ -0,0 +1,14 @@
+interface tabDataObject {
+
+    // 头部名称
+    label: string,
+
+    // 插槽
+    slot: string,
+
+    // 是否加载
+    loading: boolean
+
+}
+
+type tabData = Array<tabDataObject | string>;

+ 28 - 0
src/components/v-image/api/calculation.ts

@@ -0,0 +1,28 @@
+export default {
+
+    // 获取比例
+    getRatio(size:Size):number{
+        return size.width / size.height;
+    },
+
+    // 根据比例计算高度
+    getRatioHeight(diff:number,ratio:number):number {
+        return diff / ratio;
+    },
+
+    // 根据比例计算宽度
+    getRatioWidth(diff:number,ratio:number):number{
+        return diff * ratio;
+    },
+
+    // 获取左侧间距
+    getLeft(width:number,container: Size):number {
+        return (container.width - width) / 2;
+    },
+
+    // 获取上侧间距
+    getTop(height:number,container: Size):number {
+        return (container.height - height) / 2;
+    },
+
+}

+ 188 - 0
src/components/v-image/api/image.ts

@@ -0,0 +1,188 @@
+import calculation from './calculation';
+
+export default <ImageApi>{
+
+    /*
+    * 缩放模式,不保持纵横比缩放图片,使图片的宽高完全拉伸至填满 image 元素
+    * */
+    scaleToFill:function(params:ImageParams):ImageExport{
+        return {
+            width: '100%',
+            height: '100%',
+            top:0,
+            left:0
+        }
+    },
+
+    center:function (params:ImageParams):ImageExport {
+
+        let width = 0;
+        let height = 0;
+
+        // 如果全部小于该尺寸触发
+        if (
+            params.container.width >= params.image.width
+            &&
+            params.container.height >= params.image.height
+        ) {
+            width = params.image.width;
+            height = params.image.height;
+        } else {
+
+            // 否则 选择最大的边进行 重新设置
+            let isHeightMax = params.image.height > params.image.width;
+
+            // 获取
+            let ratio = calculation.getRatio(params.image);
+
+            // 对长边进行设置
+            if (isHeightMax) {
+                height = params.container.height;
+                width = calculation.getRatioWidth(height,ratio);
+            } else {
+                width = params.container.width;
+                height = calculation.getRatioHeight(width,ratio);
+            }
+
+        }
+
+        return {
+            width: width,
+            height:height,
+            left: calculation.getLeft(width,params.container),
+            top: calculation.getTop(height,params.container)
+        }
+    },
+
+    /*
+    * 缩放模式,保持纵横比缩放图片,可以完整地将图片显示出来。
+    * */
+    cover:function (params:ImageParams):ImageExport {
+
+        // 获取
+        let ratio = calculation.getRatio(params.image);
+
+        // 设置宽度
+        let width = params.container.width;
+
+        // 计算高度
+        let height = calculation.getRatioHeight(width,ratio);
+
+
+        // 如果高度小于 视图高度
+        if (height <  params.container.height) {
+            // 计算差
+            let diff = params.container.height - height;
+            // 重新设置高度
+            height = params.container.height;
+            // 增加宽度比例
+            width += calculation.getRatioWidth(diff,ratio);
+        }
+
+        return {
+            width: width,
+            height:height,
+            left: calculation.getLeft(width,params.container),
+            top: calculation.getTop(height,params.container)
+        }
+
+    },
+
+    /* 缩放模式,保持纵横比缩放图片,使图片的长边能完全显示出来。也就是说,可以完整地将图片显示出来。但是不已中间为布局 */
+    topCover:function (params:ImageParams):ImageExport {
+
+        // 获取
+        let ratio = calculation.getRatio(params.image);
+
+        // 设置宽度
+        let width = params.container.width;
+
+        // 计算高度
+        let height = calculation.getRatioHeight(width,ratio);
+
+
+        // 如果高度小于 视图高度
+        if (height <  params.container.height) {
+            // 计算差
+            let diff = params.container.height - height;
+            // 重新设置高度
+            height = params.container.height;
+            // 增加宽度比例
+            width += calculation.getRatioWidth(diff,ratio);
+        }
+
+        return {
+            width: width,
+            height:height,
+            left: calculation.getLeft(width,params.container),
+            top: 0
+        }
+
+    },
+
+    /* 缩放模式,保持纵横比缩放图片,只保证图片的短边能完全显示出来。也就是说,图片通常只在水平或垂直方向是完整的,另一个方向将会发生截取。 */
+    aspectFill:function (params:ImageParams):ImageExport{
+
+        // 计算比例
+        let ratio = calculation.getRatio(params.image);
+        // 容器
+        let container = params.container;
+        // 宽度是否为短边
+        let widthShort = container.width <= container.height;
+
+        let width = widthShort ? container.width : calculation.getRatioWidth(container.height,ratio);
+
+        let height = widthShort ? calculation.getRatioHeight(container.width,ratio) : container.height;
+
+        return {
+            width,
+            height,
+            left: calculation.getLeft(width,container),
+            top: calculation.getTop(height,container)
+        }
+    },
+
+    /* 缩放模式,保持纵横比缩放图片,使图片的长边能完全显示出来。也就是说,可以完整地将图片显示出来。 */
+    aspectFit:function (params:ImageParams):ImageExport{
+
+        // 计算比例
+        let ratio = calculation.getRatio(params.image);
+        // 容器
+        let container = params.container;
+        // 宽度是否为长边
+        let widthLogin = container.width >= container.height;
+        // 宽度
+        let width = widthLogin ? container.width : calculation.getRatioWidth(container.height,ratio);
+        // 高度
+        let height = widthLogin ? calculation.getRatioHeight(container.width,ratio) : container.height;
+
+        return {
+            width,
+            height,
+            left: calculation.getLeft(width,container),
+            top: calculation.getTop(height,container)
+        }
+    },
+
+    /* 缩放模式,宽度不变,高度自动变化,保持原图宽高比不变 */
+    widthFix:function (params:ImageParams):ImageExport{
+
+        return {
+            width: params.container.width,
+            height: calculation.getRatioHeight(params.container.width,calculation.getRatio(params.image)),
+            left: 0,
+            top: 0
+        }
+    },
+
+    /* 缩放模式,高度不变,宽度自动变化,保持原图宽高比不变 */
+    heightFix:function (params:ImageParams):ImageExport{
+        return {
+            width: calculation.getRatioWidth(params.container.height,calculation.getRatio(params.image)),
+            height: params.container.height,
+            left: 0,
+            top: 0
+        }
+    }
+
+}

+ 18 - 0
src/components/v-image/api/unit.ts

@@ -0,0 +1,18 @@
+export default {
+
+    unit(size:ImageExport,unit:string='px',units:Array<string>=['width','height','top','left','right','bottom']):ImageExport{
+
+        units.map((item:string)=> {
+             // @ts-ignore
+            if (typeof size[item] ==='number') {
+                // @ts-ignore
+                size[item] += unit;
+            }
+
+        });
+
+        return size;
+
+    }
+
+}

+ 4 - 0
src/components/v-image/const/container.ts

@@ -0,0 +1,4 @@
+export default <ContainerConstD>{
+    'widthFix':['height'],
+    'heightFix':['width']
+}

+ 8 - 0
src/components/v-image/const/status.ts

@@ -0,0 +1,8 @@
+enum ImageStatus {
+    success,
+    loading,
+    fail,
+    none
+}
+
+export default ImageStatus;

+ 3 - 0
src/components/v-image/index.ts

@@ -0,0 +1,3 @@
+import main from './lib/main.vue';
+
+export default main;

+ 76 - 0
src/components/v-image/lib/main.vue

@@ -0,0 +1,76 @@
+<template>
+	<div
+		class="v-image"
+		ref="container"
+		:style="containerStyle"
+	>
+		<!-- 视频加载器 -->
+		<video
+			v-if="videoSrc"
+		   ref="video"
+		   preload="auto"
+		   style="display: none"
+		   @loadeddata="videoLoad"
+		   :src="videoSrc"
+		></video>
+
+		<div class="v-image-container"
+			:style="containerImageStyle"
+		>
+			<img
+				v-if="imageSrc&&!useVideo&&status !== imageStatus.fail"
+				:src="imageSrc"
+				@load="onLoadSuccess"
+				@error="onLoadFail"
+				ref="image"
+				alt="image"
+				class="v-image-style"
+				:style="imageStyle"
+				:class="imageClassStyle"
+			/>
+			<video
+				v-if="imageSrc&&useVideo&&status !== imageStatus.fail"
+				:src="imageSrc"
+				:poster="usePoster"
+				:preload="usePoster?'meta':'auto'"
+				:controls="controls"
+				webkit-playsinline
+				controlslist="nodownload nofullscreen noremoteplayback"
+				oncontextmenu="return false"
+				@loadeddata="onLoadSuccess"
+				@error="onLoadFail"
+				ref="image"
+				class="v-image-style"
+				:style="imageStyle"
+				:class="imageClassStyle"
+			></video>
+
+			<aside class="v-image-icon" :style="containerIconStyle" v-if="icon">
+				<v-image
+					:src="icon"
+					backgroundColor="transparent"
+					class="v-image-icon-image"
+					:mode="iconMode"
+				/>
+			</aside>
+
+		</div>
+		<div class="v-image-slot" :class="{'v-image-pointer':pointer}">
+			<slot></slot>
+			<slot name="success" v-if="status === imageStatus.success"></slot>
+			<slot name="fail" v-if="status === imageStatus.fail"></slot>
+			<slot name="loading" v-if="status === imageStatus.loading"></slot>
+		</div>
+	</div>
+</template>
+<script lang="ts">
+import mixins from '../mixins/index';
+import props from '../props';
+export default {
+	name: "v-image",
+    mixins,
+    props,
+}
+</script>
+<style scoped lang="scss" src="../style/index.scss"></style>
+<style lang="scss" src="../style/global.scss"></style>

+ 19 - 0
src/components/v-image/mixins/el.ts

@@ -0,0 +1,19 @@
+export default <LibMixins>{
+
+    computed:{
+
+        container(){
+            return this.$refs.container;
+        },
+
+        image(){
+            return this.$refs.image;
+        },
+
+        videoEl(){
+            return this.$refs.video
+        }
+
+    }
+
+}

+ 31 - 0
src/components/v-image/mixins/icon.ts

@@ -0,0 +1,31 @@
+export default <LibMixins>{
+
+
+    computed:{
+
+        icon(){
+
+            if (this.defaultIcon && this.status === this.imageStatus.loading) {
+                return this.defaultIcon;
+            } else if ( (this.defaultIcon || this.failIcon) && this.status === this.imageStatus.fail) {
+                return this.failIcon || this.defaultIcon;
+            } else {
+                return undefined;
+            }
+
+        },
+
+        iconMode(){
+            if (this.defaultIcon && this.status === this.imageStatus.loading) {
+                return this.defaultMode || this.mode;
+            } else if ( (this.defaultIcon || this.failIcon) && this.status === this.imageStatus.fail) {
+                return this.failMode || this.mode;
+            } else {
+                return undefined;
+            }
+        }
+
+    }
+
+
+}

+ 16 - 0
src/components/v-image/mixins/index.ts

@@ -0,0 +1,16 @@
+// 状态变更
+import status from './status';
+// 样式提供表
+import style from './style';
+// size 获取
+import size from './size';
+// 标签资源
+import el from './el';
+// 图标
+import icon from './icon';
+// 视频
+import src from './src';
+// 引入视频播放
+import video from './video';
+
+export default <LibMixins>[status,style,size,el,icon,src,video];

+ 183 - 0
src/components/v-image/mixins/size.ts

@@ -0,0 +1,183 @@
+export default <LibMixins> {
+
+    data(){
+      return {
+          // 图片
+          imageSize: undefined,
+          // 外层容器
+          containerSize: undefined,
+          // 图标
+          iconSize: undefined
+      }
+    },
+
+    computed:{
+        useIconSize():Size | undefined{
+            if (this.iconSize && this.iconSize.width > 0 && this.imageSize.height > 0) {
+                return this.iconSize;
+            } else {
+                return undefined;
+            }
+        },
+        useImageSize():Size | undefined {
+            if (this.imageSize && this.imageSize.width > 0 && this.imageSize.height > 0) {
+                return this.imageSize;
+            } else {
+                return undefined;
+            }
+        },
+        useContainerSize(): Size | undefined {
+            if (this.containerSize && this.containerSize.width > 0 || this.containerSize && this.containerSize.height > 0) {
+                return this.containerSize;
+            } else {
+                return undefined;
+            }
+        },
+
+        useGetBodySize():Boolean{
+            return this.getSizeFailBody && this.show === undefined;
+        }
+    },
+
+    watch:{
+      src:function () {
+        if (this.useSrc !== this.src) {
+            return this.installSize();
+        }
+      },
+      show(value:boolean){
+          if (value === true && this.container && this.containerSize && this.containerSize.width <= 0 && this.containerSize.height <= 0) {
+              this.containerSize = this.getElementSize(this.container);
+
+              /* 如果图片加载成功 */
+              if (
+                  this.status === this.imageStatus.success
+                  &&
+                  this.imageSize
+                  &&
+                  this.imageSize.width <= 0
+                  &&
+                  this.imageSize.height <= 0
+              ) {
+                  this.imageSize = this.getElementSize(this.image);
+              }
+          }
+      }
+    },
+
+    methods:{
+
+        /*
+        *   返回 预设置 size
+        *   如果没有开启预设置且高度和宽度有一个小于 0 将为 undefined
+        * */
+        getPresetSize(): Size | undefined{
+            if(this.preset && this.presetWidth > 0 && this.presetHeight > 0){
+                return {
+                    width: this.presetWidth,
+                    height: this.presetHeight
+                };
+            } else {
+                return undefined;
+            }
+
+        },
+
+        /*
+        *   返回 Element 标签的 Size
+        * */
+        getElementSize(el:Element): Size | undefined {
+            if(el){
+
+                let size:Size = {
+                    width: el.clientWidth,
+                    height: el.clientHeight
+                };
+
+                // 如果都不可见触发
+                if (size.width <= 0 && size.height <= 0) {
+
+                    // 如果启用去body里获取
+                    if (this.useGetBodySize) {
+                        size = this.getBodySize(el);
+                    }
+
+                }
+
+                // 返回格式大小
+                return size
+            } else {
+                return undefined;
+            }
+        },
+
+        /* 通过全局获得尺寸
+        *   该方式针对 百分百获取存在问题
+        * */
+        getBodySize(el:Element):Size | undefined{
+
+
+            if (el) {
+                // @ts-ignore
+                let useEl:Element = el.cloneNode();
+                useEl.setAttribute('class',el.getAttribute('class') + ' __v-image-get-size');
+                document.body.append(useEl);
+                let size:Size = {
+                    width: useEl.clientWidth,
+                    height: useEl.clientHeight
+                };
+                document.body.removeChild(useEl);
+
+                return size;
+            } else {
+                return undefined;
+            }
+
+
+        },
+        /* 通过全局获得尺寸 */
+
+        /*
+        *   设置 图片 尺寸
+        * */
+        setImageSize(el:Element): boolean {
+          let size: Size | undefined = this.getElementSize(el);
+          if(size) {
+              // 设置 图片尺寸
+              this.imageSize = size;
+          }
+
+          return size !== undefined;
+
+        },
+
+        /*
+        *   初始化size
+        * */
+        installSize() : void{
+            // 设置 容器 大小
+            this.containerSize = this.getElementSize(this.container);
+            // 获取预设置
+            let presetSize = this.getPresetSize();
+            // 如果存在预设 设置 图片 size
+            if (presetSize)  this.imageSize = presetSize;
+            else this.imageSize = undefined;
+            // 初始化样式状态
+            this.setStatus(this.imageStatus.loading);
+            // 设置本次加载的src
+            return this.setImageSrc(this.src);
+        }
+
+    },
+
+    mounted(): void {
+        return this.installSize();
+    },
+
+    created(): void {
+        if (this.initialization) {
+            this.setImageSrc(this.src,false);
+        }
+    }
+
+}

+ 115 - 0
src/components/v-image/mixins/src.ts

@@ -0,0 +1,115 @@
+import url from '$utils/tool/url';
+
+export default <LibMixins>{
+
+    data(){
+      return {
+          videoSrc:'',
+          imageSrc:'',
+          useVideo: false
+      }
+    },
+
+    computed:{
+        usePoster(){
+            if (this.baseURL) {
+                return url.addBaseURL(this.poster,this.baseURL);
+            } else {
+                return this.poster;
+            }
+        }
+    },
+
+    videoSuffix:{
+        'mp4':1,
+        'MP4':1,
+        'ogg':1,
+        'OGG':1,
+        'webm':1,
+        'webM':1
+    },
+
+    methods:{
+
+        setImageSrc(src:string,async:boolean=true){
+
+            if (!src) return;
+
+            if (src.indexOf('data:') === 0) {
+                if (src.indexOf('data:video') === 0) {
+                    return this.setVideoSrc(src);
+                }
+            } else {
+                let suffix:string = src.substr(src.lastIndexOf('.')+1);
+
+                // 执行添加 url
+                if (this.baseURL) {
+                    src = url.addBaseURL(src,this.baseURL);
+                }
+
+                // if (this.baseURL && src.indexOf('http') !== 0) {
+                //     src= this.baseURL + src;
+                // }
+
+                if (this.$options.videoSuffix[suffix]) {
+                    return this.setVideoSrc(src,async);
+                }
+            }
+
+            // 如果不为视频
+            if (this.useVideo) this.useVideo = false;
+
+            if (async){
+                return this.$nextTick(()=> this.imageSrc = src);
+            } else {
+                this.imageSrc = src
+            }
+
+        },
+
+        setVideoSrc(src:string,async:boolean=true){
+            if (!this.useVideo) this.useVideo = true;
+            if (this.video || this.poster) {
+                if (async) {
+                    return this.$nextTick(()=> this.imageSrc = src);
+                } else {
+                    this.imageSrc = src;
+                }
+
+            } else {
+                if (async) {
+                    return this.$nextTick(()=> this.videoSrc = src);
+                } else {
+                    this.videoSrc = src;
+                }
+
+            }
+        },
+
+        videoLoad(){
+            let canvas:HTMLCanvasElement = document.createElement('canvas');
+            canvas.width = this.videoEl.videoWidth;
+            canvas.height = this.videoEl.videoHeight;
+            // @ts-ignore
+            canvas.getContext('2d').drawImage(this.videoEl,0,0,canvas.width,canvas.height);
+
+            try {
+                let src:string = canvas.toDataURL('image/png');
+                this.setImageSrc(src);
+            } catch (e) {
+
+                if (this.videoMode) {
+                    let src =  this.videoSrc;
+                    this.$nextTick(()=> this.imageSrc = src)
+                }
+            }
+
+            // @ts-ignore
+            canvas = null;
+            this.videoSrc = '';
+
+        }
+
+    }
+
+}

+ 48 - 0
src/components/v-image/mixins/status.ts

@@ -0,0 +1,48 @@
+import imageStatus from '../const/status';
+
+export default <LibMixins> {
+
+    data:function () {
+        return {
+            // 枚举 类型
+            imageStatus,
+            // 当前图片加载的状态
+            status: imageStatus.loading
+        }
+    },
+
+    methods:{
+        // 设置加载状态
+        setStatus(status:imageStatus):boolean{
+            if (this.status !== status) {
+                this.status = status;
+
+                if (status === imageStatus.success){
+                    this.$emit('success');
+                } else if (status === imageStatus.fail) {
+                    this.$emit('fail');
+                }
+
+                return true;
+            }
+            return false;
+        },
+        // 加载成功后触发
+        onLoadSuccess(): void {
+            // 设置图片尺寸
+            this.setImageSize(this.image);
+            // 如果为视频触发
+            if (this.video) this.image.disablePictureInPicture = true;
+            // 设置图片尺寸成功后 设置 状态为 加载成功
+            return this.$nextTick(()=> this.setStatus(imageStatus.success));
+        },
+        // 加载失败后触发
+        onLoadFail(): void {
+
+            //  设置 状态为 加载失败
+            return this.$nextTick(()=> this.setStatus(imageStatus.fail));
+        }
+
+    }
+
+};

+ 113 - 0
src/components/v-image/mixins/style.ts

@@ -0,0 +1,113 @@
+import image from '../api/image';
+
+import unit from '../api/unit';
+
+import containerMode from '../const/container';
+
+export default <LibMixins> {
+
+    computed:{
+
+        /* 为图片设置的样式表 */
+        imageStyle(){
+            if (this.useImageSize && this.useContainerSize) {
+                return unit.unit(image[this.mode]({
+                    image: this.useImageSize,
+                    container: this.useContainerSize
+                }));
+            }
+        },
+
+        /* 为图标设置的样式 */
+        iconStyle(){
+            if (this.useIconSize && this.useContainerSize) {
+                return unit.unit(image[this.mode]({
+                    image: this.useIconSize,
+                    container: this.useContainerSize
+                }));
+            }
+        },
+
+        /* 容器样式 */
+        containerStyle(){
+            if (this.imageStyle && containerMode[this.mode]) {
+                let size:object = {};
+                containerMode[this.mode].map((item:string)=>{
+                    // @ts-ignore
+                    size[item] = this.imageStyle[item];
+                });
+                return size;
+            }
+        },
+
+        /* 提供给image的状态class */
+        imageClassStyle(){
+            switch (this.status) {
+                case this.imageStatus.loading: return ['v-image-start',this.loadClass,this.entryClass,this.imageClass];
+                case this.imageStatus.success: return [this.levelClass,this.imageClass];
+                default: return undefined;
+            }
+        },
+
+        /* 图片外部容器 */
+        containerImageStyle(){
+
+            let style:any = {};
+
+            if (this.radius) {
+                style.borderRadius = this.radius;
+            }
+
+            if (
+                this.status === this.imageStatus.success
+                &&
+                this.mergeSuccessColor
+            ) {
+                style.backgroundColor = this.getBackgroundColor(this.mergeSuccessColor);
+            } else if (!this.icon && this.containerIconStyle) {
+                style.backgroundColor = this.containerIconStyle.backgroundColor;
+            }
+
+            return style;
+
+        },
+
+        /* 图标容器颜色 */
+        containerIconStyle(){
+            if (
+                this.status === this.imageStatus.loading
+                &&
+                (!this.defaultIcon || this.defaultIcon && this.mergeDefaultColor)
+                ||
+                this.status === this.imageStatus.fail
+                &&
+                (!this.failIcon && !this.defaultIcon || (this.failIcon || this.defaultIcon) && this.mergeFailColor)
+            ) {
+
+                return {
+                    backgroundColor: this.getBackgroundColor(this.status === this.imageStatus.loading ?
+                        this.mergeDefaultColor
+                        :
+                        this.mergeFailColor
+                    )
+                }
+
+            }
+        }
+
+
+    },
+
+    methods:{
+        getBackgroundColor(color:boolean | string) : string{
+            return typeof color === 'boolean' ? this.backgroundColor : color;
+        }
+    },
+
+    created(){
+
+
+
+    }
+
+};

+ 29 - 0
src/components/v-image/mixins/video.ts

@@ -0,0 +1,29 @@
+export default <LibMixins>{
+
+
+    methods:{
+        // 播放视频
+        play():Promise<void> | void{
+            if (this.video) {
+               return this.image.play();
+            }
+        },
+
+        // 暂停
+        pause():void{
+            if (this.video) {
+                return this.image.pause();
+            }
+        },
+
+        // 获取自定义
+        getVideoElement():Element | void{
+            if (this.video) {
+                return this.image;
+            }
+        }
+    }
+
+
+
+}

+ 185 - 0
src/components/v-image/props.ts

@@ -0,0 +1,185 @@
+export default {
+
+    /*
+    *   图片地址
+    *   如果为视频默认截取视频第一张, 注意 针对 跨域等问题 可根据 videoMode 最终采用 video 加载
+    * */
+    src:{
+        type: String,
+        default:''
+    },
+
+    /* 设置baseURL */
+    baseURL:{
+        type: String,
+        default:undefined
+    },
+
+    /* 如果为视频 设置为预设置第一帧图像 */
+    poster:{
+        type: String,
+        default:undefined
+    },
+
+    /* 直接使用video */
+    video:{
+      type: Boolean,
+      default: false
+    },
+
+    /* 如果为视频且存在跨域是否启用视频加载第一帧 */
+    videoMode:{
+        type:Boolean,
+        default:false
+    },
+
+    /* 立刻设置路径 */
+    initialization:{
+      type: Boolean,
+      default: false
+    },
+
+    /*
+    *   加载时的背景颜色
+    * */
+    backgroundColor:{
+        type: String,
+        default:'#eee'
+    },
+
+    /* 加载成功是否允许背景色一直存在 */
+    mergeSuccessColor:{
+        type: [Boolean,String],
+        default: false
+    },
+
+    /* 提供默认的的图标后 是否允许使用背景色 */
+    mergeDefaultColor:{
+      type: [Boolean,String],
+      default: true
+    },
+
+    /*
+    *  默认的的图标 或者 加载失败的图标(如果不提供加载失败)
+    * */
+    defaultIcon:{
+        type: String
+    },
+
+    /* 提供加载失败的的图标后 是否允许使用背景色 */
+    mergeFailColor:{
+        type: [Boolean,String],
+        default: true
+    },
+
+    /*
+    *  加载失败的图标
+    * */
+    failIcon:{
+        type: String
+    },
+
+    /*
+     *  预设置 图片宽度和高度
+     *      设置后如果外层不指定 宽度和高度 将 自动根据适配 为 预设置宽度 高度
+     * */
+    preset:{
+        type: Boolean,
+        default: false
+    },
+
+    /*
+    *  预设置 宽度
+    * */
+    presetWidth:{
+        type: Number
+    },
+
+    /*
+     *  预设置 高度
+    */
+    presetHeight:{
+        type:Number
+    },
+
+    /* 圆角 */
+    radius:{
+        type: String,
+        value: '0'
+    },
+
+    /*
+    *   加载中的样式
+    * */
+    loadClass:{
+        type: String
+    },
+
+    /* 一直存在的 image 控制 class */
+    imageClass:{
+        type: String
+    },
+
+    /*
+    *   img 初始化的 样式 此时不显示 请注意 设置 必要的 初始化样式
+    * */
+    entryClass:{
+        type: String
+    },
+
+    /*
+    *   img 加载成功后的 样式 执行动作 根据 duration 是否为 真 来绝对是否一直保留
+    * */
+    levelClass:{
+        type: String
+    },
+
+    /* 是否显示视频控件 */
+    controls:{
+      type: Boolean,
+      default: false
+    },
+
+    /* 是否开启事件穿透 */
+    pointer:{
+      type:Boolean,
+      default: false
+    },
+
+    /* 图片模式
+    *  scaleToFill	缩放模式,不保持纵横比缩放图片,使图片的宽高完全拉伸至填满 image 元素
+    *  aspectFit	缩放模式,保持纵横比缩放图片,使图片的长边能完全显示出来。也就是说,可以完整地将图片显示出来。
+    *  aspectFill	缩放模式,保持纵横比缩放图片,只保证图片的短边能完全显示出来。也就是说,图片通常只在水平或垂直方向是完整的,另一个方向将会发生截取。
+    *  widthFix	缩放模式,宽度不变,高度自动变化,保持原图宽高比不变
+    *  heightFix	缩放模式,高度不变,宽度自动变化,保持原图宽高比不变
+    *  cover 缩放模式,保持纵横比缩放图片,可以完整地将图片显示出来。
+    *  center 缩放模式 保证图片会居中显示出来
+    * */
+    mode:{
+        type:String,
+        default:'cover'
+    },
+
+    /* 默认图标 */
+    defaultMode:{
+        type: String
+    },
+
+    /* 失败的图标 */
+    failMode:{
+        type: String
+    },
+
+    /* 如果外层尺寸为 display:none 等不可见的形式 默认采用body 执行获取 */
+    getSizeFailBody:{
+        type: Boolean,
+        default: true
+    },
+
+    /* 如果不希望通过body 获取 可以指定当前的显示状态 会根据显示的状态来设置获取 */
+    show:{
+        type: Boolean,
+        default:undefined
+    }
+
+}

+ 6 - 0
src/components/v-image/style/global.scss

@@ -0,0 +1,6 @@
+.__v-image-get-size {
+  opacity: 0;
+  position: absolute;
+  z-index: -99999;
+  visibility: hidden;
+}

+ 40 - 0
src/components/v-image/style/index.scss

@@ -0,0 +1,40 @@
+.v-image{
+  display: block;
+  width: 100%;
+  @extend .v-image-relative;
+}
+
+.v-image-relative{
+  position: relative;
+}
+
+.v-image-container{
+  width: 100%;
+  height: 100%;
+  @extend .v-image-relative;
+  overflow: hidden;
+}
+
+.v-image-style,.v-image-icon,.v-image-slot{
+  position: absolute;
+}
+
+.v-image-icon,.v-image-slot{
+  top: 0;
+  right: 0;
+  left: 0;
+  bottom: 0;
+}
+
+.v-image-start {
+  visibility: hidden;
+}
+
+.v-image-icon-image{
+  width: 100%;
+  height: 100%;
+}
+
+.v-image-pointer{
+  pointer-events: none;
+}

+ 3 - 0
src/components/v-image/types/container-const.d.ts

@@ -0,0 +1,3 @@
+interface ContainerConstD {
+    [propertys:string]:Array<string>
+}

+ 27 - 0
src/components/v-image/types/image-api.d.ts

@@ -0,0 +1,27 @@
+interface ImageApi {
+
+    [propertys:string]: ImageApiExportFunction
+
+}
+
+
+interface ImageExport {
+    width: number | string,
+    height: number | string,
+    top ?: number | string,
+    left ?: number | string,
+    bottom ?: number | string,
+    right ?: number | string
+}
+
+interface ImageParams {
+
+    image: Size,
+
+    container: Size
+
+}
+
+interface ImageApiExportFunction {
+    (params:ImageParams): ImageExport
+}

+ 7 - 0
src/components/v-image/types/size.d.ts

@@ -0,0 +1,7 @@
+interface Size {
+
+    width:number,
+
+    height:number
+
+}

+ 7 - 0
src/config/config.ts

@@ -0,0 +1,7 @@
+export default <Config>{
+
+    baseApiURL:'',
+
+    baseURL:'',
+
+}

+ 3 - 0
src/config/index.ts

@@ -0,0 +1,3 @@
+import config from './config';
+
+export default config;

+ 12 - 0
src/pages/home/data/tabData.ts

@@ -0,0 +1,12 @@
+export default [
+    {
+        label:'找陪玩',
+        component:'view-play-with',
+        slot:'tab-0'
+    },
+    {
+        label:'语聊厅',
+        component:'view-play-with',
+        slot:'tab-1'
+    }
+] as LibDataArray

+ 36 - 3
src/pages/home/src/main.vue

@@ -2,7 +2,24 @@
   <layout-entry>
     <section class="screen flex">
       <view-header></view-header>
-      <section class="flex-1"></section>
+      <section class="flex-1 row container">
+        <article class="flex-1 content-container">
+          <tab :data="tabData">
+            <template
+                v-for="(item,index) in tabData"
+                v-slot:[item.slot]
+            >
+              <component
+                  :key="'tab-component-'+index"
+                  :is="item.component"
+              ></component>
+            </template>
+
+          </tab>
+        </article>
+        <view-menu></view-menu>
+
+      </section>
       <view-footer></view-footer>
     </section>
 
@@ -14,15 +31,31 @@ import {
   layoutEntry
 } from '$layout';
 import {
+  tab
+} from '$components';
+import {
   viewHeader,
-  viewFooter
+  viewFooter,
+  viewMenu,
+  viewPlayWith
 } from '$views';
+
+import tabData from "../data/tabData";
+
 export default {
   name: "home",
+  data(){
+    return {
+      tabData
+    }
+  },
   components:{
     layoutEntry,
     viewHeader,
-    viewFooter
+    viewFooter,
+    tab,
+    viewMenu,
+    viewPlayWith
   }
 }
 </script>

+ 11 - 0
src/pages/home/style.scss

@@ -0,0 +1,11 @@
+/* 最外层容器 */
+.container{
+  padding-right: 15px;
+  padding-top: 20px;
+}
+/* 最外层容器 */
+/* 内容容器 */
+.content-container{
+  padding: 0 35px 0 20px;
+}
+/* 内容容器 */

+ 28 - 0
src/types/config.d.ts

@@ -0,0 +1,28 @@
+
+interface URL {
+    baseURL: string,
+    baseApiURL: string
+}
+
+declare const enum ConfigModeEnum {
+    default
+}
+
+type ConfigMode  = {
+    [key in ConfigModeEnum]:URL
+}
+
+
+interface Config {
+
+    // 安装
+    install?:Function,
+
+    // 模式
+    configMode?: ConfigMode,
+
+    baseURL: string,
+    baseApiURL: string
+
+}
+

+ 3 - 0
src/types/lib.data.d.ts

@@ -30,6 +30,9 @@ interface LibData extends TriggerData{
     // 规则
     rules?: LibDataRules | string,
 
+    // 组件
+    component?:string,
+
     [propName:string]:any
 
 }

+ 13 - 0
src/types/lib.mixins.d.ts

@@ -0,0 +1,13 @@
+interface LibMixins {
+
+    computed?:{
+        [propName:string]:Record<string, any> | LibMixinsComputed
+    };
+
+    [propName:string]: any
+
+}
+
+interface LibMixinsComputed {
+    (this:any):any
+}

+ 15 - 0
src/utils/tool/url.ts

@@ -0,0 +1,15 @@
+import $config from '$config/index.ts';
+export default {
+
+    addBaseURL(url:string,baseURL:string=$config.baseURL):string{
+
+        if (!url) return url;
+
+        if (url.indexOf('data:') < 0 && url.indexOf('http') < 0) {
+            url = baseURL + url;
+        }
+        return url;
+
+    }
+
+}

+ 9 - 1
src/views/index.ts

@@ -1,3 +1,11 @@
+// 头部
 export { default as viewHeader } from './view-header';
 
-export { default as viewFooter } from './view-footer';
+// 底部
+export { default as viewFooter } from './view-footer';
+
+// 菜单栏
+export { default as viewMenu } from './view-menu';
+
+// 陪玩模块
+export { default as viewPlayWith } from './view-play-with';

+ 42 - 0
src/views/view-cate/data/cate.ts

@@ -0,0 +1,42 @@
+export default [
+    {
+        label:'收藏',
+        icon:require('../images/cate/1.png')
+    },
+    {
+        label:'热门',
+        icon:require('../images/cate/2.png')
+    },
+    {
+        label:'派单',
+        icon:require('../images/cate/3.png')
+    },
+    {
+        label:'交友',
+        icon:require('../images/cate/4.png')
+    },
+    {
+        label:'和平精英',
+        icon:require('../images/cate/5.png')
+    },
+    {
+        label:'王者荣耀',
+        icon:require('../images/cate/6.png')
+    },
+    {
+        label:'英雄联盟',
+        icon:require('../images/cate/7.png')
+    },
+    {
+        label:'绝地求生',
+        icon:require('../images/cate/8.png')
+    },
+    {
+        label:'云顶之弈',
+        icon:require('../images/cate/9.png')
+    },
+    {
+        label:'更多',
+        icon:require('../images/cate/10.png')
+    }
+] as LibDataArray

BIN
src/views/view-cate/images/cate/1.png


BIN
src/views/view-cate/images/cate/10.png


BIN
src/views/view-cate/images/cate/2.png


BIN
src/views/view-cate/images/cate/3.png


BIN
src/views/view-cate/images/cate/4.png


BIN
src/views/view-cate/images/cate/5.png


BIN
src/views/view-cate/images/cate/6.png


BIN
src/views/view-cate/images/cate/7.png


BIN
src/views/view-cate/images/cate/8.png


BIN
src/views/view-cate/images/cate/9.png


+ 3 - 0
src/views/view-cate/index.ts

@@ -0,0 +1,3 @@
+import main from './src/main.vue';
+
+export default main;

+ 79 - 0
src/views/view-cate/src/main.vue

@@ -0,0 +1,79 @@
+<template>
+  <article class="row between relative">
+    <aside
+        v-for="(item,index) in cateData"
+        class="cate-item center cursor-pointer"
+        :key="'view-cate-'+index"
+        @click="value = !value"
+    >
+      <v-image mode="center" backgroundColor="transparent"
+               class="cate-icon"
+               :src="item.icon"
+      ></v-image>
+      <div class="cate-item-label">{{item.label}}</div>
+    </aside>
+    <transition name="skewHeight">
+      <section v-show="value" class="absolute view-cate-container">
+        <scroll-view>
+          <header class="view-cate-header">
+            <div class="view-cate-line"></div>
+            <div>游戏类</div>
+          </header>
+          <article class="row wrap cate-all">
+            <aside
+                v-for="(item,index) in [...cateData,...cateData]"
+                class="cate-all-item center cursor-pointer"
+                :key="'view-cate-'+index"
+            >
+              <v-image mode="center" backgroundColor="transparent"
+                       class="cate-all-icon"
+                       :src="item.icon"
+              ></v-image>
+              <div class="cate-all-item-label">{{item.label}}</div>
+            </aside>
+          </article>
+          <header class="view-cate-header">
+            <div class="view-cate-line"></div>
+            <div>娱乐类</div>
+          </header>
+          <article class="row wrap cate-all">
+            <aside
+                v-for="(item,index) in [...cateData,...cateData]"
+                class="cate-all-item center cursor-pointer"
+                :key="'view-cate-'+index"
+            >
+              <v-image mode="center" backgroundColor="transparent"
+                       class="cate-all-icon"
+                       :src="item.icon"
+              ></v-image>
+              <div class="cate-all-item-label">{{item.label}}</div>
+            </aside>
+          </article>
+        </scroll-view>
+      </section>
+    </transition>
+  </article>
+</template>
+
+<script>
+import cateData from '../data/cate';
+import {
+  vImage,
+  scrollView
+} from '$components';
+export default {
+  name: "view-cate",
+  data(){
+    return {
+      cateData,
+      value:false
+    }
+  },
+  components:{
+    vImage,
+    scrollView
+  }
+}
+</script>
+
+<style scoped lang="scss" src="../style.scss"></style>

+ 109 - 0
src/views/view-cate/style.scss

@@ -0,0 +1,109 @@
+/* 分类模块 */
+.cate-item{
+  width: 98px;
+  height: 107px;
+  background-color: rgba(255,255,255,0.08);
+  border-radius: 10px;
+}
+.cate-icon{
+  width: 50px;
+  height: 50px;
+}
+.cate-item-label{
+  font-size: 16px;
+  line-height: 19px;
+  margin-top: 12px;
+  color: rgba(255,255,255,0.4);
+  overflow: hidden;
+  white-space: nowrap;
+  width: 82%;
+  text-align: center;
+}
+.cate-item:hover,.cate-item-select{
+  background: $main-linear;
+  .cate-item-label{
+    color: #fff;
+  }
+}
+/* 分类模块 */
+
+/* 全部分类 */
+$view-cate-container-radius:20px;
+$view-cate-height:382px;
+$view-cate-animate:.5s;
+.view-cate-container{
+  left: 0;
+  right: 0;
+  top: 107px;
+  background-color: #131324;
+  height: $view-cate-height;
+  border-radius: 0 0 $view-cate-container-radius $view-cate-container-radius;
+  overflow: hidden;
+}
+
+
+.skewHeight-enter-active {
+  animation: skewHeight $view-cate-animate forwards;
+}
+
+.skewHeight-leave-active {
+  animation: skewHeight $view-cate-animate forwards reverse;
+}
+
+@keyframes skewHeight {
+  0%{
+
+    height: 0;
+  }
+  100%{
+    height: $view-cate-height;
+  }
+}
+
+
+
+.view-cate-margin {
+  margin-bottom: 12.5px;
+}
+.view-cate-header{
+  font-size: 16px;
+  line-height: 28px;
+  font-weight: 500;
+  padding: 0 17px;
+  @extend .view-cate-margin;
+  margin-top: 10px;
+}
+
+.view-cate-line{
+  width: 22px;
+  height: 4px;
+  background-color: #00D5AF;
+  margin-bottom: 1px;
+}
+.cate-all{
+  padding: 0 5px;
+}
+.cate-all-item{
+  font-size: 14px;
+  line-height: 20px;
+  color: #fff;
+  width: 77.3px;
+  height: 80px;
+  @extend .view-cate-margin;
+  border-radius: 10px;
+}
+.cate-all-item:hover{
+  background: $main-linear;
+}
+.cate-all-icon{
+  width: 34px;
+  height: 34px;
+}
+.cate-all-item-label{
+  margin-top: 7px;
+  white-space: nowrap;
+  text-align: center;
+  overflow: hidden;
+  width: 93%;
+}
+/* 全部分类 */

+ 3 - 0
src/views/view-menu/index.ts

@@ -0,0 +1,3 @@
+import main from './src/main.vue';
+
+export default main;

+ 3 - 0
src/views/view-menu/mixins/index.ts

@@ -0,0 +1,3 @@
+import trigger from '$mixins/trigger';
+
+export default [trigger];

+ 48 - 0
src/views/view-menu/src/main.vue

@@ -0,0 +1,48 @@
+<template>
+  <section class="view-menu">
+    <!--   banner   -->
+    <swiper class="view-menu-banner overflow" :autoplay="true">
+      <swiper-item
+        v-for="(item,index) in banner"
+        :key="'view-menu-banner-'+index"
+      >
+        <v-image mode="cover" class="view-menu-banner overflow" radius="0.05rem" :src="item.src"></v-image>
+      </swiper-item>
+    </swiper>
+  </section>
+</template>
+
+<script>
+import mixins from '../mixins';
+import {
+  swiper,
+  swiperItem,
+  vImage
+} from '$components';
+export default {
+  name: "view-menu",
+
+  mixins,
+
+  data:function () {
+    return {
+      banner:[
+        {
+          src:'https://image.youmaiyy.com/ym/20210429/1619667220NS.png?x-oss-process=image/resize,w_754'
+        },
+        {
+          src:'https://image.youmaiyy.com/ym/20210519/1621431755Pv.png?x-oss-process=image/resize,w_754'
+        }
+      ]
+    }
+  },
+
+  components:{
+    swiper,
+    swiperItem,
+    vImage
+  }
+}
+</script>
+
+<style scoped lang="scss" src="../style.scss"></style>

+ 14 - 0
src/views/view-menu/style.scss

@@ -0,0 +1,14 @@
+@import "style/variable";
+/* 最外层 */
+.view-menu{
+  width: 237px;
+}
+/* 最外层 */
+
+/* banner */
+.view-menu-banner {
+  width: 100%;
+  height: 96px;
+  border-radius: 5px;
+}
+/* banner */

+ 8 - 0
src/views/view-menu/style/variable.scss

@@ -0,0 +1,8 @@
+/* 变量 */
+$footer-height:73px;
+
+$footer-min-height:30px;
+
+$footer-surplus-height: $footer-height - $footer-min-height;
+
+/* 变量 */

+ 3 - 0
src/views/view-play-with/index.ts

@@ -0,0 +1,3 @@
+import main from './src/main.vue';
+
+export default main;

+ 21 - 0
src/views/view-play-with/src/main.vue

@@ -0,0 +1,21 @@
+<template>
+  <section class="screen">
+    <view-cate></view-cate>
+  </section>
+</template>
+
+<script>
+import {
+  vImage
+} from '$components';
+import viewCate from '../../view-cate';
+export default {
+  name: "view-play-with",
+  components:{
+    vImage,
+    viewCate
+  }
+}
+</script>
+
+<style scoped lang="scss" src="../style.scss"></style>

+ 28 - 0
src/views/view-play-with/style.scss

@@ -0,0 +1,28 @@
+/* 分类模块 */
+.cate-item{
+  width: 98px;
+  height: 107px;
+  background-color: rgba(255,255,255,0.08);
+  border-radius: 10px;
+}
+.cate-icon{
+  width: 50px;
+  height: 50px;
+}
+.cate-item-label{
+  font-size: 16px;
+  line-height: 19px;
+  margin-top: 12px;
+  color: rgba(255,255,255,0.4);
+  overflow: hidden;
+  white-space: nowrap;
+  width: 82%;
+  text-align: center;
+}
+.cate-item:hover,.cate-item-select{
+  background: $main-linear;
+  .cate-item-label{
+    color: #fff;
+  }
+}
+/* 分类模块 */