作者 REN·WANG

init

  1 +> 1%
  2 +last 2 versions
  3 +not dead
  1 +NODE_ENV = 'development'
  2 +VUE_APP_NAME = 'dev'
  1 +NODE_ENV = 'development'
  2 +VUE_APP_NAME = 'devOnLine'
  1 +NODE_ENV = 'production'
  2 +VUE_APP_NAME = 'pro'
  1 +NODE_ENV = 'production'
  2 +VUE_APP_NAME = 'uat'
  1 +.DS_Store
  2 +node_modules
  3 +/dist
  4 +
  5 +
  6 +# local env files
  7 +.env.local
  8 +.env.*.local
  9 +
  10 +# Log files
  11 +npm-debug.log*
  12 +yarn-debug.log*
  13 +yarn-error.log*
  14 +pnpm-debug.log*
  15 +
  16 +# Editor directories and files
  17 +.idea
  18 +.vscode
  19 +*.suo
  20 +*.ntvs*
  21 +*.njsproj
  22 +*.sln
  23 +*.sw?
  1 +{
  2 + "endOfLine": "lf",
  3 + "semi": false,
  4 + "singleQuote": true,
  5 + "trailingComma": "none",
  6 + "useTabs": false,
  7 + "tabWidth": 2,
  8 + "printWidth": 180,
  9 + "proseWrap": "preserve",
  10 + "arrowParens": "avoid",
  11 + "bracketSpacing": true,
  12 + "quoteProps": "as-needed"
  13 +}
  1 +# kraken-vue-app
  2 +
  3 +## Project setup
  4 +```
  5 +npm install
  6 +```
  7 +
  8 +### Compiles and hot-reloads for development
  9 +```
  10 +npm run serve
  11 +```
  12 +
  13 +### Compiles and minifies for production
  14 +```
  15 +npm run build
  16 +```
  17 +
  18 +### Customize configuration
  19 +See [Configuration Reference](https://cli.vuejs.org/config/).
  1 +module.exports = {
  2 + presets: [
  3 + '@vue/cli-plugin-babel/preset'
  4 + ]
  5 +}
  1 +{
  2 + "compilerOptions": {
  3 + "target": "es5",
  4 + "module": "esnext",
  5 + "baseUrl": "./",
  6 + "moduleResolution": "node",
  7 + "paths": {
  8 + "@/*": [
  9 + "src/*"
  10 + ]
  11 + },
  12 + "lib": [
  13 + "esnext",
  14 + "dom",
  15 + "dom.iterable",
  16 + "scripthost"
  17 + ]
  18 + }
  19 +}
  1 +{
  2 + "name": "kraken-vue-app",
  3 + "version": "0.1.0",
  4 + "private": true,
  5 + "scripts": {
  6 + "serve": "vue-cli-service serve",
  7 + "build": "vue-cli-service build",
  8 + "dev": "vue-cli-service serve --mode dev --open",
  9 + "dev:online": "vue-cli-service serve --mode devOnLine --open",
  10 + "uat": "vue-cli-service build --mode uat",
  11 + "pro": "vue-cli-service build --mode pro"
  12 + },
  13 + "dependencies": {
  14 + "axios": "^1.4.0",
  15 + "core-js": "^3.8.3",
  16 + "vant": "^2.12.54",
  17 + "vue": "^2.6.14",
  18 + "vue-router": "^3.5.1",
  19 + "vuex": "^3.6.2"
  20 + },
  21 + "devDependencies": {
  22 + "@vue/cli-plugin-babel": "~5.0.0",
  23 + "@vue/cli-plugin-router": "~5.0.0",
  24 + "@vue/cli-plugin-vuex": "~5.0.0",
  25 + "@vue/cli-service": "~5.0.0",
  26 + "sass": "^1.32.7",
  27 + "sass-loader": "^12.0.0",
  28 + "vue-template-compiler": "^2.6.14"
  29 + }
  30 +}
此 diff 太大无法显示。
  1 +{
  2 + "name": "kraken-vue-app",
  3 + "version": "0.1.0",
  4 + "private": true,
  5 + "scripts": {
  6 + "serve": "vue-cli-service serve",
  7 + "build": "vue-cli-service build",
  8 + "dev": "vue-cli-service serve --mode dev --open",
  9 + "dev:online": "vue-cli-service serve --mode devOnLine --open",
  10 + "uat": "vue-cli-service build --mode uat --no-source-map",
  11 + "pro": "vue-cli-service build --mode pro --no-source-map"
  12 + },
  13 + "dependencies": {
  14 + "axios": "^1.4.0",
  15 + "vant": "^2.12.54",
  16 + "vue": "^2.6.14",
  17 + "vue-drag-resize": "^1.5.4",
  18 + "vue-router": "^3.5.1",
  19 + "vuex": "^3.6.2"
  20 + },
  21 + "devDependencies": {
  22 + "@vue/cli-plugin-babel": "~5.0.0",
  23 + "@vue/cli-plugin-router": "~5.0.0",
  24 + "@vue/cli-plugin-vuex": "~5.0.0",
  25 + "@vue/cli-service": "~5.0.0",
  26 + "sass": "^1.32.7",
  27 + "sass-loader": "^12.0.0",
  28 + "vue-template-compiler": "^2.6.14"
  29 + }
  30 +}
不能预览此文件类型
  1 +<!DOCTYPE html>
  2 +<html lang="">
  3 +
  4 +<head>
  5 + <meta charset="utf-8">
  6 + <meta http-equiv="X-UA-Compatible" content="IE=edge">
  7 + <meta name="viewport" content="width=device-width,initial-scale=1.0">
  8 + <link rel="icon" href="<%= BASE_URL %>favicon.ico">
  9 + <title>
  10 + <%= htmlWebpackPlugin.options.title %>
  11 + </title>
  12 +</head>
  13 +
  14 +<body>
  15 + <noscript>
  16 + <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled.
  17 + Please enable it to continue.</strong>
  18 + </noscript>
  19 + <div id="app"></div>
  20 + <!-- built files will be auto injected -->
  21 +</body>
  22 +
  23 +</html>
  1 +<template>
  2 + <div class="app" id="app" :style="{ paddingTop: systemInfo.statusHeight + 'px' }">
  3 + <MyNavBar
  4 + :offset-top="systemInfo.statusHeight"
  5 + :title="$route.meta.title"
  6 + :show="getIsApp && $route.meta.isTabBarMenu && $route.meta.showNavBar"
  7 + :right-text="rightBtnText"
  8 + @right-callback="othersBtnFun"
  9 + />
  10 + <MyNavBar
  11 + :offset-top="systemInfo.statusHeight"
  12 + :title="$route.meta.title"
  13 + :show="getIsApp && !$route.meta.isTabBarMenu && $route.meta.showNavBar"
  14 + :left-arrow="true"
  15 + :left-arrow-style="{ color: '#f00' }"
  16 + :right-text="rightBtnText"
  17 + left-text="返回"
  18 + @left-callback="backPrePage"
  19 + @right-callback="othersBtnFun"
  20 + />
  21 +
  22 + <div class="router-page-view" :style="{ marginTop: getAppNavBarShow ? '46px' : '' }" :class="{ 'is-tab-bar-menu-page': $route.meta.isTabBarMenu }">
  23 + <!-- <keep-alive :include="keepAlivePageNameList"> -->
  24 + <router-view />
  25 + <!-- </keep-alive> -->
  26 + </div>
  27 +
  28 + <template v-if="$route.meta.isTabBarMenu">
  29 + <MyTabBar v-model="currentTabBarName" :tab-bar-list="tabBarList" @change="tabBarChange" />
  30 + </template>
  31 + <MyDialog
  32 + :show-popup="dialogOpen"
  33 + :title="myDialogConfig.title"
  34 + :description="myDialogConfig.description"
  35 + :descriptionType="myDialogConfig.descriptionType"
  36 + :show-cancel-btn="true"
  37 + :cancel-btn-text="myDialogConfig.cancelBtnText"
  38 + :ok-close="true"
  39 + @ok="dialogOk"
  40 + @cancel="dialogCancel"
  41 + />
  42 + <MyToast loading-icon="icon-loading-1" ref="myToast" />
  43 + <!-- <MyConsole ref="MyConsole" theme="dark" /> -->
  44 + </div>
  45 +</template>
  46 +<script>
  47 +import { versionFormatting } from '@/libs/tools'
  48 +import { getVersionApi } from '@/apis/commApis'
  49 +import MyNavBar from '@/components/MyNavBar/MyNavBar.vue'
  50 +import MyTabBar from '@/components/MyTabBar/MyTabBar.vue'
  51 +import MyDialog from '@/components/MyDialog/MyDialog.vue'
  52 +import MyToast from '@/components/MyToast/MyToast.vue'
  53 +import MyConsole from '@/components/MyConsole/MyConsole.vue'
  54 +
  55 +const myDialogConfig = {
  56 + title: '提示',
  57 + description: '',
  58 + descriptionType: 'html',
  59 + cancelBtnText: '取消'
  60 +}
  61 +export default {
  62 + name: 'myApp',
  63 + components: {
  64 + MyNavBar,
  65 + MyTabBar,
  66 + MyDialog,
  67 + MyToast,
  68 + MyConsole
  69 + },
  70 + data() {
  71 + return {
  72 + currentTabBarName: '',
  73 + dialogOpen: false,
  74 + isOpenToast: false,
  75 + systemInfo: {
  76 + version: '0.0.0',
  77 + statusHeight: 0
  78 + },
  79 + newVersion: '',
  80 + myDialogConfig: myDialogConfig,
  81 + rightBtnText: '',
  82 + loadingToast: null
  83 + }
  84 + },
  85 + computed: {
  86 + keepAlivePageNameList() {
  87 + return this.$router.options.routes
  88 + .filter(item => {
  89 + return item.meta.keepAlive
  90 + })
  91 + .map(item => {
  92 + return item.name
  93 + })
  94 + },
  95 + tabBarList() {
  96 + return this.$router.options.routes
  97 + .filter(item => {
  98 + return item.meta.isTabBarMenu
  99 + })
  100 + .map(item => {
  101 + return {
  102 + name: item.name,
  103 + icon: item.meta.icon,
  104 + label: item.meta.title
  105 + }
  106 + })
  107 + }
  108 + },
  109 + watch: {
  110 + $route(newVal) {
  111 + if (this.getIsApp) {
  112 + this.$store.commit('setAppNavBarShow', newVal.meta.showNavBar)
  113 + }
  114 + }
  115 + },
  116 + created() {
  117 + if (this.getIsApp) {
  118 + this.$store.commit('setAppNavBarShow', this.$route.meta.showNavBar)
  119 + // 监听App端传过来的系统信息
  120 + webf.methodChannel.addMethodCallHandler('postSystemInfo', event => {
  121 + console.log('event:', event)
  122 + this.systemInfo = event
  123 + this.$store.commit('setSystemInfo', event)
  124 + })
  125 + // 监听下载进度
  126 + webf.methodChannel.addMethodCallHandler('updateVersionProgress', event => {
  127 + this.loadingToast.ylTotasHintText = `下载中 ${event.toFixed(2)}%`
  128 + if (event === 100) {
  129 + this.loadingToast.ylTotasOpen = false
  130 + this.dialogOk = this.updateVerOkRestart
  131 + this.myDialogConfig.title = '下载完成'
  132 + this.myDialogConfig.description = '重启App即可更新'
  133 + this.myDialogConfig.cancelBtnText = '稍后重启'
  134 + this.dialogOpen = true
  135 + }
  136 + })
  137 + }
  138 + },
  139 + async mounted() {
  140 + setTimeout(() => {
  141 + this.currentTabBarName = this.$route.name
  142 + }, 60)
  143 + if (this.getIsApp) {
  144 + // 如果是APP,需要检查版本号判断有没有更新
  145 + try {
  146 + let res = await getVersionApi()
  147 + let oldVersion = versionFormatting(this.systemInfo.version)
  148 + let newVersion = versionFormatting(res.currentVersion)
  149 + this.newVersion = res.currentVersion
  150 + if (newVersion - oldVersion > 0) {
  151 + this.myDialogConfig.title = '更新提示'
  152 + this.myDialogConfig.description = res.description
  153 + this.dialogOpen = true
  154 + }
  155 + } catch (error) {
  156 + console.log('error:', error)
  157 + }
  158 + }
  159 + },
  160 + methods: {
  161 + // tabBar切换
  162 + tabBarChange(event) {
  163 + this.$router.replace({
  164 + name: event.name
  165 + })
  166 + },
  167 + backPrePage() {
  168 + console.log('返回:')
  169 + this.$router.go(-1)
  170 + },
  171 + othersBtnFun() {
  172 + console.log('this.getAppNavBarShow:', this.getAppNavBarShow)
  173 + },
  174 + dialogCancel() {
  175 + this.rightBtnText = '立即更新'
  176 + },
  177 + dialogOk() {
  178 + webf.methodChannel.invokeMethod('updateVersionDownloadConfirm', {
  179 + msg: '确定更新',
  180 + newVersion: this.newVersion
  181 + })
  182 + this.dialogOpen = false
  183 + this.loadingToast = this.$refs.myToast.loading({
  184 + hintText: '下载中',
  185 + duration: 0,
  186 + isOpen: true
  187 + })
  188 + },
  189 + // 确定重启更新-触发flutter中webf监听的事件
  190 + updateVerOkRestart() {
  191 + webf.methodChannel.invokeMethod('updateVersionRestart', {
  192 + msg: '确定更新重启'
  193 + })
  194 + }
  195 + }
  196 +}
  197 +</script>
  198 +
  199 +<style lang="scss">
  200 +@import '@/assets/style/iconfont/iconfont.css';
  201 +@import '@/assets/style/vant-icon/vant-icon.css';
  202 +// @import '@/assets/style/common.scss';
  203 +
  204 +* {
  205 + margin: 0;
  206 + padding: 0;
  207 + box-sizing: border-box;
  208 +}
  209 +.router-page-view {
  210 + overflow-x: hidden;
  211 + &.is-tab-bar-menu-page {
  212 + padding-bottom: 54px;
  213 + }
  214 +}
  215 +#app {
  216 + overflow-x: hidden;
  217 + .my-tabbar-view {
  218 + position: fixed;
  219 + left: 0;
  220 + bottom: 0;
  221 + z-index: 999;
  222 + width: 100%;
  223 + }
  224 +}
  225 +</style>
  1 +import axios from '@/libs/api.request'
  2 +
  3 +//获取版本信息
  4 +export const getVersionApi = params => {
  5 + return axios.request({
  6 + url: 'api/getVersion',
  7 + params,
  8 + method: 'get'
  9 + })
  10 +}
  1 +/* 多行行文本溢出出现省略号 */
  2 +.clamp {
  3 + overflow: hidden;
  4 + text-overflow: ellipsis;
  5 + display: -webkit-box;
  6 + -webkit-box-orient: vertical;
  7 +}
  8 +
  9 +.clamp.clamp-a {
  10 + -webkit-line-clamp: 1;
  11 +}
  12 +
  13 +.clamp.clamp-b {
  14 + -webkit-line-clamp: 2;
  15 +}
  16 +
  17 +.clamp.clamp-c {
  18 + -webkit-line-clamp: 3;
  19 +}
  20 +
  21 +/* 开启硬件加速 */
  22 +.threeD {
  23 + transform: translateZ(0);
  24 +}
  25 +
  26 +.anim {
  27 + transition: all 0.4s;
  28 +}
  29 +
  30 +@keyframes turn {
  31 + 0% {
  32 + transform: rotate(0deg);
  33 + }
  34 + 25% {
  35 + transform: rotate(90deg);
  36 + }
  37 + 50% {
  38 + transform: rotate(180deg);
  39 + }
  40 + 75% {
  41 + transform: rotate(270deg);
  42 + }
  43 + 100% {
  44 + transform: rotate(360deg);
  45 + }
  46 +}
  47 +
  48 +@keyframes flicker {
  49 + 0% {
  50 + transform: scale(1); /*开始为原始大小*/
  51 + }
  52 + 25% {
  53 + transform: scale(1.1); /*放大1.1倍*/
  54 + }
  55 + 50% {
  56 + transform: scale(1);
  57 + }
  58 + 75% {
  59 + transform: scale(1.1);
  60 + }
  61 +}
  62 +.flicker {
  63 + animation: flicker 2s infinite;
  64 +}
  65 +
  66 +@keyframes spin {
  67 + to {
  68 + transform: rotate(0deg);
  69 + }
  70 + from {
  71 + transform: rotate(-360deg);
  72 + }
  73 +}
  74 +
  75 +.spin {
  76 + animation: spin 1s infinite;
  77 + animation-fill-mode: forwards;
  78 +}
  79 +
  80 +.turn {
  81 + animation: turn 1s infinite;
  82 + animation-fill-mode: forwards;
  83 +}
  84 +
  85 +.transform90 {
  86 + transform: rotate(90deg);
  87 +}
  88 +
  89 +.pos-rel {
  90 + position: relative;
  91 +}
  92 +
  93 +.pos-abs {
  94 + position: absolute;
  95 +}
  96 +
  97 +.text-l {
  98 + text-align: left !important;
  99 +}
  100 +
  101 +.text-r {
  102 + text-align: right !important;
  103 +}
  104 +
  105 +.text-c {
  106 + text-align: center !important;
  107 +}
  108 +
  109 +.left {
  110 + float: left;
  111 +}
  112 +
  113 +.right {
  114 + float: right;
  115 +}
  116 +
  117 +.clear:after {
  118 + display: block;
  119 + content: '.';
  120 + clear: both;
  121 + visibility: hidden;
  122 + overflow: hidden;
  123 + width: 100%;
  124 + height: 0;
  125 +}
  126 +
  127 +.clear {
  128 + clear: both;
  129 +}
  130 +
  131 +.flex {
  132 + display: flex;
  133 +}
  134 +.al-i-c {
  135 + align-items: center;
  136 +}
  137 +.ju-c-sb {
  138 + justify-content: space-between;
  139 +}
  140 +.ju-c-sa {
  141 + justify-content: space-around;
  142 +}
  143 +.ju-c-c {
  144 + justify-content: center;
  145 +}
  146 +.ju-c-end {
  147 + justify-content: flex-end;
  148 +}
  149 +@for $i from 1 through 50 {
  150 + .p-#{$i} {
  151 + padding: (1px * $i) !important;
  152 + }
  153 + .p-t-#{$i} {
  154 + padding-top: (1px * $i) !important;
  155 + }
  156 + .p-r-#{$i} {
  157 + padding-right: (1px * $i) !important;
  158 + }
  159 + .p-b-#{$i} {
  160 + padding-bottom: (1px * $i) !important;
  161 + }
  162 + .p-l-#{$i} {
  163 + padding-left: (1px * $i) !important;
  164 + }
  165 + .m-#{$i} {
  166 + margin: (1px * $i) !important;
  167 + }
  168 + .m-t-#{$i} {
  169 + margin-top: (1px * $i) !important;
  170 + }
  171 + .m-r-#{$i} {
  172 + margin-right: (1px * $i) !important;
  173 + }
  174 + .m-b-#{$i} {
  175 + margin-bottom: (1px * $i) !important;
  176 + }
  177 + .m-l-#{$i} {
  178 + margin-left: (1px * $i) !important;
  179 + }
  180 + .fz-#{$i} {
  181 + font-size: (1px * $i) !important;
  182 + }
  183 +}
  184 +
  185 +.text-line-thr {
  186 + text-decoration: line-through !important;
  187 +}
  1 +@font-face {
  2 + font-family: 'iconfont';
  3 + src: url('iconfont.ttf?t=1687638242320') format('truetype');
  4 +}
  5 +
  6 +.iconfont {
  7 + font-family: 'iconfont' !important;
  8 + font-size: 16px;
  9 + font-style: normal;
  10 + -webkit-font-smoothing: antialiased;
  11 + -moz-osx-font-smoothing: grayscale;
  12 +}
  13 +
  14 +.icon-kaobei:before {
  15 + content: '\e601';
  16 +}
  17 +
  18 +.icon-shezhi:before {
  19 + content: '\e827';
  20 +}
  21 +
  22 +.icon-kefu:before {
  23 + content: '\e642';
  24 +}
  25 +
  26 +.icon-loading-1:before {
  27 + content: '\e61d';
  28 +}
  29 +
  30 +.icon-loading-2:before {
  31 + content: '\e93d';
  32 +}
  33 +
  34 +.icon-loading-3:before {
  35 + content: '\e67b';
  36 +}
不能预览此文件类型
  1 +@font-face {
  2 + font-family: 'vant-icon';
  3 + src: url('vant-icon.ttf') format('truetype');
  4 +}
  5 +
  6 +.van-icon {
  7 + font-family: 'vant-icon' !important;
  8 + font-size: 16px;
  9 + font-style: normal;
  10 + -webkit-font-smoothing: antialiased;
  11 + -moz-osx-font-smoothing: grayscale;
  12 +}
不能预览此文件类型
  1 +<template>
  2 + <div class="collapse-com" :style="{ backgroundColor: backgroundColor }">
  3 + <slot></slot>
  4 + </div>
  5 +</template>
  6 +
  7 +<script>
  8 +export default {
  9 + name: 'Collapse',
  10 + props: {
  11 + value: [String, Array, Number],
  12 + accordion: {
  13 + type: Boolean,
  14 + default: () => {
  15 + return false
  16 + }
  17 + },
  18 + backgroundColor: {
  19 + type: String,
  20 + default: '#f5f5f5'
  21 + }
  22 + },
  23 + data() {
  24 + return {
  25 + currentValue: this.value
  26 + }
  27 + },
  28 + mounted() {
  29 + this.setActive()
  30 + },
  31 + methods: {
  32 + setActive() {
  33 + // 为它下面的子元素都设置一个index值
  34 + // console.log("setActive");
  35 + const activeKey = this.getActiveKey()
  36 + this.$children.forEach((child, index) => {
  37 + const name = child.name || index.toString() // toString 1=>"1"整数转换成为字符串
  38 + child.isActive = activeKey.indexOf(name) > -1 // 给选中的元素赋值活跃状态
  39 + // console.log(child);
  40 + child.index = index
  41 + })
  42 + },
  43 + toggle(data) {
  44 + // console.log("toggle");
  45 + const name = data.name.toString() // 强行转换成为字符串
  46 + let newActivekey = []
  47 + if (this.accordion) {
  48 + // 如果是手风琴模式
  49 + if (!data.isActive) {
  50 + newActivekey.push(name)
  51 + }
  52 + } else {
  53 + let activeKey = this.getActiveKey()
  54 + const nameIndex = activeKey.indexOf(name)
  55 + if (data.isActive) {
  56 + // 如果当前是展开状态
  57 + if (nameIndex > -1) {
  58 + activeKey.splice(nameIndex, 1)
  59 + }
  60 + } else {
  61 + if (nameIndex < 0) {
  62 + activeKey.push(name)
  63 + }
  64 + }
  65 + newActivekey = activeKey
  66 + }
  67 + this.currentValue = newActivekey
  68 + // console.log(data);
  69 + this.$emit('input', newActivekey)
  70 + this.$emit('on-change', newActivekey)
  71 + },
  72 + getActiveKey() {
  73 + // 获取当前展开的元素,并且做成数组的形式 1 => ["1"]
  74 + let activeKey = this.currentValue || []
  75 + const accordion = this.accordion
  76 + if (!Array.isArray(activeKey)) {
  77 + // 判断 activeKey 是不是数组
  78 + activeKey = [activeKey] // 不是数组则让它变成数组
  79 + }
  80 + if (accordion && activeKey.length > 1) {
  81 + // 如果是手风琴模式,必定是只会有一个元素
  82 + activeKey = [activeKey[0]]
  83 + }
  84 +
  85 + for (let i = 0; i < activeKey.length; i++) {
  86 + activeKey[i] = activeKey[i].toString()
  87 + }
  88 + return activeKey
  89 + }
  90 + },
  91 + watch: {
  92 + value(val) {
  93 + this.currentValue = val
  94 + },
  95 + currentValue() {
  96 + this.setActive()
  97 + }
  98 + }
  99 +}
  100 +</script>
  101 +
  102 +<style lang="scss" scoped></style>
  1 +<template>
  2 + <div class="collapse-item-com" :style="{ marginBottom: marginBottom + 'px' }">
  3 + <div class="title-view" @click="toggle">
  4 + <div class="title-str">
  5 + <slot v-if="$slots.leftIcon" name="leftIcon"></slot>
  6 + <span v-else class="iconfont left-icon" :class="leftIcon"></span>
  7 + <slot v-if="$slots.title" name="title"></slot>
  8 + <span v-else class="title-text">{{ title }}</span>
  9 + </div>
  10 + <slot v-if="$slots.rightIcon" name="rightIcon"></slot>
  11 + <span v-else class="iconfont right-icon" :class="_rightIcon"></span>
  12 + </div>
  13 + <div class="collapse-extra" :style="{ height: isActive ? height + 'px' : '0' }" ref="collapseExtra">
  14 + <slot v-if="$slots.collapseExtra" name="collapseExtra"></slot>
  15 + </div>
  16 + </div>
  17 +</template>
  18 +
  19 +<script>
  20 +export default {
  21 + name: 'CollapseItem',
  22 + props: {
  23 + name: {
  24 + type: String
  25 + },
  26 + title: {
  27 + type: String,
  28 + default: null
  29 + },
  30 + leftIcon: {
  31 + type: String,
  32 + default: null
  33 + },
  34 + rightIcon: {
  35 + type: String,
  36 + default: null
  37 + },
  38 + marginBottom: {
  39 + type: [String, Number]
  40 + }
  41 + },
  42 + data() {
  43 + return {
  44 + index: 0,
  45 + height: 0,
  46 + isActive: false
  47 + }
  48 + },
  49 + computed: {
  50 + _rightIcon() {
  51 + if (this.rightIcon) {
  52 + return this.rightIcon
  53 + } else {
  54 + if (this.isActive) {
  55 + return 'icon-xiangshang2'
  56 + } else {
  57 + return 'icon-xiangxia2'
  58 + }
  59 + }
  60 + }
  61 + },
  62 + methods: {
  63 + toggle() {
  64 + this.$parent.toggle({
  65 + name: this.name || this.index,
  66 + isActive: this.isActive
  67 + })
  68 + }
  69 + },
  70 + mounted() {
  71 + this.$nextTick(() => {
  72 + this.height = this.$refs.collapseExtra.childNodes[0].offsetHeight
  73 + })
  74 + }
  75 +}
  76 +</script>
  77 +
  78 +<style lang="scss" scoped>
  79 +.collapse-item-com {
  80 + background-color: #fff;
  81 + .title-view {
  82 + display: flex;
  83 + align-items: center;
  84 + justify-content: space-between;
  85 + padding: 10px;
  86 + border-bottom: 1px solid #f5f5f5;
  87 + .title-str {
  88 + .left-icon {
  89 + color: #666;
  90 + font-size: 12px;
  91 + }
  92 + .title-text {
  93 + font-size: 16px;
  94 + color: #333;
  95 + }
  96 + }
  97 + .right-icon {
  98 + color: #666;
  99 + font-size: 12px;
  100 + }
  101 + }
  102 + .collapse-extra {
  103 + height: 0;
  104 + will-change: 'height';
  105 + overflow: hidden;
  106 + transition: all 0.2s ease 0s;
  107 + }
  108 +}
  109 +</style>
  1 +<template>
  2 + <div class="my-console-view" :class="[myPopupOpen ? 'show' : '']">
  3 + <!-- <vue-drag-resize
  4 + v-if="!getIsApp"
  5 + :w="81"
  6 + :h="32"
  7 + :x="230"
  8 + :y="400"
  9 + :isResizable="false"
  10 + :isActive="dragActive"
  11 + :parentLimitation="true"
  12 + @dragging="onDragging"
  13 + @activated="onClicked"
  14 + @deactivated="onDeactivated"
  15 + @dragstop="onDragStop"
  16 + >
  17 + <van-button class="open-console-btn" size="small" type="primary" @click.stop="_opeConsole">MyConsole</van-button>
  18 + </vue-drag-resize> -->
  19 + <van-button v-if="!getIsApp" class="open-console-btn not-app" size="small" type="primary" @click.stop="_opeConsole">MyConsole</van-button>
  20 +
  21 + <MyPopup v-model="myPopupOpen" position="bottom" height="60" :theme="theme" content-bg-color="#1E1E20" :round="false" :show-close-icon="true" popup-title="控制台">
  22 + <div class="console-content">
  23 + <div class="tab-list" :class="`theme-${theme}`">
  24 + <div class="tab-item" v-for="(item, index) in tabList" :key="index" :class="{ 'current-item': current === item.value }" @click="changeTab(item.value)">
  25 + {{ item.name }}
  26 + </div>
  27 + </div>
  28 + <div class="all-cell" ref="allCell">
  29 + <template v-if="current !== 'network'">
  30 + <div class="cell-item" :class="`theme-${theme}`" v-for="(item, index) in consoleList" :key="index">
  31 + <template v-for="(oItem, oIndex) in item.data">
  32 + <div class="log-item" :class="oItem.logType === 'log' ? 'log' : oItem.logType === 'error' ? 'error' : ''" :key="oIndex">{{ oItem.log }}</div>
  33 + </template>
  34 + </div>
  35 + </template>
  36 + <template v-else-if="current === 'network'">
  37 + <div class="cell-item network-cell-item network-cell-header" :class="`theme-${theme}`">
  38 + <div class="network-item">Name</div>
  39 + <div class="network-item">Type</div>
  40 + <div class="network-item">Status</div>
  41 + <div class="network-item">Size</div>
  42 + <div class="network-item">Time</div>
  43 + </div>
  44 + <div style="padding-top: 34px; height: 100%; overflow-y: auto">
  45 + <div v-for="(item, index) in consoleList" class="cell-item network-cell-item" :class="[`theme-${theme}`]" :key="index" @click="lookNetworkDetail(item)">
  46 + <div class="network-item">{{ item.name }}</div>
  47 + <div class="network-item">{{ item.type }}</div>
  48 + <div class="network-item">{{ item.status }}</div>
  49 + <div class="network-item">{{ item.size }}</div>
  50 + <div class="network-item">{{ item.time }} ms</div>
  51 + </div>
  52 + </div>
  53 + </template>
  54 + </div>
  55 + <MyPopup
  56 + v-model="openApiDetailPopup"
  57 + position="right"
  58 + height="60"
  59 + width="70"
  60 + bottom="0"
  61 + content-bg-color="#1E1E20"
  62 + :theme="theme"
  63 + :round="false"
  64 + :show-close-icon="true"
  65 + popup-title="接口详情"
  66 + >
  67 + <div class="console-content" v-if="currentApiDetail">
  68 + <div class="all-cell detail-cell">
  69 + <van-cell title="名称" :label="currentApiDetail.name" />
  70 + <van-cell title="类型" :label="currentApiDetail.type" />
  71 + <van-cell class="pos-rel" title="地址" :label="currentApiDetail.responseURL">
  72 + <template #right-icon>
  73 + <span class="pos-abs iconfont icon-kaobei"></span>
  74 + </template>
  75 + </van-cell>
  76 + <van-cell title="状态" :label="currentApiDetail.status" />
  77 + <van-cell class="pos-rel" title="返回结果" style="white-space: pre" :label="currentApiDetail.response | prettyPrint">
  78 + <template #right-icon>
  79 + <span class="pos-abs iconfont icon-kaobei"></span>
  80 + </template>
  81 + </van-cell>
  82 + </div>
  83 + </div>
  84 + </MyPopup>
  85 + </div>
  86 + </MyPopup>
  87 + </div>
  88 +</template>
  89 +
  90 +<script>
  91 +import { abilitySort } from '@/libs/tools'
  92 +import MyPopup from '@/components/MyPopup/MyPopup.vue'
  93 +export default {
  94 + name: 'MyConsole',
  95 + components: { MyPopup },
  96 + props: {
  97 + /**
  98 + * 主题色
  99 + * light | dark
  100 + */
  101 + theme: {
  102 + type: String,
  103 + default: 'light'
  104 + }
  105 + },
  106 + data() {
  107 + return {
  108 + myPopupOpen: false,
  109 + consoleList: [],
  110 + dragActive: false,
  111 + top: 0,
  112 + left: 0,
  113 + sT: null,
  114 + draggNum: 0,
  115 + tabList: [
  116 + { name: 'All', value: 'all' },
  117 + { name: 'Log', value: 'log' },
  118 + { name: 'Error', value: 'error' },
  119 + { name: 'Network', value: 'network' }
  120 + ],
  121 + current: 'all',
  122 + currentApiDetail: null,
  123 + openApiDetailPopup: false,
  124 + global: this.$global
  125 + }
  126 + },
  127 + watch: {
  128 + getLoggerData() {
  129 + this.changeTab(this.current)
  130 + },
  131 + getNetworkLoggerData() {
  132 + this.changeTab(this.current)
  133 + },
  134 + myPopupOpen(newVal) {
  135 + if (!newVal) {
  136 + this.consoleList = []
  137 + }
  138 + },
  139 + draggNum(newVal) {
  140 + if (newVal > 1) {
  141 + if (this.sT) {
  142 + clearTimeout(this.sT)
  143 + this.sT = null
  144 + }
  145 + }
  146 + }
  147 + },
  148 + computed: {
  149 + getLoggerData() {
  150 + return this.$store.getters.getLoggerData
  151 + },
  152 + getNetworkLoggerData() {
  153 + return this.$store.getters.getNetworkLoggerData
  154 + }
  155 + },
  156 + created() {
  157 + if (this.getIsApp) {
  158 + /**
  159 + * @description: 监听打开调试窗口
  160 + * @return {*}
  161 + */
  162 + webf.methodChannel.addMethodCallHandler('openMyConsole', event => {
  163 + this._opeConsole()
  164 + })
  165 + /**
  166 + * @description: 监听请求拦截器新增请求
  167 + * @return {*}
  168 + */
  169 + webf.methodChannel.addMethodCallHandler('addHttpInterceptors', event => {
  170 + console.log('addHttpInterceptors:', event)
  171 + console.netwokrLog({
  172 + uid: JSON.parse(event.headers).uid,
  173 + status: event.status || 0,
  174 + statusText: event.statusText || '',
  175 + name: event.url.split('/').pop().split('?')[0],
  176 + type: event.type,
  177 + requestHeaders: event.headers,
  178 + method: event.method,
  179 + responseURL: event.url,
  180 + response: null,
  181 + size: event.size || 0,
  182 + time: 0
  183 + })
  184 + })
  185 + // 监听请求拦截器更新请求
  186 + webf.methodChannel.addMethodCallHandler('updateHttpInterceptors', event => {
  187 + console.log('updateHttpInterceptors:', event)
  188 + let updateItem = this.$global.cNetworkData.filter(item => {
  189 + return item.uid === event.uid
  190 + })[0]
  191 + if (updateItem) {
  192 + Object.assign(updateItem, event)
  193 + // 触发获取返回结果
  194 + if (updateItem.type === 'Fetch/XHR') {
  195 + webf.methodChannel.invokeMethod('getResponseBody', event.uid)
  196 + }
  197 + }
  198 + })
  199 + // 监听请求拦截器更新返回结果事件
  200 + webf.methodChannel.addMethodCallHandler('httpInterceptorsSetResponseBody', event => {
  201 + console.log('httpInterceptorsSetResponseBody:', event)
  202 + let updateItem = this.$global.cNetworkData.filter(item => {
  203 + return item.uid === event.uid
  204 + })[0]
  205 + if (updateItem) {
  206 + updateItem.response = event.response
  207 + }
  208 + })
  209 + }
  210 + },
  211 + methods: {
  212 + _opeConsole() {
  213 + this.changeTab(this.current)
  214 + this.myPopupOpen = true
  215 + },
  216 + onDragging(newRect) {
  217 + this.draggNum += 1
  218 + this.top = newRect.top
  219 + this.left = newRect.left
  220 + },
  221 + onClicked() {
  222 + this.sT = setTimeout(() => {
  223 + this._opeConsole()
  224 + }, 200)
  225 + this.dragActive = true
  226 + },
  227 + onDeactivated() {
  228 + this.draggNum = 0
  229 + this.dragActive = false
  230 + },
  231 + onDragStop() {
  232 + this.dragActive = false
  233 + },
  234 + changeTab(value) {
  235 + this.current = value
  236 + let temp = []
  237 + if (value === 'all') {
  238 + temp = JSON.parse(JSON.stringify(this.getLoggerData))
  239 + } else if (value === 'log') {
  240 + temp = JSON.parse(JSON.stringify(this.getLoggerData)).filter(item => {
  241 + return item.logType === 'log'
  242 + })
  243 + } else if (value === 'error') {
  244 + temp = JSON.parse(JSON.stringify(this.getLoggerData)).filter(item => {
  245 + return item.logType === 'error'
  246 + })
  247 + } else if (value === 'network') {
  248 + temp = JSON.parse(JSON.stringify(this.getNetworkLoggerData))
  249 + this.consoleList = temp
  250 + return
  251 + }
  252 + this.consoleList = abilitySort(temp, 'logId')
  253 + this.$nextTick(() => {
  254 + this.scrollToBottom()
  255 + })
  256 + },
  257 + // 滚动到最底部
  258 + scrollToBottom() {
  259 + try {
  260 + let allCell = this.$refs['allCell']
  261 + allCell.scrollTop = allCell.scrollHeight
  262 + } catch (error) {}
  263 + },
  264 + // 查看接口详情
  265 + lookNetworkDetail(detail) {
  266 + this.currentApiDetail = detail
  267 + this.openApiDetailPopup = true
  268 + }
  269 + }
  270 +}
  271 +</script>
  272 +
  273 +<style lang="scss" scoped>
  274 +.my-console-view {
  275 + position: fixed;
  276 + top: 0;
  277 + left: 0;
  278 + z-index: 1000;
  279 + // pointer-events: none;
  280 +
  281 + .console-content {
  282 + position: relative;
  283 + height: 100%;
  284 + .all-cell {
  285 + padding-top: 34px;
  286 + height: 100%;
  287 + overflow-y: auto;
  288 + position: relative;
  289 + &.detail-cell {
  290 + padding-top: 0;
  291 + .iconfont {
  292 + right: 20px;
  293 + }
  294 + }
  295 + }
  296 + .van-cell__title {
  297 + width: 100%;
  298 + .van-cell__label {
  299 + width: 100%;
  300 + overflow-x: auto;
  301 + }
  302 + }
  303 + }
  304 + .tab-list {
  305 + width: 100%;
  306 + height: 34px;
  307 + display: flex;
  308 + align-items: center;
  309 + justify-content: start;
  310 + position: absolute;
  311 + left: 0;
  312 + top: 0;
  313 + z-index: 100;
  314 + .tab-item {
  315 + height: 100%;
  316 + display: flex;
  317 + align-items: center;
  318 + padding: 0 10px;
  319 + }
  320 + &.theme-light {
  321 + color: #333;
  322 + border: 1px solid #eee;
  323 + background-color: #fff;
  324 + .tab-item {
  325 + border-right: 1px solid #eee;
  326 + }
  327 + }
  328 + &.theme-dark {
  329 + color: #a9b0b9;
  330 + border: 1px solid #333;
  331 + border-top: 0;
  332 + background-color: #252528;
  333 + .tab-item {
  334 + border-right: 1px solid #333;
  335 + &.current-item {
  336 + background-color: #000;
  337 + color: #fff;
  338 + }
  339 + }
  340 + }
  341 + }
  342 + .cell-item {
  343 + min-height: 44px;
  344 + display: flex;
  345 + flex-direction: column;
  346 + align-items: start;
  347 + justify-content: center;
  348 + font-size: 14px;
  349 + &:last-child {
  350 + border-bottom: 0;
  351 + }
  352 + &.network-cell-item {
  353 + flex-direction: row;
  354 + align-items: center;
  355 + justify-content: start;
  356 + &.network-cell-header {
  357 + position: absolute;
  358 + top: 34px;
  359 + left: 0;
  360 + min-height: unset;
  361 + height: 34px;
  362 + width: 100%;
  363 + .network-item {
  364 + height: 34px;
  365 + line-height: 34px;
  366 + }
  367 + }
  368 + .network-item {
  369 + height: 44px;
  370 + line-height: 44px;
  371 + flex: 1;
  372 + padding-left: 6px;
  373 + overflow: hidden;
  374 + }
  375 + }
  376 + .log-item {
  377 + padding: 4px 6px;
  378 + width: 100%;
  379 + &.error {
  380 + background-color: #210700;
  381 + color: #f00;
  382 + border-bottom: 1px solid #491100;
  383 + }
  384 + }
  385 +
  386 + &.theme-light {
  387 + color: #333;
  388 + border-bottom: 1px solid #eee;
  389 + background-color: #fff;
  390 + &.network-cell-header {
  391 + background-color: #fff;
  392 + }
  393 + &.network-cell-item {
  394 + .network-item {
  395 + border-right: 1px solid #eee;
  396 + }
  397 + }
  398 + }
  399 + &.theme-dark {
  400 + color: #a9b0b9;
  401 + border-bottom: 1px solid #333;
  402 + &.network-cell-header {
  403 + background-color: #252528;
  404 + }
  405 + &.network-cell-item {
  406 + .network-item {
  407 + border-right: 1px solid #333;
  408 + }
  409 + }
  410 + }
  411 + }
  412 + .open-console-btn {
  413 + // pointer-events: auto;
  414 + z-index: 1000;
  415 + border-radius: 6px;
  416 +
  417 + &.not-app {
  418 + position: fixed;
  419 + bottom: 20%;
  420 + right: 50px;
  421 + }
  422 + }
  423 + .close-drag-active {
  424 + position: fixed;
  425 + top: -100%;
  426 + opacity: 0;
  427 + }
  428 +}
  429 +</style>
  1 +<template>
  2 + <div class="my-popup" v-if="isShow">
  3 + <div class="mask-view"></div>
  4 + <div class="popup-content">
  5 + <div class="popup-title">{{ title }}</div>
  6 + <div class="popup-description" v-if="descriptionType === ''">{{ description }}</div>
  7 + <div class="popup-description" v-if="descriptionType === 'html'" v-html="description"></div>
  8 + <div class="popup-btn-list">
  9 + <div class="btn" v-if="showCancelBtn" @click="_cancel">{{ cancelBtnText }}</div>
  10 + <div class="btn" v-if="showOkBtn" @click="_ok">{{ okBtnText }}</div>
  11 + </div>
  12 + </div>
  13 + </div>
  14 +</template>
  15 +
  16 +<script>
  17 +export default {
  18 + name: 'MyPopup',
  19 + props: {
  20 + showPopup: {
  21 + type: Boolean,
  22 + default: () => {
  23 + return false
  24 + }
  25 + },
  26 + title: {
  27 + type: String,
  28 + default: '提示'
  29 + },
  30 + description: {
  31 + type: String,
  32 + default: '内容'
  33 + },
  34 + descriptionType: {
  35 + type: String,
  36 + default: ''
  37 + },
  38 + showOkBtn: {
  39 + type: Boolean,
  40 + default: () => {
  41 + return true
  42 + }
  43 + },
  44 + showCancelBtn: {
  45 + type: Boolean,
  46 + default: () => {
  47 + return false
  48 + }
  49 + },
  50 + okBtnText: {
  51 + type: String,
  52 + default: '确定'
  53 + },
  54 + cancelBtnText: {
  55 + type: String,
  56 + default: '取消'
  57 + },
  58 + okClose: {
  59 + type: Boolean,
  60 + default: () => {
  61 + return false
  62 + }
  63 + }
  64 + },
  65 + watch: {
  66 + showPopup(newVal) {
  67 + this.isShow = newVal
  68 + }
  69 + },
  70 + created() {
  71 + this.isShow = this.showPopup
  72 + },
  73 + data() {
  74 + return {
  75 + isShow: false
  76 + }
  77 + },
  78 + methods: {
  79 + _ok() {
  80 + if (this.okClose) {
  81 + this.isShow = false
  82 + }
  83 + this.$emit('ok')
  84 + },
  85 + _cancel() {
  86 + this.isShow = false
  87 + // this.$emit('cancel')
  88 + }
  89 + }
  90 +}
  91 +</script>
  92 +
  93 +<style lang="scss" scoped>
  94 +.my-popup {
  95 + position: fixed;
  96 + width: 100%;
  97 + height: 100%;
  98 + top: 0;
  99 + left: 0;
  100 + z-index: 999;
  101 + .mask-view {
  102 + position: fixed;
  103 + width: 100%;
  104 + height: 100%;
  105 + top: 0;
  106 + left: 0;
  107 + background-color: #000;
  108 + opacity: 0.5;
  109 + z-index: 10;
  110 + }
  111 + .popup-content {
  112 + position: fixed;
  113 + left: 20px;
  114 + right: 20px;
  115 + top: 50%;
  116 + transform: translateY(-50%);
  117 + background-color: #fff;
  118 + border-radius: 14px;
  119 + z-index: 11;
  120 + .popup-title {
  121 + font-size: 16px;
  122 + font-weight: bold;
  123 + height: 44px;
  124 + line-height: 44px;
  125 + text-align: center;
  126 + border-bottom: 1px solid #ededed;
  127 + }
  128 + .popup-description {
  129 + padding: 15px;
  130 + }
  131 + .popup-btn-list {
  132 + display: flex;
  133 + align-items: center;
  134 + justify-content: space-between;
  135 + border-top: 1px solid #ededed;
  136 + .btn {
  137 + flex: 1;
  138 + text-align: center;
  139 + height: 44px;
  140 + line-height: 44px;
  141 + font-size: 14px;
  142 + }
  143 + }
  144 + }
  145 +}
  146 +</style>
  1 +<template>
  2 + <div class="app-nav-bar" v-if="show" :style="{ height: 46 + offsetTop + 'px', paddingTop: offsetTop + 'px', backgroundColor: background }">
  3 + <slot v-if="$slots.leftArrow" name="leftArrow"></slot>
  4 + <div v-else-if="leftArrow" class="left-arrow" @click="leftClick" :style="leftArrowStyle">
  5 + <van-icon name="arrow-left" />
  6 + <span class="l-text">{{ leftText }}</span>
  7 + </div>
  8 + <div class="nav-title" :style="navTitleStyle">{{ title }}</div>
  9 + <slot v-if="$slots.rightArrow" name="rightArrow"></slot>
  10 + <div v-else-if="rightArrow" class="right-arrow" @click="rightClick" :style="rightArrowStyle">
  11 + <span class="r-text">{{ rightText }}</span>
  12 + </div>
  13 + </div>
  14 +</template>
  15 +
  16 +<script>
  17 +export default {
  18 + name: 'MyNavBar',
  19 + props: {
  20 + show: {
  21 + type: Boolean,
  22 + default: () => {
  23 + return false
  24 + }
  25 + },
  26 + title: {
  27 + type: String,
  28 + default: ''
  29 + },
  30 + navTitleStyle: {
  31 + type: Boolean,
  32 + default: () => {
  33 + return false
  34 + }
  35 + },
  36 + offsetTop: {
  37 + type: Number,
  38 + default: 0
  39 + },
  40 + background: {
  41 + type: String,
  42 + default: '#fff'
  43 + },
  44 + leftArrow: {
  45 + type: Boolean,
  46 + default: () => {
  47 + return false
  48 + }
  49 + },
  50 + leftText: {
  51 + type: String,
  52 + default: ''
  53 + },
  54 + leftArrowStyle: {
  55 + type: Object,
  56 + default: () => {
  57 + return {}
  58 + }
  59 + },
  60 + rightArrow: {
  61 + type: Boolean,
  62 + default: () => {
  63 + return false
  64 + }
  65 + },
  66 + rightText: {
  67 + type: String,
  68 + default: ''
  69 + },
  70 + rightArrowStyle: {
  71 + type: Object,
  72 + default: () => {
  73 + return {}
  74 + }
  75 + }
  76 + },
  77 + data() {
  78 + return {}
  79 + },
  80 + methods: {
  81 + leftClick() {
  82 + this.$emit('left-callback')
  83 + },
  84 + rightClick() {
  85 + this.$emit('right-click')
  86 + }
  87 + }
  88 +}
  89 +</script>
  90 +
  91 +<style lang="scss" scoped>
  92 +.app-nav-bar {
  93 + position: fixed;
  94 + top: 0;
  95 + left: 0;
  96 + right: 0;
  97 + z-index: 100;
  98 + height: 46px;
  99 + background-color: #fff;
  100 + display: flex;
  101 + align-items: center;
  102 + justify-content: center;
  103 + border-bottom: 1px solid #ebedf0;
  104 +
  105 + .nav-title {
  106 + font-size: 16px;
  107 + font-weight: bold;
  108 + color: #323233;
  109 + }
  110 + .left-arrow {
  111 + position: absolute;
  112 + z-index: 10;
  113 + top: 0;
  114 + left: 10px;
  115 + min-width: 46px;
  116 + height: 46px;
  117 + display: flex;
  118 + align-items: center;
  119 + color: #333;
  120 + font-size: 14px;
  121 + .l-text {
  122 + margin-left: 4px;
  123 + }
  124 + }
  125 + .right-arrow {
  126 + position: absolute;
  127 + z-index: 10;
  128 + top: 0;
  129 + right: 10px;
  130 + min-width: 46px;
  131 + height: 46px;
  132 + display: flex;
  133 + align-items: center;
  134 + color: #333;
  135 + font-size: 14px;
  136 + }
  137 +}
  138 +</style>
  1 +<template>
  2 + <div class="my-popup-view" :class="`theme-${theme}`" v-if="open">
  3 + <div v-if="overlay" class="popup-mask-view" @click="closeOnClickOverlay ? _close() : ''"></div>
  4 + <div class="popup-content-view" :class="`position-${position} ${transition ? `position-${position}-enter-to` : `position-${position}-leave-to`}`" :style="contentStyle">
  5 + <div
  6 + v-if="popupTitle !== '' || showCloseIcon"
  7 + class="popup-title"
  8 + :style="[popupTitleTextStyle, theme !== 'light' && theme !== 'dark' ? { backgroundColor: contentBgColor } : {}]"
  9 + >
  10 + <div v-if="showCloseIcon" class="close-btn van-icon van-icon-cross" @click="_close"></div>
  11 + <span>{{ popupTitle }}</span>
  12 + </div>
  13 + <div class="popup-slot">
  14 + <slot></slot>
  15 + </div>
  16 + </div>
  17 + </div>
  18 +</template>
  19 +
  20 +<script>
  21 +export default {
  22 + name: 'MyPopup',
  23 + props: {
  24 + theme: {
  25 + type: String,
  26 + default: 'light'
  27 + },
  28 + // 是否显示
  29 + value: {
  30 + type: Boolean,
  31 + required: true
  32 + },
  33 + // 遮罩
  34 + overlay: {
  35 + type: Boolean,
  36 + default: () => {
  37 + return true
  38 + }
  39 + },
  40 + closeOnClickOverlay: {
  41 + type: Boolean,
  42 + default: () => {
  43 + return true
  44 + }
  45 + },
  46 + popupTitle: {
  47 + type: String,
  48 + default: ''
  49 + },
  50 + popupTitleTextStyle: {
  51 + type: Object,
  52 + default: () => {
  53 + return {}
  54 + }
  55 + },
  56 + contentBgColor: {
  57 + type: String,
  58 + default: '#fff'
  59 + },
  60 + showCloseIcon: {
  61 + type: Boolean,
  62 + default: () => {
  63 + return false
  64 + }
  65 + },
  66 +
  67 + // 弹出位置
  68 + // position.options: top bottom right left
  69 + position: {
  70 + type: String,
  71 + default: 'center'
  72 + },
  73 + // 圆角
  74 + round: {
  75 + type: Boolean,
  76 + default: () => {
  77 + return false
  78 + }
  79 + },
  80 + width: {
  81 + type: [String, Number],
  82 + default: '80'
  83 + },
  84 + height: {
  85 + type: [String, Number],
  86 + default: '300'
  87 + },
  88 + bottom: {
  89 + type: String,
  90 + default: null
  91 + }
  92 + },
  93 + watch: {
  94 + isOpen(newVal) {
  95 + if (typeof newVal === 'boolean') {
  96 + this.open = newVal
  97 + } else {
  98 + newVal.then(res => (this.open = res))
  99 + }
  100 + }
  101 + },
  102 + computed: {
  103 + isOpen: {
  104 + get() {
  105 + if (this.value) {
  106 + setTimeout(() => {
  107 + this.transition = true
  108 + }, 100)
  109 + return this.value
  110 + } else {
  111 + this.transition = false
  112 + return new Promise(resolve => {
  113 + setTimeout(() => {
  114 + resolve(false)
  115 + }, 300)
  116 + })
  117 + }
  118 + },
  119 + set(show) {
  120 + this.$emit('input', show)
  121 + }
  122 + },
  123 + contentStyle() {
  124 + let bc = {}
  125 + if (this.theme !== 'light' && this.theme !== 'dark') {
  126 + bc = {
  127 + backgroundColor: this.contentBgColor
  128 + }
  129 + }
  130 + if (this.position === 'center') {
  131 + return Object.assign(bc, {
  132 + left: '50%',
  133 + top: '50%',
  134 + transform: 'translate(-50%, -50%)'
  135 + })
  136 + } else if (this.position === 'top') {
  137 + return Object.assign(bc, {
  138 + left: 0,
  139 + top: '-100%',
  140 + maxHeight: '100%',
  141 + width: '100%',
  142 + height:
  143 + typeof this.height === 'string' && this.height.indexOf('%') !== -1
  144 + ? this.height
  145 + : typeof this.height === 'string' && this.height.indexOf('%') === -1 && Number(this.height) < 100
  146 + ? this.height + '%'
  147 + : typeof this.height === 'string' && this.height.indexOf('%') === -1 && Number(this.height) > 100
  148 + ? this.height + 'px'
  149 + : typeof this.height === 'number'
  150 + ? this.height + 'px'
  151 + : '300px',
  152 + 'border-bottom-left-radius': this.round ? '12px' : '0',
  153 + 'border-bottom-right-radius': this.round ? '12px' : '0'
  154 + })
  155 + } else if (this.position === 'bottom') {
  156 + return Object.assign(bc, {
  157 + left: 0,
  158 + bottom: '-100%',
  159 + maxHeight: '100%',
  160 + width: '100%',
  161 + height:
  162 + typeof this.height === 'string' && this.height.indexOf('%') !== -1
  163 + ? this.height
  164 + : typeof this.height === 'string' && this.height.indexOf('%') === -1 && Number(this.height) < 100
  165 + ? this.height + '%'
  166 + : typeof this.height === 'string' && this.height.indexOf('%') === -1 && Number(this.height) > 100
  167 + ? this.height + 'px'
  168 + : typeof this.height === 'number'
  169 + ? this.height + 'px'
  170 + : '300px',
  171 + 'border-top-left-radius': this.round ? '12px' : '0',
  172 + 'border-top-right-radius': this.round ? '12px' : '0'
  173 + })
  174 + } else if (this.position === 'left') {
  175 + return Object.assign(bc, {
  176 + left: '-100%',
  177 + top: this.bottom ? 'unset' : '0',
  178 + bottom: this.bottom ? this.bottom : 'unset',
  179 + maxWidth: '100%',
  180 + width:
  181 + typeof this.width === 'string' && this.width.indexOf('%') !== -1
  182 + ? this.width
  183 + : typeof this.width === 'string' && this.width.indexOf('%') === -1 && Number(this.width) < 100
  184 + ? this.width + '%'
  185 + : typeof this.width === 'string' && this.width.indexOf('%') === -1 && Number(this.width) > 100
  186 + ? this.width + 'px'
  187 + : typeof this.width === 'number'
  188 + ? this.width + 'px'
  189 + : '240px',
  190 + height:
  191 + typeof this.height === 'string' && this.height.indexOf('%') !== -1
  192 + ? this.height
  193 + : typeof this.height === 'string' && this.height.indexOf('%') === -1 && Number(this.height) < 100
  194 + ? this.height + '%'
  195 + : typeof this.height === 'string' && this.height.indexOf('%') === -1 && Number(this.height) > 100
  196 + ? this.height + 'px'
  197 + : typeof this.height === 'number'
  198 + ? this.height + 'px'
  199 + : '100%',
  200 + 'border-top-right-radius': this.round ? '12px' : '0',
  201 + 'border-bottom-right-radius': this.round ? '12px' : '0'
  202 + })
  203 + } else if (this.position === 'right') {
  204 + return Object.assign(bc, {
  205 + right: '-100%',
  206 + top: this.bottom ? 'unset' : '0',
  207 + bottom: this.bottom ? this.bottom : 'unset',
  208 + maxWidth: '100%',
  209 + width:
  210 + typeof this.width === 'string' && this.width.indexOf('%') !== -1
  211 + ? this.width
  212 + : typeof this.width === 'string' && this.width.indexOf('%') === -1 && Number(this.width) < 100
  213 + ? this.width + '%'
  214 + : typeof this.width === 'string' && this.width.indexOf('%') === -1 && Number(this.width) > 100
  215 + ? this.width + 'px'
  216 + : typeof this.width === 'number'
  217 + ? this.width + 'px'
  218 + : '240px',
  219 + height:
  220 + typeof this.height === 'string' && this.height.indexOf('%') !== -1
  221 + ? this.height
  222 + : typeof this.height === 'string' && this.height.indexOf('%') === -1 && Number(this.height) < 100
  223 + ? this.height + '%'
  224 + : typeof this.height === 'string' && this.height.indexOf('%') === -1 && Number(this.height) > 100
  225 + ? this.height + 'px'
  226 + : typeof this.height === 'number'
  227 + ? this.height + 'px'
  228 + : '100%',
  229 + 'border-top-left-radius': this.round ? '12px' : '0',
  230 + 'border-bottom-left-radius': this.round ? '12px' : '0'
  231 + })
  232 + }
  233 + }
  234 + },
  235 + data() {
  236 + return {
  237 + open: false,
  238 + transition: false
  239 + }
  240 + },
  241 + mounted() {
  242 + this.isOpen.then(value => {
  243 + this.open = value
  244 + })
  245 + },
  246 + methods: {
  247 + _close() {
  248 + this.isOpen = false
  249 + }
  250 + }
  251 +}
  252 +</script>
  253 +
  254 +<style lang="scss" scoped>
  255 +.my-popup-view {
  256 + position: fixed;
  257 + width: 100%;
  258 + height: 100%;
  259 + top: 0;
  260 + left: 0;
  261 + z-index: 999999;
  262 + pointer-events: auto;
  263 +
  264 + .popup-mask-view {
  265 + width: 100%;
  266 + height: 100%;
  267 + position: absolute;
  268 + top: 0;
  269 + left: 0;
  270 + z-index: 10;
  271 + background-color: #000;
  272 + opacity: 0.5;
  273 + }
  274 + .popup-content-view {
  275 + position: absolute;
  276 + z-index: 11;
  277 + overflow-y: auto;
  278 + transition: all 0.3s;
  279 + &.position-top-leave-to {
  280 + top: -100% !important;
  281 + }
  282 + &.position-top-enter-to {
  283 + top: 0 !important;
  284 + }
  285 + &.position-bottom-leave-to {
  286 + bottom: -100% !important;
  287 + }
  288 + &.position-bottom-enter-to {
  289 + bottom: 0 !important;
  290 + }
  291 + &.position-left-leave-to {
  292 + left: -100% !important;
  293 + }
  294 + &.position-left-enter-to {
  295 + left: 0 !important;
  296 + }
  297 + &.position-right-leave-to {
  298 + right: -100% !important;
  299 + }
  300 + &.position-right-enter-to {
  301 + right: 0 !important;
  302 + }
  303 +
  304 + .popup-title {
  305 + padding: 0 10px;
  306 + font-size: 16px;
  307 + font-weight: bold;
  308 + text-align: center;
  309 + height: 44px;
  310 + line-height: 44px;
  311 + position: relative;
  312 + }
  313 + .close-btn {
  314 + width: 36px;
  315 + height: 36px;
  316 + line-height: 36px;
  317 + position: absolute;
  318 + top: 4px;
  319 + right: 5px;
  320 + text-align: center;
  321 + font-size: 16px;
  322 + color: #888;
  323 + }
  324 + .popup-slot {
  325 + height: calc(100% - 44px);
  326 + }
  327 + }
  328 + &.theme-light {
  329 + .popup-content-view {
  330 + background-color: #fff;
  331 + .popup-title {
  332 + background-color: #fff;
  333 + border-bottom: 1px solid #eee;
  334 + color: #333;
  335 + }
  336 + }
  337 + }
  338 + &.theme-dark {
  339 + .popup-content-view {
  340 + background-color: #1e1e20;
  341 + .popup-title {
  342 + background-color: #252528;
  343 + border-bottom: 1px solid #414346;
  344 + color: #797c82;
  345 + }
  346 + }
  347 + }
  348 +}
  349 +</style>
  1 +<template>
  2 + <div class="tab-bar-view">
  3 + <div class="tab-bar-item" v-for="(item, index) in tabBarList" :key="index" :class="{ activity: activityTabBarIndex === index }" @click="itemClick(item, index)">
  4 + <div v-if="item.icon" class="tab-bar-icon van-icon" :class="`van-icon-${item.icon}`" @click.stop="itemClick(item, index)"></div>
  5 + <div class="tab-bar-name">{{ item.label }}</div>
  6 + </div>
  7 + </div>
  8 +</template>
  9 +
  10 +<script>
  11 +export default {
  12 + name: 'MyTabBar',
  13 + props: {
  14 + value: {
  15 + type: [String, Number],
  16 + required: true
  17 + },
  18 + tabBarList: {
  19 + type: Array,
  20 + default: () => {
  21 + return []
  22 + }
  23 + }
  24 + },
  25 + computed: {
  26 + activityTabBarIndex: {
  27 + get() {
  28 + let currentIndex = 0
  29 + if (typeof this.value === 'string') {
  30 + this.tabBarList.forEach((item, index) => {
  31 + if (item.name === this.value) {
  32 + currentIndex = index
  33 + }
  34 + })
  35 + } else if (typeof this.value === 'number') {
  36 + currentIndex = this.value
  37 + }
  38 + return currentIndex
  39 + },
  40 + set(index) {
  41 + this.$emit('input', index)
  42 + }
  43 + }
  44 + },
  45 + data() {
  46 + return {}
  47 + },
  48 + methods: {
  49 + itemClick(item, index) {
  50 + console.log('tabbar-itemClick-index', index)
  51 + this.activityTabBarIndex = index
  52 + this.$emit('change', item)
  53 + }
  54 + }
  55 +}
  56 +</script>
  57 +
  58 +<style lang="scss" scoped>
  59 +.tab-bar-view {
  60 + position: fixed;
  61 + left: 0;
  62 + bottom: 0;
  63 + width: 100%;
  64 + height: 51px;
  65 + display: flex;
  66 + align-items: center;
  67 + padding-bottom: constant(safe-area-inset-bottom);
  68 + padding-bottom: env(safe-area-inset-bottom);
  69 + background-color: #fff;
  70 + box-shadow: 0 0 10px #d9d9d9;
  71 + border-top: 1px solid #e3e3e3;
  72 + .tab-bar-item {
  73 + height: 100%;
  74 + display: flex;
  75 + flex-direction: column;
  76 + align-items: center;
  77 + justify-content: center;
  78 + flex: 1;
  79 + font-size: 12px;
  80 + line-height: 1;
  81 + color: #7d7e80;
  82 + cursor: pointer;
  83 + .tab-bar-icon {
  84 + margin-bottom: 4px;
  85 + font-size: 22px;
  86 + }
  87 + &.activity {
  88 + color: #1989fa;
  89 + }
  90 + }
  91 +}
  92 +</style>
  1 +<template>
  2 + <div class="dyson-toast" v-if="ylTotasOpen">
  3 + <div v-if="ylToastType === 'hint'" class="toast-view hint-toast">
  4 + <div class="totas-text">{{ ylTotasHintText }}</div>
  5 + </div>
  6 + <div v-if="ylToastType === 'loading'" class="toast-view loading-toast">
  7 + <div class="loading-img" v-if="isShowIcon">
  8 + <span class="iconfont" :class="ylTotasLoadingIcon"></span>
  9 + </div>
  10 + <div class="totas-text">{{ ylTotasHintText }}</div>
  11 + </div>
  12 + </div>
  13 +</template>
  14 +
  15 +<script>
  16 +export default {
  17 + name: 'MyToast',
  18 + props: {
  19 + isOpen: {
  20 + type: Boolean,
  21 + default: () => {
  22 + return false
  23 + }
  24 + },
  25 + isShowIcon: {
  26 + type: Boolean,
  27 + default: () => {
  28 + return true
  29 + }
  30 + },
  31 + toastType: {
  32 + type: String,
  33 + default: 'hint'
  34 + },
  35 + hintText: {
  36 + type: String,
  37 + default: ''
  38 + },
  39 + duration: {
  40 + type: Number,
  41 + default: 3000
  42 + },
  43 + loadingIcon: {
  44 + type: String,
  45 + default: 'icon-loading-3'
  46 + }
  47 + },
  48 + data() {
  49 + return {
  50 + ylTotasOpen: false,
  51 + ylToastType: '',
  52 + ylTotasHintText: '',
  53 + ylTotasDuration: 0,
  54 + ylTotasLoadingIcon: '',
  55 + st: null
  56 + }
  57 + },
  58 + watch: {
  59 + ylTotasOpen(newVal) {
  60 + if (newVal && this.ylToastType !== 'loading') {
  61 + if (this.ylTotasDuration !== 0) {
  62 + this.st = setTimeout(() => {
  63 + this.ylTotasOpen = false
  64 + }, this.ylTotasDuration)
  65 + }
  66 + }
  67 + },
  68 + isOpen(newVal) {
  69 + this.ylTotasOpen = newVal
  70 + },
  71 + hintText(newVal) {
  72 + this.ylTotasHintText = newVal
  73 + }
  74 + },
  75 + created() {
  76 + this.ylTotasOpen = this.isOpen
  77 + this.ylTotasHintText = this.hintText
  78 + this.ylToastType = this.toastType
  79 + this.ylTotasDuration = this.duration
  80 + this.ylTotasLoadingIcon = this.loadingIcon
  81 + },
  82 + methods: {
  83 + loading(config) {
  84 + if (this.st) {
  85 + clearTimeout(this.st)
  86 + this.st = null
  87 + }
  88 + this.ylToastType = 'loading'
  89 + this.ylTotasHintText = config.hintText
  90 + this.ylTotasDuration = config.duration
  91 + this.ylTotasOpen = config.isOpen
  92 + if (config.loadingIcon) {
  93 + this.ylTotasLoadingIcon = config.loadingIcon
  94 + }
  95 + return this
  96 + }
  97 + }
  98 +}
  99 +</script>
  100 +
  101 +<style lang="scss" scoped>
  102 +.dyson-toast {
  103 + position: fixed;
  104 + width: 100%;
  105 + height: 100%;
  106 + top: 0;
  107 + left: 0;
  108 + display: flex;
  109 + align-items: center;
  110 + justify-content: center;
  111 + .toast-view {
  112 + min-width: 130px;
  113 + padding: 8px 30px;
  114 + border-radius: 6px;
  115 + background-color: #333;
  116 + .totas-text {
  117 + font-size: 12px;
  118 + color: #fff;
  119 + text-align: center;
  120 + }
  121 + }
  122 + .loading-toast {
  123 + .loading-img {
  124 + text-align: center;
  125 + margin-top: 10px;
  126 + margin-bottom: 10px;
  127 + animation: rotate 1s linear infinite;
  128 + .iconfont {
  129 + color: #fff;
  130 + font-size: 18px;
  131 + }
  132 + }
  133 + }
  134 +}
  135 +
  136 +@keyframes rotate {
  137 + 0% {
  138 + transform: rotate(0deg);
  139 + }
  140 + 25% {
  141 + transform: rotate(90deg);
  142 + }
  143 + 50% {
  144 + transform: rotate(180deg);
  145 + }
  146 + 75% {
  147 + transform: rotate(270deg);
  148 + }
  149 + 100% {
  150 + transform: rotate(360deg);
  151 + }
  152 +}
  153 +</style>
  1 +export default {
  2 + /**
  3 + * @description 配置显示在浏览器标签的title
  4 + */
  5 + title: 'Admin',
  6 + /**
  7 + * @description token在Cookie中存储的天数,默认1天
  8 + */
  9 + cookieExpires: 1,
  10 + /**
  11 + * @description 是否使用国际化,默认为false
  12 + * 如果不使用,则需要在路由中给需要在菜单中展示的路由设置meta: {title: 'xxx'}
  13 + * 用来在菜单中显示文字
  14 + */
  15 + useI18n: true,
  16 + /**
  17 + * @description api请求基础路径
  18 + */
  19 + baseUrl: {
  20 + dev: 'https://mock.apifox.cn/m1/2852848-0-default/',
  21 + devOnLine: 'https://mock.apifox.cn/m1/2852848-0-default/',
  22 + uat: 'https://mock.apifox.cn/m1/2852848-0-default/',
  23 + pro: 'https://mock.apifox.cn/m1/2852848-0-default/'
  24 + },
  25 + imgToCos: {
  26 + dev: false,
  27 + uat: true,
  28 + pro: true
  29 + },
  30 + //配置图片请求地址
  31 + imgBaseUrl: {
  32 + dev: 'http://localhost:3001',
  33 + // dev: 'http://localhost:3001/admin/cosGetObject?key=',
  34 + devOnLine: 'http://localhost:3001/admin/cosGetObject?key=',
  35 + uat: 'http://localhost:3001/admin/cosGetObject?key=',
  36 + pro: 'http://localhost:3001/admin/cosGetObject?key='
  37 + },
  38 + /**
  39 + * @description 默认打开的首页的路由name值,默认为home
  40 + */
  41 + homeName: 'home',
  42 + /**
  43 + * @description 需要加载的插件
  44 + */
  45 + plugin: {
  46 + 'error-store': {
  47 + showInHeader: false, // 设为false后不会在顶部显示错误日志徽标
  48 + developmentOff: true // 设为true后在开发环境不会收集错误信息,方便开发中排查错误
  49 + }
  50 + }
  51 +}
  1 +export function prettyPrint(value) {
  2 + if (value === null) {
  3 + return '-'
  4 + }
  5 + return JSON.stringify(JSON.parse(value), null, '\t')
  6 +}
  1 +// import HttpRequest from '@/libs/axios'
  2 +// import config from '@/config'
  3 +// const baseUrl = config.baseUrl[process.env.VUE_APP_NAME]
  4 +// const axios = new HttpRequest(baseUrl)
  5 +// export default axios
  6 +
  7 +import HttpRequest from '@/libs/fetch'
  8 +import config from '@/config'
  9 +const baseUrl = config.baseUrl[process.env.VUE_APP_NAME]
  10 +const axios = new HttpRequest(baseUrl)
  11 +export default axios
  1 +import axios from 'axios'
  2 +import router from '@/router'
  3 +import store from '@/store'
  4 +import { Notify } from 'vant'
  5 +import { setToken, getToken } from '@/libs/util'
  6 +
  7 +const addErrorLog = errorInfo => {
  8 + const { statusText, status, config } = errorInfo
  9 + let info = {
  10 + type: 'ajax',
  11 + code: status,
  12 + // osInfo: getOs(),
  13 + // browserInfo: getBrowser().browser + '/' + getBrowser().version,
  14 + msg: errorInfo.data.message ? errorInfo.data.message : statusText,
  15 + url: config.url,
  16 + dataInfo: config.data ? JSON.parse(config.data) : {},
  17 + headers: config.headers
  18 + }
  19 + if (!config.url.includes('save_error_logger')) {
  20 + store.dispatch('addErrorLog', info)
  21 + }
  22 +}
  23 +
  24 +class HttpRequest {
  25 + path = ''
  26 + curPath = ''
  27 +
  28 + constructor(baseUrl = baseURL) {
  29 + this.baseUrl = baseUrl
  30 + this.queue = {}
  31 + }
  32 +
  33 + getInsideConfig() {
  34 + const config = {
  35 + baseURL: this.baseUrl,
  36 + headers: {}
  37 + }
  38 + if (getToken()) {
  39 + config.headers.Authorization = 'Bearer ' + getToken()
  40 + }
  41 + return config
  42 + }
  43 +
  44 + destroy(url) {
  45 + delete this.queue[url]
  46 + if (!Object.keys(this.queue).length) {
  47 + // Spin.hide()
  48 + }
  49 + }
  50 +
  51 + interceptors(instance, url) {
  52 + // 请求拦截
  53 + instance.interceptors.request.use(
  54 + config => {
  55 + // 添加全局的loading...
  56 + // if (!Object.keys(this.queue).length) {
  57 + // Spin.show() // 不建议开启,因为界面不友好
  58 + // }
  59 + this.queue[url] = true
  60 + return config
  61 + },
  62 + error => {
  63 + return Promise.reject(error)
  64 + }
  65 + )
  66 + // 响应拦截
  67 + instance.interceptors.response.use(
  68 + response => {
  69 + this.destroy(url)
  70 + // console.log(response)
  71 + if (response.data.code == 200) {
  72 + return response.data
  73 + } else {
  74 + Notify({ type: 'warning', message: response.data.message })
  75 + return Promise.reject(response.data.message)
  76 + }
  77 + },
  78 + error => {
  79 + this.destroy(url)
  80 + let errorInfo = error.response
  81 + console.log('errorInfo:', errorInfo)
  82 + if (!errorInfo) {
  83 + const {
  84 + request: { statusText, status },
  85 + config
  86 + } = JSON.parse(JSON.stringify(error))
  87 + console.log('config:', config)
  88 + errorInfo = {
  89 + statusText,
  90 + status,
  91 + config
  92 + }
  93 + }
  94 + addErrorLog(errorInfo)
  95 + if (errorInfo.status === 401) {
  96 + setToken('')
  97 + router.replace({
  98 + name: 'login'
  99 + })
  100 + }
  101 + return Promise.reject(error)
  102 + }
  103 + )
  104 + }
  105 +
  106 + setPath(...paths) {
  107 + this.curPath = `${this.path}/${paths.join('/')}`
  108 + return this
  109 + }
  110 +
  111 + replace(...params) {
  112 + let count = 0
  113 + this.curPath = this.curPath.replace(/\{.*?\}/g, _match => params[count++])
  114 + return this
  115 + }
  116 +
  117 + request(options) {
  118 + const instance = axios.create()
  119 + options = Object.assign(this.getInsideConfig(), options)
  120 + if (this.curPath !== '') {
  121 + options.url = this.curPath
  122 + this.curPath = ''
  123 + this.path = ''
  124 + }
  125 + this.interceptors(instance, options.url)
  126 + return instance(options)
  127 + }
  128 +}
  129 +
  130 +export default HttpRequest
  1 +import router from '@/router'
  2 +// import store from '@/store'
  3 +// import { Toast } from 'vant'
  4 +import { setToken, getToken } from '@/libs/util'
  5 +
  6 +const addErrorLog = errorInfo => {
  7 + const { statusText, status, url, headers, data } = errorInfo
  8 + let info = {
  9 + type: 'ajax',
  10 + code: status,
  11 + // osInfo: getOs(),
  12 + // browserInfo: getBrowser().browser + '/' + getBrowser().version,
  13 + msg: errorInfo.message ? errorInfo.message : statusText,
  14 + url: url,
  15 + dataInfo: data,
  16 + headers: headers
  17 + }
  18 + // if (!url.includes('save_error_logger')) {
  19 + // store.dispatch('addErrorLog', info)
  20 + // }
  21 +}
  22 +
  23 +class HttpRequest {
  24 + path = ''
  25 + curPath = ''
  26 +
  27 + constructor(baseURL) {
  28 + this.baseURL = baseURL
  29 + }
  30 +
  31 + setPath(...paths) {
  32 + this.curPath = `${this.path}/${paths.join('/')}`
  33 + return this
  34 + }
  35 +
  36 + replace(...params) {
  37 + let count = 0
  38 + this.curPath = this.curPath.replace(/\{.*?\}/g, _match => params[count++])
  39 + return this
  40 + }
  41 +
  42 + request(parames) {
  43 + let url = parames.url || ''
  44 + let method = parames.method || 'GET'
  45 + let data = parames.data || parames.params
  46 + let requestUrl = url.indexOf('http://') === -1 && url.indexOf('https://') === -1 ? this.baseURL + url : url
  47 + if (this.curPath !== '') {
  48 + requestUrl = this.curPath
  49 + this.curPath = ''
  50 + this.path = ''
  51 + }
  52 +
  53 + let options = {
  54 + method: method.toUpperCase(),
  55 + headers: {
  56 + 'Content-Type': 'application/json; charset=UTF-8'
  57 + },
  58 + body: JSON.stringify(data)
  59 + }
  60 +
  61 + // 设置token
  62 + if (getToken()) {
  63 + options.headers.token = getToken()
  64 + }
  65 +
  66 + // get请求转换参数
  67 + if (method == 'GET') {
  68 + delete options.body
  69 + let qs = '?'
  70 + for (const key in parames.data) {
  71 + qs += key + '=' + parames.data[key] + '&'
  72 + }
  73 + qs = qs.substring(0, qs.length - 1)
  74 + qs.length > 1 ? (requestUrl += qs) : ''
  75 + }
  76 +
  77 + return new Promise((resolve, reject) => {
  78 + fetch(requestUrl, options)
  79 + .then(function (response) {
  80 + if (response.ok) {
  81 + return response.json()
  82 + } else {
  83 + const errorInfo = {
  84 + statusText: response.statusText,
  85 + status: response.status,
  86 + url: response.url,
  87 + headers: JSON.stringify(response.headers),
  88 + data: response.data
  89 + }
  90 + addErrorLog(errorInfo)
  91 + if (response.status === 404) {
  92 + error('服务器开小差了,请联系客服~')
  93 + }
  94 + reject(response)
  95 + }
  96 + })
  97 + .then(res => {
  98 + successCallBack(res, resolve, reject)
  99 + })
  100 + .catch(err => {
  101 + error()
  102 + })
  103 + })
  104 + }
  105 +}
  106 +
  107 +function error(msg = '请检查您的网络') {
  108 + // Toast({
  109 + // message: msg,
  110 + // icon: 'none',
  111 + // duration: 3500
  112 + // })
  113 +}
  114 +function successCallBack(res, resolve, reject) {
  115 + switch (res.code) {
  116 + case 200:
  117 + resolve(res.data)
  118 + break
  119 + case 401:
  120 + error('请重新登录!')
  121 + setToken('')
  122 + router.replace({
  123 + name: 'login'
  124 + })
  125 + break
  126 + default:
  127 + businessError(res, reject)
  128 + break
  129 + }
  130 +}
  131 +function businessError(err, reject) {
  132 + // Toast({
  133 + // message: err.message,
  134 + // duration: 2000
  135 + // })
  136 + reject(err)
  137 +}
  138 +
  139 +export default HttpRequest
  1 +var storage = {
  2 + get: (name) => {
  3 + return localStorage.getItem(name)
  4 + ? JSON.parse(localStorage.getItem(name))
  5 + : null
  6 + },
  7 + set: (name, val) => {
  8 + localStorage.setItem(name, JSON.stringify(val))
  9 + },
  10 + remove: (name) => {
  11 + localStorage.removeItem(name)
  12 + }
  13 +}
  14 +export default storage
  1 +export const forEach = (arr, fn) => {
  2 + if (!arr.length || !fn) return
  3 + let i = -1
  4 + let len = arr.length
  5 + while (++i < len) {
  6 + let item = arr[i]
  7 + fn(item, i, arr)
  8 + }
  9 +}
  10 +
  11 +/**
  12 + * @param {Array} arr1
  13 + * @param {Array} arr2
  14 + * @description 得到两个数组的交集, 两个数组的元素为数值或字符串
  15 + */
  16 +export const getIntersection = (arr1, arr2) => {
  17 + let len = Math.min(arr1.length, arr2.length)
  18 + let i = -1
  19 + let res = []
  20 + while (++i < len) {
  21 + const item = arr2[i]
  22 + if (arr1.indexOf(item) > -1) res.push(item)
  23 + }
  24 + return res
  25 +}
  26 +
  27 +/**
  28 + * @param {Array} arr1
  29 + * @param {Array} arr2
  30 + * @description 得到两个数组的并集, 两个数组的元素为数值或字符串
  31 + */
  32 +export const getUnion = (arr1, arr2) => {
  33 + return Array.from(new Set([...arr1, ...arr2]))
  34 +}
  35 +
  36 +/**
  37 + * @param {Array} target 目标数组
  38 + * @param {Array} arr 需要查询的数组
  39 + * @description 判断要查询的数组是否至少有一个元素包含在目标数组中
  40 + */
  41 +export const hasOneOf = (targetarr, arr) => {
  42 + return targetarr.some(_ => arr.indexOf(_) > -1)
  43 +}
  44 +
  45 +/** 根据传入的字段进行分组
  46 + * @param arr 需要分组的数组
  47 + * @param property 分组的字段
  48 + * @returns {*[]} 已分好组的数组
  49 + */
  50 +export const abilitySort = (arr, property) => {
  51 + let map = {}
  52 + for (let i = 0; i < arr.length; i++) {
  53 + const ai = arr[i]
  54 + if (!map[ai[property]]) map[ai[property]] = [ai]
  55 + else map[ai[property]].push(ai)
  56 + }
  57 + let res = []
  58 + Object.keys(map).forEach(key => {
  59 + res.push({ [property]: key, data: map[key] })
  60 + })
  61 + return res
  62 +}
  63 +
  64 +/**
  65 + * @param {String|Number} value 要验证的字符串或数值
  66 + * @param {*} validList 用来验证的列表
  67 + */
  68 +export function oneOf(value, validList) {
  69 + for (let i = 0; i < validList.length; i++) {
  70 + if (value === validList[i]) {
  71 + return true
  72 + }
  73 + }
  74 + return false
  75 +}
  76 +
  77 +/**
  78 + * @param {Number} timeStamp 判断时间戳格式是否是毫秒
  79 + * @returns {Boolean}
  80 + */
  81 +const isMillisecond = timeStamp => {
  82 + const timeStr = String(timeStamp)
  83 + return timeStr.length > 10
  84 +}
  85 +
  86 +/**
  87 + * @param {Number} timeStamp 传入的时间戳
  88 + * @param {Number} currentTime 当前时间时间戳
  89 + * @returns {Boolean} 传入的时间戳是否早于当前时间戳
  90 + */
  91 +const isEarly = (timeStamp, currentTime) => {
  92 + return timeStamp < currentTime
  93 +}
  94 +
  95 +/**
  96 + * @param {Number} num 数值
  97 + * @returns {String} 处理后的字符串
  98 + * @description 如果传入的数值小于10,即位数只有1位,则在前面补充0
  99 + */
  100 +const getHandledValue = num => {
  101 + return num < 10 ? '0' + num : num
  102 +}
  103 +
  104 +/**
  105 + * @param {Number} timeStamp 传入的时间戳
  106 + * @param {Number} startType 要返回的时间字符串的格式类型,传入'year'则返回年开头的完整时间
  107 + */
  108 +export const getDate = (timeStamp, startType) => {
  109 + const d = new Date(timeStamp * 1000)
  110 + const year = d.getFullYear()
  111 + const month = getHandledValue(d.getMonth() + 1)
  112 + const date = getHandledValue(d.getDate())
  113 + const hours = getHandledValue(d.getHours())
  114 + const minutes = getHandledValue(d.getMinutes())
  115 + const second = getHandledValue(d.getSeconds())
  116 + let resStr = ''
  117 + if (startType === 'year') {
  118 + resStr = year + '-' + month + '-' + date + ' ' + hours + ':' + minutes + ':' + second
  119 + } else resStr = month + '-' + date + ' ' + hours + ':' + minutes
  120 + return resStr
  121 +}
  122 +
  123 +/**
  124 + * @param {String|Number} timeStamp 时间戳
  125 + * @returns {String} 相对时间字符串
  126 + */
  127 +export const getRelativeTime = timeStamp => {
  128 + // 判断当前传入的时间戳是秒格式还是毫秒
  129 + const IS_MILLISECOND = isMillisecond(timeStamp)
  130 + // 如果是毫秒格式则转为秒格式
  131 + if (IS_MILLISECOND) Math.floor((timeStamp /= 1000))
  132 + // 传入的时间戳可以是数值或字符串类型,这里统一转为数值类型
  133 + timeStamp = Number(timeStamp)
  134 + // 获取当前时间时间戳
  135 + const currentTime = Math.floor(Date.parse(new Date()) / 1000)
  136 + // 判断传入时间戳是否早于当前时间戳
  137 + const IS_EARLY = isEarly(timeStamp, currentTime)
  138 + // 获取两个时间戳差值
  139 + let diff = currentTime - timeStamp
  140 + // 如果IS_EARLY为false则差值取反
  141 + if (!IS_EARLY) diff = -diff
  142 + let resStr = ''
  143 + const dirStr = IS_EARLY ? '前' : '后'
  144 + // 少于等于59秒
  145 + if (diff <= 59) resStr = diff + '秒' + dirStr
  146 + // 多于59秒,少于等于59分钟59秒
  147 + else if (diff > 59 && diff <= 3599) {
  148 + resStr = Math.floor(diff / 60) + '分钟' + dirStr
  149 + }
  150 + // 多于59分钟59秒,少于等于23小时59分钟59秒
  151 + else if (diff > 3599 && diff <= 86399) {
  152 + resStr = Math.floor(diff / 3600) + '小时' + dirStr
  153 + }
  154 + // 多于23小时59分钟59秒,少于等于29天59分钟59秒
  155 + else if (diff > 86399 && diff <= 2623859) {
  156 + resStr = Math.floor(diff / 86400) + '天' + dirStr
  157 + }
  158 + // 多于29天59分钟59秒,少于364天23小时59分钟59秒,且传入的时间戳早于当前
  159 + else if (diff > 2623859 && diff <= 31567859 && IS_EARLY) {
  160 + resStr = getDate(timeStamp)
  161 + } else resStr = getDate(timeStamp, 'year')
  162 + return resStr
  163 +}
  164 +
  165 +/**
  166 + * @returns {String} 当前浏览器名称
  167 + */
  168 +export const getExplorer = () => {
  169 + const ua = window.navigator.userAgent
  170 + const isExplorer = exp => {
  171 + return ua.indexOf(exp) > -1
  172 + }
  173 + if (isExplorer('MSIE')) return 'IE'
  174 + else if (isExplorer('Firefox')) return 'Firefox'
  175 + else if (isExplorer('Chrome')) return 'Chrome'
  176 + else if (isExplorer('Opera')) return 'Opera'
  177 + else if (isExplorer('Safari')) return 'Safari'
  178 +}
  179 +
  180 +/**
  181 + * @description 绑定事件 on(element, event, handler)
  182 + */
  183 +export const on = (function () {
  184 + if (document.addEventListener) {
  185 + return function (element, event, handler) {
  186 + if (element && event && handler) {
  187 + element.addEventListener(event, handler, false)
  188 + }
  189 + }
  190 + } else {
  191 + return function (element, event, handler) {
  192 + if (element && event && handler) {
  193 + element.attachEvent('on' + event, handler)
  194 + }
  195 + }
  196 + }
  197 +})()
  198 +
  199 +/**
  200 + * @description 解绑事件 off(element, event, handler)
  201 + */
  202 +export const off = (function () {
  203 + if (document.removeEventListener) {
  204 + return function (element, event, handler) {
  205 + if (element && event) {
  206 + element.removeEventListener(event, handler, false)
  207 + }
  208 + }
  209 + } else {
  210 + return function (element, event, handler) {
  211 + if (element && event) {
  212 + element.detachEvent('on' + event, handler)
  213 + }
  214 + }
  215 + }
  216 +})()
  217 +
  218 +/**
  219 + * 判断一个对象是否存在key,如果传入第二个参数key,则是判断这个obj对象是否存在key这个属性
  220 + * 如果没有传入key这个参数,则判断obj对象是否有键值对
  221 + */
  222 +export const hasKey = (obj, key) => {
  223 + if (key) return key in obj
  224 + else {
  225 + let keysArr = Object.keys(obj)
  226 + return keysArr.length
  227 + }
  228 +}
  229 +
  230 +/**
  231 + * @param {*} obj1 对象
  232 + * @param {*} obj2 对象
  233 + * @description 判断两个对象是否相等,这两个对象的值只能是数字或字符串
  234 + */
  235 +export const objEqual = (obj1, obj2) => {
  236 + const keysArr1 = Object.keys(obj1)
  237 + const keysArr2 = Object.keys(obj2)
  238 + if (keysArr1.length !== keysArr2.length) return false
  239 + else if (keysArr1.length === 0 && keysArr2.length === 0) return true
  240 + /* eslint-disable-next-line */ else {
  241 + return !keysArr1.some(key => obj1[key] != obj2[key])
  242 + }
  243 +}
  244 +
  245 +// 检测json格式
  246 +export const isJSON = str => {
  247 + if (typeof str === 'string') {
  248 + try {
  249 + var obj = JSON.parse(str)
  250 + if (typeof obj === 'object' && obj) {
  251 + return true
  252 + } else {
  253 + return false
  254 + }
  255 + } catch (e) {
  256 + return false
  257 + }
  258 + } else if (typeof str === 'object' && str) {
  259 + return true
  260 + }
  261 +}
  262 +
  263 +// 美化json字符串
  264 +export const beautifyJSONFormat = str => {
  265 + if (!str) return
  266 + return JSON.stringify(JSON.parse(str), null, '\t')
  267 +}
  268 +
  269 +export const handleFullscreen = type => {
  270 + let main = document.body
  271 + if (!type) {
  272 + if (document.exitFullscreen) {
  273 + document.exitFullscreen()
  274 + } else if (document.mozCancelFullScreen) {
  275 + document.mozCancelFullScreen()
  276 + } else if (document.webkitCancelFullScreen) {
  277 + document.webkitCancelFullScreen()
  278 + } else if (document.msExitFullscreen) {
  279 + document.msExitFullscreen()
  280 + }
  281 + } else {
  282 + if (main.requestFullscreen) {
  283 + main.requestFullscreen()
  284 + } else if (main.mozRequestFullScreen) {
  285 + main.mozRequestFullScreen()
  286 + } else if (main.webkitRequestFullScreen) {
  287 + main.webkitRequestFullScreen()
  288 + } else if (main.msRequestFullscreen) {
  289 + main.msRequestFullscreen()
  290 + }
  291 + }
  292 +}
  293 +
  294 +//深拷贝对象
  295 +export const deepCopyObj = obj => {
  296 + return JSON.parse(JSON.stringify(obj))
  297 +}
  298 +
  299 +// 正则去除字符串空格
  300 +export const strTrim = str => {
  301 + return str.replace(/\s*/g, '')
  302 +}
  303 +
  304 +// 版本号处理
  305 +export const versionFormatting = version => {
  306 + return Number(version.split('.').join(''))
  307 +}
  308 +
  309 +// 递归打印对象
  310 +export const logProperties = obj => {
  311 + if (typeof obj !== 'object' || obj === null) {
  312 + console.log(obj)
  313 + return
  314 + }
  315 +
  316 + var properties = Object.getOwnPropertyNames(obj)
  317 + properties.forEach(function (prop) {
  318 + console.log(prop + ':', obj[prop])
  319 + logProperties(obj[prop])
  320 + })
  321 +}
  1 +import config from '@/config'
  2 +import storage from '@/libs/storageUtil'
  3 +import { forEach, hasOneOf, objEqual } from '@/libs/tools'
  4 +
  5 +const { title, useI18n } = config
  6 +
  7 +export const TOKEN_KEY = 'token'
  8 +
  9 +export const setToken = token => {
  10 + storage.set(TOKEN_KEY, token)
  11 +}
  12 +
  13 +export const getToken = () => {
  14 + const token = storage.get(TOKEN_KEY)
  15 + if (token) return token
  16 + else return false
  17 +}
  18 +
  19 +export const hasChild = item => {
  20 + return item.children && item.children.length !== 0
  21 +}
  22 +
  23 +const showThisMenuEle = (item, access) => {
  24 + if (item.meta && item.meta.access && item.meta.access.length) {
  25 + if (hasOneOf(item.meta.access, access)) return true
  26 + else return false
  27 + } else return true
  28 +}
  29 +/**
  30 + * @param {Array} list 通过路由列表得到菜单列表
  31 + * @returns {Array}
  32 + */
  33 +export const getMenuByRouter = (list, access) => {
  34 + let res = []
  35 + forEach(list, item => {
  36 + if (!item.meta || (item.meta && !item.meta.hideInMenu)) {
  37 + let obj = {
  38 + icon: (item.meta && item.meta.icon) || '',
  39 + name: item.name,
  40 + meta: item.meta
  41 + }
  42 + if ((hasChild(item) || (item.meta && item.meta.showAlways)) && showThisMenuEle(item, access)) {
  43 + obj.children = getMenuByRouter(item.children, access)
  44 + }
  45 + if (item.meta && item.meta.href) obj.href = item.meta.href
  46 + if (showThisMenuEle(item, access)) res.push(obj)
  47 + }
  48 + })
  49 + return res
  50 +}
  51 +
  52 +/**
  53 + * @param {Array} routeMetched 当前路由metched
  54 + * @returns {Array}
  55 + */
  56 +export const getBreadCrumbList = (route, homeRoute) => {
  57 + let homeItem = { ...homeRoute, icon: homeRoute.meta.icon }
  58 + let routeMetched = route.matched
  59 + if (routeMetched.some(item => item.name === homeRoute.name)) return [homeItem]
  60 + let res = routeMetched
  61 + .filter(item => {
  62 + return item.meta === undefined || !item.meta.hideInBread
  63 + })
  64 + .map(item => {
  65 + let meta = { ...item.meta }
  66 + if (meta.title && typeof meta.title === 'function') {
  67 + meta.__titleIsFunction__ = true
  68 + meta.title = meta.title(route)
  69 + }
  70 + let obj = {
  71 + icon: (item.meta && item.meta.icon) || '',
  72 + name: item.name,
  73 + meta: meta
  74 + }
  75 + return obj
  76 + })
  77 + res = res.filter(item => {
  78 + return !item.meta.hideInMenu
  79 + })
  80 + return [{ ...homeItem, to: homeRoute.path }, ...res]
  81 +}
  82 +
  83 +export const getRouteTitleHandled = route => {
  84 + let router = { ...route }
  85 + let meta = { ...route.meta }
  86 + let title = ''
  87 + if (meta.title) {
  88 + if (typeof meta.title === 'function') {
  89 + meta.__titleIsFunction__ = true
  90 + title = meta.title(router)
  91 + } else title = meta.title
  92 + }
  93 + meta.title = title
  94 + router.meta = meta
  95 + return router
  96 +}
  97 +
  98 +export const showTitle = (item, vm) => {
  99 + let { title, __titleIsFunction__ } = item.meta
  100 + if (!title) return
  101 + if (useI18n) {
  102 + if (title.includes('{{') && title.includes('}}') && useI18n) title = title.replace(/({{[\s\S]+?}})/, (m, str) => str.replace(/{{([\s\S]*)}}/, (m, _) => vm.$t(_.trim())))
  103 + else if (__titleIsFunction__) title = item.meta.title
  104 + else title = vm.$t(item.name)
  105 + } else title = (item.meta && item.meta.title) || item.name
  106 + return title
  107 +}
  108 +
  109 +/**
  110 + * @description 本地存储和获取标签导航列表
  111 + */
  112 +export const setTagNavListInLocalstorage = list => {
  113 + localStorage.tagNaveList = JSON.stringify(list)
  114 +}
  115 +/**
  116 + * @returns {Array} 其中的每个元素只包含路由原信息中的name, path, meta三项
  117 + */
  118 +export const getTagNavListFromLocalstorage = () => {
  119 + const list = localStorage.tagNaveList
  120 + return list ? JSON.parse(list) : []
  121 +}
  122 +
  123 +/**
  124 + * @param {Array} routers 路由列表数组
  125 + * @description 用于找到路由列表中name为home的对象
  126 + */
  127 +export const getHomeRoute = (routers, homeName = 'home') => {
  128 + let i = -1
  129 + let len = routers.length
  130 + let homeRoute = {}
  131 + while (++i < len) {
  132 + let item = routers[i]
  133 + if (item.children && item.children.length) {
  134 + let res = getHomeRoute(item.children, homeName)
  135 + if (res.name) return res
  136 + } else {
  137 + if (item.name === homeName) homeRoute = item
  138 + }
  139 + }
  140 + return homeRoute
  141 +}
  142 +
  143 +/**
  144 + * @param {*} list 现有标签导航列表
  145 + * @param {*} newRoute 新添加的路由原信息对象
  146 + * @description 如果该newRoute已经存在则不再添加
  147 + */
  148 +export const getNewTagList = (list, newRoute) => {
  149 + const { name, path, meta } = newRoute
  150 + let newList = [...list]
  151 + if (newList.findIndex(item => item.name === name) >= 0) return newList
  152 + else newList.push({ name, path, meta })
  153 + return newList
  154 +}
  155 +
  156 +/**
  157 + * @param {*} access 用户权限数组,如 ['super_admin', 'admin']
  158 + * @param {*} route 路由列表
  159 + */
  160 +const hasAccess = (access, route) => {
  161 + if (route.meta && route.meta.access) return hasOneOf(access, route.meta.access)
  162 + else return true
  163 +}
  164 +
  165 +/**
  166 + * 权鉴
  167 + * @param {*} name 即将跳转的路由name
  168 + * @param {*} access 用户权限数组
  169 + * @param {*} routes 路由列表
  170 + * @description 用户是否可跳转到该页
  171 + */
  172 +export const canTurnTo = (name, access, routes) => {
  173 + const routePermissionJudge = list => {
  174 + return list.some(item => {
  175 + if (item.children && item.children.length) {
  176 + return routePermissionJudge(item.children)
  177 + } else if (item.name === name) {
  178 + return hasAccess(access, item)
  179 + }
  180 + })
  181 + }
  182 +
  183 + return routePermissionJudge(routes)
  184 +}
  185 +
  186 +/**
  187 + * @param {String} url
  188 + * @description 从URL中解析参数
  189 + */
  190 +export const getParams = url => {
  191 + const keyValueArr = url.split('?')[1].split('&')
  192 + let paramObj = {}
  193 + keyValueArr.forEach(item => {
  194 + const keyValue = item.split('=')
  195 + paramObj[keyValue[0]] = keyValue[1]
  196 + })
  197 + return paramObj
  198 +}
  199 +
  200 +/**
  201 + * @param {Array} list 标签列表
  202 + * @param {String} name 当前关闭的标签的name
  203 + */
  204 +export const getNextRoute = (list, route) => {
  205 + let res = {}
  206 + if (list.length === 2) {
  207 + res = getHomeRoute(list)
  208 + } else {
  209 + const index = list.findIndex(item => routeEqual(item, route))
  210 + if (index === list.length - 1) res = list[list.length - 2]
  211 + else res = list[index + 1]
  212 + }
  213 + return res
  214 +}
  215 +
  216 +/**
  217 + * @param {Number} times 回调函数需要执行的次数
  218 + * @param {Function} callback 回调函数
  219 + */
  220 +export const doCustomTimes = (times, callback) => {
  221 + let i = -1
  222 + while (++i < times) {
  223 + callback(i)
  224 + }
  225 +}
  226 +
  227 +/**
  228 + * @param {Object} file 从上传组件得到的文件对象
  229 + * @returns {Promise} resolve参数是解析后的二维数组
  230 + * @description 从Csv文件中解析出表格,解析成二维数组
  231 + */
  232 +export const getArrayFromFile = file => {
  233 + let nameSplit = file.name.split('.')
  234 + let format = nameSplit[nameSplit.length - 1]
  235 + return new Promise((resolve, reject) => {
  236 + let reader = new FileReader()
  237 + reader.readAsText(file) // 以文本格式读取
  238 + let arr = []
  239 + reader.onload = function (evt) {
  240 + let data = evt.target.result // 读到的数据
  241 + let pasteData = data.trim()
  242 + arr = pasteData
  243 + .split(/[\n\u0085\u2028\u2029]|\r\n?/g)
  244 + .map(row => {
  245 + return row.split('\t')
  246 + })
  247 + .map(item => {
  248 + return item[0].split(',')
  249 + })
  250 + if (format === 'csv') resolve(arr)
  251 + else reject(new Error('[Format Error]:你上传的不是Csv文件'))
  252 + }
  253 + })
  254 +}
  255 +
  256 +/**
  257 + * @param {Array} array 表格数据二维数组
  258 + * @returns {Object} { columns, tableData }
  259 + * @description 从二维数组中获取表头和表格数据,将第一行作为表头,用于在iView的表格中展示数据
  260 + */
  261 +export const getTableDataFromArray = array => {
  262 + let columns = []
  263 + let tableData = []
  264 + if (array.length > 1) {
  265 + let titles = array.shift()
  266 + columns = titles.map(item => {
  267 + return {
  268 + title: item,
  269 + key: item
  270 + }
  271 + })
  272 + tableData = array.map(item => {
  273 + let res = {}
  274 + item.forEach((col, i) => {
  275 + res[titles[i]] = col
  276 + })
  277 + return res
  278 + })
  279 + }
  280 + return {
  281 + columns,
  282 + tableData
  283 + }
  284 +}
  285 +
  286 +export const findNodeUpper = (ele, tag) => {
  287 + if (ele.parentNode) {
  288 + if (ele.parentNode.tagName === tag.toUpperCase()) {
  289 + return ele.parentNode
  290 + } else {
  291 + return findNodeUpper(ele.parentNode, tag)
  292 + }
  293 + }
  294 +}
  295 +
  296 +export const findNodeUpperByClasses = (ele, classes) => {
  297 + let parentNode = ele.parentNode
  298 + if (parentNode) {
  299 + let classList = parentNode.classList
  300 + if (classList && classes.every(className => classList.contains(className))) {
  301 + return parentNode
  302 + } else {
  303 + return findNodeUpperByClasses(parentNode, classes)
  304 + }
  305 + }
  306 +}
  307 +
  308 +export const findNodeDownward = (ele, tag) => {
  309 + const tagName = tag.toUpperCase()
  310 + if (ele.childNodes.length) {
  311 + let i = -1
  312 + let len = ele.childNodes.length
  313 + while (++i < len) {
  314 + let child = ele.childNodes[i]
  315 + if (child.tagName === tagName) return child
  316 + else return findNodeDownward(child, tag)
  317 + }
  318 + }
  319 +}
  320 +
  321 +export const showByAccess = (access, canViewAccess) => {
  322 + return hasOneOf(canViewAccess, access)
  323 +}
  324 +
  325 +/**
  326 + * @description 根据name/params/query判断两个路由对象是否相等
  327 + * @param {*} route1 路由对象
  328 + * @param {*} route2 路由对象
  329 + */
  330 +export const routeEqual = (route1, route2) => {
  331 + const params1 = route1.params || {}
  332 + const params2 = route2.params || {}
  333 + const query1 = route1.query || {}
  334 + const query2 = route2.query || {}
  335 + return route1.name === route2.name && objEqual(params1, params2) && objEqual(query1, query2)
  336 +}
  337 +
  338 +/**
  339 + * 判断打开的标签列表里是否已存在这个新添加的路由对象
  340 + */
  341 +export const routeHasExist = (tagNavList, routeItem) => {
  342 + let len = tagNavList.length
  343 + let res = false
  344 + doCustomTimes(len, index => {
  345 + if (routeEqual(tagNavList[index], routeItem)) res = true
  346 + })
  347 + return res
  348 +}
  349 +
  350 +export const localSave = (key, value) => {
  351 + localStorage.setItem(key, value)
  352 +}
  353 +
  354 +export const localRead = key => {
  355 + return localStorage.getItem(key) || ''
  356 +}
  357 +
  358 +// scrollTop animation
  359 +export const scrollTop = (el, from = 0, to, duration = 500, endCallback) => {
  360 + if (!window.requestAnimationFrame) {
  361 + window.requestAnimationFrame =
  362 + window.webkitRequestAnimationFrame ||
  363 + window.mozRequestAnimationFrame ||
  364 + window.msRequestAnimationFrame ||
  365 + function (callback) {
  366 + return window.setTimeout(callback, 1000 / 60)
  367 + }
  368 + }
  369 + const difference = Math.abs(from - to)
  370 + const step = Math.ceil((difference / duration) * 50)
  371 +
  372 + const scroll = (start, end, step) => {
  373 + if (start === end) {
  374 + endCallback && endCallback()
  375 + return
  376 + }
  377 +
  378 + let d = start + step > end ? end : start + step
  379 + if (start > end) {
  380 + d = start - step < end ? end : start - step
  381 + }
  382 +
  383 + if (el === window) {
  384 + window.scrollTo(d, d)
  385 + } else {
  386 + el.scrollTop = d
  387 + }
  388 + window.requestAnimationFrame(() => scroll(d, end, step))
  389 + }
  390 + scroll(from, to, step)
  391 +}
  392 +
  393 +/**
  394 + * @description 根据当前跳转的路由设置显示在浏览器标签的title
  395 + * @param {Object} routeItem 路由对象
  396 + * @param {Object} vm Vue实例
  397 + */
  398 +export const setTitle = (routeItem, vm) => {
  399 + const handledRoute = getRouteTitleHandled(routeItem)
  400 + const pageTitle = showTitle(handledRoute, vm)
  401 + const resTitle = pageTitle ? `${title} - ${pageTitle}` : title
  402 + window.document.title = resTitle
  403 +}
  1 +import Vue from 'vue'
  2 +import App from './App.vue'
  3 +import router from './router'
  4 +import store from './store'
  5 +import Vant from 'vant'
  6 +import { Lazyload } from 'vant'
  7 +import commMixin from '@/mixin/comm'
  8 +import 'vant/lib/index.css'
  9 +import { prettyPrint } from './filters'
  10 +import loggerPlugin from './plugin/loggerPlugin'
  11 +
  12 +// import VueDragResize from 'vue-drag-resize'
  13 +// Vue.component('vue-drag-resize', VueDragResize)
  14 +Vue.use(Vant)
  15 +Vue.use(Lazyload)
  16 +Vue.use(loggerPlugin, store)
  17 +
  18 +Vue.filter('prettyPrint', prettyPrint)
  19 +Vue.config.productionTip = false
  20 +
  21 +Vue.config.errorHandler = function (error, vm, info) {
  22 + console.error('Error message:' + error.message, 'Error stack:' + error.stack)
  23 +}
  24 +
  25 +// function getObjToArrayBufferDoLength(obj) {
  26 +// // 将对象序列化为 JSON 字符串
  27 +// const json = JSON.stringify(obj)
  28 +// // 创建一个新的 TextEncoder 对象
  29 +// const encoder = new TextEncoder()
  30 +// // 将 JSON 字符串编码为 Uint8Array
  31 +// const data = encoder.encode(json)
  32 +// // 创建一个新的 ArrayBuffer,大小为数据长度
  33 +// const buffer = new ArrayBuffer(data.length)
  34 +// // 获取 ArrayBuffer 的视图(Uint8Array)
  35 +// const view = new Uint8Array(buffer)
  36 +// // 将数据复制到 ArrayBuffer 中
  37 +// view.set(data)
  38 +// return view.byteLength
  39 +// }
  40 +
  41 +Vue.mixin(commMixin)
  42 +const app = new Vue({
  43 + router,
  44 + store,
  45 + created() {
  46 + this.setIsApp()
  47 + setTimeout(() => {
  48 + if (!this.getIsApp) {
  49 + rewriteXHR()
  50 + }
  51 + }, 0)
  52 + },
  53 + methods: {
  54 + setIsApp() {
  55 + let isApp = false
  56 + try {
  57 + isApp = webf ? true : false
  58 + } catch (error) {
  59 + isApp = false
  60 + }
  61 + this.$store.commit('setIsApp', isApp)
  62 + }
  63 + },
  64 + render: h => h(App)
  65 +}).$mount('#app')
  66 +
  67 +function rewriteXHR() {
  68 + // 保存原始的 XMLHttpRequest 构造函数
  69 + let OriginalXHR = window.XMLHttpRequest
  70 + // 创建新的 XMLHttpRequest 构造函数,扩展了原始的构造函数
  71 + function CustomXHR(app) {
  72 + let xhr = new OriginalXHR()
  73 + let sendDate = 0
  74 + xhr.addEventListener('loadstart', function (event) {
  75 + sendDate = Date.now()
  76 + })
  77 + xhr.addEventListener('load', function (event) {
  78 + const responseURL = xhr.response.url || xhr.responseURL
  79 + const response = xhr.response.body || xhr.response
  80 + try {
  81 + console.netwokrLog({
  82 + name: responseURL.split('/').pop(),
  83 + status: xhr.status,
  84 + statusText: xhr.statusText,
  85 + response: response,
  86 + responseURL: responseURL,
  87 + size: 0,
  88 + time: Date.now() - sendDate
  89 + })
  90 + } catch (error) {
  91 + console.log('error:', error)
  92 + }
  93 + })
  94 + xhr.addEventListener('error', function (event) {
  95 + console.log('error:', event)
  96 + })
  97 + return xhr
  98 + }
  99 + // 重写全局的 XMLHttpRequest 构造函数
  100 + window.XMLHttpRequest = CustomXHR
  101 +}
  102 +
  103 +// new Vue({
  104 +// router,
  105 +// store,
  106 +// render: h => h(App)
  107 +// }).$mount(document.body)
  1 +import { mapGetters } from 'vuex'
  2 +
  3 +export default {
  4 + computed: {
  5 + ...mapGetters(['getIsApp', 'getAppNavBarShow', 'getSystemInfo', 'getTopOffsetHeight'])
  6 + }
  7 +}
  1 +// loggerPlugin.js
  2 +export default {
  3 + install(Vue, options) {
  4 + // 创建一个 Vue 实例作为事件总线
  5 + // const bus = new Vue()
  6 + const cLog = console.log
  7 + const cError = console.error
  8 + options.commit('setStoreLog', cLog)
  9 +
  10 + console.netwokrLog = function (data) {
  11 + options.commit('addNetworkLoggerData', {
  12 + sendTime: Date.now(),
  13 + ...data
  14 + })
  15 + }
  16 +
  17 + console.log = function (data) {
  18 + cLog(...arguments)
  19 + if (arguments[0]) {
  20 + let length = arguments.length
  21 + let logTime = Date.now()
  22 + for (let index = 0; index < length; index++) {
  23 + const item = arguments[index]
  24 + options.commit('addLoggerData', {
  25 + logType: 'log',
  26 + logId: logTime,
  27 + type: typeof item,
  28 + log: item
  29 + })
  30 + }
  31 + }
  32 + }
  33 +
  34 + console.error = function () {
  35 + cError(...arguments)
  36 + let length = arguments.length
  37 + let logTime = Date.now()
  38 + for (let index = 0; index < length; index++) {
  39 + const item = arguments[index]
  40 + options.commit('addLoggerData', {
  41 + logType: 'error',
  42 + logId: logTime,
  43 + type: typeof item,
  44 + log: typeof item === 'object' ? JSON.stringify(item) : item
  45 + })
  46 + }
  47 + }
  48 +
  49 + Vue.config.errorHandler = function (error, vm, info) {
  50 + console.error('Error message:' + error.message, 'Error stack:' + error.stack)
  51 + }
  52 + }
  53 +}
  1 +import Vue from 'vue'
  2 +import VueRouter from 'vue-router'
  3 +import HomeView from '@/views/HomeView.vue'
  4 +
  5 +Vue.use(VueRouter)
  6 +
  7 +// 获取原型对象push函数
  8 +const originalPush = VueRouter.prototype.push
  9 +
  10 +// 获取原型对象replace函数
  11 +const originalReplace = VueRouter.prototype.replace
  12 +
  13 +// 修改原型对象中的push函数
  14 +VueRouter.prototype.push = function push(location) {
  15 + return originalPush.call(this, location).catch(err => err)
  16 +}
  17 +
  18 +// 修改原型对象中的replace函数
  19 +VueRouter.prototype.replace = function replace(location) {
  20 + return originalReplace.call(this, location).catch(err => err)
  21 +}
  22 +
  23 +const routes = [
  24 + {
  25 + path: '/',
  26 + name: 'HomeView',
  27 + meta: {
  28 + title: '首页',
  29 + icon: 'home-o',
  30 + isTabBarMenu: true,
  31 + keepAlive: true,
  32 + showNavBar: true
  33 + },
  34 + component: HomeView
  35 + },
  36 + {
  37 + path: '/caricature',
  38 + name: 'CaricatureView',
  39 + meta: {
  40 + title: '漫画',
  41 + icon: 'home-o',
  42 + isTabBarMenu: true,
  43 + keepAlive: true,
  44 + showNavBar: true
  45 + },
  46 + component: () => import('../views/CaricatureView.vue')
  47 + },
  48 + {
  49 + path: '/active',
  50 + name: 'ActiveView',
  51 + meta: {
  52 + title: '活动',
  53 + icon: 'home-o',
  54 + isTabBarMenu: true,
  55 + keepAlive: true,
  56 + showNavBar: true
  57 + },
  58 + component: () => import('../views/ActiveView.vue')
  59 + },
  60 + {
  61 + path: '/live',
  62 + name: 'LiveView',
  63 + meta: {
  64 + title: '直播',
  65 + icon: 'home-o',
  66 + isTabBarMenu: true,
  67 + keepAlive: true,
  68 + showNavBar: true
  69 + },
  70 + component: () => import('../views/LiveView.vue')
  71 + },
  72 + {
  73 + path: '/mine',
  74 + name: 'MineView',
  75 + meta: {
  76 + title: '我的',
  77 + icon: 'home-o',
  78 + isTabBarMenu: true,
  79 + keepAlive: true,
  80 + showNavBar: true
  81 + },
  82 + component: () => import('../views/MineView.vue')
  83 + },
  84 + {
  85 + path: '/detail',
  86 + name: 'DetailView',
  87 + meta: {
  88 + title: '详情',
  89 + isTabBarMenu: false,
  90 + showNavBar: true
  91 + },
  92 + component: () => import('../views/DetailView.vue')
  93 + }
  94 +]
  95 +
  96 +const router = new VueRouter({
  97 + // mode: 'history',
  98 + // base: process.env.BASE_URL,
  99 + routes
  100 +})
  101 +
  102 +export default router
  1 +import Vue from 'vue'
  2 +import Vuex from 'vuex'
  3 +
  4 +Vue.use(Vuex)
  5 +export default new Vuex.Store({
  6 + state: {
  7 + isApp: false,
  8 + appNavBarShow: false,
  9 + systemInfo: {
  10 + statusHeight: 0
  11 + },
  12 + loggerData: [],
  13 + networkLoggerData: [],
  14 + /**
  15 + * @description: 如果需要在addLoggerData/addNetworkLoggerData打印log
  16 + * @description: 请使用:storeLog(log)的方式避免死循环
  17 + * @return {*}
  18 + */
  19 + storeLog: null
  20 + },
  21 + getters: {
  22 + getIsApp: state => state.isApp,
  23 + getAppNavBarShow: state => state.appNavBarShow,
  24 + getSystemInfo: state => state.systemInfo,
  25 + getTopOffsetHeight: state => {
  26 + if (state.isApp) {
  27 + if (state.appNavBarShow) {
  28 + return 46 + state.systemInfo.statusHeight
  29 + } else {
  30 + return 46
  31 + }
  32 + } else {
  33 + return 0
  34 + }
  35 + },
  36 + getLoggerData: state => state.loggerData,
  37 + getNetworkLoggerData: state => state.networkLoggerData
  38 + },
  39 + mutations: {
  40 + setIsApp(state, isApp) {
  41 + state.isApp = isApp
  42 + },
  43 + setAppNavBarShow(state, isShow) {
  44 + state.appNavBarShow = isShow
  45 + },
  46 +
  47 + setSystemInfo(state, systemInfo) {
  48 + state.systemInfo = systemInfo
  49 + },
  50 + setStoreLog(state, log) {
  51 + state.storeLog = log
  52 + },
  53 + addLoggerData(state, log) {
  54 + state.loggerData.push(log)
  55 + },
  56 + addNetworkLoggerData(state, networkLog) {
  57 + state.networkLoggerData.push(networkLog)
  58 + }
  59 + },
  60 + actions: {},
  61 + modules: {}
  62 +})
  1 +<template>
  2 + <div>活动</div>
  3 +</template>
  4 +
  5 +<script>
  6 +export default {
  7 + name: 'ActiveView'
  8 +}
  9 +</script>
  10 +
  11 +<style lang="scss" scoped></style>
  1 +<template>
  2 + <div class="about">
  3 + <h1>漫画</h1>
  4 + <van-button type="primary" @click="jump">测试跳转</van-button>
  5 + </div>
  6 +</template>
  7 +<script>
  8 +export default {
  9 + name: 'CaricatureView',
  10 + data() {
  11 + return {}
  12 + },
  13 + methods: {
  14 + jump() {
  15 + this.$router.push({
  16 + name: 'DetailView'
  17 + })
  18 + }
  19 + }
  20 +}
  21 +</script>
  1 +<template>
  2 + <div>
  3 + <div>详情页</div>
  4 + </div>
  5 +</template>
  6 +
  7 +<script>
  8 +export default {
  9 + name: 'DetailView',
  10 + data() {
  11 + return {}
  12 + },
  13 + methods: {}
  14 +}
  15 +</script>
  16 +
  17 +<style lang="scss" scoped></style>
  1 +<template>
  2 + <div class="home-page">
  3 + <div class="header-nav-bar" :style="{ top: getTopOffsetHeight + 'px' }">
  4 + <div class="tabs-view">
  5 + <div class="tab-item" :class="{ active: activeIndex === index }" v-for="(item, index) in tabList" :key="item.value" @click="changeTabIndex(index)">
  6 + {{ item.label }}
  7 + <div class="active-line" :class="{ show: activeIndex === index }" />
  8 + </div>
  9 + </div>
  10 + <div class="icon-list">
  11 + <span class="iconfont icon-shezhi"></span>
  12 + <span class="iconfont icon-kefu"></span>
  13 + </div>
  14 + </div>
  15 + <div class="search-view" :style="{ top: getTopOffsetHeight + 44 + 'px' }">
  16 + <van-search shape="round" v-model="searchKeyWord" placeholder="请输入搜索关键词" />
  17 + </div>
  18 + <div v-if="activeIndex === 0">
  19 + <div class="banner-view">
  20 + <van-swipe class="my-swipe" indicator-color="white">
  21 + <van-swipe-item v-for="(item, index) in images" :key="index">
  22 + <img class="swipe-img" :src="item" />
  23 + </van-swipe-item>
  24 + </van-swipe>
  25 + </div>
  26 + <div class="notice-view">
  27 + <van-notice-bar left-icon="volume-o" background="#f8f8fb" text="在代码阅读过程中人们说脏话的频率是衡量代码质量的唯一标准。">
  28 + <template #right-icon>
  29 + <van-button class="hot-live-btn" size="mini" icon="fire-o" type="primary" @click="showGlobalLog">热门直播</van-button>
  30 + </template>
  31 + </van-notice-bar>
  32 + </div>
  33 + <div class="list-view">
  34 + <collapse v-model="competitionActiveNames">
  35 + <collapse-item v-for="(item, index) in competitionList" :key="index" :title="`标题${index + 1}`" :margin-bottom="4">
  36 + <template #collapseExtra>
  37 + <div class="competition-content">
  38 + <div>{{ item.a }}</div>
  39 + <div>{{ item.b }}</div>
  40 + </div>
  41 + </template>
  42 + </collapse-item>
  43 + </collapse>
  44 + </div>
  45 + </div>
  46 + <div v-if="activeIndex === 1">娱乐123</div>
  47 + <div v-if="activeIndex === 2">小说</div>
  48 + <div v-if="activeIndex === 3">GPT</div>
  49 + <MyPopup v-model="myPopupOpen" position="right" width="80%" :round="false" :show-close-icon="true" popup-title="弹出框标题" :popup-title-text-style="{ textAlign: 'left' }">
  50 + 内容
  51 + </MyPopup>
  52 + </div>
  53 +</template>
  54 +
  55 +<script>
  56 +import Collapse from '@/components/Collapse/Collapse.vue'
  57 +import CollapseItem from '@/components/Collapse/CollapseItem.vue'
  58 +import MyPopup from '@/components/MyPopup/MyPopup.vue'
  59 +export default {
  60 + name: 'HomeView',
  61 + components: { Collapse, CollapseItem, MyPopup },
  62 + data() {
  63 + return {
  64 + myPopupOpen: false,
  65 + searchKeyWord: '',
  66 + activeIndex: 0,
  67 + isBottom: false,
  68 + tabList: [
  69 + {
  70 + label: '新闻',
  71 + value: 'news'
  72 + },
  73 + {
  74 + label: '娱乐',
  75 + value: 'recreation'
  76 + },
  77 + {
  78 + label: '小说',
  79 + value: 'fiction'
  80 + },
  81 + {
  82 + label: 'GPT',
  83 + value: 'gpt'
  84 + }
  85 + ],
  86 + images: ['https://img01.yzcdn.cn/vant/apple-1.jpg?time=' + Date.now(), 'https://img01.yzcdn.cn/vant/apple-2.jpg'],
  87 + competitionActiveNames: [],
  88 + competitionList: [
  89 + { title: '名称', a: 'A', b: 'B' },
  90 + { title: '名称', a: 'A', b: 'B' },
  91 + { title: '名称', a: 'A', b: 'B' },
  92 + { title: '名称', a: 'A', b: 'B' },
  93 + { title: '名称', a: 'A', b: 'B' },
  94 + { title: '名称', a: 'A', b: 'B' },
  95 + { title: '名称', a: 'A', b: 'B' },
  96 + { title: '名称', a: 'A', b: 'B' },
  97 + { title: '名称', a: 'A', b: 'B' },
  98 + { title: '名称', a: 'A', b: 'B' },
  99 + { title: '名称', a: 'A', b: 'B' },
  100 + { title: '名称', a: 'A', b: 'B' },
  101 + { title: '名称', a: 'A', b: 'B' },
  102 + { title: '名称', a: 'A', b: 'B' },
  103 + { title: '名称', a: 'A', b: 'B' },
  104 + { title: '名称', a: 'A', b: 'B' },
  105 + { title: '名称', a: 'A', b: 'B' },
  106 + { title: '名称', a: 'A', b: 'B' },
  107 + { title: '名称', a: 'A', b: 'B' },
  108 + { title: '名称', a: 'A', b: 'B' }
  109 + ]
  110 + }
  111 + },
  112 + mounted() {
  113 + console.log('mounted:HomePage')
  114 + console.log('mounted:Object', { a: 123 })
  115 +
  116 + this.$nextTick(() => {
  117 + window.addEventListener('scroll', this.pageScroll)
  118 + })
  119 + },
  120 + activated() {
  121 + console.log('HomePage page is activated')
  122 + // 执行页面被缓存时的逻辑
  123 + },
  124 + deactivated() {
  125 + console.log('HomePage page is deactivated')
  126 + // 执行页面离开缓存时的逻辑
  127 + },
  128 + methods: {
  129 + testRequest() {
  130 + // 创建一个 XMLHttpRequest 实例
  131 + var xhr = new XMLHttpRequest()
  132 + // 发送请求
  133 + xhr.open('GET', 'https://mock.apifox.cn/m1/2852848-0-default/api/getVersion')
  134 + xhr.send()
  135 + },
  136 + showGlobalLog() {
  137 + this.testRequest()
  138 + // this.myPopupOpen = !this.myPopupOpen
  139 + console.log(asdfasf)
  140 + // this.myPopupOpen = true
  141 + },
  142 + changeTabIndex(index) {
  143 + this.activeIndex = index
  144 + },
  145 + emitWebF() {
  146 + console.log('getIsApp:', this.getIsApp)
  147 + if (this.getIsApp) {
  148 + webf.methodChannel.invokeMethod('somemethod-webf', {
  149 + isBottom: this.isBottom,
  150 + getIsApp: this.getIsApp
  151 + })
  152 + }
  153 + },
  154 + pageScroll(e) {
  155 + // console.log('pageScroll:', e)
  156 + //scrollTop是滚动条滚动时,距离顶部的距离
  157 + var scrollTop = document.documentElement.scrollTop || document.body.scrollTop
  158 + //windowHeight是可视区的高度
  159 + var windowHeight = document.documentElement.clientHeight || document.body.clientHeight
  160 + //scrollHeight是滚动条的总高度
  161 + var scrollHeight = document.documentElement.scrollHeight || document.body.scrollHeight
  162 + //滚动条到底部的条件
  163 + if (scrollTop + windowHeight == scrollHeight) {
  164 + //到了这个就可以进行业务逻辑加载后台数据了
  165 + // console.log('到了底部')
  166 + // console.log('getIsApp:', this.getIsApp)
  167 + if (this.getIsApp) {
  168 + // 有webf则表示是app
  169 + // webf.methodChannel.invokeMethod('somemethod-webf', {
  170 + // isBottom: true,
  171 + // msg: '到底啦'
  172 + // })
  173 + }
  174 + } else if (scrollTop === 0) {
  175 + console.log('到了顶部')
  176 + }
  177 + },
  178 + onRefresh() {
  179 + setTimeout(() => {
  180 + this.isLoading = false
  181 + }, 1000)
  182 + }
  183 + }
  184 +}
  185 +</script>
  186 +<style lang="scss" scoped>
  187 +.home-page {
  188 + width: 100%;
  189 + overflow-x: hidden;
  190 + padding-top: 98px;
  191 + .header-nav-bar {
  192 + position: fixed;
  193 + width: 100%;
  194 + height: 44px;
  195 + z-index: 12;
  196 + display: flex;
  197 + align-items: center;
  198 + background-color: #fff;
  199 + box-shadow: 2px 2px 20px #ededed;
  200 +
  201 + .tabs-view {
  202 + height: 44px;
  203 + flex: 4;
  204 + margin-right: 10px;
  205 + display: flex;
  206 + align-items: center;
  207 + padding: 0 10px;
  208 +
  209 + .tab-item {
  210 + flex: 1;
  211 + text-align: center;
  212 + font-size: 14px;
  213 + color: #666;
  214 + position: relative;
  215 + transition: all 0.1s ease-in 0s;
  216 + height: 44px;
  217 + line-height: 44px;
  218 +
  219 + &.active {
  220 + color: #f00;
  221 + }
  222 +
  223 + .active-line {
  224 + position: absolute;
  225 + left: 50%;
  226 + bottom: 2px;
  227 + width: 0;
  228 + height: 2px;
  229 + background-color: #f00;
  230 + transition: all 0.2s ease-in 0s;
  231 +
  232 + &.show {
  233 + width: 70%;
  234 + left: 15%;
  235 + }
  236 + }
  237 + }
  238 + }
  239 +
  240 + .icon-list {
  241 + flex: 1;
  242 + height: 44px;
  243 + display: flex;
  244 + align-items: center;
  245 + justify-content: space-around;
  246 + }
  247 + }
  248 +
  249 + .icon-img {
  250 + width: 50px;
  251 + height: 50px;
  252 + margin-right: 10px;
  253 + }
  254 +
  255 + .search-view {
  256 + position: fixed;
  257 + width: 100%;
  258 + height: 54px;
  259 + z-index: 11;
  260 + background-color: #fff;
  261 + }
  262 +
  263 + .notice-view {
  264 + position: sticky;
  265 + top: 98px;
  266 + z-index: 11;
  267 + background-color: #fff;
  268 +
  269 + .hot-live-btn {
  270 + margin-left: 5px;
  271 + }
  272 + }
  273 +
  274 + .list-view {
  275 + margin-top: 5px;
  276 +
  277 + .competition-content {
  278 + padding: 15px;
  279 + }
  280 + }
  281 +}
  282 +
  283 +::v-deep .my-swipe {
  284 + .van-swipe-item {
  285 + color: #fff;
  286 + height: 150px;
  287 + text-align: center;
  288 + padding: 10px;
  289 + box-sizing: border-box;
  290 +
  291 + .swipe-img {
  292 + width: 100%;
  293 + height: 100%;
  294 + border-radius: 10px;
  295 + }
  296 + }
  297 +}
  298 +</style>
  1 +<template>
  2 + <div>直播</div>
  3 +</template>
  4 +
  5 +<script>
  6 +export default {
  7 + name: 'LivewView'
  8 +}
  9 +</script>
  10 +
  11 +<style lang="scss" scoped></style>
  1 +<template><div>我的</div></template>
  2 +
  3 +<script>
  4 +export default {
  5 + name: 'MineView',
  6 + data() {
  7 + return {}
  8 + }
  9 +}
  10 +</script>
  11 +
  12 +<style lang="scss" scoped></style>
  1 +const { defineConfig } = require('@vue/cli-service')
  2 +module.exports = defineConfig({
  3 + transpileDependencies: true,
  4 +
  5 + chainWebpack: config => {
  6 + config.mode('development')
  7 + config.optimization.delete('splitChunks')
  8 + },
  9 +
  10 + filenameHashing: false,
  11 + productionSourceMap: false,
  12 +
  13 + configureWebpack(config) {
  14 + config.devtool = config.mode === 'production' ? false : 'source-map'
  15 + },
  16 +
  17 + css: {
  18 + extract: false
  19 + },
  20 +
  21 + publicPath: './'
  22 +})