Просмотр исходного кода

feat: kaifain: new solution page

Acathur 5 лет назад
Родитель
Сommit
e303edf823

+ 38 - 2
kaifain_v2/apis/solution.ts

@@ -3,6 +3,7 @@ import request from '../utils/request'
 export const listSolutions = async (opts: {
   page: number
   size: number
+  uid?: string
   city_id?: number | string
   cat_id?: string
   sort?: number
@@ -18,13 +19,17 @@ export const listSolutions = async (opts: {
   return res.data.data
 }
 
-export const getSolution = async (id: string) => {
+export const getSolution = async (id: string, opts?: {
+  headers: any
+}) => {
+  const { headers } = opts || {}
   const res = await request({
     method: 'POST',
     url: '/api/kaifawu/get_provider',
     data: {
       id
-    }
+    },
+    headers
   })
 
   return res.data.data
@@ -43,3 +48,34 @@ export const collectSolution = async (id: string) => {
 
   return res.data.data
 }
+
+export const listProviderSolution = async (params: {
+  uid: string
+  hash_id: string
+  page: number
+  size: number
+}, { headers }) => {
+  const res = await request({
+    method: 'POST',
+    url: '/api/kaifain/getSolutionByUid',
+    data: params,
+    headers
+  })
+
+  return res.data.data
+}
+
+export const listRelatedSolution = async (params: {
+  tags_ids: string // json array string
+  hash_id: string
+  size: number
+}, { headers }) => {
+  const res = await request({
+    method: 'POST',
+    url: '/api/kaifain/getRelevanceSolution',
+    data: params,
+    headers
+  })
+
+  return res.data.data
+}

+ 14 - 1
kaifain_v2/assets/styles/buefy.scss

@@ -16,7 +16,11 @@ body.has-navbar-fixed-top {
 
 .container {
   &.is-content {
-    max-width: 960px;
+    max-width: 1000px;
+  }
+
+  &.is-wide {
+    max-width: 1080px;
   }
 }
 
@@ -45,6 +49,15 @@ body.has-navbar-fixed-top {
     box-shadow: inset 0 0 1px rgba(0, 0, 0, 0.2);
   }
 
+  &.is-link.is-light {
+    box-shadow: inset 0 0 1px rgba(33, 96, 196, 0.2);
+  }
+
+  &.is-success.is-light {
+    color: #288147;
+    box-shadow: inset 0 0 1px rgba(37, 121, 66, 0.2);
+  }
+
   &:not(body).is-primary.is-light {
     background: #eaf3ff;
     color: $primary;

+ 208 - 0
kaifain_v2/assets/styles/ext_aliyun_market.scss

@@ -0,0 +1,208 @@
+.rich-text img {
+  max-width: 100%
+}
+
+.broderno h2 {
+  border-bottom: 0 !important
+}
+
+.param-table .title {
+  color: #000
+}
+
+.param-table .borderleft {
+  border-left: 0 !important
+}
+
+.param-table .borderright {
+  border-right: 0 !important
+}
+
+.param-table .pl20 {
+  padding-left: 20px
+}
+
+.r-product-detail .active-area {
+  position: relative;
+  padding: 20px 0
+}
+
+.r-product-detail .active-area img {
+  width: 100%
+}
+
+.r-product-detail .active-area a,
+.r-product-detail .active-area i {
+  position: absolute;
+  z-index: 10;
+  cursor: pointer
+}
+
+.r-product-detail .active-area a {
+  color: #fff;
+  border: 1px solid #fff;
+  line-height: 40px;
+  left: 400px;
+  top: 50px;
+  font-size: 16px;
+  padding: 0 20px
+}
+
+.r-product-detail .active-area i {
+  color: #69d3ff;
+  left: 400px;
+  top: 108px;
+  font-size: 14px
+}
+
+.r-product-detail .d-item h2 {
+  font-size: 14px;
+  font-weight: 700;
+  line-height: 32px;
+  margin-top: 30px;
+  margin-bottom: 10px;
+  border-bottom: 1px solid #eaeaea
+}
+
+.r-product-detail .param-table td {
+  border: 1px solid #eaeaea;
+  line-height: 32px;
+  height: 32px;
+  padding: 4px
+}
+
+.r-product-detail .status-list {
+  margin: 0;
+  padding: 0
+}
+
+.r-product-detail .status-list li {
+  width: 30%;
+  float: left;
+  margin-right: 3%;
+  background: #f5f5f5;
+  text-align: center
+}
+
+.r-product-detail .status-list li img {
+  max-width: 80%
+}
+
+.anli img {
+  max-width: 100% !important;
+  height: auto !important
+}
+
+.small-orange-font {
+  font-size: 18px;
+  color: #f60
+}
+
+.txt-right {
+  text-align: right
+}
+
+.nav-left h6 {
+  box-sizing: content-box !important
+}
+
+.ant-affix {
+  z-index: 2
+}
+
+.r-bg-gray {
+  background: #f5f5f5 !important
+}
+
+.ant-affix {
+  position: fixed
+}
+
+.r-bg-white {
+  background: #fff
+}
+
+.r-line {
+  height: 1px;
+  border-bottom: 1px solid #eee
+}
+
+.clear:after {
+  visibility: hidden;
+  display: block;
+  font-size: 0;
+  content: " ";
+  clear: both;
+  height: 0
+}
+
+.clear {
+  zoom: 1
+}
+
+.r-market-left {
+  width: 73%;
+  height: 100%;
+  position: relative;
+  z-index: 2;
+  overflow: hidden
+}
+
+.r-market-left .item-intro {
+  margin-top: 24px;
+  background: #fff;
+  overflow: hidden
+}
+
+.r-market-left .item-intro .box-icon {
+  padding: 0 40px;
+  margin-top: 20px
+}
+
+.r-market-left .item-intro .box-icon h5 {
+  color: #000;
+  font-size: 16px;
+  font-weight: 400;
+  height: 46px;
+  line-height: 46px;
+  border-bottom: 1px solid #eae8e8;
+  margin-bottom: 15px
+}
+
+.comment-page {
+  text-align: center;
+  padding: 20px 0
+}
+
+.r-market-right {
+  width: 25%
+}
+
+.r-fiexd {
+  position: fixed;
+  top: 110px
+}
+
+.r-ml10 {
+  margin-right: 10px !important
+}
+
+/* kaifain fix */
+
+.r-product-detail {
+  font-size: 13px;
+  color: #272727;
+  line-height: 1.35;
+
+  .slick-track {
+    width: auto !important;
+    transform: none !important;
+  }
+
+  .param-table {
+    .title {
+      font-size: inherit;
+      font-weight: inherit;
+    }
+  }
+}

Разница между файлами не показана из-за своего большого размера
+ 1639 - 0
kaifain_v2/assets/styles/ext_huawei_market.scss


Разница между файлами не показана из-за своего большого размера
+ 1357 - 0
kaifain_v2/assets/styles/ext_tencent_market.scss


+ 22 - 6
kaifain_v2/assets/styles/icon.scss

@@ -1,11 +1,11 @@
 @font-face {
   font-family: 'progico';  /* project id 1895023 */
-  src: url('//at.alicdn.com/t/font_1895023_bwo5ih2dw6m.eot');
-  src: url('//at.alicdn.com/t/font_1895023_bwo5ih2dw6m.eot?#iefix') format('embedded-opentype'),
-  url('//at.alicdn.com/t/font_1895023_bwo5ih2dw6m.woff2') format('woff2'),
-  url('//at.alicdn.com/t/font_1895023_bwo5ih2dw6m.woff') format('woff'),
-  url('//at.alicdn.com/t/font_1895023_bwo5ih2dw6m.ttf') format('truetype'),
-  url('//at.alicdn.com/t/font_1895023_bwo5ih2dw6m.svg#progico') format('svg');
+  src: url('//at.alicdn.com/t/font_1895023_3n875b0emyx.eot');
+  src: url('//at.alicdn.com/t/font_1895023_3n875b0emyx.eot?#iefix') format('embedded-opentype'),
+  url('//at.alicdn.com/t/font_1895023_3n875b0emyx.woff2') format('woff2'),
+  url('//at.alicdn.com/t/font_1895023_3n875b0emyx.woff') format('woff'),
+  url('//at.alicdn.com/t/font_1895023_3n875b0emyx.ttf') format('truetype'),
+  url('//at.alicdn.com/t/font_1895023_3n875b0emyx.svg#progico') format('svg');
 }
 
 .progico {
@@ -22,3 +22,19 @@
 .progico-plus-o:before {
   content: "\e719";
 }
+
+.progico-api:before {
+  content: "\e738";
+}
+
+.progico-stats:before {
+  content: "\e662";
+}
+
+.progico-home:before {
+  content: "\e634";
+}
+
+.progico-debug:before {
+  content: "\e69a";
+}

+ 2 - 0
kaifain_v2/assets/styles/vars.scss

@@ -1,6 +1,8 @@
 $primary: #2487ff;
 $ink: #272727;
 
+$grey-lighter: #eaeaea;
+
 $tag-background-color: #f2f3f3;
 $tag-color: #3a3a3a;
 

+ 172 - 0
kaifain_v2/components/ApiDoc.vue

@@ -0,0 +1,172 @@
+<template lang="pug">
+  .api-doc
+    .b-tabs.is-vertical
+      nav.tabs(v-show="routes.length > 1")
+        ul
+          li(
+            v-for="(route, idx) in routes"
+            :class="{'is-active': curIndex === idx}"
+            )
+            a(@click.stop="curIndex = idx") {{route.summary}}
+
+      section.tab-content
+        .tab-item.api-item(
+          v-for="(route, idx) in routes"
+          v-show="curIndex === idx"
+          )
+          h2 {{route.summary}}
+
+          .chunk.inline
+            .label 接口地址
+            .value
+              code {{route.url}}
+          .chunk.inline(v-if="route.produces && route.produces.length")
+            .label 支持格式
+            .value
+              template(v-for="(part, i) of route.produces")
+                code {{part}}
+                span(v-if="i < route.produces.length - 1") ,
+          .chunk.inline
+            .label 请求方法
+            .value
+              code {{route.method.toUpperCase()}}
+
+          .chunk(v-if="route.paramsMap.header")
+            .label 请求参数 (Headers)
+            .value
+              ParamsTable(:data="route.paramsMap.header")
+
+          .chunk(v-if="route.paramsMap.query")
+            .label 请求参数 (Query)
+            .value
+              ParamsTable(:data="route.paramsMap.query")
+
+          .chunk(v-if="route.paramsMap.body")
+            .label 请求参数 (Body)
+            .value
+              ParamsTable(:data="route.paramsMap.body")
+
+          .chunk(v-if="route.successResponse && route.successResponse.params")
+            .label 返回参数 (Body)
+            .value
+              ParamsTable(
+                :data="route.successResponse.params"
+                :show-required="false"
+                )
+
+          .chunk(v-if="route.successResponse && route.successResponse.example")
+            .label 请求返回示例
+            .value
+              pre
+                code(v-highlight) {{route.successResponse.example}}
+
+          .chunk(v-if="route.responses")
+            .label 错误码定义
+            .value
+              table.api-params-table
+                tbody
+                  tr
+                    th 错误码
+                    th 描述
+                  tr(v-for="(value, code) in route.responses")
+                    td {{code}}
+                    td {{value.description}}
+
+</template>
+
+<script lang="ts">
+import Vue from 'vue'
+import ParamsTable from './ParamsTable.vue'
+import { formatApiDocToRoutes } from '../helpers/apiDocHelper'
+
+export default Vue.extend({
+  name: 'ApiDoc',
+  components: {
+    ParamsTable
+  },
+
+  props: {
+    data: {
+      type: Object,
+      default: () => {}
+    }
+  },
+
+  data() {
+    return {
+      curIndex: 0
+    }
+  },
+
+  computed: {
+    routes(): any[] {
+      return formatApiDocToRoutes(this.data)
+    }
+  }
+})
+</script>
+
+<style lang="scss">
+@import '../assets/styles/vars';
+
+.api-doc {
+  .tabs {
+    margin-right: 1.5rem;
+    border-right: 1px solid #eaeaea;
+
+    li {
+      font-size: 0.875rem;
+
+      a {
+        padding: 0.75rem 0 0.75em 1rem;
+        border: 0;
+      }
+
+      &.is-active {
+        a {
+          color: $primary;
+        }
+      }
+    }
+  }
+
+  .b-tabs .tab-content {
+    flex: 1;
+    overflow: hidden;
+  }
+
+  h2 {
+    font-size: 1.25rem;
+    font-weight: 500;
+    margin-bottom: 2rem;
+  }
+
+  .chunk {
+    margin-bottom: 1rem;
+
+    .label {
+      font-size: 0.875rem;
+      font-weight: 500;
+      margin: 2rem 0 1rem;
+    }
+
+    .value {
+      font-size: 0.875rem;
+    }
+
+    &.inline {
+      margin-bottom: 0.5rem;
+
+      > * {
+        display: inline-block;
+      }
+
+      .label {
+        min-width: 64px;
+        margin: 0 4px 0 0;
+        font-weight: 400;
+      }
+    }
+  }
+}
+</style>

+ 5 - 0
kaifain_v2/components/Carousel.vue

@@ -13,6 +13,7 @@
         :key="idx"
         )
         .pic(
+            :class="{clickable: item.jump_target && !item.button_text}"
             :style="{backgroundImage: `url('${item.image_url}')`, backgroundColor: item.theme_color || 'transparent'}"
             @click="onClick(item, false)"
             )
@@ -124,6 +125,10 @@ export default Vue.extend({
       left: 0;
       width: 100%;
       height: 100%;
+
+      &.clickable {
+        cursor: pointer;
+      }
     }
 
     .text-content {

+ 83 - 0
kaifain_v2/components/CaseCell.vue

@@ -0,0 +1,83 @@
+<template lang="pug">
+  router-link.case-cell(:to="`/d/${data.hash_id}`")
+    .figure
+      img.logo(:src="data.logo")
+    .info
+      .title {{data.title}}
+      .subtitle 【{{solutionTitle}}】帮助【{{data.title}}】提供解决方案
+      .file
+        .icon
+        .filename {{data.title}}.pdf
+
+</template>
+
+<script lang="ts">
+import Vue from 'vue'
+
+export default Vue.extend({
+  name: 'CaseCell',
+
+  props: {
+    solutionTitle: String,
+    data: Object
+  }
+})
+</script>
+
+
+<style lang="less">
+.case-cell {
+  padding: 16px 8px;
+  display: flex;
+
+  &:hover {
+    text-decoration: none;
+  }
+
+  .figure {
+    .logo {
+      width: 112px;
+      height: 112px;
+      margin-right: 24px;
+    }
+  }
+
+  .info {
+    flex: 1;
+
+    .title {
+      font-size: 17px;
+      font-weight: 500;
+      margin: 0 !important;
+    }
+
+    .subtitle {
+      font-size: 14px;
+      margin: 10px 0 !important;
+    }
+  }
+
+  .file {
+    height: 26px;
+    display: flex;
+    align-items: center;
+    margin-top: 24px;
+
+    .icon {
+      width: 25px;
+      height: 25px;
+      background: url('~@/assets/img/kaifain/detail/icon_pdf@2x.png') no-repeat;
+      background-size: cover;
+    }
+
+    .filename {
+      margin-left: 7px;
+      height: 17px;
+      font-size: 12px;
+      font-weight: 600;
+      color: rgba(51, 51, 51, 1);
+      line-height: 17px;
+    }
+  }
+}
+</style>

+ 2 - 2
kaifain_v2/components/Pagination.vue

@@ -6,7 +6,7 @@
       :total="total"
       :per-page="perPage"
       :range-before="isMobile ? 1 : 3"
-      :range-after="isMobile ? 2 : 4"
+      :range-after="isMobile ? 2 : 3"
       order="is-centered"
       aria-next-label="下一页"
       aria-previous-label="上一页"
@@ -121,7 +121,7 @@ export default Vue.extend({
   }
 
   .pagination {
-    max-width: 720px;
+    max-width: 800px;
     margin: 0 auto;
     justify-content: center;
     padding: 3rem 0;

+ 101 - 0
kaifain_v2/components/ParamsTable.vue

@@ -0,0 +1,101 @@
+<template lang="pug">
+  table.api-params-table
+    tbody
+      tr
+        th 参数名
+        th(v-if="showType") 类型
+        th(v-if="showRequired") 是否必须
+        th 描述
+      tr(
+        v-for="item in flatRows"
+        :class="item.depth ? `depth-${item.depth}` : ''"
+        )
+        td
+          .sub-symbol(v-if="item.depth > 0")
+            span └
+            span.dash(v-for="idx in item.depth - 1") ─
+          span {{item.name}}
+        td(v-if="showType")
+          code(v-if="item.type") {{item.type}}
+          code(v-if="item.items && item.items.type !== 'object'") <{{item.items.type}}>
+        td(v-if="showRequired") {{item.required ? '是' : '否'}}
+        td {{item.description}}
+
+</template>
+
+<script lang="ts">
+import Vue from 'vue'
+
+const pushRow = (array: any[], row: any, depth = 0) => {
+  row.depth = depth
+  array.push(row)
+
+  if (row.children) {
+    for (const child of row.children) {
+      pushRow(array, child, depth + 1)
+    }
+  }
+}
+
+export default Vue.extend({
+  name: 'ParamsTable',
+  props: {
+    data: {
+      type: Array,
+      default: () => []
+    },
+    showType: {
+      type: Boolean,
+      default: true
+    },
+    showRequired: {
+      type: Boolean,
+      default: true
+    }
+  },
+
+  computed: {
+    flatRows(): any[] {
+      const ret: any[] = []
+
+      // @ts-ignore
+      for (const row of this.data) {
+        pushRow(ret, row)
+      }
+
+      return ret
+    }
+  }
+})
+</script>
+
+<style lang="scss">
+.api-params-table {
+  min-width: 100%;
+  border: 1px solid #eaeaea;
+  border-collapse: 0;
+
+  th,
+  td {
+    padding: 8px 12px;
+    border-bottom: 1px solid #eaeaea;
+  }
+
+  th {
+    background: #f5f6f6;
+    font-weight: 500;
+  }
+
+  th,
+  code {
+    white-space: nowrap;
+  }
+
+  .sub-symbol {
+    opacity: 0.3;
+    font-size: 0.75em;
+    margin: 0 6px 0 -2px;
+    display: inline;
+  }
+}
+</style>

+ 95 - 0
kaifain_v2/components/SideSolutionCell.vue

@@ -0,0 +1,95 @@
+<template lang="pug">
+  a.side-solution-cell(:href="`/s/${data.hash_id}`")
+    .cover
+      img(:src="data.images")
+    .title {{data.title}}
+</template>
+
+<script lang="ts">
+import Vue from 'vue'
+
+export default Vue.extend({
+  props: {
+    data: Object
+  }
+})
+</script>
+
+<style lang="scss">
+@import '../assets/styles/vars';
+
+.side-solution-cell {
+  margin: 0.5rem 0;
+  padding: 0.375rem 0;
+  display: flex;
+  position: relative;
+
+  &::after {
+    content: '';
+    width: 100%;
+    width: calc(100% - 62px);
+    height: 1px;
+    background: #ddd;
+    display: block;
+    position: absolute;
+    bottom: 0;
+    right: 0;
+    transform: scale(1, 0.5);
+  }
+
+  .cover {
+    width: 48px;
+    height: 48px;
+    margin: 1px 16px 1px 1px;
+    overflow: hidden;
+    border-radius: 10px;
+    position: relative;
+    flex: none;
+
+    img {
+      width: 100%;
+      height: 100%;
+      object-fit: cover;
+      display: block;
+    }
+
+    &::after {
+      content: '';
+      width: 100%;
+      height: 100%;
+      position: absolute;
+      top: 0;
+      left: 0;
+      border: 1px solid rgba(0,0,0,0.08);
+      border-radius: 10px;
+    }
+  }
+
+  .title {
+    font-size: 0.875rem;
+    font-weight: 400;
+    margin: 0.375rem 0;
+    line-height: 1.35;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    -webkit-box-orient: vertical;
+    display: -webkit-box;
+    -webkit-line-clamp: 2;
+    max-height: 2.362rem;
+  }
+
+  &:hover {
+    text-decoration: none;
+
+    .title {
+      color: $primary;
+    }
+  }
+
+  &:last-child {
+    &::after {
+      display: none;
+    }
+  }
+}
+</style>

+ 65 - 5
kaifain_v2/components/SolutionCell.vue

@@ -10,8 +10,20 @@
         .title
           span {{data.title}}
           .tag.is-choice(v-if="data.is_choice == 1") 优选
+          .tag.is-success.is-light(v-if="data.type === 'gateway'") HTTPS
         .desc {{data.description}}
-        .provider 服务商: {{data.company_name}}
+        .provider
+          span 服务商: {{data.company_name}}
+          span(v-if="data.type === 'gateway'") 交付方式:API接口 (即买即用)
+
+      .price
+        div(v-if="data.is_signing == 1 && data.min_price != null")
+          .free(v-if="Number(data.min_price) == 0")
+            .price-tag 0元用
+            .small 限时免费
+          .price-tag(v-else)
+            span ¥{{Number(data.min_price)}}
+        .price-tag(v-else) 咨询
 
     .tags
       .tag.is-primary.is-light.is-border(v-for="item in data.cats") {{item.name}}
@@ -40,17 +52,18 @@ export default Vue.extend({
 @import '../assets/styles/vars';
 
 .kaifain-view .solution-cell {
-  padding: 24px 24px 20px;
+  padding: 24px 32px 20px;
   border-bottom: 1px solid #eee;
 
   .figure {
-    margin: 4px 24px 0 0;
+    margin: 4px 30px 0 0;
     flex: none;
 
     .cover {
       width: 64px;
       height: 64px;
       display: block;
+      object-fit: cover;
     }
   }
 
@@ -60,6 +73,32 @@ export default Vue.extend({
     overflow: hidden;
   }
 
+  .price {
+    display: flex;
+    align-items: center;
+    padding: 1.5rem 1rem 0 5rem;
+
+    .price-tag {
+      color: $primary;
+      font-size: 1.125rem;
+      font-weight: 500;
+    }
+
+    .free {
+      display: flex;
+      flex-direction: column;
+      justify-content: center;
+      align-items: center;
+
+      .small {
+        font-size: 11px;
+        font-weight: 300;
+        color: #999;
+        margin-top: 6px;
+      }
+    }
+  }
+
   .tag {
     font-size: 0.6875rem;
     cursor: default;
@@ -94,17 +133,25 @@ export default Vue.extend({
 
   .desc {
     color: lighten($ink, 10%);
-    margin: 10px 0;
+    margin: 10px 0 12px;
     font-size: 0.8125rem;
   }
 
   .provider {
     color: lighten($ink, 42%);
+
+    span {
+      margin-right: 1.75rem;
+
+      &:last-child {
+        margin-right: 0;
+      }
+    }
   }
 
   .tags {
     padding: 2px 0;
-    margin: 10px 0 0 88px;
+    margin: 10px 0 0 94px;
   }
 
   &.card {
@@ -122,5 +169,18 @@ export default Vue.extend({
       }
     }
   }
+
+  @media screen and (max-width: 768px) {
+    padding-left: 24px;
+
+    .figure {
+      margin-right: 24px;
+    }
+
+    .price {
+      padding-right: 0;
+      padding-left: 1.5rem;
+    }
+  }
 }
 </style>

+ 13 - 2
kaifain_v2/components/Topnav.vue

@@ -12,7 +12,7 @@
 
     template(slot="start")
       b-navbar-item.search-box-lite(
-        v-show="fixed"
+        v-show="fixed && showSearch"
         tag="div"
         )
         b-field
@@ -25,7 +25,7 @@
 
     template(slot="end")
       b-navbar-item(
-        v-show="fixed"
+        v-show="fixed && showPublish"
         @click="onPublishClick"
         )
         i.progico.progico-plus-o
@@ -72,6 +72,14 @@ export default Vue.extend({
     q: {
       type: String,
       default: ''
+    },
+    showSearch: {
+      type: Boolean,
+      default: true
+    },
+    showPublish: {
+      type: Boolean,
+      default: true
     }
   },
 
@@ -166,6 +174,7 @@ $theme-color: #20242d;
     background: transparent !important;
     color: #fff;
     font-size: 14px;
+    text-decoration: none !important;
   }
 
   .navbar-dropdown {
@@ -212,6 +221,8 @@ $theme-color: #20242d;
     padding: 0;
     height: 54px;
 
+    // animation
+
     .logo {
       height: 36px;
     }

+ 5 - 2
kaifain_v2/components/UserWidget.vue

@@ -1,6 +1,9 @@
 <template>
   <div class="user-widget">
-    <el-dropdown class="nav-dropdown">
+    <b-navbar-item :href="`/dashboard`">
+      <i class="el-icon-monitor"></i> 控制台
+    </b-navbar-item>
+    <!-- <el-dropdown class="nav-dropdown">
       <el-button type="text" class="dashboard-title">
         <i class="el-icon-tickets"></i>工作台
       </el-button>
@@ -26,7 +29,7 @@
           </a>
         </el-dropdown-item>
       </el-dropdown-menu>
-    </el-dropdown>
+    </el-dropdown> -->
     <el-dropdown class="nav-dropdown">
       <el-button type="text" class="message-box-title">
         <i class="el-icon-message"></i>消息

+ 124 - 0
kaifain_v2/helpers/apidocHelper.ts

@@ -0,0 +1,124 @@
+import SwaggerParser from 'swagger-parser'
+
+export const parseApiDocFile = async (remoteUrl: string) => {
+  let apiDoc
+
+  try {
+    // @ts-ignore
+    apiDoc = await SwaggerParser.parse(remoteUrl)
+  } catch (e) {
+    console.error('[parse api doc failed]', e)
+  }
+
+  return apiDoc
+}
+
+const convertKeyValuePropertiesToArray = (obj: any) => {
+  if (!obj || !Object.keys(obj).length) {
+    return null
+  }
+
+  const params: any[] = []
+
+  if (obj) {
+    for (const key in obj) {
+      const row = obj[key]
+
+      if (row.properties || row.items && row.items.properties) {
+        row.children = convertKeyValuePropertiesToArray(row.properties || row.items.properties)
+      }
+
+      params.push({
+        name: key,
+        ...row
+      })
+    }
+  }
+
+  return params
+}
+
+export const formatApiDocToRoutes = (data: any, opts?: {
+  injectAccessKey?: boolean
+}) => {
+  const {
+    host = '',
+    basePath = '',
+    schemes,
+    paths
+  } = data
+  const {
+    injectAccessKey = true
+  } = opts || {}
+
+  const routes: any[] = []
+
+  if (paths) {
+    let index = 0
+
+    for (const path in paths) {
+      for (const method in paths[path]) {
+        const item = paths[path][method]
+        const url = `${schemes[schemes.length -1]}://${host}${basePath}${path}`.replace(/\/+$/, '')
+        const paramsMap: any = {}
+        let successResponse: any
+
+        if (injectAccessKey) {
+          paramsMap.query = [{
+            name: 'key',
+            in: 'query',
+            description: '请求 AccessKey, 请在控制台中查看',
+            required: true,
+            type: 'string',
+            _certif: true
+          }]
+        }
+
+        if (item.parameters && item.parameters.length) {
+          for (const param of item.parameters) {
+            // header, path, query, body
+            const _in = param.in
+
+            if (!_in) {
+              continue
+            }
+
+            if (!paramsMap[_in]) {
+              paramsMap[_in] = []
+            }
+
+            paramsMap[_in].push(param)
+          }
+        }
+
+        if (item.responses) {
+          for (const code in item.responses) {
+            const schema = item.responses[code].schema
+
+            if (/^20/.test(code) && schema) {
+              successResponse = {
+                schema,
+                params: convertKeyValuePropertiesToArray(schema.properties),
+                example: schema.example
+              }
+              break
+            }
+          }
+        }
+
+        routes.push({
+          index,
+          url,
+          method,
+          paramsMap,
+          successResponse,
+          ...item
+        })
+
+        index++
+      }
+    }
+  }
+
+  return routes
+}

+ 655 - 0
kaifain_v2/pages/solution.vue

@@ -0,0 +1,655 @@
+<template lang="pug">
+  .view.solution-page
+    link(href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" rel="stylesheet")
+    Topnav(
+      :fixed="true"
+      :show-publish="false"
+      :show-search="topnavFunctional"
+      @publish-click="connectPopupVisible = true"
+      )
+
+    .summary(
+      v-if="data"
+      ref="summary"
+      )
+      .container.is-content.is-wide
+        .columns
+          .column.left
+            .cover
+              img(:src="data.images")
+          .column
+            .info
+              h1.title {{data.title}}
+              .extends
+                .tag.is-choice(v-if="data.is_choice == 1") 优选
+                .tag.is-link.is-light(v-if="data.type === 'gateway'") API
+                .tag.is-success.is-light(v-if="data.type === 'gateway'") HTTPS
+                span
+                  a.link(:href="`${siteUrl}/company/${data.uid}`") 服务商: {{data.company_info && data.company_info.name}}
+              .desc {{data.description}}
+              .skus(v-if="skus")
+                .item(
+                  v-for="item in skus"
+                  :class="{cur: curSkuId === item.id, disabled: item.overlimit}"
+                  @click="changeSku(item)"
+                  ) {{item.label}}
+              .price(v-if="skus && curSkuId && skuMap[curSkuId]")
+                span.symbol ¥
+                span.value {{formatPrice(skuMap[curSkuId].price)}}
+                span.explain(v-show="!!skuMap[curSkuId].quota")  / {{Number(skuMap[curSkuId].quota).toLocaleString()}} {{skuMap[curSkuId].unit}}
+              .action
+                .button.is-primary.is-rounded(
+                  v-if="skus"
+                  @click="purchase") {{curSkuId && skuMap[curSkuId] && skuMap[curSkuId].price == 0 ? '免费开通' : '立即购买'}}
+                .button.is-primary.is-rounded(
+                  v-else
+                  @click="connectPopupVisible = true") 了解咨询
+
+    .page-nav
+      .container.is-content.is-wide
+        .tabs
+          ul
+            li(
+              v-for="(item, i) in navItems"
+              :class="{'is-active': curNavIndex === i}"
+              )
+              a(@click="changeNavTab(item.id, i)") {{item.label}}
+
+    .main
+      .container.is-content.is-wide(v-if="data")
+        .paper.columns
+          .column.is-9
+            .main-cont
+              //- api doc
+              ApiDoc#apidoc(
+                v-if="apiDoc"
+                :data="apiDoc"
+                v-observe-visibility="visibilityChanged"
+                )
+
+              //- details: aliyun
+              .details#details(
+                v-if="data.source == 2"
+                :class="detailsClass"
+                v-observe-visibility="visibilityChanged"
+                )
+                .d-item.rich-text(v-html="data.detail")
+              .details#details(
+                v-else
+                :class="detailsClass"
+                v-html="data.detail"
+                v-observe-visibility="visibilityChanged"
+                )
+
+              .case-list#case(v-if="data.successful_case")
+                .caption.bordered 成功案例
+                CaseCell(
+                  :solution-title="data.title"
+                  :data="data.successful_case"
+                  )
+
+          .column.is-3
+            .side-cont
+              .rcmd-list(v-if="providerSolutionList && providerSolutionList.length")
+                .caption 同服务商产品
+                SideSolutionCell.item(
+                  v-for="item in providerSolutionList"
+                  :key="item.hash_id"
+                  :data="item"
+                  )
+              .rcmd-list(v-if="relatedSolutionList && relatedSolutionList.length")
+                .caption 相似方案推荐
+                SideSolutionCell.item(
+                  v-for="item in relatedSolutionList"
+                  :key="item.hash_id"
+                  :data="item"
+                  )
+
+    KaifainFooter(:data="footer")
+    ConnectUs(
+      source="开发屋详情"
+      :isShowToast="connectPopupVisible"
+      @close="connectPopupVisible = false"
+      :sourceId="this.data && this.data.id"
+      )
+</template>
+
+<script lang="ts">
+import 'quill/dist/quill.core.css'
+import 'quill/dist/quill.snow.css'
+import Vue from 'vue'
+import Topnav from '../components/Topnav.vue'
+import { getSolution, listProviderSolution, listRelatedSolution } from '../apis/solution'
+import { genDocumentFooterData } from '../helpers/seoHelper'
+import SideSolutionCell from '../components/SideSolutionCell.vue'
+import CaseCell from '../components/CaseCell.vue'
+import ApiDoc from '../components/ApiDoc.vue'
+import ConnectUs from '@/components/common/connectUsKaifain.vue'
+import KaifainFooter from '@/components/SeoFooter.vue'
+import { formatPrice } from '../utils/misc'
+import { parseApiDocFile } from '../helpers/apiDocHelper'
+import { ObserveVisibility } from 'vue-observe-visibility'
+
+Vue.directive('observe-visibility', ObserveVisibility)
+
+export default Vue.extend({
+  name: 'Solution',
+  components: {
+    Topnav,
+    SideSolutionCell,
+    CaseCell,
+    ApiDoc,
+    ConnectUs,
+    KaifainFooter
+  },
+
+  layout: 'kaifain_v2',
+
+  head() {
+    // @ts-ignore
+    const { title = '' } = this.data || {}
+
+    return {
+      title: title ? `${title}介绍,功能,开发解决方案-开发屋` : '开发屋-开发解决方案',
+      meta: [
+        {
+          name: 'keywords',
+          content: title,
+        },
+        {
+          name: 'description',
+          content: `${title}详细介绍,包括不限于功能、接口、企业开发着对接和布局${title}的方法和详细的开发文档说明。`,
+        },
+        {
+          name: 'h1',
+          content: `${title}`,
+        }
+      ]
+    }
+  },
+
+  data() {
+    return {
+      topnavFunctional: false,
+      connectPopupVisible: false,
+      data: {},
+      curSkuId: null,
+      curNavIndex: 0,
+      primaryBtnOffsetY: 300,
+      cacheNavElMap: {}
+    }
+  },
+
+  computed: {
+    siteUrl(): string {
+      return this.$store.state.domainConfig.siteUrl
+    },
+
+    skus(): any[] | null {
+      // @ts-ignore
+      return this.data && this.data.is_signing == 1 && this.data.skus && this.data.skus.length ? this.data.skus : null
+    },
+
+    skuMap() {
+      // @ts-ignore
+      return this.skus && this.skus.reduce((map, item) => {
+        map[item.id] = item
+        return map
+      }, {}) || {}
+    },
+
+    detailsClass() {
+      // @ts-ignore
+      const source = Number(this.data && this.data.source) || 0
+
+      switch (source) {
+        // aliyun
+        case 2:
+          return 'r-product-detail'
+
+        // tencent
+        case 3:
+          return 'mp__productguide-bd'
+
+        // huawei
+        case 4:
+          return 'detail-description-content product-description'
+
+        default:
+          return 'ql-editor'
+      }
+    }
+  },
+
+  async asyncData(ctx: any) {
+    const headers = ctx.req && {
+      cookie: ctx.req.headers.cookie
+    }
+    const data = await getSolution(ctx.params.id, { headers })
+
+    if (!data) {
+      return
+    }
+
+    let relatedSolutionList!: any[]
+    let apiDoc!: any
+
+    if (data.tags_ids && data.tags_ids !== '[]') {
+      relatedSolutionList = await listRelatedSolution({
+        tags_ids: data.tags_ids,
+        hash_id: data.hash_id,
+        size: 5
+      }, { headers })
+    }
+
+    const providerSolutionList = await listProviderSolution({
+      uid: data.uid,
+      hash_id: data.hash_id,
+      page: 1,
+      size: relatedSolutionList && relatedSolutionList.length ? 3 : 5
+    }, { headers })
+
+    // parse api doc
+    if (data.doc_path) {
+      apiDoc = await parseApiDocFile(data.doc_path)
+    }
+
+    if (!ctx.store.state.kaifain.inited) {
+      await ctx.store.dispatch('parameters:load')
+    }
+
+    const { classes } = ctx.store.state.kaifain
+
+    const footer = genDocumentFooterData({
+      ctx,
+      classes
+    })
+
+    const navItems = [{
+      id: 'details',
+      label: '产品介绍'
+    }]
+
+    if (apiDoc) {
+      navItems.unshift({
+        id: 'apidoc',
+        label: 'API文档'
+      })
+    }
+
+    if (data.successful_case) {
+      navItems.push({
+        id: 'case',
+        label: '成功案例'
+      })
+    }
+
+    return {
+      data,
+      providerSolutionList,
+      relatedSolutionList,
+      apiDoc,
+      footer,
+      navItems
+    }
+  },
+
+  methods: {
+    formatPrice,
+
+    changeNavTab(id, idx) {
+      // @ts-ignore
+      this.curNavIndex = idx
+      // @ts-ignore
+      let el = this.cacheNavElMap[id]
+
+      if (!el) {
+        el = document.querySelector(`#${id}`)
+        // @ts-ignore
+        this.cacheNavElMap[id] = el
+      }
+
+      if (el) {
+        el.scrollIntoView({
+          behavior: 'smooth'
+        })
+      }
+    },
+
+    changeSku(sku) {
+      if (sku.overlimit) {
+        return
+      }
+
+      // @ts-ignore
+      this.curSkuId = sku.id
+    },
+
+    getIndexByNavId(id: string, previous: boolean) {
+      // @ts-ignore
+      for (let i = 0, len = this.navItems.length; i < len; i++) {
+        // @ts-ignore
+        if (this.navItems[i].id === id) {
+          return previous ? Math.min(i - 1, 0) : i
+        }
+      }
+
+      return null
+    },
+
+    visibilityChanged(isVisible: boolean, { target }) {
+      // @ts-ignore
+      const idx = this.getIndexByNavId(target.id, !isVisible)
+
+      if (idx != null) {
+        // @ts-ignore
+        this.curNavIndex = idx
+      }
+    },
+
+    purchase() {
+      // @ts-ignore
+      window.location.href = `https://web.test.proginn.com/pay?product_type=14&product_id=${this.curSkuId}&number=1&next=/kaifain/dashboard`
+    },
+
+    onScroll() {
+      // @ts-ignore
+      this.topnavFunctional = window.scrollY > (this.primaryBtnOffsetY || 300)
+    }
+  },
+
+  mounted() {
+    // @ts-ignore
+    window.addEventListener('scroll', this.onScroll)
+
+    this.$nextTick(() => {
+      const el = this.$refs['summary']
+
+      if (el) {
+        // @ts-ignore
+        const rect = el.getBoundingClientRect()
+        // @ts-ignore
+        this.primaryBtnOffsetY = rect.height - 80
+      }
+
+      // @ts-ignore
+      this.onScroll()
+    })
+  },
+
+  destroyed() {
+    // @ts-ignore
+    window.removeEventListener('scroll', this.onScroll)
+  },
+
+  created() {
+    // @ts-ignore
+    if (this.skus) {
+      // @ts-ignore
+      const availableSku = this.skus.find((item) => !item.overlimit)
+
+      if (availableSku) {
+        // @ts-ignore
+        this.curSkuId = availableSku.id
+      }
+    }
+  }
+})
+</script>
+
+<style lang="scss">
+@import '../assets/styles/vars';
+@import '../assets/styles/ext_aliyun_market';
+@import '../assets/styles/ext_tencent_market';
+@import '../assets/styles/ext_huawei_market';
+
+.solution-page {
+  .summary {
+    color: $ink;
+    padding: 128px 0 80px;
+
+    .column.left {
+      flex: none;
+    }
+
+    .cover {
+      width: 152px;
+      height: 152px;
+      border-radius: 32px;
+      margin-right: 40px;
+      overflow: hidden;
+      position: relative;
+
+      img {
+        width: 100%;
+        height: 100%;
+        object-fit: cover;
+      }
+
+      &::after {
+        content: '';
+        width: 100%;
+        height: 100%;
+        border: 1px solid rgba(0, 0, 0, 0.1);
+        border-radius: 32px;
+        position: absolute;
+        top: 0;
+        left: 0;
+      }
+    }
+
+    .title {
+      margin-bottom: 1rem;
+      line-height: 1.4;
+      margin-top: -3px;
+      overflow: hidden;
+      text-overflow: ellipsis;
+      -webkit-box-orient: vertical;
+      display: -webkit-box;
+      -webkit-line-clamp: 2;
+    }
+
+    .extends {
+      font-size: 0.875rem;
+      margin-bottom: 1rem;
+      line-height: 1.4;
+
+      .tag {
+        margin-right: 0.5rem;
+
+        span {
+          margin-left: 1.25rem;
+        }
+      }
+
+      a.link {
+        color: #888;
+      }
+    }
+
+    .desc {
+      font-size: 0.875rem;
+      margin-bottom: 1.5rem;
+      line-height: 1.4;
+    }
+
+    .price {
+      font-weight: 600;
+      margin-bottom: 1.75rem;
+
+      .symbol {
+        font-size: 1.25rem;
+        margin-right: 3px;
+      }
+      .value {
+        font-size: 1.625rem;
+      }
+      .explain {
+        font-weight: 400;
+        font-size: 0.875rem;
+        margin: 0 0 1px 2px;
+        opacity: 0.54;
+      }
+    }
+
+    .action {
+      .button {
+        font-size: 0.875rem;
+        font-weight: 500;
+        min-width: 106px;
+        height: 2.75em;
+        letter-spacing: 0.01em;
+      }
+    }
+  }
+
+  .skus {
+    margin: 0 -0.5rem 1rem 0;
+
+    .item {
+      color: #666;
+      font-size: 0.8125rem;
+      padding: 8px 12px;
+      border: 1px solid #dfdfdf;
+      border-radius: 2px;
+      display: inline-block;
+      margin: 0 1em 0.75em 0;
+      min-width: 64px;
+      text-align: center;
+      cursor: pointer;
+
+      &.cur {
+        color: $primary;
+        border-color: $primary;
+      }
+
+      &.disabled {
+        opacity: 0.6;
+        cursor: not-allowed;
+      }
+    }
+  }
+
+  .page-nav {
+    background: #fff;
+    height: 54px;
+    border: 1px solid #eaeaea;
+    border-left: 0;
+    border-right: 0;
+    position: sticky;
+    top: 52px;
+    z-index: 9;
+
+    .tabs {
+      font-weight: 500;
+
+      ul, a {
+        border: 0;
+      }
+
+      a {
+        color: $ink !important;
+        height: 54px;
+      }
+
+      li.is-active {
+        position: relative;
+
+        &::after {
+          content: '';
+          position: absolute;
+          bottom: 0;
+          left: 24%;
+          width: 52%;
+          height: 3px;
+          background: $primary;
+          border-radius: 4px;
+        }
+      }
+    }
+  }
+
+  .paper {
+    padding: 1.5rem 0 3rem;
+  }
+
+  .details {
+    padding-left: 0.5rem;
+    padding-right: 0.5rem;
+  }
+
+  .main-cont {
+    .caption {
+      font-size: 1.25rem;
+      font-weight: 500;
+      margin: 2rem 0.5rem 1rem;
+
+      &.bordered {
+        border-top: 1px solid #eaeaea;
+        padding-top: 2rem;
+      }
+    }
+  }
+
+  .side-cont {
+    color: $ink;
+
+    .caption {
+      font-weight: 500;
+      line-height: 1;
+      padding: 0.75rem 0;
+
+      &::before {
+        content: '';
+        background: $primary;
+        width: 3px;
+        height: 16px;
+        border-radius: 4px;
+        display: inline-block;
+        margin-right: 10px;
+        vertical-align: bottom;
+      }
+    }
+
+    .rcmd-list {
+      margin-bottom: 3rem;
+    }
+  }
+
+  @media screen and (min-width: 768px) {
+    .tabs li {
+      margin-right: 2em;
+    }
+
+    .main-cont {
+      padding-right: 1.5rem;
+    }
+
+    .side-cont {
+      padding-left: 1rem;
+    }
+
+    .summary {
+      .cover {
+        margin-left: 1rem;
+      }
+      .title {
+        margin-right: 2rem;
+      }
+      .info {
+        margin-right: 4rem;
+      }
+    }
+  }
+
+  @media screen and (max-width: 768px) {
+    .summary {
+      .cover {
+        width: 128px;
+        height: 128px;
+        border-radius: 28px;
+      }
+    }
+  }
+}
+</style>

+ 4 - 0
kaifain_v2/utils/misc.ts

@@ -99,3 +99,7 @@ export const parseCatIdAndServiceType = (input: {
 export const genCatIdSearchSentence = (catIds: string[], serviceType: string) => {
   return [catIds.join('|'), serviceType].filter((val) => !!val && val != '_').join('&&')
 }
+
+export const formatPrice = (input: number) => {
+  return Number((input / 100).toFixed(2))
+}

+ 25 - 1
kaifain_v2/utils/request.ts

@@ -3,8 +3,32 @@ import { KAIFAIN_ENDPOINT } from '../constant'
 
 console.log('[debug]', process.env.NODE_ENV, KAIFAIN_ENDPOINT)
 
+class RequestError extends Error {
+  status: any
+  url!: string
+  method!: string
+  data!: any
+
+  constructor(msg: string, status: any, res: any) {
+    const { url, method, data } = res && res.config || {}
+    const trackID = res.data && res.data.trackID
+    const message = msg + (trackID ? ` [${trackID}]` : '')
+
+    super(message)
+    this.status = status
+    this.url = url
+    this.method = method
+    this.data = data
+
+    console.error(`${message}\n${method && method.toUpperCase()} ${url}\nData: ${data}\nResponse: ${res.data && JSON.stringify(res.data)}`)
+  }
+}
+
 const request = ProginnRequest.create({
-  baseURL: KAIFAIN_ENDPOINT
+  baseURL: KAIFAIN_ENDPOINT,
+  notifier(msg, status, res) {
+    throw new RequestError(msg, status, res)
+  }
 })
 
 export default request

+ 3 - 1
nuxt.config.js

@@ -119,6 +119,7 @@ module.exports = {
    ** Customize the progress-bar color
    */
   loading: {
+    color: '#2487ff',
     continuous: true
   },
 
@@ -253,7 +254,8 @@ module.exports = {
     ["nuxt-buefy", {
       css: false,
       materialDesignIcons: false
-    }]
+    }],
+    'nuxt-highlightjs'
   ],
   router: {
     middleware: ["initialize"],

+ 3 - 0
package.json

@@ -36,13 +36,16 @@
     "murmurhash": "^1.0.0",
     "nuxt": "^2.14.0",
     "nuxt-buefy": "^0.4.2",
+    "nuxt-highlightjs": "^1.0.1",
     "proginn-lib": "ssh://git@www.gitinn.com:proginn/proginn-lib.git",
     "qrcode": "^1.4.4",
     "qs": "^6.8.0",
+    "swagger-parser": "^10.0.2",
     "vant": "^2.5.2",
     "vconsole": "^3.3.4",
     "vue-aliplayer": "^0.0.8",
     "vue-awesome-swiper": "^3.1.3",
+    "vue-observe-visibility": "^0.4.6",
     "vue-quill-editor": "^3.0.6",
     "vuescroll": "^4.15.0"
   },

+ 4 - 4
pages/kaifain/detail/_tid/index.vue

@@ -1,8 +1,8 @@
 <template>
   <div :class="{kaifainDetailMobile: mobile, kaifainDetail: !mobile}">
-    <div class="kaifain-view">
+    <!-- <div class="kaifain-view">
       <Topnav :fixed="topnavFixed" @publish-click="isShowToast=true" />
-    </div>
+    </div> -->
     <div class="topArea" :style="{backgroundImage: 'url(' + detail.bg_image + ')' }">
       <div class="topContent" :class="{noneMobileAPP: true}">
         <h1 class="title">{{detail.title}}</h1>
@@ -134,12 +134,12 @@ import ConnectUs from "@/components/common/connectUsKaifain.vue";
 import ChangeBgImage from "@/components/kaifain/ChangeBgImage.vue";
 import KaifainFooter from "@/components/SeoFooter.vue";
 import { HashIDUtil, GenType } from "../../../../plugins/genHashId";
-import Topnav from '@/kaifain_v2/components/Topnav.vue'
+// import Topnav from '@/kaifain_v2/components/Topnav.vue'
 import { genDocumentFooterData } from '@/kaifain_v2/helpers/seoHelper'
 
 export default {
   layout: "opacity_header_kf_tmp",
-  components: { ConnectUs, KaifainFooter, ChangeBgImage, Topnav },
+  components: { ConnectUs, KaifainFooter, ChangeBgImage/* , Topnav */ },
   head() {
     const { title = '' } = this.detail || {}
     let docTitle = title ? `${title}介绍,功能,开发解决方案-开发屋` : '开发屋-开发解决方案'

+ 2 - 2
plugins/seoRouter.js

@@ -24,8 +24,8 @@ const extendRoutes = (routes, resolve) => {
       component: resolve(__dirname, '../kaifain_v2/pages/search.vue')
     }, {
       name: 'kaifainSeoDetail',
-      path: '/kaifain/s/:tid',
-      component: resolve(__dirname, '../pages/kaifain/detail/_tid/index.vue')
+      path: '/kaifain/s/:id',
+      component: resolve(__dirname, '../kaifain_v2/pages/solution.vue')
     }, {
       name: 'kaifainCaseSeoDetail',
       path: '/kaifain/d/:tid',

+ 105 - 14
yarn.lock

@@ -92,6 +92,37 @@
   dependencies:
     "@antv/gl-matrix" "^2.7.1"
 
+"@apidevtools/json-schema-ref-parser@^9.0.6":
+  version "9.0.6"
+  resolved "https://registry.npm.taobao.org/@apidevtools/json-schema-ref-parser/download/@apidevtools/json-schema-ref-parser-9.0.6.tgz#5d9000a3ac1fd25404da886da6b266adcd99cf1c"
+  integrity sha1-XZAAo6wf0lQE2ohtprJmrc2Zzxw=
+  dependencies:
+    "@jsdevtools/ono" "^7.1.3"
+    call-me-maybe "^1.0.1"
+    js-yaml "^3.13.1"
+
+"@apidevtools/openapi-schemas@^2.0.4":
+  version "2.0.4"
+  resolved "https://registry.npm.taobao.org/@apidevtools/openapi-schemas/download/@apidevtools/openapi-schemas-2.0.4.tgz#bae1cef77ebb2b3705c7cc6911281da5153c1ab3"
+  integrity sha1-uuHO9367KzcFx8xpESgdpRU8GrM=
+
+"@apidevtools/swagger-methods@^3.0.2":
+  version "3.0.2"
+  resolved "https://registry.npm.taobao.org/@apidevtools/swagger-methods/download/@apidevtools/swagger-methods-3.0.2.tgz#b789a362e055b0340d04712eafe7027ddc1ac267"
+  integrity sha1-t4mjYuBVsDQNBHEur+cCfdwawmc=
+
+"@apidevtools/swagger-parser@10.0.2":
+  version "10.0.2"
+  resolved "https://registry.npm.taobao.org/@apidevtools/swagger-parser/download/@apidevtools/swagger-parser-10.0.2.tgz#f4145afb7c3a3bafe0376f003b5c3bdeae17a952"
+  integrity sha1-9BRa+3w6O6/gN28AO1w73q4XqVI=
+  dependencies:
+    "@apidevtools/json-schema-ref-parser" "^9.0.6"
+    "@apidevtools/openapi-schemas" "^2.0.4"
+    "@apidevtools/swagger-methods" "^3.0.2"
+    "@jsdevtools/ono" "^7.1.3"
+    call-me-maybe "^1.0.1"
+    z-schema "^4.2.3"
+
 "@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.8.3":
   version "7.10.4"
   resolved "https://registry.npm.taobao.org/@babel/code-frame/download/@babel/code-frame-7.10.4.tgz#168da1a36e90da68ae8d49c0f1b48c7c6249213a"
@@ -1847,6 +1878,11 @@
   resolved "https://registry.npm.taobao.org/@csstools/convert-colors/download/@csstools/convert-colors-1.4.0.tgz#ad495dc41b12e75d588c6db8b9834f08fa131eb7"
   integrity sha1-rUldxBsS511YjG24uYNPCPoTHrc=
 
+"@jsdevtools/ono@^7.1.3":
+  version "7.1.3"
+  resolved "https://registry.npm.taobao.org/@jsdevtools/ono/download/@jsdevtools/ono-7.1.3.tgz#9df03bbd7c696a5c58885c34aa06da41c8543796"
+  integrity sha1-nfA7vXxpalxYiFw0qgbaQchUN5Y=
+
 "@nodelib/fs.scandir@2.1.3":
   version "2.1.3"
   resolved "https://registry.npm.taobao.org/@nodelib/fs.scandir/download/@nodelib/fs.scandir-2.1.3.tgz#3a582bdb53804c6ba6d146579c46e52130cf4a3b"
@@ -3180,7 +3216,7 @@ axios-retry@^3.1.8:
 
 axios@^0.19.2:
   version "0.19.2"
-  resolved "https://registry.npm.taobao.org/axios/download/axios-0.19.2.tgz?cache=0&sync_timestamp=1579670738528&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Faxios%2Fdownload%2Faxios-0.19.2.tgz#3ea36c5d8818d0d5f8a8a97a6d36b86cdc00cb27"
+  resolved "https://registry.npm.taobao.org/axios/download/axios-0.19.2.tgz?cache=0&sync_timestamp=1597979584536&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Faxios%2Fdownload%2Faxios-0.19.2.tgz#3ea36c5d8818d0d5f8a8a97a6d36b86cdc00cb27"
   integrity sha1-PqNsXYgY0NX4qKl6bTa4bNwAyyc=
   dependencies:
     follow-redirects "1.5.10"
@@ -3504,10 +3540,10 @@ browserslist@^4.0.0, browserslist@^4.12.0, browserslist@^4.6.4, browserslist@^4.
     node-releases "^1.1.53"
     pkg-up "^2.0.0"
 
-buefy@^0.9.2:
-  version "0.9.2"
-  resolved "https://registry.npm.taobao.org/buefy/download/buefy-0.9.2.tgz#c92398b95a5372845484df0bb1ccd4f7c20edb0c"
-  integrity sha1-ySOYuVpTcoRUhN8LsczU98IO2ww=
+buefy@^0.9.3:
+  version "0.9.3"
+  resolved "https://registry.npm.taobao.org/buefy/download/buefy-0.9.3.tgz#c33b3309e9458ca5fe3ef14955e4bc2df7a0e3e2"
+  integrity sha1-wzszCelFjKX+PvFJVeS8Lfeg4+I=
   dependencies:
     bulma "0.9.0"
 
@@ -3653,6 +3689,11 @@ cache-loader@^4.1.0:
     neo-async "^2.6.1"
     schema-utils "^2.0.0"
 
+call-me-maybe@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.npm.taobao.org/call-me-maybe/download/call-me-maybe-1.0.1.tgz#26d208ea89e37b5cbde60250a15f031c16a4d66b"
+  integrity sha1-JtII6onje1y95gJQoV8DHBak1ms=
+
 caller-callsite@^2.0.0:
   version "2.0.0"
   resolved "https://registry.npm.taobao.org/caller-callsite/download/caller-callsite-2.0.0.tgz#847e0fce0a223750a9a027c54b33731ad3154134"
@@ -4067,7 +4108,7 @@ combined-stream@^1.0.6, combined-stream@~1.0.6:
   dependencies:
     delayed-stream "~1.0.0"
 
-commander@^2.18.0, commander@^2.19.0, commander@^2.20.0, commander@~2.20.3:
+commander@^2.18.0, commander@^2.19.0, commander@^2.20.0, commander@^2.7.1, commander@~2.20.3:
   version "2.20.3"
   resolved "https://registry.npm.taobao.org/commander/download/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
   integrity sha1-/UhehMA+tIgcIHIrpIA16FMa6zM=
@@ -4710,7 +4751,7 @@ debug@2.6.9, debug@^2.2.0, debug@^2.3.3:
 
 debug@=3.1.0:
   version "3.1.0"
-  resolved "https://registry.npm.taobao.org/debug/download/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
+  resolved "https://registry.npm.taobao.org/debug/download/debug-3.1.0.tgz?cache=0&sync_timestamp=1600502855763&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fdebug%2Fdownload%2Fdebug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
   integrity sha1-W7WgZyYotkFJVmuhaBnmFRjGcmE=
   dependencies:
     ms "2.0.0"
@@ -5621,7 +5662,7 @@ fmin@0.0.2:
 
 follow-redirects@1.5.10:
   version "1.5.10"
-  resolved "https://registry.npm.taobao.org/follow-redirects/download/follow-redirects-1.5.10.tgz?cache=0&sync_timestamp=1592338306588&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Ffollow-redirects%2Fdownload%2Ffollow-redirects-1.5.10.tgz#7b7a9f9aea2fdff36786a94ff643ed07f4ff5e2a"
+  resolved "https://registry.npm.taobao.org/follow-redirects/download/follow-redirects-1.5.10.tgz?cache=0&sync_timestamp=1597058070545&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Ffollow-redirects%2Fdownload%2Ffollow-redirects-1.5.10.tgz#7b7a9f9aea2fdff36786a94ff643ed07f4ff5e2a"
   integrity sha1-e3qfmuov3/NnhqlP9kPtB/T/Xio=
   dependencies:
     debug "=3.1.0"
@@ -6111,6 +6152,11 @@ hex-color-regex@^1.1.0:
   resolved "https://registry.npm.taobao.org/hex-color-regex/download/hex-color-regex-1.1.0.tgz#4c06fccb4602fe2602b3c93df82d7e7dbf1a8a8e"
   integrity sha1-TAb8y0YC/iYCs8k9+C1+fb8aio4=
 
+highlight.js@^10.1.2:
+  version "10.2.0"
+  resolved "https://registry.npm.taobao.org/highlight.js/download/highlight.js-10.2.0.tgz#367151bcf813adebc54822f1cb51d2e1e599619f"
+  integrity sha1-NnFRvPgTrevFSCLxy1HS4eWZYZ8=
+
 hljs@^6.2.3:
   version "6.2.3"
   resolved "https://registry.npm.taobao.org/hljs/download/hljs-6.2.3.tgz#d4d6208fa2a84f294956bc50f2c812e9cbd49bcc"
@@ -7221,6 +7267,16 @@ lodash._reinterpolate@^3.0.0:
   resolved "https://registry.npm.taobao.org/lodash._reinterpolate/download/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d"
   integrity sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=
 
+lodash.get@^4.4.2:
+  version "4.4.2"
+  resolved "https://registry.npm.taobao.org/lodash.get/download/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99"
+  integrity sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=
+
+lodash.isequal@^4.5.0:
+  version "4.5.0"
+  resolved "https://registry.npm.taobao.org/lodash.isequal/download/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0"
+  integrity sha1-QVxEePK8wwEgwizhDtMib30+GOA=
+
 lodash.kebabcase@^4.1.1:
   version "4.1.1"
   resolved "https://registry.npm.taobao.org/lodash.kebabcase/download/lodash.kebabcase-4.1.1.tgz#8489b1cb0d29ff88195cceca448ff6d6cc295c36"
@@ -7950,11 +8006,18 @@ number-is-nan@^1.0.0:
   integrity sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=
 
 nuxt-buefy@^0.4.2:
-  version "0.4.2"
-  resolved "https://registry.npm.taobao.org/nuxt-buefy/download/nuxt-buefy-0.4.2.tgz#9179672b1097d29642ed48429d067b474fe1fec3"
-  integrity sha1-kXlnKxCX0pZC7UhCnQZ7R0/h/sM=
+  version "0.4.3"
+  resolved "https://registry.npm.taobao.org/nuxt-buefy/download/nuxt-buefy-0.4.3.tgz#2ecafd9256743f05d123a5487acc3488ea7023f4"
+  integrity sha1-Lsr9klZ0PwXRI6VIesw0iOpwI/Q=
+  dependencies:
+    buefy "^0.9.3"
+
+nuxt-highlightjs@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.npm.taobao.org/nuxt-highlightjs/download/nuxt-highlightjs-1.0.1.tgz#6809a9ccebfff8993d69a85a9095650069268d66"
+  integrity sha1-aAmpzOv/+Jk9aahakJVlAGkmjWY=
   dependencies:
-    buefy "^0.9.2"
+    highlight.js "^10.1.2"
 
 nuxt@^2.14.0:
   version "2.14.0"
@@ -9200,8 +9263,8 @@ process@^0.11.10:
   integrity sha1-czIwDoQBYb2j5podHZGn1LwW8YI=
 
 "proginn-lib@ssh://git@www.gitinn.com:proginn/proginn-lib.git":
-  version "0.1.1"
-  resolved "ssh://git@www.gitinn.com:proginn/proginn-lib.git#0a30fc554d84704f739723f5143a41b9d5406906"
+  version "0.2.2"
+  resolved "ssh://git@www.gitinn.com:proginn/proginn-lib.git#c70e605fd5a72e5b4650aec3292ba6693fd4170a"
   dependencies:
     axios "^0.19.2"
     js-cookie "^2.2.1"
@@ -10671,6 +10734,13 @@ svgo@^1.0.0:
     unquote "~1.1.1"
     util.promisify "~1.0.0"
 
+swagger-parser@^10.0.2:
+  version "10.0.2"
+  resolved "https://registry.npm.taobao.org/swagger-parser/download/swagger-parser-10.0.2.tgz#d7f18faa09c9c145e938977c9bd6c3435998b667"
+  integrity sha1-1/GPqgnJwUXpOJd8m9bDQ1mYtmc=
+  dependencies:
+    "@apidevtools/swagger-parser" "10.0.2"
+
 swiper@^4.0.7:
   version "4.5.1"
   resolved "https://registry.npm.taobao.org/swiper/download/swiper-4.5.1.tgz#ed43998e780ceb478610079c8d23fd425eca636f"
@@ -11286,6 +11356,11 @@ validate-npm-package-license@^3.0.1:
     spdx-correct "^3.0.0"
     spdx-expression-parse "^3.0.0"
 
+validator@^12.0.0:
+  version "12.2.0"
+  resolved "https://registry.npm.taobao.org/validator/download/validator-12.2.0.tgz#660d47e96267033fd070096c3b1a6f2db4380a0a"
+  integrity sha1-Zg1H6WJnAz/QcAlsOxpvLbQ4Cgo=
+
 vant@^2.5.2:
   version "2.8.6"
   resolved "https://registry.npm.taobao.org/vant/download/vant-2.8.6.tgz?cache=0&sync_timestamp=1592037438255&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fvant%2Fdownload%2Fvant-2.8.6.tgz#83ffd70dd6fbf8c3d80fbd6f43f38311c26542ef"
@@ -11402,6 +11477,11 @@ vue-no-ssr@^1.1.1:
   resolved "https://registry.npm.taobao.org/vue-no-ssr/download/vue-no-ssr-1.1.1.tgz#875f3be6fb0ae41568a837f3ac1a80eaa137b998"
   integrity sha1-h1875vsK5BVoqDfzrBqA6qE3uZg=
 
+vue-observe-visibility@^0.4.6:
+  version "0.4.6"
+  resolved "https://registry.npm.taobao.org/vue-observe-visibility/download/vue-observe-visibility-0.4.6.tgz#878cb8ebcf3078e40807af29774e97105ebd519e"
+  integrity sha1-h4y4688weOQIB68pd06XEF69UZ4=
+
 vue-quill-editor@^3.0.6:
   version "3.0.6"
   resolved "https://registry.npm.taobao.org/vue-quill-editor/download/vue-quill-editor-3.0.6.tgz#1f85646211d68a31a80a72cb7f45bb2f119bc8fb"
@@ -11883,3 +11963,14 @@ yn@3.1.1:
   version "3.1.1"
   resolved "https://registry.npm.taobao.org/yn/download/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50"
   integrity sha1-HodAGgnXZ8HV6rJqbkwYUYLS61A=
+
+z-schema@^4.2.3:
+  version "4.2.3"
+  resolved "https://registry.npm.taobao.org/z-schema/download/z-schema-4.2.3.tgz#85f7eea7e6d4fe59a483462a98f511bd78fe9882"
+  integrity sha1-hffup+bU/lmkg0YqmPURvXj+mII=
+  dependencies:
+    lodash.get "^4.4.2"
+    lodash.isequal "^4.5.0"
+    validator "^12.0.0"
+  optionalDependencies:
+    commander "^2.7.1"