浏览代码

no message

laosan2382995021@163.com 4 年之前
父节点
当前提交
a565bd4752
共有 100 个文件被更改,包括 3938 次插入159 次删除
  1. 344 74
      package-lock.json
  2. 2 0
      package.json
  3. 2 2
      public/font/font.css
  4. 12 0
      public/index.html
  5. 1 1
      src/App.vue
  6. 二进制
      src/assets/images/currency.png
  7. 二进制
      src/assets/images/exit.png
  8. 1 0
      src/assets/scss/entry/global.scss
  9. 4 4
      src/assets/scss/entry/style.scss
  10. 0 0
      src/components/address/data/address.js
  11. 3 0
      src/components/address/index.ts
  12. 201 0
      src/components/address/mixins/handle.ts
  13. 3 0
      src/components/address/mixins/index.ts
  14. 15 0
      src/components/address/props.ts
  15. 58 0
      src/components/address/src/main.vue
  16. 16 0
      src/components/address/style.scss
  17. 3 0
      src/components/file/index.js
  18. 27 0
      src/components/file/mixins/handle.js
  19. 3 0
      src/components/file/mixins/index.js
  20. 28 0
      src/components/file/props.js
  21. 40 0
      src/components/file/src/main.vue
  22. 0 0
      src/components/file/style.scss
  23. 7 1
      src/components/index.ts
  24. 6 0
      src/components/popup/props.ts
  25. 1 1
      src/components/popup/src/main.vue
  26. 15 1
      src/components/popup/style.scss
  27. 28 0
      src/components/sound-recording/const/audio.ts
  28. 20 0
      src/components/sound-recording/const/soundStatus.ts
  29. 3 0
      src/components/sound-recording/index.ts
  30. 168 0
      src/components/sound-recording/mixins/audio.ts
  31. 160 0
      src/components/sound-recording/mixins/handle.ts
  32. 5 0
      src/components/sound-recording/mixins/index.ts
  33. 21 0
      src/components/sound-recording/props.ts
  34. 91 0
      src/components/sound-recording/src/main.vue
  35. 75 0
      src/components/sound-recording/style.scss
  36. 3 0
      src/components/upload/index.ts
  37. 144 0
      src/components/upload/mixins/handle.ts
  38. 3 0
      src/components/upload/mixins/index.ts
  39. 39 0
      src/components/upload/props.ts
  40. 69 0
      src/components/upload/src/main.vue
  41. 63 0
      src/components/upload/style.scss
  42. 4 1
      src/config/user.ts
  43. 15 0
      src/const/component.ts
  44. 1 1
      src/layout/layout-entry/style.scss
  45. 5 3
      src/main.ts
  46. 0 1
      src/mixins/status/index.ts
  47. 27 0
      src/mixins/user.ts
  48. 5 2
      src/popup/popup-export/components.ts
  49. 19 0
      src/popup/popup-export/const/index.ts
  50. 3 1
      src/popup/popup-export/global.ts
  51. 3 0
      src/popup/popup-export/src/main.vue
  52. 5 13
      src/popup/popup-export/types/lib.popup-export.d.ts
  53. 3 0
      src/popup/popup-loading/index.ts
  54. 15 0
      src/popup/popup-loading/mixins/handle.ts
  55. 3 0
      src/popup/popup-loading/mixins/index.ts
  56. 20 0
      src/popup/popup-loading/props.ts
  57. 70 0
      src/popup/popup-loading/src/main.vue
  58. 31 0
      src/popup/popup-loading/style.scss
  59. 11 3
      src/popup/popup-login/components/login/src/main.vue
  60. 8 0
      src/popup/popup-personal/components/index.ts
  61. 12 0
      src/popup/popup-personal/components/personal-data/data/footer.ts
  62. 56 0
      src/popup/popup-personal/components/personal-data/data/info.ts
  63. 二进制
      src/popup/popup-personal/components/personal-data/images/alipay.png
  64. 二进制
      src/popup/popup-personal/components/personal-data/images/authentication.png
  65. 二进制
      src/popup/popup-personal/components/personal-data/images/info.png
  66. 3 0
      src/popup/popup-personal/components/personal-data/index.ts
  67. 100 0
      src/popup/popup-personal/components/personal-data/mixins/assets.ts
  68. 5 0
      src/popup/popup-personal/components/personal-data/mixins/index.ts
  69. 137 0
      src/popup/popup-personal/components/personal-data/mixins/info.ts
  70. 198 0
      src/popup/popup-personal/components/personal-data/src/main.vue
  71. 179 0
      src/popup/popup-personal/components/personal-data/style.scss
  72. 27 0
      src/popup/popup-personal/data/menu.ts
  73. 二进制
      src/popup/popup-personal/images/attention.png
  74. 二进制
      src/popup/popup-personal/images/dress.png
  75. 二进制
      src/popup/popup-personal/images/gift.png
  76. 二进制
      src/popup/popup-personal/images/information.png
  77. 二进制
      src/popup/popup-personal/images/order.png
  78. 二进制
      src/popup/popup-personal/images/wallet.png
  79. 3 0
      src/popup/popup-personal/index.ts
  80. 66 0
      src/popup/popup-personal/mixins/component.ts
  81. 28 0
      src/popup/popup-personal/mixins/handle.ts
  82. 5 0
      src/popup/popup-personal/mixins/index.ts
  83. 14 0
      src/popup/popup-personal/props.ts
  84. 90 0
      src/popup/popup-personal/src/main.vue
  85. 99 0
      src/popup/popup-personal/style.scss
  86. 66 4
      src/store/modules/user.ts
  87. 13 1
      src/types/lib.mixins.d.ts
  88. 41 0
      src/utils/request/index.ts
  89. 1 1
      src/utils/request/instructions/plugins/config.ts
  90. 62 41
      src/utils/request/instructions/plugins/instructions.ts
  91. 1 1
      src/utils/request/types/lib.request.d.ts
  92. 184 0
      src/utils/tool/audio.ts
  93. 117 0
      src/utils/tool/compress.ts
  94. 145 0
      src/utils/tool/date.ts
  95. 26 0
      src/utils/tool/extend.ts
  96. 40 0
      src/utils/tool/file.ts
  97. 50 2
      src/utils/tool/popup.ts
  98. 203 0
      src/utils/tool/sound-recording/index.ts
  99. 22 0
      src/utils/tool/sound-recording/types/sound-recording.ts
  100. 13 0
      src/utils/tool/utils.ts

文件差异内容过多而无法显示
+ 344 - 74
package-lock.json


+ 2 - 0
package.json

@@ -7,8 +7,10 @@
     "build": "vue-cli-service build"
   },
   "dependencies": {
+    "ant-design-vue": "^2.1.6",
     "axios": "^0.21.1",
     "core-js": "^3.6.5",
+    "recorder-js": "^1.0.7",
     "swiper": "^6.6.2",
     "vue": "^3.0.0",
     "vue-class-component": "^8.0.0-0",

+ 2 - 2
public/font/font.css

@@ -42,11 +42,11 @@
   content: "\e633";
 }
 
-.icon-nan:before {
+.icon-boy:before {
   content: "\e8b3";
 }
 
-.icon-nv:before {
+.icon-girl:before {
   content: "\e67b";
 }
 

+ 12 - 0
public/index.html

@@ -58,6 +58,18 @@
               window.addEventListener('resize',win.handle_size);
 
           })(window,document);
+
+          window.console = (function () {
+
+              var c = {
+                  log: window.console.log
+              };
+
+              c.warn = c.debug = c.info = c.error = c.time = c.dir = c.profile = c.clear = c.exception = c.trace = c.assert = function () { };
+
+              return c;
+
+          })();
       </script>
   </head>
   <body ondragstart="window.event.returnValue=false;return false;" oncontextmenu="window.event.returnValue=false;return false;" onselectstart="event.returnValue=false;return false;">

+ 1 - 1
src/App.vue

@@ -13,7 +13,7 @@ export default {
   },
 
   created() {
-    this.$store.commit('initializationUser');
+    this.$store.dispatch('initializationUserPromise');
   }
 
 }

二进制
src/assets/images/currency.png


二进制
src/assets/images/exit.png


+ 1 - 0
src/assets/scss/entry/global.scss

@@ -17,6 +17,7 @@
 
 .button-active{
   transition: opacity .5s;
+  cursor: pointer;
 }
 
 .button{

+ 4 - 4
src/assets/scss/entry/style.scss

@@ -3,9 +3,9 @@ html, body,#app{height: 100%;width: 100%;overflow: hidden}
 body{
   background-color: #fff;
   overflow: hidden;
-  font-size: 16px;
-  line-height: 24px;
-  color: #fff;
+  font-size: 16px !important;
+  line-height: 24px !important;
+  color: #fff !important;
   -moz-user-select: none; /*火狐*/
   -webkit-user-select: none; /*webkit浏览器*/
   -ms-user-select: none; /*IE10*/
@@ -14,7 +14,7 @@ body{
   user-select: none;
   word-break:break-all;
   word-wrap:break-word;
-  font-family: PingFang SC;
+  font-family: PingFang SC !important;
   -webkit-overflow-scrolling: touch;
 }
 #app{

文件差异内容过多而无法显示
+ 0 - 0
src/components/address/data/address.js


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

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

+ 201 - 0
src/components/address/mixins/handle.ts

@@ -0,0 +1,201 @@
+import address from '../data/address';
+export default {
+
+    data(){
+        return {
+            data:{
+                province:[],
+                city:[],
+                area:[],
+            },
+            province:[],
+            city:[],
+            area:[],
+            vValue:[]
+        }
+    },
+
+    watch:{
+        value:{
+            handler:function (value) {
+                return this.setValue(value);
+            },
+            immediate:true
+        }
+    },
+
+    methods:{
+
+        open:function(){
+            if (this.storeValueTarget && this.storeValueTarget.join('') !== this.vValue.join('')) {
+                this.vValue = this.storeValueTarget;
+                this.changeLevel('0',0);
+            }
+        },
+
+        /* 点击确认触发 */
+        confirm:function(){
+
+            let code = '0';
+            let cCode = [];
+            let cAddress = [];
+            this.vValue.map((item,index)=>{
+
+                code = this.getCode(index,code);
+                if (address[code]&&address[code][item]) {
+                    cAddress.push(address[code][item].name);
+                    code = address[code][item].code;
+                    cCode.push(code);
+                }
+
+            });
+            this.storeValue = cAddress.join('');
+            this.storeValueTarget = [...this.vValue];
+
+            if (this.$attrs.input) {
+                this.$emit('input',cAddress);
+            }
+
+            if (this.$attrs.onChange) {
+                this.$emit('change',{
+                    postcode: code,
+                    code: cCode,
+                    value: cAddress
+                });
+            }
+
+
+        },
+
+        /* 更改展示数据 */
+        changeLevel:function(code = '0',level=0){
+
+
+            let parentCode = code;
+            let nowCode = '';
+            for (let i=level;i<this.use.length;i++) {
+                nowCode = this.getCode(i,parentCode);
+
+                if (address[nowCode]) {
+                    this.data[this.use[i]] = address[nowCode] || [];
+                    if (address[nowCode][this.vValue[i] || 0]){
+                        parentCode = address[nowCode][this.vValue[i] || 0].code || '';
+                    }else {
+                        parentCode = '';
+                    }
+                } else {
+                    this.data[this.use[i]] = [];
+                }
+            }
+
+        },
+
+
+        /* 获取默认选择的地址 */
+        getAddressValue:function (value) {
+
+            let newValue = [];
+            let key = 0;
+            let parentItem = address;
+            for(let i=0;i<this.use.length;i++) {
+
+
+                if (value[i]) {
+                    key = i===0?this.use[i]:parentItem.code+':'+this.use[i];
+
+                    newValue[i] = address[key].indexOf(value[i]);
+                    if (newValue[i] < 0) newValue[i] = 0;
+                } else {
+                    newValue[i] = 0;
+                }
+
+                if (i === 0) {
+                    parentItem = address['0:0'][newValue[i]];
+                } else {
+                    parentItem = address[this.getCode(i,parentItem.code)][newValue[i]];
+                }
+
+            }
+
+            return newValue;
+        },
+
+        changeColumn:function (key,index,value) {
+
+            let newValue = [...this.vValue];
+
+            newValue[index] = value;
+
+            let item ={
+                code:'0'
+            };
+
+            let change = false;
+
+            let storageI = 0;
+
+            let oldValue = [...this.vValue];
+
+
+            for (let i=0;i<this.use.length;i++) {
+
+                if (change) {
+                    newValue[i] = 0;
+                }else {
+                    item = this.getItem(this.getCode(i,item.code),newValue[i]);
+                }
+
+                if (oldValue[i] !== newValue[i]) {
+                    if (!change) {
+                        storageI = i;
+                    }
+                    change = true;
+                }
+            }
+
+
+            if (change) {
+                this.vValue = newValue;
+                this.changeLevel(item.code,storageI+1);
+                this.confirm();
+            }
+
+        },
+
+        getCode:function(level,code){
+            return level+':'+code;
+        },
+
+        getItem:function (code='0:0',index) {
+
+            if (address[code]) {
+                return address[code][index];
+            } else {
+                return {
+                    code:-1
+                };
+            }
+
+
+        },
+
+        setValue:function (value) {
+            let storeValue = value.join('');
+            if (this.storeValue !== storeValue) {
+                this.vValue = this.getAddressValue(value);
+                this.storeValue = storeValue;
+                this.changeLevel('0',0);
+                this.confirm();
+            }
+
+        }
+
+    },
+
+    created(){
+        this.open();
+
+        console.log(this);
+    }
+
+}

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

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

+ 15 - 0
src/components/address/props.ts

@@ -0,0 +1,15 @@
+export default {
+
+	/* 中文提示 */
+	value:{
+		type: Array,
+		default: []
+	},
+
+	/* 显示的字段 */
+	use:{
+		type:Array,
+		default:['province','city']
+	}
+
+}

+ 58 - 0
src/components/address/src/main.vue

@@ -0,0 +1,58 @@
+<template>
+  <div class="screen address rowACenter">
+    <aside
+      v-for="(key,pIndex) in use"
+      :key="'key-'+key"
+      class="address-item flex-1"
+    >
+      <section class="screen address-use-item">
+        <a-select v-if="data[key]" @change="changeColumn(key,pIndex,$event)" :value="vValue[pIndex]" class="screen address-item-select">
+          <a-select-option
+              v-for="(item,index) in data[key]"
+              :key="'option-'+key+'-'+index"
+              :value="index"
+          >{{item.name}}</a-select-option>
+        </a-select>
+      </section>
+    </aside>
+  </div>
+</template>
+
+<script>
+import mixins from '../mixins';
+import props from '../props';
+import {
+  Select
+} from 'ant-design-vue';
+export default {
+  name: "v-address",
+  mixins,
+  props,
+  components:{
+    [Select.name]:Select,
+    [Select.Option.displayName]:Select.Option,
+  }
+}
+</script>
+
+<style scoped lang="scss" src="../style.scss"></style>
+
+<style>
+.address-item-select .ant-select-selector{
+  background-color: transparent !important;
+  border: none !important;
+  outline: none !important;
+  color: #fff;
+  box-shadow: none !important;
+  opacity: 1 !important;
+}
+.address-item-select .ant-select-selection-item {
+  line-height: 32px;
+}
+
+
+.address-item-select .ant-select-selection-item,.address-item-select .ant-select-arrow {
+  color: #fff !important;
+
+}
+</style>

+ 16 - 0
src/components/address/style.scss

@@ -0,0 +1,16 @@
+/* 地址 */
+.address{
+  margin-right: -15px;
+}
+.address-item{
+  padding-right: 15px;
+}
+.address-item:last-child{
+  padding-right: 0;
+}
+.address-use-item{
+  background-color: rgba(255,255,255,0.08);
+  border-radius: 10px;
+  min-height: 32px;
+}
+/* 地址 */

+ 3 - 0
src/components/file/index.js

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

+ 27 - 0
src/components/file/mixins/handle.js

@@ -0,0 +1,27 @@
+export default {
+
+	computed:{
+		input:function () {
+			return this.$refs.input;
+		}
+	},
+
+	methods:{
+
+		onChange:function () {
+			this.$emit('change',this.input.files);
+
+			if (this.clear) {
+				this.input.value = '';
+			}
+
+		},
+
+		/* 打开 */
+		open:function () {
+			return this.input.click();
+		}
+	}
+
+
+}

+ 3 - 0
src/components/file/mixins/index.js

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

+ 28 - 0
src/components/file/props.js

@@ -0,0 +1,28 @@
+export default {
+
+	// 允许的图片类型
+	accept:{
+		type: String,
+		default:'image/png,image/jpg,image/jpeg,image.gif'
+	},
+
+	// 是否支持多选
+	multiple:{
+		type: Boolean,
+		default: false
+	},
+
+	// 使用拍照
+	capture:{
+		type: Boolean,
+		default: false
+	},
+
+	// 每次选择完成之后是否清楚value
+	clear:{
+		type: Boolean,
+		default: true
+	}
+
+
+}

+ 40 - 0
src/components/file/src/main.vue

@@ -0,0 +1,40 @@
+<script>
+import mixins from '../mixins';
+import props from '../props';
+import { createVNode } from 'vue';
+export default {
+    name:'file',
+    mixins,
+    props,
+    render:function (vm,vNode,props) {
+
+        let attrs = {
+            accept: props.accept,
+            type:'file',
+
+        };
+        // 是否支持多选
+        if (props.multiple) {
+            attrs.multiple = 'multiple';
+        }
+
+        // 是否支持拍照
+		    if (props.capture) {
+            attrs.capture = 'camera';
+        }
+
+        // 返回组件
+        return createVNode('input',{
+            ref:'input',
+            style:{
+              display:'none'
+            },
+            ...attrs,
+            onChange: this.onChange
+        });
+
+    }
+}
+</script>
+
+<style scoped lang="scss" src="../style.scss" ></style>

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


+ 7 - 1
src/components/index.ts

@@ -12,4 +12,10 @@ export { default as Popup } from './popup';
 
 export { default as icon } from './icon';
 
-export { default as vButton } from './button';
+export { default as vButton } from './button';
+
+export { default as vAddress } from './address';
+
+export { default as vUpload } from './upload';
+
+export { default as soundRecording } from './sound-recording';

+ 6 - 0
src/components/popup/props.ts

@@ -15,6 +15,12 @@ export default {
     center:{
         type:Boolean,
         default: true
+    },
+
+    // 内部样式
+    contentAnimate:{
+        type:String,
+        default:'none'
     }
 
 }

+ 1 - 1
src/components/popup/src/main.vue

@@ -3,7 +3,7 @@
            @click="trigger && changeValue"
            :class="{'center':center}"
   >
-    <section @click.stop>
+    <section @click.stop :class="['popup-content-'+contentAnimate]">
       <slot></slot>
     </section>
   </section>

+ 15 - 1
src/components/popup/style.scss

@@ -7,4 +7,18 @@
   z-index: 999;
   background-color: rgba(0,0,0,0.3);
 }
-/* 弹窗 */
+/* 弹窗 */
+
+/* 内部动画 scale */
+.popup-content-scale {
+  animation: animateScale .5s forwards;
+}
+@keyframes animateScale {
+  0%{
+    transform: scale(0);
+  }
+  100%{
+    transform: scale(1);
+  }
+}
+/* 内部动画 scale */

+ 28 - 0
src/components/sound-recording/const/audio.ts

@@ -0,0 +1,28 @@
+enum SoundStatus {
+
+    // 无状态
+    none,
+
+    // 读取中
+    loading,
+
+    // 播放
+    play,
+
+    // 暂停
+    pause,
+
+    // 失败
+    fail,
+
+    // 浏览器无法转换URL
+    noURL,
+
+    // 不支持
+    supper,
+
+
+
+}
+
+export default SoundStatus;

+ 20 - 0
src/components/sound-recording/const/soundStatus.ts

@@ -0,0 +1,20 @@
+enum SoundStatus {
+
+    // 无状态
+    none,
+
+    // 录音中
+    sound,
+
+    // 结束录音
+    end,
+
+    // 授权
+    jurisdiction,
+
+    // 支持
+    support
+
+}
+
+export default SoundStatus;

+ 3 - 0
src/components/sound-recording/index.ts

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

+ 168 - 0
src/components/sound-recording/mixins/audio.ts

@@ -0,0 +1,168 @@
+import audio, {CustomAudio, PlayStatus, TriggerAudioListener} from "$utils/tool/audio";
+import SoundStatus from '../const/soundStatus';
+import AudioStatus from '../const/audio';
+
+import request,{ InstructionsMessageType } from '$utils/request';
+import Status from "$mixins/status/const/status";
+
+
+export default <LibMixins>{
+
+    data(){
+        return {
+            vSrc:'',
+            AudioStatus,
+            audioStatus: CustomAudio.supper ? AudioStatus.loading : AudioStatus.supper,
+            audioDuration:0,
+            audioCurrentDuration:0,
+            audioFailMessage:'',
+            status_fetch_key:'upload-next'
+        }
+    },
+
+    methods:{
+
+        installAudioSrc(src,clear:boolean=false){
+            if (this.vSrc !== src) {
+                this.vSrc = src;
+                // 设置暂时不需要录音
+                this.setSoundStatus(SoundStatus.end);
+                // 清楚资源
+                clear && this.clearAssets();
+                // 设置播放路径
+                this.switchSrc(src);
+            }
+        },
+
+        triggerAudio(){
+            if (this.audioStatus === AudioStatus.supper || this.audioStatus === AudioStatus.loading) return;
+
+            if (this.audioStatus === AudioStatus.none || this.audioStatus === AudioStatus.pause) {
+                return audio.play();
+            } else if (this.audioStatus === AudioStatus.play) {
+                return audio.paused();
+            }
+
+        },
+
+        switchSrc(src){
+
+            if (this.audioStatus === AudioStatus.supper) return;
+
+            this.setAudioStatus(AudioStatus.loading);
+            audio
+                .setSrc(src)
+                .addListener(TriggerAudioListener.status,(status)=>{
+                    if (status === PlayStatus.play) {
+                        this.setAudioStatus(AudioStatus.play);
+                    } else if (status === PlayStatus.pause) {
+                        this.setAudioStatus(AudioStatus.pause);
+                    }
+                })
+                .addListener(TriggerAudioListener.canplay, ({duration})=>{
+                    this.audioDuration = duration;
+                    this.audioCurrentDuration = duration;
+                    this.setAudioStatus(AudioStatus.none);
+                })
+                .addListener(TriggerAudioListener.fail, ({message})=>{
+                    this.audioFailMessage = message;
+                    this.setAudioStatus(AudioStatus.fail);
+                })
+                .addListener(TriggerAudioListener.speed, ({currDuration,duration})=>{
+                    let useDuration = duration - currDuration -1;
+
+                    this.audioCurrentDuration = useDuration < 0 ? 0 : useDuration;
+                })
+
+        },
+
+        setAudioStatus(status){
+            if (this.audioStatus === AudioStatus.supper) return;
+
+            if (this.audioStatus !== status) {
+                this.audioStatus = status;
+            }
+        },
+
+        // 提交资源
+        submitAssets(){
+
+            if (!this.assets) return;
+
+            if(this.status === Status.loading) return;
+
+            if (this.$attrs.onSubmitVerification) {
+                return this.$emit('submit-verification',{
+                    submit: ()=> this.submit()
+                });
+            } else {
+                return this.submit();
+            }
+
+        },
+
+        // 提交
+        submit(){
+            if (!this.assets) return;
+
+            if(this.status === Status.loading) return;
+
+            this.setStatus(Status.loading);
+
+            if (this.stroageUpload) {
+                return  this.triggerSuccess(this.stroageUpload);
+            } else {
+                return request.uploadFile({
+                    url:'hxupload/img_upload',
+                    data:{
+                        file: this.stroageBlob
+                    },
+                    token:true,
+                    message: InstructionsMessageType.other,
+                    failMessage:true
+                }).then((data)=>{
+
+                    if (data.isSuccess) {
+                        return this.triggerSuccess();
+                    } else {
+                        return this.setStatus(Status.fail);
+                    }
+                }).catch(()=>{
+                    return this.setStatus(Status.fail);
+                });
+            }
+
+        },
+
+        triggerSuccess(data){
+
+            this.stroageUpload = data.data;
+
+            if (this.$attrs.onUploadNext) {
+                return this.statusFetch(true,data.data);
+            } else {
+                // 设置
+                this.$emit('upload-success',data.data);
+                // 设置加载状态成功
+                return this.setStatus(Status.success);
+            }
+        },
+
+        triggerChangeStatus(status) {
+            if (status === Status.success) {
+                // 清除资源
+                this.clearAssets();
+            }
+        }
+
+    },
+
+    created(){
+        if (this.src) {
+            return this.installAudioSrc(this.src,true);
+        }
+    }
+
+
+
+}

+ 160 - 0
src/components/sound-recording/mixins/handle.ts

@@ -0,0 +1,160 @@
+import SoundStatus from '../const/soundStatus';
+
+import SoundRecording, {TriggerListener} from '$utils/tool/sound-recording';
+
+import popup from '$utils/tool/popup';
+
+import file from '$utils/tool/file';
+import AudioStatus from "$components/sound-recording/const/audio";
+import Status from "$mixins/status/const/status";
+
+export default <LibMixins>{
+
+    data(){
+        return {
+            SoundStatus,
+            soundStatus:SoundStatus.none,
+            soundStatusMessage:{
+                [SoundStatus.none]:'长按开始录音',
+                [SoundStatus.sound]:'正在录音',
+                [SoundStatus.jurisdiction]:'浏览器授权后,请点击重试'
+            },
+            countdown:0,
+            support:true,
+            assets:false
+        }
+    },
+
+    methods:{
+
+        // 设置当前录音状态
+        setSoundStatus(status:SoundStatus){
+            if (this.soundStatus !== status) {
+
+                // 如果状态为 none 清楚资源
+                if (status === SoundStatus.none) {
+                    this.clearAssets();
+                }
+
+                this.soundStatus = status;
+            }
+        },
+
+        reRecording(){
+            if (this.status === Status.loading) return;
+            return this.setSoundStatus(SoundStatus.none);
+        },
+
+        // 设置授权状态
+        setJurisdictionStatus(config:Record<string, any>){
+
+            if (config.support === false) {
+                return this.setSoundStatus(SoundStatus.support);
+            }
+
+            if (config.jurisdiction === false ) {
+                return this.setSoundStatus(SoundStatus.jurisdiction);
+            }
+        },
+
+        start(){
+            return this.soundRecording.start();
+        },
+
+        stop(){
+            return this.soundRecording.stop();
+        },
+
+        // 安装
+        installSoundRecording(){
+
+            if (this.soundRecording.initializationStatus) return;
+
+            // 安装
+            this.soundRecording.initialization().then(()=>{
+
+                this.setJurisdictionStatus({
+                    jurisdiction:true,
+                    support:true
+                });
+
+                this.soundRecording
+                  .addListener(TriggerListener.fail,(message)=>{
+
+                    // 设置状态从头开始
+                    this.setSoundStatus(SoundStatus.none);
+
+                    message && popup.$toast(message);
+                  })
+                  .addListener(TriggerListener.speed,(duration)=>{
+                        if (this.countdown !== duration) {
+                            this.countdown = duration;
+                        }
+                  })
+                  .addListener(TriggerListener.soundStatus,(status:boolean)=>{
+
+                      this.setSoundStatus(status ? SoundStatus.sound : SoundStatus.end);
+
+                  })
+                  .addListener(TriggerListener.success,(data)=>{
+
+                      if (this.audioStatus === AudioStatus.loading) return;
+                      // 设置资源
+                      this.setAssets(data.blob);
+
+                      this.setAudioStatus(AudioStatus.loading);
+                      // 设置路径
+                      file.createdPath(data.blob).then((data)=>{
+                          return this.switchSrc(data);
+                      }).catch((error)=>{
+                          return this.setAudioStatus(AudioStatus.noURL);
+                      });
+                  })
+
+
+            }).catch((fail)=>{
+
+
+                if (typeof fail === 'object') {
+                    this.setJurisdictionStatus(fail);
+                }
+
+            });
+
+        },
+
+        installUseSoundRecording(){
+            // 创建录音对象
+            this.soundRecording = new SoundRecording({
+                min: this.min,
+                max: this.max
+            });
+
+            this.installSoundRecording();
+        },
+
+        // 设置资源
+        setAssets(blob){
+            if (blob) {
+                this.stroageBlob = blob;
+                this.assets = true;
+                this.stroageUpload = null;
+            }
+        },
+
+        // 清楚资源
+        clearAssets(){
+            if (this.assets) {
+                this.assets = false;
+                this.stroageBlob = null;
+                this.stroageUpload = null;
+            }
+        }
+
+    },
+
+    created(){
+        !this.src && this.installUseSoundRecording();
+    }
+
+}

+ 5 - 0
src/components/sound-recording/mixins/index.ts

@@ -0,0 +1,5 @@
+import handle from './handle';
+import audio from './audio';
+import status from '$mixins/status';
+
+export default [handle,audio,status];

+ 21 - 0
src/components/sound-recording/props.ts

@@ -0,0 +1,21 @@
+export default {
+
+    // 录制声音时间必须小于3秒
+    min:{
+        type:Number,
+        default: 3
+    },
+
+    // 最大录音时间
+    max:{
+        type:Number,
+        default:15
+    },
+
+    // 播放路径
+    src:{
+        type:String,
+        default:''
+    }
+
+}

+ 91 - 0
src/components/sound-recording/src/main.vue

@@ -0,0 +1,91 @@
+<template>
+  <div class="screen">
+
+    <!--  如果录音模块属于结束触发  -->
+    <section class="screen center" v-if="SoundStatus.end !== soundStatus">
+      <!--  录音模块  -->
+      <section class="sound-recording cursor-pointer" @mousedown="start" @mouseup="stop" @mouseout="stop">
+
+        <aside  @click="soundStatus === SoundStatus.jurisdiction?installSoundRecording:''" class="screen rowACenter">
+          <div class="sound-icon center">
+            <svg v-if="soundStatus === SoundStatus.jurisdiction" t="1622539974324" class="sound-svg-icon" viewBox="0 0 1024 1024" p-id="2481" width="200" height="200"><path d="M102.425742 102.393565v819.148516l614.361387-409.574258z" fill="#ffffff" p-id="2482"></path><path d="M153.622524 102.393565v819.148516l614.361387-409.574258z" fill="#ffffff" p-id="2483"></path><path d="M259.599863 15.871003V834.507551l619.481066-405.478515z" fill="#ffffff" p-id="2484"></path><path d="M261.135767 189.428094l-1.535904 818.636549L875.497154 599.002353z" fill="#ffffff" p-id="2485"></path><path d="M204.819306 102.393565m-102.393564 0a102.393565 102.393565 0 1 0 204.787129 0 102.393565 102.393565 0 1 0-204.787129 0Z" fill="#ffffff" p-id="2486"></path><path d="M819.180694 409.574258c-56.316461 0-102.393565 46.077104-102.393565 102.393565s46.077104 100.345693 102.393565 102.393564c57.852364 2.047871 102.905532-45.053168 102.393564-102.393564-0.511968-56.316461-46.077104-102.393565-102.393564-102.393565zM204.819306 819.148517c-56.316461 0-102.393565 46.077104-102.393564 102.393564s46.077104 100.345693 102.393564 102.393565c53.756621 2.047871 100.857661-45.053168 102.393565-102.393565 1.535903-56.316461-46.077104-102.393565-102.393565-102.393564z" fill="#ffffff" p-id="2487"></path></svg>
+            <svg v-else-if="soundStatus === SoundStatus.sound" t="1622540201545" class="sound-suspend-icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3391" width="200" height="200"><path d="M648.45 247.01c-31.64 0-57.348 27.685-57.348 57.348v427.149c0 31.64 27.685 57.348 57.348 57.348S705.8 761.17 705.8 731.507V304.358c0-31.64-27.686-57.348-57.349-57.348z m-272.9 0c-31.64 0-57.349 27.685-57.349 57.348v427.149c0 31.64 27.686 57.348 57.349 57.348s57.348-27.685 57.348-57.348V304.358c1.978-31.64-25.708-57.348-57.348-57.348z" p-id="3392" fill="#ffffff"></path></svg>
+            <svg v-else t="1622539974324" class="sound-svg-icon" viewBox="0 0 1024 1024" p-id="2481" width="200" height="200"><path d="M102.425742 102.393565v819.148516l614.361387-409.574258z" fill="#ffffff" p-id="2482"></path><path d="M153.622524 102.393565v819.148516l614.361387-409.574258z" fill="#ffffff" p-id="2483"></path><path d="M259.599863 15.871003V834.507551l619.481066-405.478515z" fill="#ffffff" p-id="2484"></path><path d="M261.135767 189.428094l-1.535904 818.636549L875.497154 599.002353z" fill="#ffffff" p-id="2485"></path><path d="M204.819306 102.393565m-102.393564 0a102.393565 102.393565 0 1 0 204.787129 0 102.393565 102.393565 0 1 0-204.787129 0Z" fill="#ffffff" p-id="2486"></path><path d="M819.180694 409.574258c-56.316461 0-102.393565 46.077104-102.393565 102.393565s46.077104 100.345693 102.393565 102.393564c57.852364 2.047871 102.905532-45.053168 102.393564-102.393564-0.511968-56.316461-46.077104-102.393565-102.393564-102.393565zM204.819306 819.148517c-56.316461 0-102.393565 46.077104-102.393564 102.393564s46.077104 100.345693 102.393564 102.393565c53.756621 2.047871 100.857661-45.053168 102.393565-102.393565 1.535903-56.316461-46.077104-102.393565-102.393565-102.393564z" fill="#ffffff" p-id="2487"></path></svg>
+          </div>
+          <div v-if="soundStatus === SoundStatus.jurisdiction" class="flex-1 sound-title">请授权麦克风</div>
+          <div v-else-if="soundStatus === SoundStatus.none" class="flex-1 sound-title">按住说话{{min}}~{{max}}S</div>
+          <div v-else-if="soundStatus === SoundStatus.sound" class="flex-1 sound-title">松开后结束({{countdown || 0}}秒)</div>
+        </aside>
+
+      </section>
+
+      <!--  提示文本  -->
+      <section class="sound-recording-title">{{soundStatusMessage[soundStatus]}}</section>
+    </section>
+    <!--  进入播放阶段  -->
+    <section class="screen row center audio-modal" v-else>
+
+      <div @click="reRecording" class="recording center"
+        :class="{
+          'cursor-pointer': constStatus.loading !== status
+        }"
+      >
+        <svg t="1622600287351" class="recording-close-icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2418" width="200" height="200"><path d="M512.001 15.678C237.414 15.678 14.82 238.273 14.82 512.86S237.414 1010.04 512 1010.04s497.18-222.593 497.18-497.18S786.589 15.678 512.002 15.678z m213.211 645.937c17.798 17.803 17.798 46.657 0 64.456-17.798 17.797-46.658 17.797-64.456 0L512.001 577.315 363.241 726.07c-17.799 17.797-46.652 17.797-64.45 0-17.804-17.799-17.804-46.653 0-64.456L447.545 512.86 298.79 364.104c-17.803-17.798-17.803-46.657 0-64.455 17.799-17.798 46.652-17.798 64.45 0l148.761 148.755 148.755-148.755c17.798-17.798 46.658-17.798 64.456 0 17.798 17.798 17.798 46.657 0 64.455L576.456 512.86l148.756 148.755z m0 0" fill="#999999" p-id="2419"></path></svg>
+        <div>重录</div>
+      </div>
+      <!--  播放模块  -->
+      <section class="sound-recording cursor-pointer" @click="triggerAudio">
+
+        <aside class="screen rowACenter">
+          <div class="sound-icon center">
+            <svg v-if="audioStatus === AudioStatus.loading" class="screen" viewBox="0 0 100 100" enable-background="new 0 0 0 0" xml:space="preserve">
+                <path fill="#fff" d="M73,50c0-12.7-10.3-23-23-23S27,37.3,27,50 M30.9,50c0-10.5,8.5-19.1,19.1-19.1S69.1,39.5,69.1,50">
+                  <animateTransform attributeName="transform" attributeType="XML" type="rotate" dur="1s" from="0 50 50" to="360 50 50" repeatCount="indefinite"></animateTransform>
+                </path>
+            </svg>
+            <svg v-else-if="audioStatus === AudioStatus.play" t="1622540201545" class="sound-suspend-icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3391" width="200" height="200"><path d="M648.45 247.01c-31.64 0-57.348 27.685-57.348 57.348v427.149c0 31.64 27.685 57.348 57.348 57.348S705.8 761.17 705.8 731.507V304.358c0-31.64-27.686-57.348-57.349-57.348z m-272.9 0c-31.64 0-57.349 27.685-57.349 57.348v427.149c0 31.64 27.686 57.348 57.349 57.348s57.348-27.685 57.348-57.348V304.358c1.978-31.64-25.708-57.348-57.348-57.348z" p-id="3392" fill="#ffffff"></path></svg>
+            <svg v-else t="1622539974324" class="sound-svg-icon" viewBox="0 0 1024 1024" p-id="2481" width="200" height="200"><path d="M102.425742 102.393565v819.148516l614.361387-409.574258z" fill="#ffffff" p-id="2482"></path><path d="M153.622524 102.393565v819.148516l614.361387-409.574258z" fill="#ffffff" p-id="2483"></path><path d="M259.599863 15.871003V834.507551l619.481066-405.478515z" fill="#ffffff" p-id="2484"></path><path d="M261.135767 189.428094l-1.535904 818.636549L875.497154 599.002353z" fill="#ffffff" p-id="2485"></path><path d="M204.819306 102.393565m-102.393564 0a102.393565 102.393565 0 1 0 204.787129 0 102.393565 102.393565 0 1 0-204.787129 0Z" fill="#ffffff" p-id="2486"></path><path d="M819.180694 409.574258c-56.316461 0-102.393565 46.077104-102.393565 102.393565s46.077104 100.345693 102.393565 102.393564c57.852364 2.047871 102.905532-45.053168 102.393564-102.393564-0.511968-56.316461-46.077104-102.393565-102.393564-102.393565zM204.819306 819.148517c-56.316461 0-102.393565 46.077104-102.393564 102.393564s46.077104 100.345693 102.393564 102.393565c53.756621 2.047871 100.857661-45.053168 102.393565-102.393565 1.535903-56.316461-46.077104-102.393565-102.393565-102.393564z" fill="#ffffff" p-id="2487"></path></svg>
+          </div>
+          <div class="flex-1 sound-title audio-title-right">
+            <span v-if="audioStatus === AudioStatus.supper || audioStatus === AudioStatus.noURL">不支持播放</span>
+            <span v-else-if="audioStatus === AudioStatus.fail" >{{ audioFailMessage }}</span>
+            <span v-else>{{ audioCurrentDuration }}s</span>
+          </div>
+        </aside>
+
+      </section>
+      <div  v-if="assets" class="recording center submit-success"
+        :class="{
+          'cursor-pointer': constStatus.loading !== status
+        }"
+        @click="submitAssets"
+      >
+        <svg v-if="constStatus.none === status" t="1622600601691" class="recording-close-icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3301" width="200" height="200"><path d="M512 1024a512 512 0 1 1 0-1024 512 512 0 0 1 0 1024z m-71.318588-361.411765a29.334588 29.334588 0 0 0 20.48-8.252235L774.625882 349.364706a27.708235 27.708235 0 0 0 0-39.936 29.575529 29.575529 0 0 0-41.08047 0l-292.74353 284.912941L290.454588 448.150588a29.575529 29.575529 0 0 0-41.08047 0 27.708235 27.708235 0 0 0 0 39.996236l170.706823 166.128941a29.274353 29.274353 0 0 0 20.540235 8.252235z" fill="#03c79c" p-id="3302"></path></svg>
+        <div v-else-if="constStatus.loading === status" class="overflow recording-close-icon center">
+          <svg  class="audio-upload-icon" viewBox="0 0 100 100" enable-background="new 0 0 0 0" xml:space="preserve">
+              <path fill="#fff" d="M73,50c0-12.7-10.3-23-23-23S27,37.3,27,50 M30.9,50c0-10.5,8.5-19.1,19.1-19.1S69.1,39.5,69.1,50">
+                <animateTransform attributeName="transform" attributeType="XML" type="rotate" dur="1s" from="0 50 50" to="360 50 50" repeatCount="indefinite"></animateTransform>
+              </path>
+          </svg>
+        </div>
+        <svg v-else-if="constStatus.fail === status" t="1622600287351" class="recording-close-icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2418" width="200" height="200"><path d="M512.001 15.678C237.414 15.678 14.82 238.273 14.82 512.86S237.414 1010.04 512 1010.04s497.18-222.593 497.18-497.18S786.589 15.678 512.002 15.678z m213.211 645.937c17.798 17.803 17.798 46.657 0 64.456-17.798 17.797-46.658 17.797-64.456 0L512.001 577.315 363.241 726.07c-17.799 17.797-46.652 17.797-64.45 0-17.804-17.799-17.804-46.653 0-64.456L447.545 512.86 298.79 364.104c-17.803-17.798-17.803-46.657 0-64.455 17.799-17.798 46.652-17.798 64.45 0l148.761 148.755 148.755-148.755c17.798-17.798 46.658-17.798 64.456 0 17.798 17.798 17.798 46.657 0 64.455L576.456 512.86l148.756 148.755z m0 0" fill="#fe2d46" p-id="2419"></path></svg>
+        <div v-if="constStatus.none === status">提交</div>
+        <div v-else-if="constStatus.fail === status" class="audio-upload-fail">重试</div>
+      </div>
+    </section>
+
+
+  </div>
+</template>
+
+<script>
+import mixins from '../mixins';
+import props from '../props';
+export default {
+  name: "sound-recording",
+  mixins,
+  props
+}
+</script>
+
+<style scoped lang="scss" src="../style.scss"></style>

+ 75 - 0
src/components/sound-recording/style.scss

@@ -0,0 +1,75 @@
+/* 录音模块 */
+.sound-recording{
+  height: 38px;
+  background: $main-linear;
+  width: 100%;
+  border-radius: 19px;
+  padding: 5px;
+}
+.sound-icon{
+  @include square(28px);
+  border-radius: 50%;
+  background-color: #FC9090;
+}
+.sound-title{
+  margin-left: 11px;
+  font-size: 16px;
+  line-height: 22px;
+  font-weight: 400;
+}
+.sound-suspend-icon{
+  @include square(20px);
+}
+.sound-svg-icon{
+  @include square(13px);
+}
+.sound-recording-title{
+  font-size: 12px;
+  line-height: 14px;
+  margin-top: 10px;
+}
+/* 录音模块 */
+
+/* 播放器模块 */
+.audio-modal{
+  margin: 0 -15px;
+  width: auto;
+}
+.audio-title-right{
+  text-align: right;
+  margin-right: 10px;
+}
+.recording {
+  flex-shrink: 0;
+  margin-right: 10px;
+}
+.recording div{
+  font-size: 12px;
+  color: #999;
+  text-align: center;
+  line-height: 14px;
+  margin-top: 4px;
+}
+.recording-close-icon{
+  @include square(15px);
+}
+.submit-success{
+  margin-right: 0;
+  margin-left: 10px;
+}
+.submit-success div {
+  color:#03c79c
+}
+//
+/* 播放器模块 */
+
+/* 上传模块 */
+.audio-upload-icon{
+  @include square(30px);
+  flex-shrink: 0;
+}
+.audio-upload-fail{
+  color: #fe2d46 !important;
+}
+
+/* 上传模块 */

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

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

+ 144 - 0
src/components/upload/mixins/handle.ts

@@ -0,0 +1,144 @@
+import Status from "$mixins/status/const/status";
+
+import filePath from '$utils/tool/file';
+
+import compress from "$utils/tool/compress";
+
+import $request from '$utils/request';
+
+export default <LibMixins>{
+
+    data(){
+      return {
+          vSrc:'',
+          status_fetch_key:'upload-next',
+          Status
+      }
+    },
+
+    created() {
+        this.setSrc(this.src);
+    },
+
+    methods:{
+
+        triggerFile(type:string){
+
+            if (this.status === Status.loading) return ;
+
+            // 如果点击类型为图片且 不允许点击图片上传触发
+            if (type === 'image' && !this.triggerImage) return;
+
+            return this.$refs.file && this.$refs.file.open();
+
+        },
+
+        setSrc(src){
+            if (src !== this.vSrc) {
+                this.vSrc = src;
+            }
+        },
+
+        uploadFile(file,retry=false) {
+
+            if (this.status === Status.loading) return ;
+
+            // 设置当前状态
+            this.setStatus(Status.loading);
+
+            // 获取文件
+            file = file[0];
+
+            // 暂存文件
+            this.storageFile = file;
+
+            // 清空路径
+            this.uploadItem = null;
+
+            if (retry) {
+                // 如果设置图片压缩触发
+                return this.triggerUpload(file);
+            } else {
+                filePath.createdPath(file).then((src)=>{
+                    // 设置图片路径
+                    this.setSrc(src);
+                    // 如果设置图片压缩触发
+                    if (this.compress) {
+                        return this.nextCompress(file,src);
+                    } else {
+                        return this.triggerUpload(file);
+                    }
+                }).catch(()=>{
+                    this.setSrc('');
+                    return  this.triggerUpload(file)
+                });
+            }
+        },
+
+        // 执行压缩操作
+        nextCompress(file,src){
+            compress.compress(file,src).then((params:Record<string, any>)=>{
+                this.storageFile = params.file;
+                return this.triggerUpload(params.file);
+            }).catch(()=>  this.triggerUpload(file));
+        },
+
+        // 执行上传操作
+        triggerUpload(file){
+
+            $request.uploadFile({
+                url:'hxupload/img_upload',
+                data:{
+                    file
+                },
+                token:true
+            }).then((data)=>{
+
+                if (data.isSuccess) {
+
+                    this.triggerUploadSuccess(data);
+
+                } else {
+                    return this.setStatus(Status.fail)
+                }
+
+            }).catch(()=> this.setStatus(Status.fail));
+
+        },
+
+        triggerUploadSuccess(data){
+
+            this.uploadItem = data;
+
+            !this.vSrc && this.setSrc(this.uploadItem.data);
+
+            console.log(this);
+            if (this.$attrs.onUploadNext) {
+                return this.statusFetch(true,data.data);
+            } else {
+                this.$emit('upload-success',data.data);
+                return this.setStatus(Status.success);
+            }
+        },
+
+        triggerRetry(){
+
+            if (this.status === Status.loading) return;
+
+            if (this.uploadItem) {
+                this.setStatus(Status.loading);
+                return this.triggerUploadSuccess(this.uploadItem);
+            } else {
+                return this.uploadFile([this.storageFile],true);
+            }
+
+        },
+
+        triggerChangeStatus(status) {
+            if (status === Status.success) {
+                this.storageFile = null;
+            }
+        }
+    }
+
+}

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

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

+ 39 - 0
src/components/upload/props.ts

@@ -0,0 +1,39 @@
+export default {
+
+    // 上传文字介绍
+    uploadText:{
+        type:String,
+        default:'上传照片'
+    },
+
+    // 图像地址
+    src:{
+        type: String,
+        default:''
+    },
+
+    // 点击图像是否允许上传
+    triggerImage:{
+        type: Boolean,
+        default: true
+    },
+
+    // 是否压缩
+    compress:{
+        type:Boolean,
+        default: true
+    },
+
+    // 压缩最大的尺寸
+    compressSize:{
+        type: Number,
+        default: 500
+    },
+
+    // size 类型 small big
+    size:{
+        type:String,
+        default:'small'
+    }
+
+}

+ 69 - 0
src/components/upload/src/main.vue

@@ -0,0 +1,69 @@
+<template>
+  <section class="screen upload-container overflow relative"
+    :class="['upload-container-'+size]"
+  >
+    <!--  上传照片  -->
+    <section @click="triggerFile" class="screen cursor-pointer">
+      <slot name="upload">
+        <aside class="screen center">
+          <div class="upload-icon center">
+            <icon type="add"></icon>
+          </div>
+          <div class="upload-title">{{uploadText}}</div>
+        </aside>
+      </slot>
+    </section>
+    <!--  已设置的图片  -->
+    <section class="absolute upload-screen"
+      :class="{
+        'cursor-pointer': triggerImage
+      }"
+      v-show="vSrc"
+      :show="!!vSrc"
+      @click="triggerFile('image')"
+    >
+      <v-image :src="vSrc" class="screen" :mergeSuccessColor="true" backgroundColor="#fff">
+        <slot name="image" :trigger="triggerFile"></slot>
+      </v-image>
+    </section>
+
+    <!--  其他状态  -->
+    <section v-if="status === Status.loading || status === Status.fail" class="absolute upload-screen upload-other">
+
+      <aside v-if="status === Status.loading" class="screen center upload-other-background">
+        <svg class="upload-other-icon" viewBox="0 0 100 100" enable-background="new 0 0 0 0" xml:space="preserve">
+            <path fill="#fff" d="M73,50c0-12.7-10.3-23-23-23S27,37.3,27,50 M30.9,50c0-10.5,8.5-19.1,19.1-19.1S69.1,39.5,69.1,50">
+              <animateTransform attributeName="transform" attributeType="XML" type="rotate" dur="1s" from="0 50 50" to="360 50 50" repeatCount="indefinite"></animateTransform>
+          </path>
+        </svg>
+        <div class="upload-other-title">上传中</div>
+      </aside>
+
+      <aside v-else @click="triggerRetry" class="screen center upload-other-background cursor-pointer">
+
+        <div class="upload-other-title">点击重试</div>
+      </aside>
+
+    </section>
+
+  </section>
+  <file ref="file" @change="uploadFile"></file>
+</template>
+
+<script>
+import File from "$components/file";
+import {
+  icon,
+  vImage
+} from '$components';
+import mixins from '../mixins';
+import props from '../props';
+export default {
+  name: "upload",
+  components: {File,icon,vImage},
+  props,
+  mixins
+}
+</script>
+
+<style scoped lang="scss" src="../style.scss"></style>

+ 63 - 0
src/components/upload/style.scss

@@ -0,0 +1,63 @@
+/* 容器 */
+.upload-container{
+  border-radius: 10px;
+  background-color: rgba(255,255,255,0.08);
+}
+/* 容器 */
+/* 上传图片 */
+.upload-icon{
+  @include square(40px);
+  border-radius: 50%;
+  border: 2px solid rgba(255,255,255,0.2);
+}
+.upload-icon i {
+  color: rgba(255,255,255,0.2);
+  font-size: 20px;
+  font-weight: bold;
+}
+.upload-title{
+  font-size: 14px;
+  line-height: 20px;
+  color: rgba(255,255,255,0.4);
+  margin-top: 15px;
+}
+/* 上传图片 */
+
+/* 默认绝对定位容器 */
+.upload-screen{
+  z-index: 2;
+  top: 0;
+  left: 0;
+  bottom: 0;
+  right: 0;
+}
+/* 默认绝对定位容器 */
+
+/* 上传的其他状态 */
+.upload-other{
+  z-index: 3;
+}
+.upload-other-background{
+  background-color: rgba(0,0,0,0.3);
+}
+.upload-other-icon{
+  @include square(80px);
+}
+.upload-other-title{
+  font-size: 18px;
+  line-height: 22px;
+  color: #fff;
+  margin-top: -3px;
+}
+/* 上传的其他状态 */
+
+/* small */
+.upload-container-small .upload-other-icon{
+  @include square(50px);
+  margin-top: -6px;
+}
+.upload-container-small .upload-other-title{
+  font-size: 14px;
+  line-height: 18px;
+}
+/* small */

+ 4 - 1
src/config/user.ts

@@ -7,6 +7,9 @@ export default {
     user:undefined,
 
     // 缓存的key
-    storageKey:'user'
+    storageKey:'user',
+
+    // 校验唯一值
+    stringify:''
 
 }

+ 15 - 0
src/const/component.ts

@@ -0,0 +1,15 @@
+enum LibComponent {
+
+    input='input',
+
+    select='select',
+
+    date='date',
+
+    textarea='textarea',
+
+    address='address'
+
+}
+
+export {LibComponent};

+ 1 - 1
src/layout/layout-entry/style.scss

@@ -1,7 +1,7 @@
 /* 主模块 */
 .user-container{
   width: 1400px;
-  z-index: 99999999;
+  z-index: 99;
 }
 .user-container-modules{
   padding-bottom: 62.15%;

+ 5 - 3
src/main.ts

@@ -1,10 +1,12 @@
-import { createApp } from 'vue'
+import {
+    createApp
+} from 'vue'
 import App from './App.vue'
 import router from './router'
 import store from './store'
+import 'ant-design-vue/dist/antd.css';
 import popup from '$popup/popup-export/global';
 import request from "$utils/request";
 
-createApp(App).use(store).use(router).use(popup).use(request).mount('#app')
-
+const app = createApp(App).use(store).use(router).use(popup).use(request).mount('#app')
 

+ 0 - 1
src/mixins/status/index.ts

@@ -48,7 +48,6 @@ export default <LibMixins>{
 
             this.setStatus(Status.loading);
 
-
             return this.$emit(this.status_fetch_key,{
                success:()=> {
                    if (!this.status_unique || unique === this._statusUseUnique) {

+ 27 - 0
src/mixins/user.ts

@@ -0,0 +1,27 @@
+import user from '$config/user';
+
+export default {
+
+    computed:{
+        user:{
+            get(){
+                return this.$store.state.user.user;
+            },
+            set(value:Record<string, any>){
+
+                let resultData:Record<string, any> = value;
+
+                if (user.user) {
+                  let useUserData = JSON.parse(JSON.stringify(user.user));
+                    resultData = {
+                        ...useUserData,
+                        ...resultData
+                    }
+                }
+
+                return this.$store.commit('setUserInfo',resultData);
+            }
+        }
+    }
+
+}

+ 5 - 2
src/popup/popup-export/components.ts

@@ -1,6 +1,9 @@
 import { defineAsyncComponent } from 'vue';
+import {PopupExportComponent} from "$popup/popup-export/const";
 
 export default {
-    popupLogin:defineAsyncComponent(()=> import('$popup/popup-login')),
-    toast:defineAsyncComponent(()=> import('$popup/popup-toast'))
+    [PopupExportComponent.login]:defineAsyncComponent(()=> import('$popup/popup-login')),
+    [PopupExportComponent.toast]:defineAsyncComponent(()=> import('$popup/popup-toast')),
+    [PopupExportComponent.personal]: defineAsyncComponent(()=> import('$popup/popup-personal')),
+    [PopupExportComponent.loading]: defineAsyncComponent(()=> import('$popup/popup-loading')),
 }

+ 19 - 0
src/popup/popup-export/const/index.ts

@@ -0,0 +1,19 @@
+const enum PopupExportComponent {
+
+    // 登录弹窗
+    login='popup-login',
+
+    // 提示框
+    toast='toast',
+
+    // 个人中心
+    personal='popup-personal',
+
+    // 加载弹窗
+    loading='loading'
+
+}
+
+export {
+    PopupExportComponent
+};

+ 3 - 1
src/popup/popup-export/global.ts

@@ -8,6 +8,8 @@ import request from "$utils/request";
 
 import store from '../../store'
 
+import {PopupExportComponent} from "$popup/popup-export/const";
+
 export default {
 
     install(app:any){
@@ -23,7 +25,7 @@ export default {
                 }
             }
 
-            // (popup.$popup as PopupComponent).open('popup-login');
+            (popup.$popup as PopupComponent).open(PopupExportComponent.personal);
 
         });
 

+ 3 - 0
src/popup/popup-export/src/main.vue

@@ -11,6 +11,9 @@
 
 <script lang="ts">
 import components from "../components";
+
+import {PopupExportComponent } from '../const';
+
 export default <LibMixins>{
   name: "popup-export",
   data() {

+ 5 - 13
src/popup/popup-export/types/lib.popup-export.d.ts

@@ -4,18 +4,9 @@ interface PopupExportComponents {
 
 interface PopupExportOptions {
     options: Record<string, any>,
-    name: PopupExportComponent
+    name: string
 }
 
-declare const enum PopupExportComponent {
-    
-    // 登录弹窗
-    login='popup-login',
-
-    // 提示框
-    toast='toast'
-    
-}
 
 
 declare const enum LibDataComponents {
@@ -30,13 +21,14 @@ declare const enum LibDataComponents {
 }
 
 interface PopupComponent {
-    open:(name:PopupExportComponent | string,options?:Record<string, any>)=>string;
+    open:(name:string,options?:Record<string, any>)=>string;
     close:(name:string)=>void
 }
 
 interface PopupToastOption {
-    title:string,
-    duration?:number
+    title?:string,
+    duration?:number,
+    description?:string
 }
 
 

+ 3 - 0
src/popup/popup-loading/index.ts

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

+ 15 - 0
src/popup/popup-loading/mixins/handle.ts

@@ -0,0 +1,15 @@
+export default <LibMixins>{
+
+    data(){
+        return {
+            value:true
+        }
+    },
+
+    methods:{
+
+
+
+    }
+
+}

+ 3 - 0
src/popup/popup-loading/mixins/index.ts

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

+ 20 - 0
src/popup/popup-loading/props.ts

@@ -0,0 +1,20 @@
+export default {
+
+    // 显示标题
+    title:{
+        type:String,
+        default:''
+    },
+
+    // 显示最大时长
+    duration:{
+        type:Number,
+        default: 30000
+    },
+
+    mask:{
+        type:Boolean,
+        default: false
+    }
+
+}

+ 70 - 0
src/popup/popup-loading/src/main.vue

@@ -0,0 +1,70 @@
+<template>
+  <popup :value="!!loadingTitle" style="z-index: 9999;background-color: transparent"
+    :class="{
+      'loading-mask': mask
+    }"
+  >
+    <aside  class="absolute loading center">
+      <svg class="loading-icon" viewBox="0 0 100 100" enable-background="new 0 0 0 0" xml:space="preserve">
+            <path fill="#fff" d="M73,50c0-12.7-10.3-23-23-23S27,37.3,27,50 M30.9,50c0-10.5,8.5-19.1,19.1-19.1S69.1,39.5,69.1,50">
+              <animateTransform attributeName="transform" attributeType="XML" type="rotate" dur="1s" from="0 50 50" to="360 50 50" repeatCount="indefinite"></animateTransform>
+          </path>
+        </svg>
+      <div>{{loadingTitle}}</div>
+    </aside>
+  </popup>
+</template>
+
+<script>
+import props from '../props';
+import {
+  Popup
+} from "$components";
+export default {
+  name: "popup-loading",
+  components: {Popup},
+  watch:{
+
+    title:{
+      handler:function () {
+        if (this.title) {
+          return this.open();
+        }else if (this.loadingTitle && !this.title) {
+          return  this.close();
+        }
+      },
+      immediate:true
+    }
+
+  },
+
+  methods:{
+    open:function () {
+
+      if (this.title) {
+        clearTimeout(this.time);
+
+        this.loadingTitle = this.title;
+
+        this.time = setTimeout(()=> this.close(),this.duration || 30000);
+      }
+
+    },
+    close(){
+      clearTimeout(this.time);
+      this.$emit('destroy-popup');
+    }
+  },
+
+  data(){
+    return {
+      loadingTitle:''
+    }
+  },
+
+  props
+
+}
+</script>
+
+<style scoped lang="scss" src="../style.scss"></style>

+ 31 - 0
src/popup/popup-loading/style.scss

@@ -0,0 +1,31 @@
+/* 是否可以穿透 */
+.loading-mask {
+  pointer-events: none;
+}
+/* 是否可以穿透 */
+/* toast */
+.loading-icon{
+  @include square(70px);
+  margin-top: -10px;
+}
+.loading{
+  top: 50%;
+  left: 50%;
+  transform: translate(-50%,-50%);
+  font-size: 18px;
+  color: #fff;
+  line-height: 24px;
+  @include square(150px);
+  padding: 0 20px;
+  background-color: rgba(0,0,0,0.5);
+  border-radius: 5px;
+  letter-spacing: 1px;
+  opacity: 1 !important;
+  z-index: 99999;
+}
+.loading div {
+  font-size: 18px;
+  line-height: 20px;
+  margin-top: 10px;
+}
+/* toast */

+ 11 - 3
src/popup/popup-login/components/login/src/main.vue

@@ -58,6 +58,8 @@ import verification from '$utils/verification';
 
 import { InstructionsMessageType } from '$utils/request';
 
+import user from '$config/user';
+
 export default {
   name: "login",
   data(){
@@ -80,11 +82,17 @@ export default {
       }).then((data)=>{
 
         if (data.isSuccess) {
-          this.$store.commit('setUserInfo',data.data);
-        }
 
+          user.user = data.data;
+
+          this.$request.getUserInfo().then((resultData)=>{
+             this.$store.commit('setUserInfo',Object.assign({},data.data,resultData));
+             return obj.success();
+          }).catch(obj.fail);
+        } else {
+          return obj.none();
+        }
 
-        return data.isSuccess ? obj.success() : obj.none();
       }).catch(obj.fail);
     },
     setLoginParams:function (data){

+ 8 - 0
src/popup/popup-personal/components/index.ts

@@ -0,0 +1,8 @@
+import { defineAsyncComponent } from 'vue';
+
+export default {
+
+    // 个人资料
+    personalData: defineAsyncComponent(()=> import('./personal-data'))
+
+}

+ 12 - 0
src/popup/popup-personal/components/personal-data/data/footer.ts

@@ -0,0 +1,12 @@
+export default [
+
+    {
+        icon:require('../images/authentication.png'),
+        label:'实名认证'
+    },
+    {
+        icon:require('../images/alipay.png'),
+        label:'支付宝认证'
+    },
+
+]

+ 56 - 0
src/popup/popup-personal/components/personal-data/data/info.ts

@@ -0,0 +1,56 @@
+import { LibComponent } from '$const/component';
+export default [
+    {
+        label:'昵称',
+        key:'nick_name',
+        placeholder:'请输入昵称',
+        rules:''
+    },
+    {
+        label: '生日',
+        key:'birthday',
+        component:LibComponent.date,
+        placeholder:'请选择生日日期',
+        rules:''
+    },
+    {
+        label: '性别',
+        data:[
+            {
+                label:'男',
+                value:1
+            },
+            {
+                label:'女',
+                value:2
+            }
+        ],
+        component:LibComponent.select,
+        key:'sex',
+        format:function(value,item){
+            return value === 1 ? '男':'女'
+        },
+        rules:'请选择性别'
+    },
+    {
+        label:'家乡',
+        key:'city',
+        component:LibComponent.address,
+        rules:'请选择家乡地址'
+    },
+    {
+        label: '星座',
+        key:'constellation',
+        type:'getters',
+        disabled:true
+    },
+    {
+        label: '签名',
+        key: 'autograph',
+        labelPlaceholder:'暂未设置签名',
+        component: LibComponent.textarea,
+        maxLength:22,
+        placeholder:'请输入签名',
+        rules:''
+    }
+] as LibDataArray

二进制
src/popup/popup-personal/components/personal-data/images/alipay.png


二进制
src/popup/popup-personal/components/personal-data/images/authentication.png


二进制
src/popup/popup-personal/components/personal-data/images/info.png


+ 3 - 0
src/popup/popup-personal/components/personal-data/index.ts

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

+ 100 - 0
src/popup/popup-personal/components/personal-data/mixins/assets.ts

@@ -0,0 +1,100 @@
+import {InstructionsMessageType} from "$utils/request";
+
+export default <LibMixins>{
+
+    data(){
+        return {
+            photoWallNumber:4
+        }
+    },
+
+    methods:{
+
+        updateAvatar(obj){
+
+            let data = this.getInfoValues();
+
+            data.head_pic = obj.data;
+
+            return this.$request({
+                url:'user/modify_user_info',
+                data,
+                token:true,
+                message:true
+            }).then((data)=>{
+                if (data.isSuccess) {
+                    return obj.success();
+                } else {
+                    return obj.fail();
+                }
+            }).catch(obj.fail);
+
+        },
+
+        updateVoice(obj){
+            return this.updateUserInfo(obj,{
+                voicefile: obj.data
+            });
+        },
+
+        // 队列
+        updatePhotoWallPool(index,obj){
+
+            if (this.updateUserWillPool.length > 0) {
+                this.updateUserWillPool.push({
+                    index,obj
+                });
+            } else {
+                return this.updatePhotoWall(index,obj);
+            }
+
+        },
+
+        updatePhotoWall(index,obj){
+
+            let userPhotoWall = JSON.parse(JSON.stringify(this.$store.getters.photoWall || []));
+
+            userPhotoWall[index] = obj.data;
+
+            return this.updateUserInfo(obj,{
+                images:userPhotoWall.join(',')
+            },()=>{
+
+                if (this.updateUserWillPool.length > 0) {
+                    let resultData = this.updateUserWillPool.unshift();
+
+                    return this.updatePhotoWall(...resultData);
+
+                }
+
+            });
+
+        },
+
+        updateUserInfo(obj,resultData,callback?:Function){
+            return this.$request({
+                url:'hxuser/profile',
+                token:true,
+                data:resultData,
+                message:InstructionsMessageType.other
+            }).then((data)=>{
+                callback && callback();
+                if (data.isSuccess) {
+                    this.user = resultData;
+                    return obj.success();
+                } else {
+                    return obj.fail();
+                }
+            }).catch(()=>{
+                callback && callback();
+                return obj.fail();
+            });
+        }
+
+    },
+
+    created(){
+        this.updateUserWillPool = [];
+    }
+
+}

+ 5 - 0
src/popup/popup-personal/components/personal-data/mixins/index.ts

@@ -0,0 +1,5 @@
+import info from './info';
+import assets from './assets';
+import user from '$mixins/user';
+
+export default [info,user,assets];

+ 137 - 0
src/popup/popup-personal/components/personal-data/mixins/info.ts

@@ -0,0 +1,137 @@
+import infoData from '../data/info';
+
+import { LibComponent } from "$const/component";
+
+import $utils from '$utils/tool/utils';
+
+import verification from '$utils/verification';
+
+export default <LibMixins>{
+
+    data(){
+        return {
+            infoData,
+            infoDataValue:{},
+            infoEdit:false,
+            LibComponent
+        }
+    },
+
+    methods:{
+
+        getInfoValue(item,format:boolean=true){
+            if (format && item.format) {
+                return item.format(this.getInfoValue(item,false),item);
+            } else if (item.type === 'getters') {
+                return  this.$store.getters[item.key];
+            } else {
+                return  this.user[item.key];
+            }
+        },
+
+        setInfoData(){
+
+            let infoDataValue = {};
+
+            this.infoData.map((item,index)=>{
+                infoDataValue[item.key] = {
+                    label: this.getInfoValue(item,true),
+                    value: this.getInfoValue(item,false)
+                }
+            });
+
+            this.infoDataValue = infoDataValue;
+        },
+
+        getInfoValues():Record<string, any>{
+            let infoDataValue = {};
+            this.infoData.map((item,index)=>{
+                infoDataValue[item.key] = this.getInfoValue(item,false)
+            });
+            return infoDataValue;
+        },
+
+        changeValue(item,value){
+
+            if (typeof value === 'object') {
+                value = value.format('YYYY-MM-DD');
+
+                if (this.infoDataValue.constellation) {
+                    this.infoDataValue.constellation.value = $utils.constellation(value);
+                }
+
+
+            }
+
+            if (this.infoDataValue[item.key]) {
+                this.infoDataValue[item.key].value = value;
+            }
+
+        },
+
+        changeAddress(item,address){
+            let value = address.value[address.value.length -1];
+            if (value && this.infoDataValue[item.key] !== value) {
+                this.infoDataValue[item.key].value = value;
+            }
+        },
+
+        // 转变加载模式
+        switchEditMode(){
+
+            if (!this.infoEdit) {
+                this.infoEdit = true;
+                this.storageData = JSON.stringify(this.getInfoValues());
+            } else {
+
+                // 设置校验data
+                let resultData = [];
+                this.infoData.map((item,index)=>{
+                    resultData.push({
+                        rules: item.rules,
+                        placeholder:item.placeholder,
+                        key: item.key,
+                        exportKey: item.exportKey,
+                        value: this.infoDataValue[item.key].value
+                    });
+                });
+
+                verification.verificationPromise(resultData,true).then((data)=>{
+
+                    if (JSON.stringify(data) !== this.storageData) {
+                        this.$request({
+                            url:'user/modify_user_info',
+                            data,
+                            token:true,
+                            loading:'保存中',
+                            message:true,
+                            failMessage:true
+                        }).then((resultData)=>{
+                            if (resultData.isSuccess) {
+                                this.exitInfoEdit();
+                                this.setInfoData();
+                            }
+                        }).catch(()=> this.exitInfoEdit());
+                    } else {
+                        this.exitInfoEdit();
+                    }
+
+                });
+            }
+
+        },
+
+        exitInfoEdit(){
+            if (this.infoEdit) {
+                this.infoEdit = false;
+            }
+        }
+
+    },
+
+    created(){
+        // 设置values
+        this.setInfoData();
+    }
+
+}

+ 198 - 0
src/popup/popup-personal/components/personal-data/src/main.vue

@@ -0,0 +1,198 @@
+<template>
+
+  <section class="screen flex">
+
+    <!--  头部部分  -->
+    <header class="personal-data-header flex-1 row">
+
+      <aside class="personal-assets">
+        <header class="row personal-assets-header">
+          <aside class="personal-avatar-container">
+            <header class="personal-header-info">头像</header>
+            <section class="personal-upload-avatar">
+              <v-upload size="big" @upload-next="updateAvatar" :src="user.head_pic" :trigger-image="false">
+                <template v-slot:image="{trigger}">
+                  <div @click="trigger" class="cursor-pointer personal-update-avatar center absolute">修改头像</div>
+                </template>
+              </v-upload>
+            </section>
+          </aside>
+          <aside class="flex-1">
+            <header class="personal-header-info">声音</header>
+            <section class="personal-audio-container">
+              <sound-recording></sound-recording>
+            </section>
+          </aside>
+        </header>
+
+        <footer class="personal-assets-footer row between">
+
+          <aside
+            v-for="(item,index) in photoWallNumber"
+            class="personal-assets-footer-item"
+            :key="'footer-'+index"
+          >
+            <v-upload
+                @upload-next="updatePhotoWallPool(index,$event)"
+                :src="$store.getters.photoWall[index]"
+            ></v-upload>
+          </aside>
+
+        </footer>
+
+
+      </aside>
+      <aside class="personal-info flex">
+        <header class="personal-header-info rowACenter">
+          <div class="flex-1">资料</div>
+          <aside @click="switchEditMode" class="rowACenter personal-edit-icon cursor-pointer">
+            <img src="../images/info.png" />
+            <span>{{infoEdit?'保存资料':'编辑资料'}}</span>
+          </aside>
+        </header>
+        <section class="flex-1 overflow personal-info-scroll">
+          <scroll-view>
+            <aside
+                v-for="(item,index) in infoData"
+                class="personal-info-item row"
+                :class="{'personal-info-edit-item':infoEdit}"
+                :key="'info-item-'+index"
+            >
+              <div class="personal-info-label">{{item.label}}:</div>
+              <div class="personal-info-value flex-1"
+                   :class="{'personal-info-placeholder': !infoDataValue[item.key].value}"
+                   v-if="infoDataValue[item.key] && !infoEdit"
+              >{{ infoDataValue[item.key].label || item.labelPlaceholder }}</div>
+              <div v-else-if="infoEdit" class="flex-1 overflow">
+
+                <v-address
+                    v-if="LibComponent.address === item.component"
+                    @change="changeAddress(item,$event)"
+                >
+
+                </v-address>
+                <a-date-picker
+                    v-else-if="LibComponent.date === item.component"
+                    :default-value="infoDataValue[item.key].value"
+                    :placeholder="item.placeholder"
+                    class="personal-edit-input personal-edit-date"
+                    @change="changeValue(item,$event)"
+                />
+                <a-select v-else-if="LibComponent.select === item.component"
+                          class="personal-edit-input personal-edit-select"
+                          :value="infoDataValue[item.key].value"
+                          :placeholder="item.placeholder"
+                          @change="changeValue(item,$event)"
+                >
+                  <a-select-option
+                      v-for="(cItem,cIndex) in item.data"
+                      :value="cItem.value"
+                  >
+                    {{cItem.label}}
+                  </a-select-option>
+                </a-select>
+
+                <div v-else-if="LibComponent.textarea === item.component" class="personal-edit-input personal-edit-textarea-container">
+                <textarea class="personal-edit-textarea screen"
+                          :placeholder="item.placeholder"
+                          :maxlength="item.maxLength"
+                          v-model="infoDataValue[item.key].value"
+                ></textarea>
+                  <section class="personal-edit-textarea-length">{{infoDataValue[item.key].value ? infoDataValue[item.key].value.length  : 0}}/{{item.maxLength}}</section>
+                </div>
+
+                <!--    默认输入     -->
+                <div v-else class="personal-edit-input">
+
+                  <input type="text" class="screen" :placeholder="item.placeholder"
+                         v-model="infoDataValue[item.key].value"
+                         :disabled="item.disabled"
+                  />
+                </div>
+              </div>
+            </aside>
+          </scroll-view>
+        </section>
+      </aside>
+
+    </header>
+
+    <!--  底部  -->
+    <footer class="personal-data-footer row">
+      <div
+        v-for="(item,index) in footerData"
+        :key="'footer-'+index"
+        class="flex-1 rowCenter"
+      >
+        <img :src="item.icon" class="personal-footer-icon"  />
+        <div class="flex-1">{{item.label}}</div>
+        <div class="cursor-pointer personal-footer-button center">认证</div>
+      </div>
+    </footer>
+
+  </section>
+
+</template>
+
+<script>
+import footerData from '../data/footer';
+import mixins from '../mixins/index';
+import {
+  Select,
+  DatePicker
+} from 'ant-design-vue';
+import {
+  vAddress,
+  scrollView,
+  vUpload,
+  soundRecording
+} from '$components';
+
+export default {
+  name: "personal-data",
+  data() {
+    return {
+      footerData
+    }
+  },
+  mixins,
+  components:{
+    [Select.name]:Select,
+    [Select.Option.displayName]:Select.Option,
+    [DatePicker.name]:DatePicker,
+    vAddress,
+    scrollView,
+    vUpload,
+    soundRecording
+  }
+}
+</script>
+
+<style scoped lang="scss" src="../style.scss"></style>
+
+<style>
+.personal-edit-select .ant-select-selector,.personal-edit-date .ant-input{
+  background-color: transparent !important;
+  border: none !important;
+  outline: none !important;
+  color: #fff;
+  padding: 0 !important;
+  box-shadow: none !important;
+  opacity: 1 !important;
+}
+.personal-edit-date .ant-input{
+  height: 32px;
+}
+
+.personal-edit-date .ant-input::placeholder{
+  color: rgba(255,255,255,0.5);
+}
+
+.personal-edit-select .ant-select-selection-item,.personal-edit-select .ant-select-arrow,.personal-edit-date .ant-calendar-picker-icon {
+  color: #fff !important;
+
+}
+.personal-edit-date .ant-calendar-picker-clear{
+  display: none;
+}
+</style>

+ 179 - 0
src/popup/popup-personal/components/personal-data/style.scss

@@ -0,0 +1,179 @@
+@mixin defaultColor() {
+  background-color: rgba(255,255,255,0.08);
+  border-radius: 10px;
+}
+
+/* 头部 */
+.personal-data-header{
+  margin-bottom:15px;
+}
+.personal-header-info{
+  font-size: 18px;
+  line-height: 22px;
+  font-weight: 400;
+  margin-top: 15px;
+}
+/* 头部 */
+
+/* 资源 */
+.personal-assets{
+  width: 540px;
+  @include defaultColor();
+}
+.personal-info,.personal-assets{
+  padding: 0 20px;
+}
+.personal-assets-header{
+  height: 286px;
+}
+/* 资源 */
+
+/* 用户头像 */
+$avatar-size: 240px;
+.personal-avatar-container{
+  width: $avatar-size;
+  margin-right: 20px;
+}
+.personal-upload-avatar{
+  @include square($avatar-size);
+  margin-top: 10px;
+}
+.personal-update-avatar{
+  right: 10px;
+  bottom: 10px;
+  width: 80px;
+  height: 26px;
+  background-color: rgba(0,0,0,0.2);
+  border-radius: 12px;
+  font-size: 14px;
+  line-height: 18px;
+}
+/* 用户头像 */
+
+/* 个人资料 */
+.personal-info-scroll{
+  padding: 20px 0;
+}
+.personal-info{
+  @include defaultColor();
+  width: 310px;
+  margin-left: 15px;
+}
+.personal-edit-icon{
+  font-size: 14px;
+  color: rgba(255,255,255,0.5);
+  line-height: 18px;
+  font-weight: 400;
+}
+.personal-edit-icon img{
+  width: 13px;
+  height: 15px;
+  margin-right: 7px;
+}
+.personal-info-item{
+  margin-top: 15px;
+  font-size: 14px;
+  line-height: 32px;
+  font-weight: 400;
+}
+.personal-info-item:first-child{
+  margin-top: 0 !important;
+}
+.personal-info-label,.personal-info-value{
+  line-height: 32px;
+}
+.personal-info-label{
+  width: 40px;
+}
+.personal-info-placeholder{
+  color: rgba(255,255,255,0.5);
+}
+
+/* 个人资料 */
+
+/* 底部上传用户照片墙 */
+.personal-assets-footer{
+  margin-top: 22px;
+}
+.personal-assets-footer-item{
+  @include square(120px);
+}
+/* 底部上传用户照片墙 */
+
+/* 音频 */
+.personal-audio-container{
+  @include defaultColor();
+  padding: 30px;
+  margin-top: 10px;
+}
+/* 音频 */
+
+/* 个人资料编辑模式 */
+.personal-info-edit-item{
+  margin-top: 11px;
+}
+.personal-info-edit-item:first-child{
+  margin-top: 22px;
+}
+.personal-edit-input{
+  height: 32px;
+  @include defaultColor();
+  padding: 0 10px;
+}
+.personal-edit-input input,.personal-edit-input textarea{
+  color: #fff;
+  font-size: 14px;
+  line-height: 16px;
+}
+.personal-edit-input input::placeholder,.personal-edit-input textarea::placeholder {
+  color: rgba(255,255,255,0.7);
+}
+/* 个人资料编辑模式 */
+
+/* textarea */
+.personal-edit-textarea-container{
+  height: auto;
+  padding: 5px 10px;
+}
+.personal-edit-textarea{
+  height: 60px;
+  resize: none;
+  line-height: 20px !important;
+}
+.personal-edit-textarea-length{
+  text-align: right;
+  line-height: 20px;
+}
+/* textarea */
+
+/* 底部 */
+.personal-data-footer,.personal-data-footer>div{
+  height: 150px;
+}
+.personal-data-footer>div {
+  margin-right: 15px;
+  @include defaultColor();
+  padding: 0 30px;
+  font-size: 18px;
+  font-weight: 400;
+  line-height: 20px;
+}
+.personal-data-footer>div:last-child{
+  margin-right: 0;
+}
+.personal-footer-button{
+  background-color: rgba(255,255,255,0.1);
+  border-radius: 18px;
+  width: 80px;
+  height: 36px;
+  color: #59ACFF;
+  font-size: 16px;
+  line-height: 20px;
+  font-weight: 400;
+}
+.personal-footer-icon{
+  width: 44px;
+  height: 44px;
+  margin-right: 15px;
+}
+/* 底部 */

+ 27 - 0
src/popup/popup-personal/data/menu.ts

@@ -0,0 +1,27 @@
+export default [
+    {
+        label:'基本资料',
+        icon:require('../images/information.png'),
+        component:'personalData'
+    },
+    {
+        label:'我的钱包',
+        icon:require('../images/wallet.png')
+    },
+    {
+        label:'我的订单',
+        icon:require('../images/order.png')
+    },
+    {
+        label:'我的礼物',
+        icon:require('../images/gift.png')
+    },
+    {
+        label:'个性装扮',
+        icon:require('../images/dress.png')
+    },
+    {
+        label:'我的关注',
+        icon:require('../images/attention.png')
+    }
+] as LibDataArray

二进制
src/popup/popup-personal/images/attention.png


二进制
src/popup/popup-personal/images/dress.png


二进制
src/popup/popup-personal/images/gift.png


二进制
src/popup/popup-personal/images/information.png


二进制
src/popup/popup-personal/images/order.png


二进制
src/popup/popup-personal/images/wallet.png


+ 3 - 0
src/popup/popup-personal/index.ts

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

+ 66 - 0
src/popup/popup-personal/mixins/component.ts

@@ -0,0 +1,66 @@
+export default {
+
+    data(){
+        return {
+            component:'',
+            componentDraw:{}
+        }
+    },
+
+    watch:{
+
+        page:{
+            handler(){
+                if (this.page) {
+                    return this.openPage(this.page);
+                }
+            },
+            immediate:true
+        }
+
+    },
+
+    provide(){
+      return {
+
+          // 打开页面
+          openPage: (name:string,params?:Record<string, any>)=> this.openPage(name,params),
+
+          // 返回页面参数
+          getPageParams:(name:string)=> {
+              return this.componentParams[name];
+          }
+
+      }
+    },
+
+    methods:{
+
+        openPage(name:string,params?:Record<string, any>){
+
+            if (name && this.component !== name) {
+
+                // 设置渲染
+                if (this.componentDraw[name] !== true) {
+                    this.componentDraw[name] = true;
+                }
+
+                if (this.componentParams === undefined) {
+                    this.componentParams = {};
+                }
+                // 重新设置 参数
+                this.componentParams[name] = params;
+
+                this.component = name;
+
+            }
+
+        }
+
+    },
+
+    created() {
+        this.componentParams = {};
+    }
+
+}

+ 28 - 0
src/popup/popup-personal/mixins/handle.ts

@@ -0,0 +1,28 @@
+import extend from '$utils/tool/extend';
+
+import menuData from '../data/menu';
+
+export default <LibMixins>{
+
+    data(){
+        return {
+            value:true,
+            menuData
+        }
+    },
+
+    methods:{
+
+        close(){
+            if (this.value) {
+                this.value = false;
+            }
+        },
+
+        copy(text:string){
+            return extend.copy(text,true);
+        }
+
+    }
+
+}

+ 5 - 0
src/popup/popup-personal/mixins/index.ts

@@ -0,0 +1,5 @@
+import handle from './handle';
+import user from '$mixins/user';
+import component from './component';
+
+export default [handle,user,component];

+ 14 - 0
src/popup/popup-personal/props.ts

@@ -0,0 +1,14 @@
+export default {
+
+    unique:{
+        type:String,
+        default:''
+    },
+
+    // 默认选中的
+    page:{
+        type: String,
+        default:'personalData'
+    }
+
+}

+ 90 - 0
src/popup/popup-personal/src/main.vue

@@ -0,0 +1,90 @@
+<template>
+  <popup v-model:value="value" content-animate="scale" @close="$emit('destroy-popup')">
+    <section class="popup-personal row">
+
+      <aside class="popup-personal-left flex overflow">
+        <header class="personal-info center overflow">
+          <v-image class="personal-avatar" radius=".1rem" :src="user.head_pic"></v-image>
+          <section class="row center personal-info-header overflow">
+            <div class="line-1 personal-nickname">{{user.nick_name}}</div>
+            <aside class="personal-sex rowACenter"
+              :class="['personal-sex-' + (user.sex===1?'boy':'girl')]"
+            >
+              <icon :type="user.sex===1?'boy':'girl'"></icon>
+              <span class="personal-sex-left">{{$store.getters.age}}</span>
+            </aside>
+          </section>
+          <section class="rowACenter personal-id">
+            <div class="flex-1 rowACenter">ID:<span class="personal-id-margin">{{user.uid}}</span></div>
+            <span class="cursor-pointer" @click="copy(user.uid)">复制</span>
+          </section>
+        </header>
+
+        <section class="personal-menu flex-1 overflow">
+          <aside
+            v-for="(item,index) in menuData"
+            :key="'menu-item-'+item.name"
+            class="personal-menu-item rowCenter"
+            @click="openPage(item.component)"
+            :class="{
+              'personal-menu-item-active': component === item.component,
+
+            }"
+          >
+            <v-image :src="item.icon" mode="center" backgroundColor="transparent" class="menu-item-icon"></v-image>
+            <span>{{item.label}}</span>
+          </aside>
+        </section>
+      </aside>
+      <!-- 展示menu对象 -->
+      <aside class="flex-1 overflow">
+
+        <template
+          v-for="(item,index) in menuData"
+        >
+          <component
+            v-if="componentDraw[item.component]"
+            v-show="item.component === component"
+            :is="item.component"
+          >
+
+          </component>
+        </template>
+
+      </aside>
+
+
+    </section>
+  </popup>
+</template>
+
+<script>
+import {
+  Popup,
+  vImage,
+  icon
+} from '$components';
+
+import props from '../props';
+
+import mixins from '../mixins';
+
+import components from '../components';
+
+export default {
+  name: "popup-personal",
+
+  mixins,
+
+  props,
+
+  components:{
+    Popup,
+    vImage,
+    icon,
+    ...components
+  }
+}
+</script>
+
+<style scoped lang="scss" src="../style.scss"></style>

+ 99 - 0
src/popup/popup-personal/style.scss

@@ -0,0 +1,99 @@
+/* 模块 */
+.popup-personal{
+  width: 1140px;
+  height: 673px;
+  background-color: #1B1839;
+  border-radius: 30px;
+  padding: 30px;
+}
+/* 模块 */
+
+/* 模块左侧 */
+$popup-personal-size:200px;
+.popup-personal-left{
+  width: $popup-personal-size;
+  margin-right: 15px;
+  height: 100%;
+}
+/* 模块左侧 */
+
+/* 用户信息 */
+.personal-info{
+  @include square($popup-personal-size);
+  background-color: rgba(255,255,255,0.08);
+  border-radius: 10px;
+  padding-top: 6px;
+}
+.personal-info-header{
+  margin-top: 13px;
+  font-size: 20px;
+  line-height: 24px;
+  font-weight: bold;
+  color: #fff;
+  width: $popup-personal-size;
+  padding: 0 10px;
+}
+.personal-nickname{
+  width: auto;
+  max-width: 100%;
+}
+.personal-avatar{
+  @include square(110px);
+}
+.personal-sex{
+  background-color: #5C88E0;
+  font-size: 14px;
+  font-weight: 400;
+  line-height: 14px;
+  height: 20px;
+  padding: 0 7px;
+  border-radius: 10px;
+  margin-left: 10px;
+}
+.personal-sex-girl{
+  background-color: #F191AC;
+}
+.personal-sex-left{
+  margin-left: 3px;
+}
+.personal-id{
+  width: 100%;
+  padding: 0 20px;
+  margin-top: 6px;
+  font-size: 14px;
+  color: #727084;
+}
+.personal-id-margin{
+  margin-left: 6px;
+
+}
+/* 用户信息 */
+
+/* 菜单栏 */
+.personal-menu{
+  background-color: rgba(255,255,255,0.08);
+  border-radius: 10px;
+  padding: 17px 20px;
+  margin-top: 15px;
+}
+.personal-menu-item{
+  width: 100%;
+  height: 30px;
+  margin-top: 20px;
+  font-size: 14px;
+  line-height: 30px;
+  cursor: pointer;
+}
+.personal-menu-item:first-child{
+  margin-top: 0;
+}
+.personal-menu-item-active{
+  background-color: rgba(255,255,255,0.1);
+  border-radius: 15px;
+  cursor: default;
+}
+.menu-item-icon{
+  @include square(18px);
+  margin-right: 8px;
+}
+/* 菜单栏 */

+ 66 - 4
src/store/modules/user.ts

@@ -4,6 +4,12 @@ import userConfig from '$config/user';
 
 import storage from '$utils/tool/storage';
 
+import { DateFormat } from '@/utils/tool/date';
+
+import $utils from '$utils/tool/utils';
+
+import $request from '$utils/request';
+
 export default <Module<any,any>>{
 
     state:{
@@ -13,6 +19,43 @@ export default <Module<any,any>>{
     getters:{
       isLogin(state){
           return state.user && state.user.login_token;
+      },
+        // 年龄
+      age(state){
+
+          if (state.user.birthday) {
+
+              let nowYear = new Date().getFullYear();
+
+              let usePeople = DateFormat.inverse(state.user.birthday,'YYYY-MM-DD');
+
+              let age = Math.ceil(nowYear - usePeople.year);
+
+              return  age < 0 ? 0 : age;
+
+          } else {
+              return 0;
+          }
+
+      },
+
+      // 星座
+      constellation(state){
+          if (state.user.birthday) {
+              let constellation =  $utils.constellation(state.user.birthday);
+              return  constellation ? constellation: state.user.constellation || ''
+          } else {
+              return state.user.constellation || '';
+          }
+      },
+
+      // 照片墙
+      photoWall(state){
+          if (state.user.images) {
+              return state.user.images.split(',') || [];
+          } else {
+              return [];
+          }
       }
     },
 
@@ -20,23 +63,42 @@ export default <Module<any,any>>{
 
         initializationUser(state){
             let userData = storage.getItem(userConfig.storageKey);
+
             if (userData) {
                 userConfig.user = userData;
                 state.user = userData;
-                console.log(userData);
             }
         },
 
         // 设置缓存
         setUserInfo(state,user){
 
-            if (user) {
-
+            let storageData = JSON.stringify(user);
+            if (user && userConfig.stringify !== JSON.stringify(user)) {
+                userConfig.stringify = storageData;
                 userConfig.user = user;
-                state.user = user;
 
+                state.user = user;
                 return storage.setItem(userConfig.storageKey,user,userConfig.storageTime);
+            }
+
+        }
+
+    },
+
+    actions:{
+
+        initializationUserPromise({commit}){
+
+            commit('initializationUser');
 
+            if (userConfig.user) {
+                return $request.getUserInfo().then((data:Record<string, any>)=>{
+                   commit('setUserInfo',{
+                      ...userConfig.user,
+                      ...data
+                   });
+                })
             }
 
         }

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

@@ -23,6 +23,18 @@ interface LibVueOption  {
 
     $popup:PopupComponent,
 
-    $toast:(option:PopupToastOptions)=>string
+    $toast:(option:PopupToastOptions)=>string,
+
+    $loading:(option:PopupToastOptions)=>string,
+
+    $success:(option:PopupToastOptions)=>any,
+
+    $fail:(option:PopupToastOptions)=>any,
+
+    $warning:(option:PopupToastOptions)=>any,
+
+    $info:(option:PopupToastOptions)=>any,
+
+    $request:(option:LibRequestOptions)=>Promise
 
 }

+ 41 - 0
src/utils/request/index.ts

@@ -43,6 +43,47 @@ const request = function (data:LibRequestOptions) {
 
 }
 
+request.uploadFile = function (data:LibRequestOptions) {
+
+    // @ts-ignore
+    data.file = true;
+
+    data.header ={
+        'content-type':'multipart/form-data'
+    }
+
+    return request(data);
+
+}
+
+request.getUserInfo = function () {
+
+    return new Promise(function (resolve, reject){
+        Promise.all(
+            [
+                request({
+                    url:'user/get_user_info',
+                    token:true
+                }),
+                request({
+                    url:'hxuser/getuserinfo',
+                    token:true
+                }),
+            ]
+        ).then((data)=>{
+
+            let resultData:Record<string, any> = {};
+
+            for (let i=0,count=data.length;i<count;i++) {
+                resultData = Object.assign({},resultData,data[i].data);
+            }
+
+            return resolve(resultData);
+        }).catch(reject);
+    })
+
+}
+
 request.install = function (app:any) {
     app.config.globalProperties.$request = request;
 };

+ 1 - 1
src/utils/request/instructions/plugins/config.ts

@@ -10,7 +10,7 @@ export default {
                 pageSize:'pageSize'
             },
             user:{
-                token:'token'
+                token:'login_token'
             },
             failMessage:{
                 default: '服务繁忙',

+ 62 - 41
src/utils/request/instructions/plugins/instructions.ts

@@ -4,7 +4,7 @@ import config from '../../config';
 
 import InConfig from './config';
 
-// import user from '../../../../user/index';
+import user from '$config/user';
 
 import {
     InstructionsWhere,
@@ -15,6 +15,7 @@ import {
 import verification from '$utils/verification/index';
 
 import { Mode } from '../../const/index';
+import popup from "$utils/tool/popup";
 
 export default instructions.pushConfig({
 
@@ -70,7 +71,8 @@ export default instructions.pushConfig({
     token:function ({requestData}){
 
         if (requestData.data === undefined) requestData.data = {};
-        requestData.data[InConfig.getTargetConfig(requestData.mode,'user').token ] =  '';
+        let tokenKey = InConfig.getTargetConfig(requestData.mode,'user').token;
+        requestData.data[tokenKey] =  user.user ? user.user[tokenKey] : '';
 
     },
 
@@ -83,45 +85,64 @@ export default instructions.pushConfig({
     },
 
     // 加载提示
-    // loading:{
-    //   triggerType:InstructionsTypes.all,
-    //   trigger:function ({requestData,status}){
-    //
-    //       console.log(requestData,status);
-    //       // // 首次触发
-    //       // if (status === LibRequestStatus.loading) {
-    //       //
-    //       //     if (typeof requestData.loading === 'string'){
-    //       //         // @ts-ignore
-    //       //         requestData.loading = {title:requestData.loading}
-    //       //     }
-    //       //
-    //       //     let resultLoading:InstructionsLoading = (requestData.loading as InstructionsLoading);
-    //       //
-    //       //     if (resultLoading.delay === undefined || resultLoading.delay > 0){
-    //       //         resultLoading._time = setTimeout( ()=>{
-    //       //             delete resultLoading._time
-    //       //             return this.triggerOpenLoading(resultLoading.title,resultLoading.mask);
-    //       //         },resultLoading.delay || 300);
-    //       //     } else {
-    //       //         return this.triggerOpenLoading(resultLoading.title,resultLoading.mask);
-    //       //     }
-    //       //
-    //       // } else {
-    //       //     let resultLoading:InstructionsLoading = (requestData.loading as InstructionsLoading);
-    //       //     if (resultLoading._time) return clearTimeout(resultLoading._time);
-    //       //     else return  toast.hideLoading();
-    //       // }
-    //
-    //   },
-    //   triggerOpenLoading(title:string,mask:boolean = true){
-    //       // return  toast.loading({
-    //       //     title,
-    //       //     mask
-    //       // });
-    //   },
-    //   zIndex:1
-    // },
+    loading:{
+      triggerType:InstructionsTypes.all,
+      trigger:function ({requestData,status}){
+
+          // 首次触发
+          if (status === LibRequestStatus.loading) {
+
+              if (typeof requestData.loading === 'string'){
+                  // @ts-ignore
+                  requestData.loading = {title:requestData.loading}
+              }
+
+              let resultLoading:InstructionsLoading = (requestData.loading as InstructionsLoading);
+
+              if (resultLoading.delay === undefined || resultLoading.delay > 0){
+                  resultLoading._time = setTimeout( ()=>{
+                      delete resultLoading._time
+                      return this.triggerOpenLoading(resultLoading.title,resultLoading.mask);
+                  },resultLoading.delay || 300);
+              } else {
+                  return this.triggerOpenLoading(resultLoading.title,resultLoading.mask);
+              }
+
+          } else {
+              let resultLoading:InstructionsLoading = (requestData.loading as InstructionsLoading);
+              if (resultLoading._time) return clearTimeout(resultLoading._time);
+              else return  popup.hideLoading();
+          }
+
+      },
+      triggerOpenLoading(title:string,mask:boolean = true){
+          return  popup.$loading({
+              title,
+              mask
+          });
+      },
+      zIndex:1
+    },
+
+    file:{
+      where: InstructionsWhere.has,
+      trigger:function (data) {
+
+          let formData = new FormData();
+
+          if (data.requestData.data) {
+              for (let key in data.requestData.data) {
+                  if (data.requestData.data.hasOwnProperty(key)) {
+                      formData.append(key,data.requestData.data[key]);
+                  }
+              }
+          }
+
+          data.requestData.data = formData;
+
+      },
+      zIndex:-9
+    },
 
     // 结束时触发
     end:{

+ 1 - 1
src/utils/request/types/lib.request.d.ts

@@ -11,7 +11,7 @@ interface LibRequestOptions extends LibRequestConfig,LibRequestInstructionsOptio
 
     fail?:Function,
 
-    filePath?:string,
+    file?:Boolean,
 
     name?:string,
 

+ 184 - 0
src/utils/tool/audio.ts

@@ -0,0 +1,184 @@
+enum PlayStatus {
+    none,
+    playBefore,
+    play,
+    playFail,
+    pause
+}
+
+enum TriggerAudioListener {
+    fail='fail',
+    canplay='canplay',
+    ended='ended',
+    speed='speed',
+    status='status'
+}
+
+class CustomAudio {
+
+    audio:HTMLAudioElement = undefined;
+
+    src:string = '';
+
+    triggers:Record<string, any>={};
+
+    status:PlayStatus = PlayStatus.none
+
+    // 设置播放路径
+    setSrc(src:string){
+
+        if (!CustomAudio.supper) return;
+
+        if (src && src !== this.src) {
+
+            // 清空所有监听事件
+            this.triggers = {};
+
+            this.src = src;
+            this.status = PlayStatus.none;
+
+            if (this.audio === undefined) {
+                this.audio = document.createElement('audio');
+                this.audio.setAttribute('style','display:none');
+                this.audio.src = src;
+
+                // 播放结束
+                this.audio.addEventListener('ended',  ()=> {
+                    this.setStatus(PlayStatus.pause);
+                    return this.triggerListener(TriggerAudioListener.canplay,{
+                        duration:  this.getDuration(this.audio.duration)
+                    });
+                }, false);
+
+                // 加载就绪
+                this.audio.addEventListener('canplay',  ()=> {
+                    return this.triggerListener(TriggerAudioListener.canplay,{
+                        duration: this.getDuration(this.audio.duration)
+                    });
+                }, false);
+
+
+                // 加载失败
+                this.audio.addEventListener('error',  ()=> {
+                    return this.triggerListener(TriggerAudioListener.fail,{
+                        message:'加载失败'
+                    });
+                }, false);
+
+                document.body.appendChild(this.audio);
+
+            } else {
+                this.audio.src = src;
+            }
+
+        }
+
+        return this;
+
+    }
+
+    getDuration(duration:number){
+
+        duration = parseFloat(duration.toFixed(2));
+
+        return Math.floor(duration);
+
+    }
+
+    // 播放
+    play(){
+        if (!this.audio || this.status === PlayStatus.play || this.status === PlayStatus.playBefore) return;
+        this.setStatus(PlayStatus.playBefore);
+        this.audio.play().then(()=>{
+            this.setStatus(PlayStatus.play);
+        }).catch(()=>{
+            this.setStatus(PlayStatus.playFail);
+        });
+
+        return this;
+    }
+
+    private triggerTime;
+    triggerSpeed(){
+        clearTimeout(this.triggerTime);
+
+        let params = {
+            duration: this.getDuration(this.audio.duration),
+            currDuration: this.getDuration(this.audio.currentTime)
+        };
+
+        let diff = this.audio.duration - this.audio.currentTime;
+
+        this.triggerListener(TriggerAudioListener.speed,params);
+
+
+        if (diff > 0) {
+            this.triggerTime = setTimeout(()=> this.triggerSpeed(),1000);
+        } else {
+            return ;
+        }
+
+
+    }
+
+    // 暂停
+    paused(){
+        if (!this.audio || this.status === PlayStatus.pause) return;
+        this.setStatus(PlayStatus.pause);
+        this.audio.pause();
+        return this;
+    }
+
+    setStatus(status:PlayStatus){
+        if (this.status !== status) {
+            this.status = status;
+
+            if (status === PlayStatus.play) {
+                this.triggerSpeed();
+            } else {
+                clearTimeout(this.triggerTime);
+            }
+
+            return this.triggerListener(TriggerAudioListener.status,status);
+        }
+    }
+
+
+    addListener(key:TriggerAudioListener,callback:Function) {
+
+        if (!callback) return;
+
+        if (this.triggers[key] === undefined) this.triggers[key] = [callback];
+        else this.triggers[key].push(callback);
+
+        return this;
+
+    }
+
+    removeListener(key:TriggerAudioListener,callback:Function) {
+
+        if (!callback || this.triggers[key] === undefined) return;
+
+        let index = this.triggers[key].indexOf(callback);
+
+        if (index>=0) this.triggers[key].splice(index,1);
+
+        return this;
+
+    }
+
+    triggerListener(key:TriggerAudioListener,data?:any) {
+
+        if (this.triggers[key]) {
+            this.triggers[key].map((item)=> item && item(data));
+        }
+
+    }
+
+    static supper:boolean = !!(document.createElement('audio').canPlayType)
+
+}
+
+export {CustomAudio,TriggerAudioListener,PlayStatus};
+
+export default new CustomAudio();

+ 117 - 0
src/utils/tool/compress.ts

@@ -0,0 +1,117 @@
+export default {
+
+	maxSize: 500,
+	compress:function (file,src:HTMLImageElement | string,base64:boolean=false,useInt:boolean=true,obj:Record<string, any> | undefined = undefined,maxSize:number = 500) {
+		return new Promise((resolve, reject)=>{
+
+			if (typeof src === 'string') {
+				let image:HTMLImageElement = document.createElement('img');
+
+				image.src = src;
+
+				image.onload = () => {
+					if (obj === undefined) {
+						obj = {
+							width: image.offsetWidth,
+							height: image.offsetHeight
+						}
+					}
+					image.parentNode.removeChild(image);
+					return this.compressTrigger(file,image,base64,useInt,obj,maxSize,resolve,reject);
+				}
+
+				image.onerror = ()=>{
+					image.parentNode.removeChild(image);
+					return  reject();
+				};
+
+				image.setAttribute('style','position:absolute;z-index:-9;top:0;left:0;opacity:0;visibility: hidden;');
+
+				document.body.appendChild(image);
+
+			} else {
+
+				if (obj === undefined) {
+					obj = {
+						width: src.offsetWidth,
+						height: src.offsetHeight
+					}
+				}
+
+				this.compressTrigger(file,src,base64,useInt,obj,maxSize,resolve,reject);
+			}
+
+
+
+		});
+	},
+
+	compressTrigger(file,image,base64,useInt,obj,maxSize,resolve,reject){
+
+		maxSize = maxSize || this.maxSize;
+
+		// 判断是否需要压缩
+		if (obj.width > maxSize) {
+			/* 统计尺寸 */
+			let width = obj.width;
+			let height = obj.height;
+			// 计算 宽度 是 高度的多少倍
+			let radio = width / height;
+
+			// 设置 压缩后的高度
+			height -= (width - this.maxSize) / radio;
+			// 设置 压缩后的宽度
+			width = this.maxSize;
+
+			// 执行格式化
+			width = useInt ? parseInt(width) : width;
+			height = useInt ? parseInt(height) : height;
+
+			// 创建画布
+			let canvas = document.createElement("canvas");
+			// 设置画布的宽高
+			canvas.width = width;
+			canvas.height = height;
+
+			// 创建 2D 的画布
+			let canvas2D = canvas.getContext('2d');
+			// 绘制图片
+			canvas2D.drawImage(image,0,0,width,height);
+
+			// 导出图片
+			if (base64) {
+				let exportImage = canvas.toDataURL(file.type, 0.7);
+				// 执行成功函数
+				return resolve({
+					path: exportImage,
+					width,
+					height
+				});
+			} else {
+				return canvas.toBlob((file)=>{
+
+					if (file) {
+
+						file = new File([file],'name.'+ file.type.split('/').pop(),{
+							type:file.type
+						})
+
+						return resolve({
+							file,
+							width,
+							height
+						});
+					} else {
+						return reject();
+					}
+
+				},file.type,0.7);
+			}
+
+
+		} else {
+			return reject();
+		}
+	}
+
+}

+ 145 - 0
src/utils/tool/date.ts

@@ -0,0 +1,145 @@
+/**
+ * @return {string}
+ */
+const DateFormat = function (date,format='YYYY-MM-DD hh:mm:ss') {
+
+	if (typeof date !== 'object' || date === null) {
+
+		if (/^\d+$/.test(date)) {
+
+			if (String(date).length <= 10) {
+				date = date +'000';
+			}
+			date = new Date(Number(date));
+		} else if (typeof date === 'string') {
+
+			if (date.indexOf('-') >= 0) date = date.replace(/-/g,'/');
+
+			date = new Date(date);
+		} else {
+			date = new Date();
+		}
+
+	}
+
+
+	return format.replace(DateFormat.reg,function (name) {
+
+		let target = DateFormat.params[name[0]];
+		if (target) {
+			if (name === 'YYYY') {
+
+				if (date instanceof Date) {
+					return date[target]();
+				} else {
+					return date[DateFormat.formatParams[name[0]]];
+				}
+
+			} else {
+
+				let value;
+				if (date instanceof Date) {
+					value = date[target]();
+
+					if (DateFormat.target[name[0]]) {
+						value += DateFormat.target[name[0]];
+					}
+				} else {
+					value = date[DateFormat.formatParams[name[0]]];
+				}
+
+				if (name.length >= 2) {
+
+					value = value <10?'0'+value:value;
+
+				}
+
+				return value;
+
+			}
+		} else {
+			return name;
+		}
+
+
+
+	});
+
+}
+
+DateFormat.inverse = function (date:string | Date,format:string='YYYY-MM-DD hh:mm:ss'):Record<string, any> {
+
+	let resultObject =  {};
+	format.replace(DateFormat.reg,function (name,start) {
+
+		let useName = DateFormat.formatParams[name[0]];
+
+		if (date instanceof Date) {
+
+			let value =parseInt(date[DateFormat.params[name[0]]]());
+			if (DateFormat.target[name[0]]) {
+				value += DateFormat.target[name[0]];
+			}
+
+			resultObject[useName] = value;
+		} else {
+			resultObject[useName] = parseInt(date.substr(start,name.length));
+		}
+
+		return '';
+
+
+	});
+
+	return resultObject;
+
+}
+
+DateFormat.createMaxDate = function (date?:Date) {
+
+	if (date === undefined ) date = new Date();
+
+	return new Date(date.getFullYear()+'/12/31 23:59:59');
+
+}
+
+DateFormat.createMinDate = function (date?:Date) {
+
+	if (date === undefined ) date = new Date();
+
+	return new Date(date.getFullYear()+'/1/1 00:00:00');
+
+}
+
+
+
+DateFormat.formatParams = {
+	'Y':'year',
+	'M':'month',
+	'D':'date',
+	'h':'hour',
+	'm':'minute',
+	's':'second'
+};
+
+
+DateFormat.params = {
+	'Y':'getFullYear',
+	'M':'getMonth',
+	'D':'getDate',
+	'h':'getHours',
+	'm':'getMinutes',
+	's':'getSeconds'
+};
+/* 递增 */
+DateFormat.target = {
+	'M': 1
+};
+
+DateFormat.reg = /YYYY|M{1,2}|D{1,2}|h{1,2}|m{1,2}|s{1,2}/g;
+
+export default 1;
+
+export {
+	DateFormat
+}

+ 26 - 0
src/utils/tool/extend.ts

@@ -0,0 +1,26 @@
+import popup from './popup';
+
+let copyEl:HTMLInputElement | undefined = undefined;
+
+export default {
+
+    // 复制
+    copy(text:string,toast=false) {
+
+        if (copyEl === undefined) {
+            copyEl = document.createElement('input');
+
+            copyEl.setAttribute('style','position:absolute;top:0;bottom:0;z-index:-9;height:0');
+
+            document.body.appendChild(copyEl);
+
+        }
+
+        copyEl.value = text;
+
+
+        copyEl.select(); // 选中文本
+        document.execCommand("copy"); // 执行浏览器复制命令
+        return toast && popup.$toast('复制成功');
+    },
+}

+ 40 - 0
src/utils/tool/file.ts

@@ -0,0 +1,40 @@
+// 文件处理
+export default {
+
+	// 创建临时路径
+	/*
+	* 	file 文件对象
+	* 	base64 是否必须转为 base64
+	* */
+	createdPath(file,base64 = false){
+
+		return new Promise((resolve, reject)=>{
+
+			if (!file || !(file instanceof File || file instanceof Blob)) reject('no have file');
+
+			// 返回临时链接
+			if (!base64 && window.URL && window.URL.createObjectURL) {
+				return resolve(window.URL.createObjectURL(file));
+			} else if (window.FileReader) {
+
+				let fileReader = new window.FileReader();
+
+				fileReader.readAsDataURL(file);
+
+				fileReader.onload = function () {
+					resolve(fileReader.result);
+				};
+
+				fileReader.onerror = function (fail) {
+					reject(fail);
+				}
+
+			} else {
+				reject('no support');
+			}
+
+		});
+
+	}
+
+}

+ 50 - 2
src/utils/tool/popup.ts

@@ -1,10 +1,59 @@
+import {
+    notification
+} from 'ant-design-vue';
+import {PopupExportComponent} from "$popup/popup-export/const";
+
 export default <Record<string, LibMixinsMethod | any>>{
 
     $popup:undefined,
 
     // 提示弹窗
     $toast:function (option:PopupToastOptions) {
+        return this.$popup && this.$popup.open(PopupExportComponent.toast,this._handleOption(option));
+    },
+
+    // 加载
+    $loading:function (option:PopupToastOptions){
+        return this.$popup && this.$popup.open(PopupExportComponent.loading,this._handleOption(option));
+    },
+
+    // 隐藏
+    hideLoading:function () {
+        return this.$popup && this.$popup.open(PopupExportComponent.loading,{title:''});
+    },
+
+    // 通知框
+    $success:function (option:PopupToastOptions) {
+        return this._handleNotice('success',option);
+    },
+    $fail:function (option:PopupToastOptions) {
+        return this._handleNotice('error',option);
+    },
+    $info:function (option:PopupToastOptions) {
+        return this._handleNotice('info',option);
+    },
+    $warning:function (option:PopupToastOptions) {
+        return this._handleNotice('warning',option);
+    },
+
+    // 处理
+    _handleNotice(type,option:PopupToastOptions){
+        let resultData:Record<string, any> = {};
+        if (typeof option === 'string') {
+            resultData={message:option}
+        } else {
+            resultData= {
+                ...option,
+                duration: option.duration ? option.duration / 1000 : 4,
+                message: option.title
+            };
+        }
 
+        return  notification[type](resultData);
+    },
+
+    // 处理基本的options
+    _handleOption(option:PopupToastOptions):Record<string, any>{
         let resultOption:PopupToastOptions;
 
         if (typeof option === 'string') {
@@ -12,8 +61,7 @@ export default <Record<string, LibMixinsMethod | any>>{
         } else {
             resultOption=option;
         }
-
-        return this.$popup && this.$popup.open('toast',resultOption);
+        return  resultOption;
     }
 
 }

+ 203 - 0
src/utils/tool/sound-recording/index.ts

@@ -0,0 +1,203 @@
+import Recorder from 'recorder-js';
+
+enum TriggerListener {
+    fail='fail',
+    end='end',
+    success='success',
+    speed='speed',
+    status='status',
+    soundStatus='soundStatus'
+}
+
+export {TriggerListener};
+
+let recorder = undefined;
+
+export default  class SoundRecording {
+
+    triggers:Record<string, any>={};
+
+    private recorder = recorder;
+
+    // 创建配置
+    constructor(public config:SoundRecordingConfig={}) {
+
+        for (let key in SoundRecording.defaultConfig) {
+            if (SoundRecording.defaultConfig.hasOwnProperty(key) && config[key] === undefined) {
+                config[key] = SoundRecording.defaultConfig[key];
+            }
+        }
+
+    }
+
+    // 是否装载
+    initializationStatus=false;
+
+    // 获取录音权限
+    initialization(){
+
+        if (this.initializationStatus) return ;
+        this.initializationStatus = true;
+
+        return new Promise<void>( (resolve, reject)=>{
+
+            if (recorder) {
+                this.recorder = recorder;
+                resolve();
+            } else {
+
+                // @ts-ignore
+                let getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia;
+
+                if (getUserMedia) {
+                    return navigator.mediaDevices.getUserMedia({audio: true}).then((stream)=>{
+                        // @ts-ignore
+                        const audioContext =  new (window.AudioContext || window.webkitAudioContext)();
+                        this.recorder = new Recorder(audioContext,{
+                            type:'audio/mp3'
+                        });
+                        this.recorder.init(stream);
+                        resolve();
+                    }).catch(()=> {
+                        this.initializationStatus = false;
+                        reject({ jurisdiction:false })
+                    });
+                } else {
+                    this.initializationStatus = false;
+                    reject({
+                        support:false
+                    });
+                }
+
+            }
+
+
+
+        });
+
+    }
+
+    // 录音
+    start(){
+
+        if (!this.recorder || this.config.status) return;
+
+        // 设置状态为开始录音
+        this.setStatus(true);
+
+        this.recorder.start().then(()=>{
+
+            this.config.countdown = 0;
+
+            this.config.startTime = this.getDate();
+
+            this.triggerListener(TriggerListener.soundStatus,true);
+
+            return this.triggerStart();
+
+        }).catch(()=>{
+            // 结束录音模式
+            this.setStatus(false);
+            // 错误
+            return this.triggerListener(TriggerListener.fail,'无法启用录音');
+        });
+
+    }
+
+    // 结束录音
+    stop(){
+        if (!this.recorder || !this.config.status) return;
+
+        clearTimeout(this.config.time);
+
+        this.recorder.stop().then((data)=>{
+            // 结束录音模式
+            this.setStatus(false);
+            if (this.config.countdown >= this.config.min) {
+                this.triggerListener(TriggerListener.soundStatus,false);
+                return this.triggerListener(TriggerListener.success,data);
+            } else {
+                return this.triggerListener(TriggerListener.fail,'录音时间不能小于'+this.config.min+'s');
+            }
+        }).catch(()=>{
+            // 结束录音模式
+            this.setStatus(false);
+            // 错误
+            return this.triggerListener(TriggerListener.fail,'无法启用录音');
+        });
+
+    }
+
+    // 开始录音计时器
+    triggerStart(){
+
+        if (!this.config.startTime) return;
+
+        this.config.countdown = this.getDate() - this.config.startTime;
+
+        this.triggerListener(TriggerListener.speed,this.config.countdown);
+
+        if (this.config.countdown > this.config.max) {
+            clearTimeout(this.config.time);
+            return this.stop();
+        } else {
+            this.config.time = setTimeout(()=> this.triggerStart(),1000);
+        }
+
+    }
+
+    // 获取时间
+    getDate(){
+        return Math.floor(new Date().getTime()/1000);
+    }
+
+    // 设置状态
+    setStatus(status:boolean){
+        if (this.config.status !== status) {
+            this.config.status = status;
+            this.triggerListener(TriggerListener.status,status);
+        }
+    }
+
+    addListener(key:TriggerListener,callback:Function) {
+
+        if (!callback) return;
+
+        if (this.triggers[key] === undefined) this.triggers[key] = [callback];
+        else this.triggers[key].push(callback);
+
+        return this;
+
+    }
+
+    removeListener(key:TriggerListener,callback:Function) {
+
+        if (!callback || this.triggers[key] === undefined) return;
+
+        let index = this.triggers[key].indexOf(callback);
+
+        if (index>=0) this.triggers[key].splice(index,1);
+
+        return this;
+
+    }
+
+    triggerListener(key:TriggerListener,data?:any) {
+
+        if (this.triggers[key]) {
+            this.triggers[key].map((item)=> item && item(data));
+        }
+
+    }
+
+    // 默认的配置文件
+    static defaultConfig:SoundRecordingConfig = {
+        min:3,
+        max:15,
+        status:false
+    }
+
+
+}
+
+console.log(Recorder);

+ 22 - 0
src/utils/tool/sound-recording/types/sound-recording.ts

@@ -0,0 +1,22 @@
+
+interface SoundRecordingConfig {
+
+    // 最短录制
+    min?:number,
+
+    // 最长录制
+    max?:number,
+
+    // 当前状态
+    status?:boolean,
+
+    // 开始录音时间
+    startTime?:number,
+
+    // 当前录制时间
+    countdown?:number
+
+    // 计时器对象
+    time?:any
+
+}

+ 13 - 0
src/utils/tool/utils.ts

@@ -0,0 +1,13 @@
+import {DateFormat} from "$utils/tool/date";
+
+export default {
+
+    constellation:function (day:string) {
+        let usePeople = DateFormat.inverse(day,'YYYY-MM-DD');
+        usePeople.month = usePeople.month - 1;
+        // @ts-ignore
+        let constellation =  "魔羯水瓶双鱼牡羊金牛双子巨蟹狮子处女天秤天蝎射手魔羯".substr(usePeople.month*2-(usePeople.date< "102223444433".charAt(usePeople.month-1)- 19)*2,2);
+        return  constellation ? constellation+'座': '';
+    }
+
+}

部分文件因为文件数量过多而无法显示