Browse Source

feat: 前端

xiansin 2 years ago
parent
commit
57b9e6ec5e
100 changed files with 2733 additions and 1704 deletions
  1. 4 4
      mini/App.vue
  2. 26 0
      mini/api/cases.js
  3. 8 3
      mini/api/index.js
  4. 33 0
      mini/api/product.js
  5. 18 0
      mini/api/setting.js
  6. 18 0
      mini/api/showroom.js
  7. 34 15
      mini/api/user/index.js
  8. 0 30
      mini/components/CheckLogin/index.vue
  9. 0 144
      mini/components/Episode/index.vue
  10. 58 0
      mini/components/Index/Logged.vue
  11. 62 0
      mini/components/Index/Login.vue
  12. 0 92
      mini/components/NavBar/index.vue
  13. 31 0
      mini/components/PageLoading/index.vue
  14. 0 307
      mini/components/Recharge/index.vue
  15. 24 18
      mini/components/SwiperBox/index.vue
  16. 0 151
      mini/components/TabBar/index.vue
  17. 2 2
      mini/manifest.json
  18. 60 31
      mini/pages.json
  19. 51 0
      mini/pages/case/detail.vue
  20. 118 0
      mini/pages/case/index.vue
  21. 116 0
      mini/pages/case/list.vue
  22. 119 0
      mini/pages/forget.vue
  23. 46 0
      mini/pages/index.vue
  24. 125 17
      mini/pages/login.vue
  25. 340 0
      mini/pages/product/detail.vue
  26. 116 0
      mini/pages/product/index.vue
  27. 116 0
      mini/pages/product/list.vue
  28. 65 0
      mini/static/css/common.scss
  29. 3 6
      mini/static/css/variable.scss
  30. BIN
      mini/static/image/default-head-img.png
  31. BIN
      mini/static/image/default-movie.png
  32. BIN
      mini/static/image/gold-bag.png
  33. BIN
      mini/static/image/gold.png
  34. BIN
      mini/static/image/icon/close.png
  35. BIN
      mini/static/image/icon/copy.png
  36. BIN
      mini/static/image/icon/down.png
  37. BIN
      mini/static/image/icon/download.png
  38. BIN
      mini/static/image/icon/eye.png
  39. BIN
      mini/static/image/icon/file.png
  40. BIN
      mini/static/image/icon/float-btn.png
  41. BIN
      mini/static/image/icon/phone.png
  42. BIN
      mini/static/image/icon/up.png
  43. BIN
      mini/static/image/icon/wechat.png
  44. BIN
      mini/static/image/login/logo-white.png
  45. BIN
      mini/static/image/login/logo.png
  46. BIN
      mini/static/image/login/wechat.png
  47. BIN
      mini/static/image/member-line-bg.png
  48. BIN
      mini/static/image/member-selected-bg.png
  49. BIN
      mini/static/image/my-page/contact.png
  50. BIN
      mini/static/image/my-page/order.png
  51. BIN
      mini/static/image/my-page/protocol.png
  52. BIN
      mini/static/image/my-page/recharge.png
  53. BIN
      mini/static/image/my-page/share.png
  54. BIN
      mini/static/image/my-recharge-bg.png
  55. BIN
      mini/static/image/playing.png
  56. BIN
      mini/static/image/tab/home-HL.png
  57. BIN
      mini/static/image/tab/home.png
  58. BIN
      mini/static/image/tab/my-HL.png
  59. BIN
      mini/static/image/tab/my.png
  60. BIN
      mini/static/image/tab/trace-HL.png
  61. BIN
      mini/static/image/tab/trace.png
  62. BIN
      mini/static/image/video.png
  63. 9 1
      mini/store/modules/user.js
  64. 162 162
      mini/uni_modules/uview-ui/components/u-search/u-search.vue
  65. 13 0
      mini/utils/auth.js
  66. 5 1
      mini/utils/constant.js
  67. 0 1
      mini/utils/index.js
  68. 3 5
      mini/utils/mixin.js
  69. 0 6
      mini/utils/request/responseInterceptors.js
  70. 46 0
      server/app/Admin/Actions/Form/BatchEditProductForm.php
  71. 40 0
      server/app/Admin/Actions/Grid/BatchProduct.php
  72. 1 1
      server/app/Admin/Controllers/AccountController.php
  73. 25 36
      server/app/Admin/Controllers/BannerController.php
  74. 10 29
      server/app/Admin/Controllers/ContactController.php
  75. 89 51
      server/app/Admin/Controllers/ProductController.php
  76. 60 41
      server/app/Admin/Controllers/ProductSpecController.php
  77. 0 71
      server/app/Admin/Controllers/ProductSpecGroupController.php
  78. 20 11
      server/app/Admin/Controllers/SettingController.php
  79. 9 18
      server/app/Admin/Controllers/ShowroomController.php
  80. 0 2
      server/app/Admin/routes.php
  81. 71 259
      server/app/Http/Controllers/V1/AuthController.php
  82. 60 0
      server/app/Http/Controllers/V1/CasesController.php
  83. 0 34
      server/app/Http/Controllers/V1/Controller.php
  84. 0 21
      server/app/Http/Controllers/V1/PayController.php
  85. 76 0
      server/app/Http/Controllers/V1/ProductController.php
  86. 36 0
      server/app/Http/Controllers/V1/SettingController.php
  87. 1 9
      server/app/Http/Controllers/V1/UserController.php
  88. 30 0
      server/app/Models/Account.php
  89. 57 1
      server/app/Models/Product.php
  90. 2 0
      server/app/Models/ProductCategory.php
  91. 26 11
      server/app/Models/ProductSpec.php
  92. 0 56
      server/app/Models/ProductSpecGroup.php
  93. 12 0
      server/app/Models/Showroom.php
  94. 39 0
      server/app/Models/StatProduct.php
  95. 39 0
      server/app/Models/StatProductDownload.php
  96. 39 0
      server/app/Models/StatShowroom.php
  97. 13 1
      server/app/Models/User.php
  98. 0 54
      server/app/Models/UserInfo.php
  99. 146 0
      server/config/wechat.php
  100. 1 2
      server/resources/lang/zh/product-spec.php

+ 4 - 4
mini/App.vue

@@ -1,19 +1,19 @@
 <script>
 <script>
-
+import Cache from './utils/cache'
 export default {
 export default {
   globalData: {
   globalData: {
-    isLogin: false
   },
   },
   async onLaunch(options) {
   async onLaunch(options) {
     console.log('App Launch')
     console.log('App Launch')
-    const path = options.path ? '/' + options.path : '/pages/index/index'
+    const path = options.path ? '/' + options.path : '/pages/index'
     if (this.$api.user.isLogin()) {
     if (this.$api.user.isLogin()) {
       await this.$api.user.info().then(res => {
       await this.$api.user.info().then(res => {
         this.$store.dispatch('user/info', res.data)
         this.$store.dispatch('user/info', res.data)
       })
       })
     } else {
     } else {
+      Cache.set('path', path)
       uni.reLaunch({
       uni.reLaunch({
-        url: '/pages/login?path=' + path
+        url: '/pages/index'
       })
       })
     }
     }
   },
   },

+ 26 - 0
mini/api/cases.js

@@ -0,0 +1,26 @@
+const request = uni.$u.http
+
+export async function search(params) {
+  return request.get(
+    'cases/search',
+    { params }
+  )
+}
+
+export async function detail(id) {
+  return request.get(
+    `cases/${id}/detail`
+  )
+}
+
+export async function viewer(id) {
+  return request.post(
+    `cases/${id}/viewer`
+  )
+}
+
+export default {
+  search,
+  detail,
+  viewer
+}

+ 8 - 3
mini/api/index.js

@@ -1,10 +1,15 @@
 import user from './user/index'
 import user from './user/index'
+import setting from './setting'
+import product from './product'
+import showroom from './showroom'
+import cases from './cases'
+
 const api = {
 const api = {
   user,
   user,
   setting,
   setting,
-  episode,
-  sign,
-  pay
+  product,
+  showroom,
+  cases
 }
 }
 
 
 export default api
 export default api

+ 33 - 0
mini/api/product.js

@@ -0,0 +1,33 @@
+const request = uni.$u.http
+
+export async function search(params) {
+  return request.get(
+    'product/search',
+    { params }
+  )
+}
+
+export async function detail(id) {
+  return request.get(
+    `product/${id}/detail`
+  )
+}
+
+export async function viewer(id) {
+  return request.post(
+    `product/${id}/viewer`
+  )
+}
+
+export async function download(id) {
+  return request.post(
+    `product/${id}/download`
+  )
+}
+
+export default {
+  search,
+  detail,
+  viewer,
+  download
+}

+ 18 - 0
mini/api/setting.js

@@ -0,0 +1,18 @@
+const request = uni.$u.http
+
+export async function loginBanner() {
+  return request.get(
+    'setting/login/banner'
+  )
+}
+
+export async function loginContact() {
+  return request.get(
+    'setting/login/contact'
+  )
+}
+
+export default {
+  loginBanner,
+  loginContact
+}

+ 18 - 0
mini/api/showroom.js

@@ -0,0 +1,18 @@
+const request = uni.$u.http
+
+export async function loginBanner() {
+  return request.get(
+    'setting/login/banner'
+  )
+}
+
+export async function loginContact() {
+  return request.get(
+    'setting/login/contact'
+  )
+}
+
+export default {
+  loginBanner,
+  loginContact
+}

+ 34 - 15
mini/api/user/index.js

@@ -1,12 +1,11 @@
 /**
 /**
  * Created by JianJia.Zhou<jianjia.zhou> on 2022/8/14.
  * Created by JianJia.Zhou<jianjia.zhou> on 2022/8/14.
  */
  */
-import { getToken } from '../../utils/auth'
+import { getAuthorize, getToken } from '../../utils/auth'
 
 
 const request = uni.$u.http
 const request = uni.$u.http
 
 
-export async function login() {
-  console.log('-->data', 1)
+export async function authorize() {
   return new Promise(resolve => {
   return new Promise(resolve => {
     uni.showLoading({
     uni.showLoading({
       title: '数据加载中...',
       title: '数据加载中...',
@@ -15,13 +14,10 @@ export async function login() {
     uni.login({
     uni.login({
       provider: uni.$u.platform,
       provider: uni.$u.platform,
       success: loginRes => {
       success: loginRes => {
-        console.log('-->data', loginRes)
         uni.hideLoading()
         uni.hideLoading()
-        const url = '/auth/kuaishou'
-
         return request.post(
         return request.post(
-          url,
-          { code: loginRes.code, anonymousCode: loginRes.anonymousCode }
+          '/auth/wechat/minni/code',
+          { code: loginRes.code }
         ).then(res => {
         ).then(res => {
           resolve(res)
           resolve(res)
         })
         })
@@ -30,11 +26,28 @@ export async function login() {
   })
   })
 }
 }
 
 
-export function update(data) {
-  return request.post(
-    'user/update',
-    data
-  )
+export async function login(account, password, code) {
+  return new Promise((resolve, reject) => {
+    return request.post(
+      '/auth/login',
+      { account, password, code }
+    ).then(res => {
+      resolve(res)
+    }).catch(err => {
+      reject(err)
+    })
+  })
+}
+
+export async function phoneQuickLogin(data) {
+  return new Promise(resolve => {
+    return request.post(
+      '/auth/wechat/minni/phone',
+      data
+    ).then(res => {
+      resolve(res)
+    })
+  })
 }
 }
 
 
 export async function info() {
 export async function info() {
@@ -47,9 +60,15 @@ export function isLogin() {
   return !!getToken()
   return !!getToken()
 }
 }
 
 
+export function isAuthorize() {
+  return !!getAuthorize()
+}
+
 export default {
 export default {
+  authorize,
   login,
   login,
-  update,
   info,
   info,
-  isLogin
+  isLogin,
+  isAuthorize,
+  phoneQuickLogin
 }
 }

+ 0 - 30
mini/components/CheckLogin/index.vue

@@ -1,30 +0,0 @@
-<template>
-  <view class="check-login">
-    <u-loading-page
-      :loading="!isLogin"
-      :bg-color="$colors.bgColor"
-      :color="$colors.primaryColor"
-      :loading-color="$colors.primaryColor"
-    />
-  </view>
-</template>
-
-<script>
-export default {
-  name: 'CheckLogin',
-  data() {
-    return {}
-  },
-  computed: {
-    isLogin() {
-      return this.$api.user.isLogin()
-    }
-  },
-  methods: {}
-}
-</script>
-
-<style lang="scss" scoped>
-    .check-login {
-    }
-</style>

+ 0 - 144
mini/components/Episode/index.vue

@@ -1,144 +0,0 @@
-<template>
-  <view class="episode" :style="[customStyle]" @click="handlePlay">
-    <view class="cover-image" :style="[imageStyle]">
-      <view v-if="rank" class="rank" :class="{first: rank === 1}">
-        <text>{{ rank }}</text>
-      </view>
-      <image :src="episode.cover_img" mode="aspectFill" :style="[imageStyle]" />
-      <view v-if="recent" class="special">最近播放 </view>
-      <view v-if="guess" class="special guess">猜你喜欢 </view>
-    </view>
-    <u-text
-      :text="episode.name"
-      :lines="1"
-      size="32rpx"
-      margin="20rpx 0 10rpx"
-      :color="$colors.defaultColor"
-    />
-    <view class="status-box dir-left-nowrap">
-      <u-text
-        :text="episode.status_text"
-        :lines="1"
-        size="24rpx"
-        :custom-style="{flex: 'unset'}"
-        :color="$colors.primaryColor"
-      />
-      <u-text
-        :text="`共${episode.total}集`"
-        :lines="1"
-        size="24rpx"
-        margin="0 0 0 10rpx"
-        :color="$colors.infoColor"
-      />
-    </view>
-  </view></template>
-
-<script>
-import UText from '../../uni_modules/uview-ui/components/u--text/u--text'
-export default {
-  name: 'Episode',
-  components: { UText },
-  props: {
-    episode: {
-      type: Object,
-      required: true
-    },
-    customStyle: {
-      type: Object,
-      default() {
-        return {}
-      }
-    },
-    imageStyle: {
-      type: Object,
-      default() {
-        return {}
-      }
-    },
-    recent: {
-      type: Boolean,
-      default: false
-    },
-    guess: {
-      type: Boolean,
-      default: false
-    },
-    rank: {
-      type: Number,
-      default: 0
-    },
-    redirect: {
-      type: Boolean,
-      default: true
-    }
-  },
-  data() {
-    return {}
-  },
-  computed: {},
-  methods: {
-    handlePlay() {
-      if (!this.redirect) return
-      this.$u.route({
-        url: '/pages/episode/play',
-        params: {
-          id: this.episode.id
-        }
-      })
-    }
-  }
-}
-</script>
-
-<style lang="scss" scoped>
-    .episode {
-      width: calc((#{750rpx} - #{80rpx}) / 3);
-      margin-bottom: 30rpx;
-      .cover-image{
-        position: relative;
-        overflow: hidden;
-        height: 330rpx;
-        image{
-          width: 100%;
-          height: inherit;
-        }
-        .special{
-          background: rgba(48, 98, 97, 0.65);
-          text-align: center;
-          color: #fff;
-          padding: 10rpx 0;
-          position: absolute;
-          bottom: 0;
-          width: 100%;
-          font-size: 26rpx;
-          &.guess{
-            background: rgba(124, 78, 112, 0.65);
-          }
-        }
-        .rank{
-          position: absolute;
-          top: -35rpx;
-          left: -35rpx;
-          width: 70rpx;
-          height: 70rpx;
-          transform: rotate(45deg);
-          background: rgba(251, 54, 81, 0.6);
-          color: #fff;
-          font-size: 26rpx;
-          z-index: 99;
-          &.first{
-            background: #FE3552;
-          }
-          text{
-            position: absolute;
-            right: 5px;
-            top: 10px;
-            transform: rotate(-45deg);
-          }
-        }
-      }
-      .status-box{
-
-      }
-    }
-</style>

+ 58 - 0
mini/components/Index/Logged.vue

@@ -0,0 +1,58 @@
+<template>
+  <view class="index">
+    <view class="logo-black logo" />
+
+    <swiper-box type="login" height="560rpx" />
+
+    <view class="btn-box  main-left cross-center">
+      <u-button
+        text="产品"
+        color="#000"
+        shape="square"
+        :plain="true"
+        :custom-style="{borderRadius: 0}"
+        @click="$u.route({url: '/pages/product/index'})"
+      />
+      <u-button
+        v-if="userInfo.account.type === $const.ACCOUNT_DESIGN || userInfo.account.type === $const.ACCOUNT_VIP"
+        text="案例"
+        color="#000"
+        shape="square"
+        :plain="true"
+        :custom-style="{marginLeft:'50rpx', borderRadius: 0}"
+        @click="$u.route({url: '/pages/case/index'})"
+      />
+    </view>
+  </view>
+</template>
+
+<script>
+import { mapState } from 'vuex'
+import SwiperBox from '../SwiperBox'
+export default {
+  name: 'IndexLogged',
+  components: { SwiperBox },
+  data() {
+    return {
+    }
+  },
+  computed: {
+    ...mapState({
+      userInfo: seate => seate.user.info
+    })
+  },
+  methods: {}
+}
+</script>
+
+<style lang="scss" scoped>
+.index {
+  .logo{
+    margin: 80rpx auto 60rpx;
+  }
+  .btn-box{
+    margin-top: 100rpx;
+    padding: 0 50rpx;
+  }
+}
+</style>

+ 62 - 0
mini/components/Index/Login.vue

@@ -0,0 +1,62 @@
+<template>
+  <view class="index-login">
+    <view class="logo-black logo" />
+
+    <swiper-box type="login" height="380rpx" />
+
+    <view class="btn-box dir-top-wrap main-center cross-center">
+      <view class="static-text">登陆极创社</view>
+      <u-button
+        text="登陆"
+        color="#000"
+        shape="square"
+        :custom-style="btnStyle"
+        @click="$u.route({url: '/pages/login'})"
+      />
+      <u-button
+        text="联系我们"
+        color="#000"
+        shape="square"
+        :plain="true"
+        :custom-style="btnStyle"
+        @click="$u.route({url: '/pages/forget'})"
+      />
+    </view>
+  </view>
+</template>
+
+<script>
+import SwiperBox from '../SwiperBox'
+export default {
+  name: 'IndexLogin',
+  components: { SwiperBox },
+
+  data() {
+    return {
+      btnStyle: {
+        marginTop: '30rpx',
+        width: '580rpx',
+        borderRadius: 0
+      }
+    }
+  },
+  computed: {},
+  methods: {}
+}
+</script>
+
+<style lang="scss" scoped>
+.index-login {
+  .logo{
+    margin: 80rpx auto 30rpx;
+  }
+  .btn-box{
+    margin-top: 120rpx;
+    letter-spacing: .1rem;
+    .static-text{
+      color: $primary-color;
+      margin-bottom: 10rpx;
+    }
+  }
+}
+</style>

+ 0 - 92
mini/components/NavBar/index.vue

@@ -1,92 +0,0 @@
-<template>
-  <view class="nav-bar main-center cross-center">
-    <view
-      v-for="(item,index) in navItem"
-      :key="index"
-      class="item dir-top-wrap cross-center"
-      @click="handleRedirect(item)"
-    >
-      <view
-        class="icon"
-        :style="{
-          backgroundImage: `url(${item.icon})`
-        }"
-      />
-      <text>{{ item.name }}</text>
-    </view>
-    <!--充值-->
-    <recharge :show.sync="recharge.show" />
-  </view>
-</template>
-
-<script>
-
-import Recharge from '../Recharge/index'
-export default {
-  name: 'NavBar',
-  components: { Recharge },
-  data() {
-    return {
-      types: {
-        1: { href: '/pages/index/rank' },
-        2: { href: '/pages/index/news' },
-        3: { href: '/pages/member/index' },
-        4: { href: '/pages/sign/index' },
-        5: { type: 'recharge' }
-      },
-      navItem: [
-        { icon: '', name: '排行榜', href: '/pages/index/rank' },
-        { icon: '', name: '最新', href: '/pages/index/news' },
-        { icon: '', name: '会员', href: '/pages/member/index' },
-        { icon: '', name: '签到', href: '/pages/sign/index' },
-        { icon: '', name: '充值', type: 'recharge' }
-      ],
-      recharge: { show: false }
-    }
-  },
-  computed: {},
-  created() {
-    this.getData()
-  },
-  methods: {
-    handleRedirect(item) {
-      if (item.type === 'recharge') {
-        this.recharge.show = true
-      } else if (item.href) {
-        this.$u.route(item.href)
-      }
-    },
-    getData() {
-      /**
-       * type 1 -排行榜 2 -最新  3 -会员 4 -签到 5 -充值
-       */
-      this.$api.setting.navBar().then(res => {
-        const data = res.data
-        data.forEach(obj => {
-          Object.assign(obj, this.types[obj.type])
-        })
-        this.navItem = data
-      })
-    }
-  }
-}
-</script>
-
-<style lang="scss" scoped>
-  .nav-bar {
-    .item{
-      flex: 1;
-      font-size: 32rpx;
-      .icon{
-        width: 100rpx;
-        height: 100rpx;
-        background-repeat: no-repeat;
-        background-size: 100%;
-        background-position: center;
-      }
-      text{
-        color: #fff;
-      }
-    }
-  }
-</style>

+ 31 - 0
mini/components/PageLoading/index.vue

@@ -0,0 +1,31 @@
+<template>
+  <view class="page-loading">
+    <u-loading-page :loading="loading" />
+    <template v-if="!loading">
+      <slot />
+    </template>
+  </view>
+</template>
+
+<script>
+export default {
+  name: 'PageLoading',
+  props: {
+    loading: {
+      type: Boolean,
+      default: true
+    }
+  },
+  data() {
+    return {}
+  },
+  computed: {},
+  methods: {}
+}
+</script>
+
+<style lang="scss" scoped>
+.page-loading {
+
+}
+</style>

+ 0 - 307
mini/components/Recharge/index.vue

@@ -1,307 +0,0 @@
-<template>
-  <view class="recharge">
-    <u-popup
-      :show="show"
-      :mode="mode"
-      round="20rpx"
-      :close-on-click-overlay="false"
-      @close="close"
-    >
-      <view class="container" :class="{bottom: mode === 'bottom'}">
-        <template v-if="type === 'play'">
-          <view class="play-container">
-            <view class="header main-between cross-center">
-              <text>感谢您的支持,本集解锁后可继续观看</text>
-              <u-icon name="arrow-down" size="28rpx" @click="close" />
-            </view>
-            <view class="episode main-between cross-center">
-              <view class="detail">
-                <view class="name">{{ episode.name }} 第{{ list.sort }}集</view>
-                <view class="sale-box main-left cross-center">
-                  <view class="sale-price cross-center">专享价{{ list.sale_price }}金币</view>
-                  <view class="origin-price cross-center">原价{{ list.origin_price }}金币</view>
-                </view>
-              </view>
-              <view class="buy-num">{{ buyNum }}人购买</view>
-            </view>
-          </view>
-          <view class="static-text main-between cross-center">
-            <text>充值金币</text>
-            <view class="overage">账户余额:<text>{{ userInfo.info.integral }}金币</text></view>
-          </view>
-        </template>
-        <template v-else>
-          <view class="static-text main-between cross-center">
-            <text>充值金币</text>
-            <u-icon name="close-circle" size="52rpx" color="#BEBDBB" @click="close" />
-          </view>
-          <view class="overage">账户余额:<text>{{ userInfo.info.integral }}金币</text></view>
-        </template>
-
-        <view class="recharge-group dir-left-wrap">
-          <view
-            v-for="(combo,index) in combos"
-            :key="index"
-            class="recharge-item dir-top-wrap main-center cross-center"
-            :class="{active: rechargeActive === index}"
-            @click="rechargeActive = index"
-          >
-            <text class="price">{{ combo.price }}元</text>
-            <text class="gold">{{ combo.gold }}+{{ combo.gift }}金币</text>
-            <text class="gift">多送{{ combo.gift }}金币</text>
-          </view>
-        </view>
-
-        <view class="btn" @click="handleRecharge">充值</view>
-      </view>
-    </u-popup>
-  </view>
-</template>
-
-<script>
-import { mapState } from 'vuex'
-export default {
-  name: 'Recharge',
-  props: {
-    show: {
-      type: Boolean,
-      default: false
-    },
-    mode: {
-      type: String,
-      default: 'center'
-    },
-    type: {
-      type: String,
-      default: 'normal'
-    },
-    episode: {
-      type: Object,
-      default() {
-        return {}
-      }
-    },
-    list: {
-      type: Object,
-      default() {
-        return {}
-      }
-    }
-  },
-  data() {
-    return {
-      modal: {
-        show: false
-      },
-      combos: [],
-      rechargeActive: 0,
-      buyNum: 0
-    }
-  },
-  computed: {
-    ...mapState({
-      userInfo: seate => seate.user.info
-    })
-  },
-  watch: {
-    show(val) {
-      this.modal.show = val
-    }
-  },
-  created() {
-    this.getCombo()
-    this.getBuyNum()
-  },
-  methods: {
-    getCombo() {
-      this.$api.setting.rechargeCombo().then(res => {
-        this.combos = res.data
-      })
-    },
-    close() {
-      this.$emit('update:show', false)
-    },
-    getBuyNum() {
-      if (this.type === 'play') {
-        this.$api.episode.listBuyNum(this.list.id).then(res => {
-          this.buyNum = res.data
-        })
-      }
-    },
-    handleRecharge() {
-      const item = this.combos[this.rechargeActive]
-      this.$loading('请稍后...')
-      this.$api.user.recharge.create({ id: item.id }).then(res => {
-        console.log('-->data', res)
-        // #ifdef MP-TOUTIAO
-        tt.pay({
-          service: 5,
-          orderInfo: {
-            order_id: res.data.order_id,
-            order_token: res.data.order_token
-          },
-          success: payRes => {
-            if (payRes.code === 0) {
-              this.$loading('支付结果查询中...')
-              this.query()
-            } else {
-              this.$u.toast('支付失败')
-            }
-          },
-          fail: err => {
-            console.log('-->data', err)
-            // 调起收银台失败处理逻辑
-          }
-        })
-        // #endif
-        // #ifdef MP-KUAISHOU
-        ks.pay({
-          serviceId: '1',
-          orderInfo: {
-            order_no: res.data.order_id,
-            order_info_token: res.data.order_token
-          },
-          success: payRes => {
-            this.$loading('支付结果查询中...')
-            this.query()
-          },
-          fail: err => {
-            console.log('-->data', err)
-            // 调起收银台失败处理逻辑
-          }
-        })
-        // #endif
-        this.$hideLoading()
-      }).catch(() => {
-        this.$hideLoading()
-      })
-    },
-    query() {
-      if (this.interval) return
-      this.interval = setInterval(() => {
-        this.$api.pay.query(this.payId).then(res => {
-          this.$hideLoading()
-          this.$u.toast('支付成功')
-          clearInterval(this.interval)
-          // 获取用户信息
-          this.$api.user.info().then(res => {
-            this.$store.dispatch('user/info', res.data)
-          })
-        }).catch(err => {
-          this.$hideLoading()
-        })
-      }, 1000)
-    }
-  }
-}
-</script>
-
-<style lang="scss" scoped>
-    .recharge {
-      font-size: 28rpx;
-      .container{
-        width: 700rpx;
-        border: 2rpx solid;
-        padding: 30rpx;
-        &.bottom{
-          width: 750rpx;
-          border: unset;
-        }
-        .static-text{
-          font-size: 36rpx;
-          font-weight: 600;
-          margin-bottom: 40rpx;
-          .overage{
-            font-size: 28rpx;
-            color: $info-color;
-          }
-        }
-        .overage{
-          color: $info-color;
-          text{
-            color: #FB3651 ;
-          }
-        }
-        // 播放充值
-        .play-container{
-          .episode{
-            margin: 40rpx 0 ;
-            background: linear-gradient(270deg, #6EEBE8, #FF74B9);
-            border-radius: 20rpx;
-            height: 180rpx;
-            padding: 20rpx;
-            .detail{
-              .name{
-                color: $default-color;
-                font-weight: 600;
-              }
-              .sale-box{
-                margin-top: 40rpx;
-                .sale-price{
-                  color: $primary-color;
-                  font-size: 28rpx;
-                }
-                .origin-price{
-                  color: $default-color;
-                  font-size: 24rpx;
-                  margin-left: 20rpx;
-                  text-decoration: line-through;
-                  opacity: .6;
-                  margin-top: 6rpx;
-                }
-              }
-            }
-            .buy-num{
-              color: $default-color;
-              min-width: 80px;
-              text-align: center;
-            }
-          }
-        }
-        // 充值套餐
-        .recharge-group{
-          margin-top: 30rpx;
-          .recharge-item{
-            border: 4rpx solid $primary-color;
-            width: calc(#{600rpx} / 2);
-            margin-right: 20rpx;
-            margin-bottom: 20rpx;
-            border-radius: 10rpx;
-            padding: 40rpx 20rpx;
-            transition: .3s;
-            &:nth-child(2n){
-              margin-right: 0;
-            }
-            &.active{
-              background: #1b1e32;
-              border: 4rpx solid #1b1e32;
-              .price{
-                color: $default-color;
-              }
-            }
-            .price{
-              margin-bottom: 40rpx;
-              font-size: 38rpx;
-            }
-            .gold{
-              color: $primary-color;
-              margin-bottom: 10rpx;
-            }
-            .gift{
-              color: $info-color;
-            }
-          }
-        }
-        .btn{
-          background: linear-gradient(270deg, #6EEBE8 0%, #FF74B9 100%);
-          width: 90%;
-          margin: 40rpx auto;
-          padding: 20rpx 0;
-          text-align: center;
-          border-radius: 40rpx;
-          color: $default-color;
-          letter-spacing: .1rem;
-        }
-      }
-    }
-</style>

+ 24 - 18
mini/components/SwiperBox/index.vue

@@ -14,12 +14,11 @@
       :list="list"
       :list="list"
       :height="height"
       :height="height"
       :radius="radius"
       :radius="radius"
-      style="width: 100%"
+      :style="{width: '100%', backgroundPosition: 'center'}"
       :indicator="true"
       :indicator="true"
       :show-title="true"
       :show-title="true"
       indicator-mode="dot"
       indicator-mode="dot"
       :indicator-style="{bottom: '24rpx'}"
       :indicator-style="{bottom: '24rpx'}"
-      @click="handleClick"
       @change="handleChange"
       @change="handleChange"
     >
     >
       <view
       <view
@@ -29,8 +28,11 @@
         <view
         <view
           v-for="(item, index) in list"
           v-for="(item, index) in list"
           :key="index"
           :key="index"
-          class="indicator__dot"
-          :class="[index === currentNum && 'indicator__dot--active']"
+          class="indicator__line"
+          :style="{
+            width: `${(380 / list.length)}rpx`
+          }"
+          :class="[index === currentNum && 'indicator__line--active']"
         />
         />
       </view>
       </view>
     </u-swiper>
     </u-swiper>
@@ -48,7 +50,11 @@ export default {
     },
     },
     radius: {
     radius: {
       type: [Number, String],
       type: [Number, String],
-      default: '10rpx'
+      default: '0rpx'
+    },
+    type: {
+      type: String,
+      default: 'login'
     }
     }
   },
   },
   data() {
   data() {
@@ -64,15 +70,12 @@ export default {
     this.getSwiper()
     this.getSwiper()
   },
   },
   methods: {
   methods: {
-    handleClick(index) {
-      const item = this.list[index]
-      console.log('-->data', item)
-    },
     handleChange(e) {
     handleChange(e) {
       this.currentNum = e.current
       this.currentNum = e.current
     },
     },
     getSwiper() {
     getSwiper() {
-      this.$api.setting.banner().then(res => {
+      const method = this.type === 'login' ? 'loginBanner' : ''
+      this.$api.setting[method]().then(res => {
         this.loading = false
         this.loading = false
         res.data.forEach(obj => {
         res.data.forEach(obj => {
           this.list.push(obj.image)
           this.list.push(obj.image)
@@ -88,23 +91,26 @@ export default {
     margin: 20rpx 0;
     margin: 20rpx 0;
     border-radius: 8rpx;
     border-radius: 8rpx;
     &.loading{
     &.loading{
-      background-color: #1B203C;
+      background-color: #FFFFFF;
     }
     }
     .indicator {
     .indicator {
       display: flex;
       display: flex;
       flex-direction: row;
       flex-direction: row;
       justify-content: center;
       justify-content: center;
+      position: relative;
+      margin-bottom: 25%;
 
 
-      &__dot {
-        height: 20rpx;
-        width: 20rpx;
-        border-radius: 50%;
-        background-color: rgba(255, 255, 255, 0.35);
-        margin: 0 10px;
+      &__line {
+        height: 1rpx;
+        width: 50rpx;
+        background-color: rgba(255, 255, 255, 0.7);
         transition: background-color 0.3s;
         transition: background-color 0.3s;
+        border-radius: 5rpx;
 
 
         &--active {
         &--active {
-          background-color: #6EEBE8;
+          height: 8rpx;
+          margin-top: -4rpx;
+          background: #fff;
         }
         }
       }
       }
     }
     }

+ 0 - 151
mini/components/TabBar/index.vue

@@ -1,151 +0,0 @@
-<template>
-  <view class="tab-bar">
-    <view class="content main-between cross-center">
-      <view
-        v-for="(item,index) in list"
-        :key="index"
-        class="tab-item dir-top-wrap cross-center main-center"
-        :class="{active: active === index}"
-        @click="handleSwitch(index)"
-      >
-        <view class="icon">
-          <image :src="active === index ? (item.selected_icon ? item.selected_icon : item.selectedIconPath) : (item.icon ? item.icon : item.iconPath)" mode="aspectFit" />
-        </view>
-        <text>{{ item.text }}</text>
-      </view>
-    </view>
-  </view>
-</template>
-
-<script>
-import { mapState } from 'vuex'
-
-export default {
-  name: 'TabBar',
-  data() {
-    return {
-      color: '#8a8a8a',
-      selectedColor: '#6EEBE8',
-      types: {
-        1: {
-          'pagePath': '/pages/index/index',
-          'iconPath': '/static/image/tab/home.png',
-          'selectedIconPath': '/static/image/tab/home-HL.png'
-        },
-        2: {
-          'pagePath': '/pages/trace/index',
-          'iconPath': '/static/image/tab/trace.png',
-          'selectedIconPath': '/static/image/tab/trace-HL.png'
-        },
-        3: {
-          'pagePath': '/pages/my/index',
-          'iconPath': '/static/image/tab/my.png',
-          'selectedIconPath': '/static/image/tab/my-HL.png'
-        }
-      },
-      list: [
-        {
-          'pagePath': '/pages/index/index',
-          'iconPath': '/static/image/tab/home.png',
-          'selectedIconPath': '/static/image/tab/home-HL.png',
-          'text': '首页'
-        },
-        {
-          'pagePath': '/pages/trace/index',
-          'iconPath': '/static/image/tab/trace.png',
-          'selectedIconPath': '/static/image/tab/trace-HL.png',
-          'text': '追剧'
-        },
-        {
-          'pagePath': '/pages/my/index',
-          'iconPath': '/static/image/tab/my.png',
-          'selectedIconPath': '/static/image/tab/my-HL.png',
-          'text': '我的'
-        }
-      ]
-    }
-  },
-  computed: {
-    ...mapState({
-      active: seate => seate.tab.index
-    })
-  },
-  created() {
-    this.calc()
-    uni.hideTabBar()
-    this.getData()
-  },
-  methods: {
-    handleSwitch(index) {
-      if (index === this.active) {
-        return
-      }
-      this.$store.dispatch('tab/index', index)
-      const item = this.list[index]
-      uni.switchTab({
-        url: item.pagePath
-      })
-    },
-    calc() {
-      let active = 1
-      const page = uni.$u.page().replace('//', '/')
-      this.list.forEach((obj, index) => {
-        if (obj.pagePath === page) {
-          active = index
-        }
-      })
-      if (active !== this.active) {
-        this.$store.dispatch('tab/index', active)
-      }
-    },
-    getData() {
-      this.$api.setting.tabBar().then(res => {
-        const data = res.data
-        data.forEach(obj => {
-          Object.assign(obj, this.types[obj.type])
-        })
-
-        console.log('-->data', data)
-
-        this.list = data
-      })
-    }
-  }
-}
-</script>
-
-<style lang="scss" scoped>
-  .tab-bar{
-    position: fixed;
-    bottom: 0;
-    left: 0;
-    width: 100%;
-    .content{
-      background: $bg-color;
-      position: absolute;
-      bottom: 0;
-      width: 100%;
-      height: 120rpx;
-      background-size: 110% 100%;
-
-      .tab-item{
-        flex: 1;
-        color: #8a8a8a;
-        transition: .3s;
-        font-size: 24rpx;
-        position: relative;
-        &.active{
-          color: #6EEBE8;
-        }
-        .icon{
-          width: 42rpx;
-          height: 42rpx;
-          image{
-            height: 100%;
-            width: 100%;
-          }
-        }
-      }
-    }
-  }
-</style>

+ 2 - 2
mini/manifest.json

@@ -50,10 +50,10 @@
     "quickapp" : {},
     "quickapp" : {},
     /* 小程序特有相关 */
     /* 小程序特有相关 */
     "mp-weixin" : {
     "mp-weixin" : {
-        "appid" : "wx4d0105fabd19dfe2",
+        "appid" : "wxf98af713ffa42ad9",
         "setting" : {
         "setting" : {
             "urlCheck" : false,
             "urlCheck" : false,
-            "minified" : true,
+            "minified" : false,
             "postcss" : false,
             "postcss" : false,
             "es6" : false
             "es6" : false
         },
         },

+ 60 - 31
mini/pages.json

@@ -5,45 +5,74 @@
   },
   },
   "pages": [
   "pages": [
     //pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages
     //pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages
+    {
+      "path": "pages/index",
+      "style": {
+        "navigationBarTitleText": "极创社",
+        "enablePullDownRefresh": false
+      }
+    },
     {
     {
       "path": "pages/login",
       "path": "pages/login",
       "style": {
       "style": {
-        "navigationBarTitleText": "极创社剧场",
+        "navigationBarTitleText": "登陆",
+        "enablePullDownRefresh": false
+      }
+    },
+    {
+      "path": "pages/forget",
+      "style": {
+        "navigationBarTitleText": "联系平台",
+        "enablePullDownRefresh": false
+      }
+    },
+    {
+      "path": "pages/product/index",
+      "style": {
+        "navigationBarTitleText": "产品",
+        "enablePullDownRefresh": false
+      }
+    },
+    {
+      "path": "pages/product/list",
+      "style": {
+        "navigationBarTitleText": "产品列表",
+        "enablePullDownRefresh": false
+      }
+    },
+    {
+      "path": "pages/product/detail",
+      "style": {
+        "navigationBarTitleText": "产品详情",
+        "enablePullDownRefresh": false
+      }
+    },
+    {
+      "path": "pages/case/index",
+      "style": {
+        "navigationBarTitleText": "案例",
+        "enablePullDownRefresh": false
+      }
+    },
+    {
+      "path": "pages/case/list",
+      "style": {
+        "navigationBarTitleText": "案例列表",
+        "enablePullDownRefresh": false
+      }
+    },
+    {
+      "path": "pages/case/detail",
+      "style": {
+        "navigationBarTitleText": "案例详情",
         "enablePullDownRefresh": false
         "enablePullDownRefresh": false
       }
       }
     }
     }
   ],
   ],
   "globalStyle": {
   "globalStyle": {
-    "navigationBarTextStyle": "white", // black or white
+    "navigationBarTextStyle": "black", // black or white
     "navigationBarTitleText": "极创社",
     "navigationBarTitleText": "极创社",
-    "navigationBarBackgroundColor": "#151728",
-    "backgroundColor": "#151728"
-  },
-  "tabBar": {
-    "custom": true,
-    "color": "#8a8a8a",
-    "selectedColor": "#6EEBE8",
-    "backgroundColor": "#17162B",
-    "borderStyle": "black",
-    "list": [
-      {
-        "pagePath": "pages/index/index",
-        "iconPath": "static/image/tab/home.png",
-        "selectedIconPath": "static/image/tab/home-HL.png",
-        "text": "极创社"
-      },
-      {
-        "pagePath": "pages/trace/index",
-        "iconPath": "static/image/tab/trace.png",
-        "selectedIconPath": "static/image/tab/trace-HL.png",
-        "text": "极创社"
-      },
-      {
-        "pagePath": "pages/my/index",
-        "iconPath": "static/image/tab/my.png",
-        "selectedIconPath": "static/image/tab/my-HL.png",
-        "text": "极创社"
-      }
-    ]
+    "navigationBarBackgroundColor": "#ffffff",
+    "backgroundColor": "#ffffff"
   }
   }
 }
 }

+ 51 - 0
mini/pages/case/detail.vue

@@ -0,0 +1,51 @@
+<template>
+  <page-loading :loading="loading" class="ProductDetail">
+    <u-parse :content="info.detail" />
+  </page-loading>
+</template>
+
+<script>
+import { mapState } from 'vuex'
+import PageLoading from '@/components/PageLoading'
+export default {
+  name: 'CaseDetail',
+  components: { PageLoading },
+  data() {
+    return {
+      id: '',
+      loading: true,
+      intoView: '',
+      info: {},
+      specs: []
+    }
+  },
+  computed: {
+    ...mapState({
+      userInfo: seate => seate.user.info
+    })
+  },
+  methods: {
+    getDetail() {
+      this.$api.cases.detail(this.id).then(res => {
+        this.loading = false
+        this.info = res.data
+      })
+    },
+    viewer() {
+      this.$api.cases.viewer(this.id).then(res => {
+
+      })
+    }
+  },
+  onLoad(options) {
+    this.id = options.id
+    this.getDetail()
+    this.viewer()
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.ProductDetail {
+}
+</style>

+ 118 - 0
mini/pages/case/index.vue

@@ -0,0 +1,118 @@
+<template>
+  <page-loading :loading="loading">
+    <view class="product-page">
+      <view class="logo logo-black" />
+      <u-search
+        v-model="keywords"
+        placeholder="搜索展厅"
+        :show-action="false"
+        shape="square"
+        bg-color="#fff"
+        border-color="#ccc"
+        height="84rpx"
+        @search="handleSearch"
+      />
+
+      <view
+        class="cases-list dir-left-wrap"
+      >
+        <view
+          v-for="(item,index) in lists"
+          :key="index"
+          class="cases-item main-left"
+          @click="$u.route({url:`/pages/case/detail?id=${item.id}`})"
+        >
+          <view class="detail dir-top-wrap">
+            <text>热销展厅排名</text>
+            <view class="name"> {{ item.name }}</view>
+          </view>
+          <view class="cover-img">
+            <image :src="item.cover_img" mode="aspectFill" />
+          </view>
+        </view>
+      </view>
+    </view>
+  </page-loading>
+</template>
+
+<script>
+import PageLoading from '../../components/PageLoading'
+export default {
+  name: 'Product',
+  components: { PageLoading },
+  data() {
+    return {
+      loading: false,
+      keywords: '',
+      limit: 3,
+      page: 1,
+      lists: []
+    }
+  },
+  computed: {},
+  methods: {
+    handleSearch() {
+      this.$u.route({ url: `/pages/case/list?keywords=${this.keywords}` })
+    },
+    getLists() {
+      this.$api.cases.search({
+        limit: this.limit,
+        page: this.page
+      }).then(res => {
+        this.loading = false
+        this.lists = res.data
+      })
+    }
+  },
+  onLoad() {
+    this.loading = true
+    this.getLists()
+  },
+  onShow() {
+    this.getLists()
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.product-page {
+  padding: 0 30rpx;
+  overflow: hidden;
+  height: 100vh;
+  .logo{
+    margin: 30rpx auto;
+  }
+  .cases-list{
+    margin-top: 80rpx;
+    .cases-item{
+      width: 690rpx;
+      margin-bottom: 10rpx;
+      &:last-child{
+        margin-bottom: 0;
+      }
+      .detail{
+        padding: 50rpx 0 20rpx 50rpx;
+        font-size: 24rpx;
+        color: #333333;
+        background: #F2F2F2;
+        flex: 1;
+        text{
+          color: #b4936d;
+        }
+        .name{
+          margin-top: 16rpx;
+          font-size: 42rpx;
+        }
+      }
+      .cover-img{
+        width: 355rpx;
+        height: 250rpx;
+        image{
+          width: 100%;
+          height: 100%;
+        }
+      }
+    }
+  }
+}
+</style>

+ 116 - 0
mini/pages/case/list.vue

@@ -0,0 +1,116 @@
+<template>
+  <page-loading :loading="loading">
+    <view class="case-page">
+      <view
+        class="case-list dir-left-wrap"
+      >
+        <view
+          v-for="(item,index) in lists"
+          :key="index"
+          class="case-item dir-top-wrap"
+          @click="$u.route({url: `/pages/case/detail?id=${item.id}`})"
+        >
+          <view class="cover-img">
+            <image :src="item.cover_img" mode="aspectFill" />
+          </view>
+          <view class="detail main-center cross-center">
+            {{ item.name }}
+          </view>
+        </view>
+      </view>
+      <u-loadmore
+        :status="loadmore.status"
+        :loading-text="loadmore.loadingText"
+        :loadmore-text="loadmore.loadmoreText"
+        :nomore-text="loadmore.nomoreText"
+      />
+    </view>
+  </page-loading>
+</template>
+
+<script>
+import PageLoading from '@/components/PageLoading'
+export default {
+  name: 'Case',
+  components: { PageLoading },
+  data() {
+    return {
+      loading: false,
+      keywords: '',
+      limit: 6,
+      page: 1,
+      isMore: true,
+      lists: [],
+      loadmore: {
+        status: 'loadmore',
+        loadingText: '努力加载中...',
+        loadmoreText: '轻轻上拉',
+        nomoreText: '没有更多了~'
+      }
+    }
+  },
+  computed: {},
+  methods: {
+    handleSearch() {
+      this.getLists()
+    },
+    getLists() {
+      this.loadmore.status = 'loading'
+      this.$api.cases.search({
+        limit: this.limit,
+        keywords: this.keywords,
+        page: this.page
+      }).then(res => {
+        this.loading = false
+        if (res.data.length) {
+          this.loadmore.status = 'loadmore'
+          this.lists = this.lists.concat(res.data)
+        } else {
+          this.loadmore.status = 'nomore'
+          this.isMore = false
+        }
+      })
+    }
+  },
+  onLoad(options) {
+    this.keywords = options.keywords
+    this.loading = true
+    this.getLists()
+  },
+  onReachBottom(e) {
+    console.log('-->data', e)
+    if (!this.isMore) return
+    this.page += 1
+    this.getLists()
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.case-page {
+  padding: 0 30rpx 80rpx;
+  .case-list{
+    margin-top: 40rpx;
+    .case-item{
+      width: 335rpx;
+      margin-bottom: 50rpx;
+      &:nth-child(2n){
+        margin-left: 20rpx;
+      }
+      .cover-img{
+        width: 335rpx;
+        height: 370rpx;
+        image{
+          width: 100%;
+          height: 100%;
+        }
+      }
+      .detail{
+        padding: 20rpx 0;
+        font-size: 24rpx;
+        color: #333333;
+      }
+    }
+  }
+}
+</style>

+ 119 - 0
mini/pages/forget.vue

@@ -0,0 +1,119 @@
+<template>
+  <page-loading class="forget-page" :loading="loading">
+    <view class="logo logo-white" />
+    <view class="static-text">本平台仅限极创设会员使用,无账号的请联系下方客服电话或微信</view>
+    <view
+      v-for="(item,index) in contacts"
+      :key="index"
+      class="contact-item"
+    >
+      <view
+        v-if="item.phone_num"
+        @click="handleCallPhone(item.phone_num)"
+      >
+        <view class="text main-center cross-center">{{ item.name }}</view>
+        <view class="phone-num main-center cross-center">
+          <view class="icon phone" />
+          <text>{{ item.phone_num }}</text>
+        </view>
+      </view>
+      <view
+        v-else-if="item.wechat_num"
+        @click="$util.copyText(item.wechat_num)"
+      >
+        <view class="text main-center cross-center">{{ item.name }}</view>
+        <view class="phone-num main-center cross-center">
+          <view class="icon wechat" />
+          <text>{{ item.wechat_num }}</text>
+          <view class="icon copy" />
+        </view>
+      </view>
+    </view>
+  </page-loading>
+</template>
+
+<script>
+
+import PageLoading from '@/components/PageLoading'
+export default {
+  name: 'Forget',
+  components: { PageLoading },
+  data() {
+    return {
+      loading: true,
+      contacts: []
+    }
+  },
+  computed: {},
+  methods: {
+    handleCallPhone(phoneNumber) {
+      uni.makePhoneCall({
+        phoneNumber: phoneNumber
+      })
+    },
+    getContact() {
+      this.$api.setting.loginContact().then(res => {
+        this.loading = false
+        this.contacts = res.data
+      })
+    }
+  },
+  onLoad() {
+    this.getContact()
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.forget-page {
+  padding: 0 30rpx;
+  .logo{
+    margin: 100rpx auto 50rpx;
+  }
+  .static-text{
+    text-align: center;
+    margin-bottom: 60rpx;
+    line-height: 1.65;
+  }
+
+  .contact-item{
+    .text{
+      position: relative;
+      font-size: 38rpx;
+      &:before,&:after{
+        content: "";
+        position: absolute;
+        width: 20rpx;
+        height: 4rpx;
+        background: #000;
+        transform: translateX(-400%);
+      }
+      &:after{
+        transform: translateX(400%);
+      }
+    }
+    .phone-num{
+      margin: 10rpx 0 40rpx;
+      .icon{
+        width: 30rpx;
+        height: 30rpx;
+        background-repeat: no-repeat;
+        background-position: center;
+        background-size: 100%;
+        margin: 0 8rpx;
+        &.phone{
+          background-image: url("/static/image/icon/phone.png");
+        }
+        &.wechat{
+          background-image: url("/static/image/icon/wechat.png");
+        }
+        &.copy{
+          background-image: url("/static/image/icon/copy.png");
+          background-size: 90%;
+          margin-left: 16rpx;
+        }
+      }
+    }
+  }
+}
+</style>

+ 46 - 0
mini/pages/index.vue

@@ -0,0 +1,46 @@
+<template>
+  <page-loading class="index-page" :loading="loading">
+    <index-logged v-if="isLogin" />
+    <index-login v-else />
+  </page-loading>
+</template>
+
+<script>
+import IndexLogged from '../components/Index/Logged'
+import IndexLogin from '../components/Index/Login'
+import PageLoading from '../components/PageLoading'
+export default {
+  name: 'Index',
+  components: { PageLoading, IndexLogged, IndexLogin },
+  data() {
+    return {
+      isLogin: false,
+      loading: true
+    }
+  },
+  computed: {},
+  methods: {
+    async authorize() {
+      this.$loading('小程序授权中...')
+      await this.$api.user.authorize().then(async res => {
+        this.$hideLoading()
+        await this.$store.dispatch('user/authorize')
+      })
+    }
+  },
+  async onShow() {
+    if (!this.$api.user.isAuthorize()) {
+      await this.authorize()
+    }
+    this.isLogin = this.$api.user.isLogin()
+    setTimeout(() => {
+      this.loading = false
+    }, 500)
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.index-page {
+}
+</style>

+ 125 - 17
mini/pages/login.vue

@@ -1,38 +1,146 @@
 <template>
 <template>
-  <view class="login" />
+  <view class="login-page">
+    <view class="logo logo-white" />
+    <view class="form-box">
+      <u-input
+        v-model="model.account"
+        placeholder="手机号码"
+        :custom-style="inputStyle"
+        :max-length="11"
+      />
+      <u-input
+        v-model="model.password"
+        :password="passwordShow"
+        placeholder="密码"
+        :custom-style="inputStyle"
+      >
+        <template slot="suffix">
+          <u-icon
+            size="42rpx"
+            :name="passwordShow?'eye-fill':'eye-off'"
+            @click="passwordShow=!passwordShow"
+          />
+        </template>
+      </u-input>
+      <u-button
+        text="登陆"
+        color="#000"
+        shape="square"
+        :custom-style="btnStyle"
+        :loading="loading"
+        @click="handleLogin"
+      />
+      <view
+        class="forget"
+        @click="$u.route({url: '/pages/forget'})"
+      >忘记密码?</view>
+    </view>
+    <view class="wechat-login">
+      <div class="static-text">其他登陆方式</div>
+      <button open-type="getPhoneNumber" @getphonenumber="getPhoneNumber"><view class="icon" /></button>
+    </view>
+  </view>
 </template>
 </template>
 
 
 <script>
 <script>
+import Cache from '../utils/cache'
 export default {
 export default {
+  name: 'Login',
   data() {
   data() {
     return {
     return {
-      path: '/pages/index/index'
+      passwordShow: true,
+      model: {
+        account: '',
+        password: ''
+      },
+      path: '/pages/index',
+      loading: false,
+      inputStyle: {
+        borderRadius: 0,
+        borderColor: '#000 !important',
+        marginTop: '30rpx',
+        padding: '20rpx 30rpx',
+        fontSize: '38rpx'
+      },
+      btnStyle: {
+        marginTop: '30rpx',
+        width: '100%',
+        borderRadius: 0,
+        height: '92rpx',
+        letterSpacing: '0.1rem',
+        fontSize: '38rpx'
+      }
     }
     }
   },
   },
   computed: {},
   computed: {},
   methods: {
   methods: {
-    login() {
-      this.$loading('登陆中...')
-      this.$api.user.login().then(async res => {
-        this.$hideLoading()
-        const { token, user_info } = res.data
-        await this.$store.dispatch('user/token', token)
-        await this.$store.dispatch('user/info', user_info)
-        uni.reLaunch({
-          url: this.path.replace('//', '/')
-        })
+    handleLogin() {
+      if (!this.model.account || this.model.account.length < 11) {
+        this.$u.toast('请输入正确的手机号')
+        return
+      }
+
+      if (!this.model.password) {
+        this.$u.toast('请输入密码')
+        return
+      }
+      this.loading = true
+      uni.login({
+        provider: uni.$u.platform,
+        success: loginRes => {
+          uni.hideLoading()
+          this.$api.user.login(this.model.account, this.model.password, loginRes.code).then(async res => {
+            this.loading = false
+            await this.$store.dispatch('user/token', res.data.token)
+            await this.$store.dispatch('user/info', res.data.user_info)
+            uni.reLaunch({
+              url: this.path.replace('//', '/')
+            })
+          }).catch(() => {
+            this.loading = false
+          })
+        }
       })
       })
+    },
+    getPhoneNumber(res) {
+      console.log('-->data', res)
     }
     }
   },
   },
-  onLoad(options) {
-    this.path = options.path ? options.path : this.path
-    this.login()
+  onLoad() {
+    if (Cache.get('path')) {
+      this.path = Cache.get('path')
+    }
   }
   }
 }
 }
 </script>
 </script>
 
 
 <style lang="scss" scoped>
 <style lang="scss" scoped>
-    .login{
-
+.login-page {
+  padding: 0 80rpx;
+  .logo{
+    margin: 100rpx auto 50rpx;
+  }
+  .forget{
+    color: #999999;
+    margin-top: 20rpx;
+    font-size: 26rpx;
+  }
+  .wechat-login{
+    margin-top: 80rpx;
+    .static-text{
+      color: #333333;
+    }
+    button{
+      width: 80rpx;
+      height: 80rpx;
+      background: transparent;
+      margin-top: 30rpx;
+      .icon{
+        width: 80rpx;
+        height: 80rpx;
+        background: url("/static/image/login/wechat.png") no-repeat center;
+      }
     }
     }
+  }
+}
 </style>
 </style>

+ 340 - 0
mini/pages/product/detail.vue

@@ -0,0 +1,340 @@
+<template>
+  <page-loading :loading="loading">
+    <scroll-view
+      class="product-detail"
+      scroll-x="false"
+      :scroll-top="scrollTop"
+      :scroll-into-view="intoView"
+      scroll-y
+    >
+      <!--  案例    -->
+      <view id="top" class="cases">
+        <image
+          v-for="(item,index) in detail.cases"
+          :key="index"
+          class="full-img"
+          :src="item"
+          mode="aspectFill"
+          @click="handlePreview(detail.cases, index)"
+        />
+      </view>
+      <!--  只有VIP用户和设计师才能查看 价格     -->
+      <template v-if="userInfo.account.type === $const.ACCOUNT_DESIGN || userInfo.account.type === $const.ACCOUNT_VIP">
+        <u-divider />
+        <!-- 资料下载  -->
+        <view id="file" class="file">
+          <view class="header main-between cross-center">
+            <view class="left main-left cross-center">
+              <view class="icon icon-file" />
+              <text>资料下载</text>
+            </view>
+            <view class="icon" @click="fileShow = !fileShow">
+              <u-icon :name="fileShow?'minus':'plus'" size="36rpx" />
+            </view>
+          </view>
+          <view class="content dir-top-wrap" :class="fileShow?'show':''">
+            <view
+              v-for="(item,index) in detail.files"
+              :key="index"
+              class="file-item main-between cross-center"
+            >
+              <text>{{ item.name }}</text>
+              <view class="icon icon-download" @click="handleCopy(item)" />
+            </view>
+          </view>
+        </view>
+      </template>
+
+      <!--  只有VIP用户才能查看 价格     -->
+      <template v-if="userInfo.account.type === $const.ACCOUNT_VIP">
+        <u-divider />
+        <!--   查看价格    -->
+        <view id="price" class="price main-between cross-center">
+          <u-text :text="specsText" color="#B4936D" size="32rpx" :lines="1" />
+          <view class="number">
+            <u-text :text="price" mode="price" color="#B4936D" size="36rpx" align="right" />
+          </view>
+        </view>
+        <!--  规格    -->
+        <u-divider />
+        <view class="specs">
+          <view
+            v-for="(item, index) in specs"
+            :key="index"
+            class="spec-item"
+          >
+            <view class="group-name main-between cross-center">
+              <text>{{ item.name }}</text>
+              <u-icon :name="item.show?'minus':'plus'" @click="handleOpenSpec(item)" />
+            </view>
+            <view v-show="item.show" class="specs-content dir-left-wrap">
+              <view
+                v-for="(spec, specIndex) in item.specs"
+                :key="specIndex"
+                class="spec main-center cross-center"
+                :class="{active: spec.active}"
+                @click="handleSelectSpec(spec)"
+              >
+                {{ spec.name }}
+              </view>
+            </view>
+            <u-divider />
+          </view>
+        </view>
+      </template>
+      <!--定位锚点-->
+      <view class="float-view dir-top-wrap  cross-center" :class="{show: showView}">
+        <view class="icon icon-close" @click="showView = false" />
+        <view class="view-item main-left cross-center" @click="scrollIntoView('top')">
+          <view class="icon icon-up" />
+          <text>回到顶部</text>
+        </view>
+        <view
+          v-if="userInfo.account.type === $const.ACCOUNT_DESIGN || userInfo.account.type === $const.ACCOUNT_VIP"
+          class="view-item main-left cross-center"
+          @click="scrollIntoView('file')"
+        >
+          <view class="icon icon-down" />
+          <text>资料下载</text>
+        </view>
+        <view
+          v-if="userInfo.account.type === $const.ACCOUNT_VIP"
+          class="view-item main-left cross-center"
+          @click="scrollIntoView('price')"
+        >
+          <view class="icon icon-eye" />
+          <text>查看价格</text>
+        </view>
+      </view>
+      <view class="float-btn icon icon-float-btn" @click="showView = !showView" />
+    </scroll-view>
+  </page-loading>
+</template>
+
+<script>
+import { mapState } from 'vuex'
+import PageLoading from '@/components/PageLoading'
+export default {
+  name: 'ProductDetail',
+  components: { PageLoading },
+  data() {
+    return {
+      id: '',
+      loading: true,
+      intoView: '',
+      detail: {},
+      specs: [],
+      showView: false,
+      scrollTop: 0,
+      fileShow: false,
+      selectedSpecs: []
+    }
+  },
+  computed: {
+    ...mapState({
+      userInfo: seate => seate.user.info
+    }),
+    price() {
+      let price = 0
+      this.selectedSpecs.forEach(obj => {
+        price += parseFloat(obj.price)
+      })
+      return price.toFixed(2)
+    },
+    specsText() {
+      const specs = []
+      this.selectedSpecs.forEach(obj => {
+        specs.push(obj.name)
+      })
+      return specs.join(' ')
+    }
+  },
+  watch: {
+    specs: {
+      handler(val) {
+        console.log('-->data', val)
+      },
+      deep: true,
+      immediate: true
+    }
+  },
+  methods: {
+    handlePreview(urls, current) {
+      uni.previewImage({
+        current,
+        urls
+      })
+    },
+    handleCopy(item) {
+      this.$api.product.download(this.id).then(res => {
+        this.$util.copyText(item.url)
+      })
+    },
+    handleOpenSpec(item) {
+      item.show = !item.show
+      console.log('-->data', item)
+      this.$forceUpdate()
+    },
+    handleSelectSpec(spec) {
+      spec.active = !spec.active
+      this.selectedSpecs = []
+      this.specs.forEach(obj => {
+        obj.specs.forEach((spec, index) => {
+          if (spec.active) {
+            this.selectedSpecs.push(spec)
+          }
+        })
+      })
+      this.$forceUpdate()
+    },
+    scrollIntoView(name) {
+      this.intoView = name
+      if (this.timeout) {
+        clearTimeout(this.timeout)
+      }
+      this.timeout = setTimeout(() => {
+        this.intoView = ''
+      })
+    },
+    getDetail() {
+      this.$api.product.detail(this.id).then(res => {
+        this.loading = false
+        res.data.specs.forEach(obj => {
+          obj.show = true
+          obj.specs.forEach((spec, index) => {
+            obj.active = false
+            spec.key = obj.id + spec.name + index
+          })
+        })
+        this.detail = res.data
+        this.specs = res.data.specs
+      })
+    },
+    viewer() {
+      this.$api.product.viewer(this.id).then(res => {
+
+      })
+    }
+  },
+  onLoad(options) {
+    this.id = options.id
+    this.getDetail()
+    this.viewer()
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.product-detail {
+  padding: 20rpx 20rpx 0;
+  width: 710rpx;
+  height: 100vh;
+  .cases{
+    .full-img{
+      width: 710rpx;
+      margin-bottom: 10rpx;
+    }
+  }
+  .file{
+    .header{
+      padding: 20rpx 0;
+      .left{
+        text{
+          margin-left: 10rpx;
+          font-size: 32rpx;
+        }
+      }
+    }
+    .content{
+      padding: 10rpx 20rpx 30rpx;
+      display: none;
+      .file-item{
+        padding: 10rpx 0 20rpx 30rpx;
+        color: #333;
+        font-size: 26rpx;
+      }
+      &.show{
+        display: flex;
+      }
+    }
+  }
+  .price{
+    margin: 50rpx 0;
+    .number{
+      max-width: 320rpx;
+    }
+  }
+  .specs{
+    padding-bottom: 80rpx;
+    .spec-item{
+      color: #333;
+      margin-bottom: 30rpx;
+      .group-name{
+        padding: 10rpx 20rpx 40rpx 0 ;
+        text{
+          font-size: 32rpx;
+          font-weight: 600;
+        }
+      }
+      .specs-content{
+        .spec{
+          margin: 0 5rpx 10rpx;
+          min-width: 130rpx;
+          padding: 10rpx 30rpx;
+          transition: .5s;
+          &.active{
+            background: #030303;
+            color: #fff;
+          }
+        }
+      }
+    }
+  }
+  .float-btn{
+    position: fixed;
+    bottom: 120rpx;
+    right: 10rpx;
+  }
+  .float-view{
+    position: fixed;
+    width: 220rpx;
+    height: 260rpx;
+    background: #fff;
+    bottom: 220rpx;
+    right: 30rpx;
+    box-shadow: 0 0 20rpx rgba(0,0,0,.1);
+    border-radius: 30rpx;
+    display: none;
+    transition: .5s;
+    padding: 10rpx;
+    &.show{
+      display: flex;
+    }
+    .icon-close{
+      position: absolute;
+      right: -10rpx;
+      top: -10rpx;
+    }
+    .view-item{
+      position: relative;
+      padding: 20rpx 0;
+      &:last-child:after{
+        content: unset;
+      }
+      &:after{
+        content: "";
+        position: absolute;
+        width: 68%;
+        bottom: 0;
+        left: 28%;
+        height: 1rpx;
+        background: #EFEFEF;
+      }
+      text{
+        margin-left: 16rpx;
+        color: #333333;
+      }
+    }
+  }
+}
+</style>

+ 116 - 0
mini/pages/product/index.vue

@@ -0,0 +1,116 @@
+<template>
+  <page-loading :loading="loading">
+    <view class="product-page">
+      <view class="logo logo-black" />
+      <u-search
+        v-model="keywords"
+        placeholder="搜索产品"
+        :show-action="false"
+        shape="square"
+        bg-color="#fff"
+        border-color="#ccc"
+        height="84rpx"
+        @search="handleSearch"
+      />
+      <view
+        class="product-list dir-left-wrap"
+      >
+        <view
+          v-for="(item,index) in lists"
+          :key="index"
+          class="product-item main-left"
+          @click="$u.route({url: `/pages/product/detail?id=${item.id}`})"
+        >
+          <view class="detail dir-top-wrap">
+            <text>热销品类排名</text>
+            <view class="name"> {{ item.name }}</view>
+          </view>
+          <view class="cover-img">
+            <image :src="item.cover_img" mode="aspectFill" />
+          </view>
+        </view>
+      </view>
+    </view>
+  </page-loading>
+</template>
+
+<script>
+import PageLoading from '../../components/PageLoading'
+export default {
+  name: 'Product',
+  components: { PageLoading },
+  data() {
+    return {
+      loading: false,
+      keywords: '',
+      limit: 3,
+      lists: []
+    }
+  },
+  computed: {},
+  methods: {
+    handleSearch() {
+      this.$u.route({ url: `/pages/product/list?keywords=${this.keywords}` })
+    },
+    getLists() {
+      this.$api.product.search({
+        limit: this.limit,
+        page: this.page
+      }).then(res => {
+        this.loading = false
+        this.lists = res.data
+      })
+    }
+  },
+  onLoad() {
+    this.loading = true
+    this.getLists()
+  },
+  onShow() {
+    this.getLists()
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.product-page {
+  padding: 0 30rpx;
+  overflow: hidden;
+  height: 100vh;
+  .logo{
+    margin: 30rpx auto;
+  }
+  .product-list{
+    margin-top: 40rpx;
+    .product-item{
+      width: 690rpx;
+      margin-bottom: 10rpx;
+      &:last-child{
+        margin-bottom: 0;
+      }
+      .detail{
+        padding: 50rpx 0 20rpx 50rpx;
+        font-size: 24rpx;
+        color: #333333;
+        background: #F2F2F2;
+        flex: 1;
+        text{
+          color: #b4936d;
+        }
+        .name{
+          margin-top: 16rpx;
+          font-size: 42rpx;
+        }
+      }
+      .cover-img{
+        width: 355rpx;
+        height: 250rpx;
+        image{
+          width: 100%;
+          height: 100%;
+        }
+      }
+    }
+  }
+}
+</style>

+ 116 - 0
mini/pages/product/list.vue

@@ -0,0 +1,116 @@
+<template>
+  <page-loading :loading="loading">
+    <view class="product-page">
+      <view
+        class="product-list dir-left-wrap"
+      >
+        <view
+          v-for="(item,index) in lists"
+          :key="index"
+          class="product-item dir-top-wrap"
+          @click="$u.route({url: `/pages/product/detail?id=${item.id}`})"
+        >
+          <view class="cover-img">
+            <image :src="item.cover_img" mode="aspectFill" />
+          </view>
+          <view class="detail main-center cross-center">
+            {{ item.name }}
+          </view>
+        </view>
+      </view>
+      <u-loadmore
+        :status="loadmore.status"
+        :loading-text="loadmore.loadingText"
+        :loadmore-text="loadmore.loadmoreText"
+        :nomore-text="loadmore.nomoreText"
+      />
+    </view>
+  </page-loading>
+</template>
+
+<script>
+import PageLoading from '../../components/PageLoading'
+export default {
+  name: 'Product',
+  components: { PageLoading },
+  data() {
+    return {
+      loading: false,
+      keywords: '',
+      limit: 6,
+      page: 1,
+      isMore: true,
+      lists: [],
+      loadmore: {
+        status: 'loadmore',
+        loadingText: '努力加载中...',
+        loadmoreText: '轻轻上拉',
+        nomoreText: '没有更多了~'
+      }
+    }
+  },
+  computed: {},
+  methods: {
+    handleSearch() {
+      this.getLists()
+    },
+    getLists() {
+      this.loadmore.status = 'loading'
+      this.$api.product.search({
+        limit: this.limit,
+        keywords: this.keywords,
+        page: this.page
+      }).then(res => {
+        this.loading = false
+        if (res.data.length) {
+          this.loadmore.status = 'loadmore'
+          this.lists = this.lists.concat(res.data)
+        } else {
+          this.loadmore.status = 'nomore'
+          this.isMore = false
+        }
+      })
+    }
+  },
+  onLoad(options) {
+    this.keywords = options.keywords
+    this.loading = true
+    this.getLists()
+  },
+  onReachBottom(e) {
+    console.log('-->data', e)
+    if (!this.isMore) return
+    this.page += 1
+    this.getLists()
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.product-page {
+  padding: 0 30rpx 80rpx;
+  .product-list{
+    margin-top: 40rpx;
+    .product-item{
+      width: 335rpx;
+      margin-bottom: 50rpx;
+      &:nth-child(2n){
+        margin-left: 20rpx;
+      }
+      .cover-img{
+        width: 335rpx;
+        height: 370rpx;
+        image{
+          width: 100%;
+          height: 100%;
+        }
+      }
+      .detail{
+        padding: 20rpx 0;
+        font-size: 24rpx;
+        color: #333333;
+      }
+    }
+  }
+}
+</style>

+ 65 - 0
mini/static/css/common.scss

@@ -15,3 +15,68 @@ uni-page-body{
   display: flex;
   display: flex;
   visibility: hidden;
   visibility: hidden;
 }
 }
+
+.logo-black{
+  background: url("/static/image/login/logo.png") no-repeat center;
+  background-size: 100%;
+  width: 120rpx;
+  height: 120rpx;
+}
+
+.logo-white{
+  width: 180rpx;
+  height: 180rpx;
+  border-radius: 50%;
+  position: relative;
+  background: url("/static/image/login/logo-white.png") #000 no-repeat center;
+  background-size: 60%;
+}
+.icon{
+  width: 40rpx;
+  height: 40rpx;
+  background-position: center;
+  background-repeat: no-repeat;
+  background-size: contain;
+  &.icon-file{
+    width: 36rpx;
+    height: 36rpx;
+    background-image: url("/static/image/icon/file.png");
+  }
+  &.icon-close{
+    background-image: url("/static/image/icon/close.png");
+  }
+  &.icon-copy{
+    background-image: url("/static/image/icon/copy.png");
+  }
+  &.icon-up{
+    width: 32rpx;
+    height: 32rpx;
+    background-image: url("/static/image/icon/up.png");
+  }
+  &.icon-down{
+    width: 32rpx;
+    height: 32rpx;
+    background-image: url("/static/image/icon/down.png");
+  }
+  &.icon-download{
+    width: 30rpx;
+    height: 34rpx;
+    background-image: url("/static/image/icon/download.png");
+  }
+  &.icon-eye{
+    width: 32rpx;
+    height: 32rpx;
+    background-image: url("/static/image/icon/eye.png");
+  }
+  &.icon-phone{
+    background-image: url("/static/image/icon/phone.png");
+  }
+  &.icon-wechat{
+    background-image: url("/static/image/icon/wechat.png");
+  }
+  &.icon-float-btn{
+    width: 100rpx;
+    height: 100rpx;
+    background-image: url("/static/image/icon/float-btn.png");
+  }
+}

+ 3 - 6
mini/static/css/variable.scss

@@ -1,8 +1,5 @@
-$bg-color: #151729;
+$bg-color: #ffffff;
 
 
-$primary-color: #6EEBE8;
-$dark-color: #48979C;
+$primary-color: #030303;
 $default-color: #FFFFFF;
 $default-color: #FFFFFF;
-$info-color: #6F717F;
-
-$pink-color: #FF74B9;
+$info-color: #333333;

BIN
mini/static/image/default-head-img.png


BIN
mini/static/image/default-movie.png


BIN
mini/static/image/gold-bag.png


BIN
mini/static/image/gold.png


BIN
mini/static/image/icon/close.png


BIN
mini/static/image/icon/copy.png


BIN
mini/static/image/icon/down.png


BIN
mini/static/image/icon/download.png


BIN
mini/static/image/icon/eye.png


BIN
mini/static/image/icon/file.png


BIN
mini/static/image/icon/float-btn.png


BIN
mini/static/image/icon/phone.png


BIN
mini/static/image/icon/up.png


BIN
mini/static/image/icon/wechat.png


BIN
mini/static/image/login/logo-white.png


BIN
mini/static/image/login/logo.png


BIN
mini/static/image/login/wechat.png


BIN
mini/static/image/member-line-bg.png


BIN
mini/static/image/member-selected-bg.png


BIN
mini/static/image/my-page/contact.png


BIN
mini/static/image/my-page/order.png


BIN
mini/static/image/my-page/protocol.png


BIN
mini/static/image/my-page/recharge.png


BIN
mini/static/image/my-page/share.png


BIN
mini/static/image/my-recharge-bg.png


BIN
mini/static/image/playing.png


BIN
mini/static/image/tab/home-HL.png


BIN
mini/static/image/tab/home.png


BIN
mini/static/image/tab/my-HL.png


BIN
mini/static/image/tab/my.png


BIN
mini/static/image/tab/trace-HL.png


BIN
mini/static/image/tab/trace.png


BIN
mini/static/image/video.png


+ 9 - 1
mini/store/modules/user.js

@@ -1,8 +1,9 @@
-import { getToken, setToken } from '@/utils/auth'
+import { getToken, setToken, getAuthorize, setAuthorize } from '@/utils/auth'
 import Cache from '@/utils/cache'
 import Cache from '@/utils/cache'
 const getDefaultState = () => {
 const getDefaultState = () => {
   return {
   return {
     token: getToken(),
     token: getToken(),
+    authorize: getAuthorize(),
     info: Cache.get('userInfo')
     info: Cache.get('userInfo')
   }
   }
 }
 }
@@ -17,10 +18,17 @@ const mutations = {
   SET_INFO: (state, info) => {
   SET_INFO: (state, info) => {
     Cache.set('userInfo', info)
     Cache.set('userInfo', info)
     state.info = info
     state.info = info
+  },
+  SET_AUTHORIZE: (state) => {
+    setAuthorize()
+    state.authorize = true
   }
   }
 }
 }
 
 
 const actions = {
 const actions = {
+  authorize({ commit }) {
+    commit('SET_AUTHORIZE')
+  },
   token({ commit }, token) {
   token({ commit }, token) {
     commit('SET_TOKEN', token)
     commit('SET_TOKEN', token)
   },
   },

+ 162 - 162
mini/uni_modules/uview-ui/components/u-search/u-search.vue

@@ -1,80 +1,80 @@
 <template>
 <template>
-	<view
-	    class="u-search"
-	    @tap="clickHandler"
-	    :style="[{
-			margin: margin,
-		}, $u.addStyle(customStyle)]"
-	>
-		<view
-		    class="u-search__content"
-		    :style="{
-				backgroundColor: bgColor,
-				borderRadius: shape == 'round' ? '100px' : '4px',
-				borderColor: borderColor,
-			}"
-		>
-			<template v-if="$slots.label || label !== null">
-				<slot name="label">
-					<text class="u-search__content__label">{{ label }}</text>
-				</slot>
-			</template>
-			<view class="u-search__content__icon">
-				<u-icon
-					@tap="clickIcon"
-				    :size="searchIconSize"
-				    :name="searchIcon"
-				    :color="searchIconColor ? searchIconColor : color"
-				></u-icon>
-			</view>
-			<input
-			    confirm-type="search"
-			    @blur="blur"
-			    :value="value"
-			    @confirm="search"
-			    @input="inputChange"
-			    :disabled="disabled"
-			    @focus="getFocus"
-			    :focus="focus"
-			    :maxlength="maxlength"
-			    placeholder-class="u-search__content__input--placeholder"
-			    :placeholder="placeholder"
-			    :placeholder-style="`color: ${placeholderColor}`"
-			    class="u-search__content__input"
-			    type="text"
-			    :style="[{
-					textAlign: inputAlign,
-					color: color,
-					backgroundColor: bgColor,
-					height: $u.addUnit(height)
-				}, inputStyle]"
-			/>
-			<view
-			    class="u-search__content__icon u-search__content__close"
-			    v-if="keyword && clearabled && focused"
-			    @tap="clear"
-			>
-				<u-icon
-				    name="close"
-				    size="11"
-				    color="#ffffff"
-					customStyle="line-height: 12px"
-				></u-icon>
-			</view>
-		</view>
-		<text
-		    :style="[actionStyle]"
-		    class="u-search__action"
-		    :class="[(showActionBtn || show) && 'u-search__action--active']"
-		    @tap.stop.prevent="custom"
-		>{{ actionText }}</text>
-	</view>
+  <view
+    class="u-search"
+    :style="[{
+      margin: margin,
+    }, $u.addStyle(customStyle)]"
+    @tap="clickHandler"
+  >
+    <view
+      class="u-search__content"
+      :style="{
+        backgroundColor: bgColor,
+        borderRadius: shape == 'round' ? '100px' : '0px',
+        borderColor: borderColor,
+      }"
+    >
+      <template v-if="$slots.label || label !== null">
+        <slot name="label">
+          <text class="u-search__content__label">{{ label }}</text>
+        </slot>
+      </template>
+      <view class="u-search__content__icon">
+        <u-icon
+          :size="searchIconSize"
+          :name="searchIcon"
+          :color="searchIconColor ? searchIconColor : color"
+          @tap="clickIcon"
+        />
+      </view>
+      <input
+        confirm-type="search"
+        :value="value"
+        :disabled="disabled"
+        :focus="focus"
+        :maxlength="maxlength"
+        placeholder-class="u-search__content__input--placeholder"
+        :placeholder="placeholder"
+        :placeholder-style="`color: ${placeholderColor}`"
+        class="u-search__content__input"
+        @blur="blur"
+        type="text"
+        @confirm="search"
+        :style="[{
+          textAlign: inputAlign,
+          color: color,
+          backgroundColor: bgColor,
+          height: $u.addUnit(height)
+        }, inputStyle]"
+        @input="inputChange"
+        @focus="getFocus"
+      >
+      <view
+        v-if="keyword && clearabled && focused"
+        class="u-search__content__icon u-search__content__close"
+        @tap="clear"
+      >
+        <u-icon
+          name="close"
+          size="11"
+          color="#ffffff"
+          custom-style="line-height: 12px"
+        />
+      </view>
+    </view>
+    <text
+      :style="[actionStyle]"
+      class="u-search__action"
+      :class="[(showActionBtn || show) && 'u-search__action--active']"
+      @tap.stop.prevent="custom"
+    >{{ actionText }}</text>
+  </view>
 </template>
 </template>
 
 
 <script>
 <script>
-	import props from './props.js';
+import props from './props.js'
 
 
-	/**
+/**
 	 * search 搜索框
 	 * search 搜索框
 	 * @description 搜索组件,集成了常见搜索框所需功能,用户可以一键引入,开箱即用。
 	 * @description 搜索组件,集成了常见搜索框所需功能,用户可以一键引入,开箱即用。
 	 * @tutorial https://www.uviewui.com/components/search.html
 	 * @tutorial https://www.uviewui.com/components/search.html
@@ -109,96 +109,96 @@
 	 * @event {Function} clear 用户点击清除按钮时触发
 	 * @event {Function} clear 用户点击清除按钮时触发
 	 * @example <u-search placeholder="日照香炉生紫烟" v-model="keyword"></u-search>
 	 * @example <u-search placeholder="日照香炉生紫烟" v-model="keyword"></u-search>
 	 */
 	 */
-	export default {
-		name: "u-search",
-		mixins: [uni.$u.mpMixin, uni.$u.mixin,props],
-		data() {
-			return {
-				keyword: '',
-				showClear: false, // 是否显示右边的清除图标
-				show: false,
-				// 标记input当前状态是否处于聚焦中,如果是,才会显示右侧的清除控件
-				focused: this.focus
-				// 绑定输入框的值
-				// inputValue: this.value
-			};
-		},
-		watch: {
-			keyword(nVal) {
-				// 双向绑定值,让v-model绑定的值双向变化
-				this.$emit('input', nVal);
-				// 触发change事件,事件效果和v-model双向绑定的效果一样,让用户多一个选择
-				this.$emit('change', nVal);
-			},
-			value: {
-				immediate: true,
-				handler(nVal) {
-					this.keyword = nVal;
-				}
-			}
-		},
-		computed: {
-			showActionBtn() {
-				return !this.animation && this.showAction
-			}
-		},
-		methods: {
-			// 目前HX2.6.9 v-model双向绑定无效,故监听input事件获取输入框内容的变化
-			inputChange(e) {
-				this.keyword = e.detail.value;
-			},
-			// 清空输入
-			// 也可以作为用户通过this.$refs形式调用清空输入框内容
-			clear() {
-				this.keyword = '';
-				// 延后发出事件,避免在父组件监听clear事件时,value为更新前的值(不为空)
-				this.$nextTick(() => {
-					this.$emit('clear');
-				})
-			},
-			// 确定搜索
-			search(e) {
-				this.$emit('search', e.detail.value);
-				try {
-					// 收起键盘
-					uni.hideKeyboard();
-				} catch (e) {}
-			},
-			// 点击右边自定义按钮的事件
-			custom() {
-				this.$emit('custom', this.keyword);
-				try {
-					// 收起键盘
-					uni.hideKeyboard();
-				} catch (e) {}
-			},
-			// 获取焦点
-			getFocus() {
-				this.focused = true;
-				// 开启右侧搜索按钮展开的动画效果
-				if (this.animation && this.showAction) this.show = true;
-				this.$emit('focus', this.keyword);
-			},
-			// 失去焦点
-			blur() {
-				// 最开始使用的是监听图标@touchstart事件,自从hx2.8.4后,此方法在微信小程序出错
-				// 这里改为监听点击事件,手点击清除图标时,同时也发生了@blur事件,导致图标消失而无法点击,这里做一个延时
-				setTimeout(() => {
-					this.focused = false;
-				}, 100)
-				this.show = false;
-				this.$emit('blur', this.keyword);
-			},
-			// 点击搜索框,只有disabled=true时才发出事件,因为禁止了输入,意味着是想跳转真正的搜索页
-			clickHandler() {
-				if (this.disabled) this.$emit('click');
-			},
-			// 点击左边图标
-			clickIcon() {
-				this.$emit('clickIcon');
-			}
-		}
-	}
+export default {
+  name: 'USearch',
+  mixins: [uni.$u.mpMixin, uni.$u.mixin, props],
+  data() {
+    return {
+      keyword: '',
+      showClear: false, // 是否显示右边的清除图标
+      show: false,
+      // 标记input当前状态是否处于聚焦中,如果是,才会显示右侧的清除控件
+      focused: this.focus
+      // 绑定输入框的值
+      // inputValue: this.value
+    }
+  },
+  computed: {
+    showActionBtn() {
+      return !this.animation && this.showAction
+    }
+  },
+  watch: {
+    keyword(nVal) {
+      // 双向绑定值,让v-model绑定的值双向变化
+      this.$emit('input', nVal)
+      // 触发change事件,事件效果和v-model双向绑定的效果一样,让用户多一个选择
+      this.$emit('change', nVal)
+    },
+    value: {
+      immediate: true,
+      handler(nVal) {
+        this.keyword = nVal
+      }
+    }
+  },
+  methods: {
+    // 目前HX2.6.9 v-model双向绑定无效,故监听input事件获取输入框内容的变化
+    inputChange(e) {
+      this.keyword = e.detail.value
+    },
+    // 清空输入
+    // 也可以作为用户通过this.$refs形式调用清空输入框内容
+    clear() {
+      this.keyword = ''
+      // 延后发出事件,避免在父组件监听clear事件时,value为更新前的值(不为空)
+      this.$nextTick(() => {
+        this.$emit('clear')
+      })
+    },
+    // 确定搜索
+    search(e) {
+      this.$emit('search', e.detail.value)
+      try {
+        // 收起键盘
+        uni.hideKeyboard()
+      } catch (e) {}
+    },
+    // 点击右边自定义按钮的事件
+    custom() {
+      this.$emit('custom', this.keyword)
+      try {
+        // 收起键盘
+        uni.hideKeyboard()
+      } catch (e) {}
+    },
+    // 获取焦点
+    getFocus() {
+      this.focused = true
+      // 开启右侧搜索按钮展开的动画效果
+      if (this.animation && this.showAction) this.show = true
+      this.$emit('focus', this.keyword)
+    },
+    // 失去焦点
+    blur() {
+      // 最开始使用的是监听图标@touchstart事件,自从hx2.8.4后,此方法在微信小程序出错
+      // 这里改为监听点击事件,手点击清除图标时,同时也发生了@blur事件,导致图标消失而无法点击,这里做一个延时
+      setTimeout(() => {
+        this.focused = false
+      }, 100)
+      this.show = false
+      this.$emit('blur', this.keyword)
+    },
+    // 点击搜索框,只有disabled=true时才发出事件,因为禁止了输入,意味着是想跳转真正的搜索页
+    clickHandler() {
+      if (this.disabled) this.$emit('click')
+    },
+    // 点击左边图标
+    clickIcon() {
+      this.$emit('clickIcon')
+    }
+  }
+}
 </script>
 </script>
 
 
 <style lang="scss" scoped>
 <style lang="scss" scoped>

+ 13 - 0
mini/utils/auth.js

@@ -1,6 +1,7 @@
 import Cache from './cache'
 import Cache from './cache'
 
 
 const TokenKey = 'auth_token'
 const TokenKey = 'auth_token'
+const AuthorizeKey = 'authorize'
 
 
 export function getToken() {
 export function getToken() {
   return Cache.get(TokenKey)
   return Cache.get(TokenKey)
@@ -13,3 +14,15 @@ export function setToken(token) {
 export function removeToken() {
 export function removeToken() {
   return Cache.remove(TokenKey)
   return Cache.remove(TokenKey)
 }
 }
+
+export function getAuthorize() {
+  return Cache.get(AuthorizeKey)
+}
+
+export function setAuthorize() {
+  return Cache.set(AuthorizeKey, true)
+}
+
+export function removeAuthorize() {
+  return Cache.remove(AuthorizeKey)
+}

+ 5 - 1
mini/utils/constant.js

@@ -6,6 +6,10 @@ module.exports = {
   CACHE_USER_TOKEN: 'vuex_user_token',
   CACHE_USER_TOKEN: 'vuex_user_token',
   CACHE_USER_DATA: 'vuex_user_data',
   CACHE_USER_DATA: 'vuex_user_data',
   CACHE_TAB_SELECTED: 'CACHE_TAB_SELECTED',
   CACHE_TAB_SELECTED: 'CACHE_TAB_SELECTED',
-  CACHE_PAGE_META: 'CACHE_PAGE_META'
+  CACHE_PAGE_META: 'CACHE_PAGE_META',
 
 
+  // 1=>'普通用户', 2=>'VIP', 3=>'设计师'
+  ACCOUNT_NORMAL: 1,
+  ACCOUNT_VIP: 2,
+  ACCOUNT_DESIGN: 3
 }
 }

+ 0 - 1
mini/utils/index.js

@@ -14,7 +14,6 @@ export function copyText(text, tips) {
   })
   })
 }
 }
 
 
-
 export default {
 export default {
   copyText
   copyText
 }
 }

+ 3 - 5
mini/utils/mixin.js

@@ -38,11 +38,9 @@ module.exports = {
     },
     },
     $colors() {
     $colors() {
       return {
       return {
-        bgColor: '#151729',
-        primaryColor: '#6EEBE8',
-        darkColor: '#48979C',
-        defaultColor: '#FFFFFF',
-        infoColor: '#6F717F'
+        bgColor: '#ffffff',
+        primaryColor: '#030303',
+        infoColor: '#333333'
       }
       }
     }
     }
   }
   }

+ 0 - 6
mini/utils/request/responseInterceptors.js

@@ -25,12 +25,6 @@ module.exports = vm => {
       // 401 登录超时 402 需要登录
       // 401 登录超时 402 需要登录
       if (typeof error.data.status_code !== 'undefined') {
       if (typeof error.data.status_code !== 'undefined') {
         if (error.data.status_code === 401 || error.data.status_code === 402) {
         if (error.data.status_code === 401 || error.data.status_code === 402) {
-          if (!getApp().globalData.isLogin) {
-            getApp().globalData.isLogin = true
-            uni.reLaunch({
-              url: '/pages/login'
-            })
-          }
           return Promise.resolve()
           return Promise.resolve()
         }
         }
       }
       }

+ 46 - 0
server/app/Admin/Actions/Form/BatchEditProductForm.php

@@ -0,0 +1,46 @@
+<?php
+
+namespace App\Admin\Actions\Form;
+
+use App\Models\Product;
+use Dcat\Admin\Contracts\LazyRenderable;
+use Dcat\Admin\Traits\LazyWidget;
+use Dcat\Admin\Widgets\Form;
+
+class BatchEditProductForm extends Form implements LazyRenderable
+{
+    use LazyWidget;
+
+    public function handle(array $input)
+    {
+        // 选择的行
+        $ids = explode(',',$input['id']??null);
+        if(empty($ids)){
+            return $this->response()->error('参数错误');
+        }
+
+
+        foreach ($ids as $id){
+            Product::where('id' , $id)->update([
+                'is_opened' => $input['is_opened'],
+            ]);
+        }
+
+        return $this->response()->success('修改成功')->refresh();
+    }
+
+    public function form()
+    {
+        $this->radio('is_opened','是否上架')
+            ->options(config('global.bool_status'))
+            ->default(1);
+
+
+        $this->hidden('id')->attribute('id','batch-id');
+    }
+
+    public function default()
+    {
+
+    }
+}

+ 40 - 0
server/app/Admin/Actions/Grid/BatchProduct.php

@@ -0,0 +1,40 @@
+<?php
+
+namespace App\Admin\Actions\Grid;
+
+use App\Admin\Actions\Form\BatchEditProductForm;
+use Dcat\Admin\Grid\BatchAction;
+use Dcat\Admin\Widgets\Modal;
+
+class BatchProduct extends BatchAction
+{
+    /**
+     * @return string
+     */
+	protected $title = '批量设置';
+
+
+
+
+    public function render()
+    {
+        $form = BatchEditProductForm::make();
+
+        return Modal::make()
+            ->lg()
+            ->title($this->title)
+            ->body($form)
+            ->onLoad($this->getModalScript())
+            ->button($this->title);
+    }
+
+    protected function getModalScript()
+    {
+        return <<<JS
+//获取选中的ID
+let key = {$this->getSelectedKeysScript()}
+$("#batch-id").val(key)
+JS;
+
+    }
+}

+ 1 - 1
server/app/Admin/Controllers/AccountController.php

@@ -90,7 +90,7 @@ class AccountController extends AdminController
                 ->default(1);
                 ->default(1);
             $form->textarea('remark');
             $form->textarea('remark');
 
 
-            $form->display('created_at');
+//            $form->display('created_at');
             //$form->display('updated_at');
             //$form->display('updated_at');
 
 
             $form->saving(function (Form $form) {
             $form->saving(function (Form $form) {

+ 25 - 36
server/app/Admin/Controllers/BannerController.php

@@ -18,38 +18,14 @@ class BannerController extends AdminController
     protected function grid()
     protected function grid()
     {
     {
         return Grid::make(new Banner(), function (Grid $grid) {
         return Grid::make(new Banner(), function (Grid $grid) {
+            $grid->model()->orderByDesc('sort');
             $grid->column('id')->sortable();
             $grid->column('id')->sortable();
             $grid->column('name');
             $grid->column('name');
-            $grid->column('image');
-            $grid->column('sort');
-            $grid->column('is_opened');
-            $grid->column('created_at');
-            $grid->column('updated_at')->sortable();
-        
-            $grid->filter(function (Grid\Filter $filter) {
-                $filter->equal('id');
-        
-            });
-        });
-    }
+            $grid->column('image')->image('',100);
+            $grid->column('sort')->editable();
+            $grid->column('is_opened')->switch();
 
 
-    /**
-     * Make a show builder.
-     *
-     * @param mixed $id
-     *
-     * @return Show
-     */
-    protected function detail($id)
-    {
-        return Show::make($id, new Banner(), function (Show $show) {
-            $show->field('id');
-            $show->field('name');
-            $show->field('image');
-            $show->field('sort');
-            $show->field('is_opened');
-            $show->field('created_at');
-            $show->field('updated_at');
+            $grid->disableViewButton();
         });
         });
     }
     }
 
 
@@ -62,13 +38,26 @@ class BannerController extends AdminController
     {
     {
         return Form::make(new Banner(), function (Form $form) {
         return Form::make(new Banner(), function (Form $form) {
             $form->display('id');
             $form->display('id');
-            $form->text('name');
-            $form->text('image');
-            $form->text('sort');
-            $form->text('is_opened');
-        
-            $form->display('created_at');
-            $form->display('updated_at');
+            $form->text('name')->required();
+            $form->image('image')->saveFullUrl()
+                ->uniqueName()->autoUpload()
+                ->autoSave(false)
+                ->removable(false)
+                ->width(4)
+                ->required()->required();
+            $form->number('sort');
+            $form->radio('is_opened')
+                ->options(config('global.bool_status'))
+            ->default(1);
+
+
+            $form->disableViewButton();
+            $form->disableDeleteButton();
+            $form->disableListButton();
+            $form->disableEditingCheck();
+            $form->disableViewCheck();
+            $form->disableCreatingCheck();
+
         });
         });
     }
     }
 }
 }

+ 10 - 29
server/app/Admin/Controllers/ContactController.php

@@ -22,35 +22,12 @@ class ContactController extends AdminController
             $grid->column('name');
             $grid->column('name');
             $grid->column('phone_num');
             $grid->column('phone_num');
             $grid->column('wechat_num');
             $grid->column('wechat_num');
-            $grid->column('created_at');
-            $grid->column('updated_at')->sortable();
-        
-            $grid->filter(function (Grid\Filter $filter) {
-                $filter->equal('id');
-        
-            });
-        });
-    }
 
 
-    /**
-     * Make a show builder.
-     *
-     * @param mixed $id
-     *
-     * @return Show
-     */
-    protected function detail($id)
-    {
-        return Show::make($id, new Contact(), function (Show $show) {
-            $show->field('id');
-            $show->field('name');
-            $show->field('phone_num');
-            $show->field('wechat_num');
-            $show->field('created_at');
-            $show->field('updated_at');
+            $grid->disableViewButton();
         });
         });
     }
     }
 
 
+
     /**
     /**
      * Make a form builder.
      * Make a form builder.
      *
      *
@@ -61,11 +38,15 @@ class ContactController extends AdminController
         return Form::make(new Contact(), function (Form $form) {
         return Form::make(new Contact(), function (Form $form) {
             $form->display('id');
             $form->display('id');
             $form->text('name');
             $form->text('name');
-            $form->text('phone_num');
+            $form->mobile('phone_num');
             $form->text('wechat_num');
             $form->text('wechat_num');
-        
-            $form->display('created_at');
-            $form->display('updated_at');
+
+            $form->disableViewButton();
+            $form->disableDeleteButton();
+            $form->disableListButton();
+            $form->disableEditingCheck();
+            $form->disableViewCheck();
+            $form->disableCreatingCheck();
         });
         });
     }
     }
 }
 }

+ 89 - 51
server/app/Admin/Controllers/ProductController.php

@@ -2,12 +2,17 @@
 
 
 namespace App\Admin\Controllers;
 namespace App\Admin\Controllers;
 
 
+use App\Admin\Actions\Grid\BatchProduct;
 use App\Models\Product;
 use App\Models\Product;
 use App\Models\ProductCategory;
 use App\Models\ProductCategory;
+use App\Models\ProductSpec;
 use Dcat\Admin\Form;
 use Dcat\Admin\Form;
 use Dcat\Admin\Grid;
 use Dcat\Admin\Grid;
+use Dcat\Admin\Layout\Content;
+use Dcat\Admin\Layout\Row;
 use Dcat\Admin\Show;
 use Dcat\Admin\Show;
 use Dcat\Admin\Http\Controllers\AdminController;
 use Dcat\Admin\Http\Controllers\AdminController;
+use Dcat\Admin\Widgets\Tab;
 
 
 class ProductController extends AdminController
 class ProductController extends AdminController
 {
 {
@@ -18,7 +23,7 @@ class ProductController extends AdminController
      */
      */
     protected function grid()
     protected function grid()
     {
     {
-        return Grid::make(Product::with('cate'), function (Grid $grid) {
+        return Grid::make(Product::with(['cate','specs']), function (Grid $grid) {
             $grid->model()->orderByDesc('sort');
             $grid->model()->orderByDesc('sort');
             $grid->column('id')->sortable();
             $grid->column('id')->sortable();
             $grid->column('name')->label('info');
             $grid->column('name')->label('info');
@@ -34,63 +39,101 @@ style="max-width:80px;max-height:80px;cursor:pointer"
 class="img img-thumbnail">';
 class="img img-thumbnail">';
                 }
                 }
                 return $html;
                 return $html;
-            });
+            })->width(300);
             $grid->column('tech_param')->display(function (){
             $grid->column('tech_param')->display(function (){
                 $html = '';
                 $html = '';
-                foreach ($this->tech_param as $key => $tech_param){
-                    $index = $key+1;
-                    $html .= "<a href='{$tech_param}' download target='_blank'>参数文件{$index}</a><br>";
+                foreach ($this->tech_param as $key => $item){
+                    $html .= "<a href='{$item['url']}' download target='_blank'>技术参数</a><br>";
                 }
                 }
                 return $html;
                 return $html;
             });
             });
             $grid->column('cad_model')->display(function (){
             $grid->column('cad_model')->display(function (){
                 $html = '';
                 $html = '';
-                foreach ($this->cad_model as $key => $tech_param){
-                    $index = $key+1;
-                    $html .= "<a href='{$tech_param}' download target='_blank'>CAD模型{$index}</a><br>";
+                foreach ($this->cad_model as $key => $item){
+                    $html .= "<a href='{$item['url']}' download target='_blank'>CAD模型</a><br>";
                 }
                 }
                 return $html;
                 return $html;
             });
             });
             $grid->column('cad_design')->display(function (){
             $grid->column('cad_design')->display(function (){
                 $html = '';
                 $html = '';
-                foreach ($this->cad_design as $key => $tech_param){
-                    $index = $key+1;
-                    $html .= "<a href='{$tech_param}' download target='_blank'>CAD设计$index</a><br>";
+                foreach ($this->cad_design as $key => $item){
+                    $html .= "<a href='{$item['url']}' download target='_blank'>CAD设计</a><br>";
                 }
                 }
                 return $html;
                 return $html;
             });
             });
             $grid->column('su_model')->display(function (){
             $grid->column('su_model')->display(function (){
                 $html = '';
                 $html = '';
-                foreach ($this->su_model as $key => $tech_param){
-                    $index = $key+1;
-                    $html .= "<a href='{$tech_param}' download target='_blank'>SU模型{$index}</a><br>";
+                foreach ($this->su_model as $key => $item){
+                    $html .= "<a href='{$item['url']}' download target='_blank'>SU模型</a><br>";
                 }
                 }
                 return $html;
                 return $html;
             });
             });
             $grid->column('other')->display(function (){
             $grid->column('other')->display(function (){
                 $html = '';
                 $html = '';
-                foreach ($this->other as $key => $tech_param){
-                    $index = $key+1;
-                    $html .= "<a href='{$tech_param}' download target='_blank'>其他文件{$index}</a><br>";
+                foreach ($this->other as $key => $item){
+                    $html .= "<a href='{$item['url']}' download target='_blank'>{$item['name']}</a><br>";
                 }
                 }
                 return $html;
                 return $html;
             });
             });
             $grid->column('is_opened')->switch();
             $grid->column('is_opened')->switch();
             $grid->column('sort')->editable();
             $grid->column('sort')->editable();
             $grid->column('created_at');
             $grid->column('created_at');
-
-            $grid->actions(function (Grid\Displayers\Actions $actions) {
-                $id = $actions->getKey();
-                // append一个操作
-                $actions->append('<a href="/admin/product/'.$id.'/spec"><i class="fa fa-align-left"></i> 规格管理</a>');
+            $grid->column('spec','规格')->display(function (){
+                $url = '/admin/product/'.$this->id.'/spec';
+                return "<a href='$url'>规格管理</a>";
             });
             });
 
 
+
             $grid->filter(function (Grid\Filter $filter) {
             $grid->filter(function (Grid\Filter $filter) {
                 $filter->panel();
                 $filter->panel();
+                $filter->equal('cate_id')->select(function (){
+                    return ProductCategory::select(['id','name'])->get()->pluck('name','id')->toArray();
+                })->width(2);
                 $filter->like('name')->width(2);
                 $filter->like('name')->width(2);
             });
             });
 
 
+            $grid->batchActions([new BatchProduct()]);
+
             $grid->disableViewButton();
             $grid->disableViewButton();
+
+            $grid->export()
+                ->titles([
+                    'name'       => '产品名称',
+                    'cate_name'  => '产品分类',
+                    'cover_img'  => '产品截面图',
+                    'cases'      => '案例',
+                    'tech_param' => '技术参数文件',
+                    'cad_model'  => 'CAD模型文件',
+                    'cad_design' => 'CAD设计文件',
+                    'su_model'   => 'SU模型文件',
+                    'other'      => '其他文件',
+                    'is_opened'  => '上架状态',
+                    'sort'       => '排序(越大越靠前)',
+                    'specs'      => '规格',
+                ])->rows(function ($rows){
+                    foreach ($rows as $index => &$row) {
+                        $specs = [];
+                        foreach ($row['specs'] as $spec){
+                            $arr = [];
+                            foreach ($spec['specs'] as $item){
+                                $arr[] = "{$item['name']}(¥{$item['price']})";
+                            }
+                            $specs[] = $spec['name'].':('.implode(",",$arr).')';
+                        }
+                        $row['cate_name'] = $row['cate']['name'];
+                        $row['cases'] = implode(",",$row['cases']);
+                        $row['tech_param']   = implode(",",$row['tech_param']);
+                        $row['cad_model']  = implode(",",$row['cad_model']);
+                        $row['cad_design']   = implode(",",$row['cad_design']);
+                        $row['su_model']  = implode(",",$row['su_model']);
+                        $row['other']  = implode(',', $row['other']);
+                        $row['is_opened']  = config('global.bool_status')[$row['is_opened']];
+                        $row['specs']  = implode(",",$specs);
+                    }
+                    return $rows;
+                })
+                ->xlsx()
+                ->disableExportSelectedRow();
         });
         });
     }
     }
 
 
@@ -129,13 +172,14 @@ class="img img-thumbnail">';
      */
      */
     protected function form()
     protected function form()
     {
     {
-        return Form::make(new Product(), function (Form $form) {
+        return Form::make(Product::with(['specs']), function (Form $form) {
             $form->display('id');
             $form->display('id');
             $cates = ProductCategory::select(['id','name'])->where('is_opened',1)->get()->toArray();
             $cates = ProductCategory::select(['id','name'])->where('is_opened',1)->get()->toArray();
+            /* @var Form $form*/
             $form->select('cate_id')
             $form->select('cate_id')
                 ->options(array_column($cates,'name','id'))
                 ->options(array_column($cates,'name','id'))
-                ->required();;
-            $form->text('name')->required();;;
+                ->required();
+            $form->text('name')->required();
             $form->image('cover_img')->saveFullUrl()
             $form->image('cover_img')->saveFullUrl()
                 ->uniqueName()->autoUpload()
                 ->uniqueName()->autoUpload()
                 ->autoSave(false)
                 ->autoSave(false)
@@ -146,32 +190,26 @@ class="img img-thumbnail">';
                 ->uniqueName()->autoUpload()
                 ->uniqueName()->autoUpload()
                 ->autoSave(false)
                 ->autoSave(false)
                 ->removable(false)
                 ->removable(false)
-                ->width(4);
-            $form->multipleFile('tech_param')->saveFullUrl()
-                ->uniqueName()->autoUpload()
-                ->autoSave(false)
-                ->removable(false)
-                ->width(4);
-            $form->multipleFile('cad_model')->saveFullUrl()
-                ->uniqueName()->autoUpload()
-                ->autoSave(false)
-                ->removable(false)
-                ->width(4);
-            $form->multipleFile('cad_design')->saveFullUrl()
-                ->uniqueName()->autoUpload()
-                ->autoSave(false)
-                ->removable(false)
-                ->width(4);
-            $form->multipleFile('su_model')->saveFullUrl()
-                ->uniqueName()->autoUpload()
-                ->autoSave(false)
-                ->removable(false)
-                ->width(4);
-            $form->multipleFile('other')->saveFullUrl()
-                ->uniqueName()->autoUpload()
-                ->autoSave(false)
-                ->removable(false)
-                ->width(4);
+                ->width(4)->sortable();
+
+            $form->table('tech_param', function (Form\NestedForm $table) {
+                $table->url('url','链接')->required();
+            });
+            $form->table('cad_model', function (Form\NestedForm $table) {
+                $table->url('url','链接')->required();
+            });
+            $form->table('cad_design', function (Form\NestedForm $table) {
+                $table->url('url','链接')->required();
+            });
+            $form->table('su_model', function (Form\NestedForm $table) {
+                $table->url('url','链接')->required();
+            });
+            $form->table('other', function (Form\NestedForm $table) {
+                $table->text('name','名称')->required();
+                $table->url('url','链接')->required();
+            });
+
+
             $form->radio('is_opened')->options(config('global.bool_status'))->default(1);
             $form->radio('is_opened')->options(config('global.bool_status'))->default(1);
             $form->number('sort');
             $form->number('sort');
 
 

+ 60 - 41
server/app/Admin/Controllers/ProductSpecController.php

@@ -2,25 +2,65 @@
 
 
 namespace App\Admin\Controllers;
 namespace App\Admin\Controllers;
 
 
+use App\Models\Product;
 use App\Models\ProductSpec;
 use App\Models\ProductSpec;
-use App\Models\ProductSpecGroup;
 use Dcat\Admin\Form;
 use Dcat\Admin\Form;
 use Dcat\Admin\Grid;
 use Dcat\Admin\Grid;
 use Dcat\Admin\Layout\Content;
 use Dcat\Admin\Layout\Content;
-use Dcat\Admin\Show;
 use Dcat\Admin\Http\Controllers\AdminController;
 use Dcat\Admin\Http\Controllers\AdminController;
 
 
 class ProductSpecController extends AdminController
 class ProductSpecController extends AdminController
 {
 {
 
 
     protected $product_id;
     protected $product_id;
+    protected $product;
 
 
     public function __construct()
     public function __construct()
     {
     {
         $route = \request()->route();
         $route = \request()->route();
         $this->product_id = $route->parameters['id'];
         $this->product_id = $route->parameters['id'];
+        $this->product = Product::find($this->product_id);
 
 
     }
     }
+    protected function content(Content $content)
+    {
+        return $content
+            ->translation($this->translation())
+            ->breadcrumb(
+                ['text' => $this->product->name,'url' => '#'],
+                ['text' => '规格管理'],
+            )
+            ->title($this->title())
+            ->description('规格管理');
+    }
+
+    public function index(Content $content, $productId = 0)
+    {
+        return $this->content($content)
+            ->body($this->grid());
+    }
+
+
+    public function create(Content $content,$productId = 0)
+    {
+        return $this->content($content)->body($this->form());
+    }
+
+
+    public function update($productId, $id = 0)
+    {
+        return $this->form()->update($id);
+    }
+
+    public function store($episodeId = 0)
+    {
+        return $this->form()->store();
+    }
+
+    public function edit($productId, Content $content, $id = 0)
+    {
+        return $this->content($content)->body($this->form()->edit($id));
+    }
 
 
     /**
     /**
      * Make a grid builder.
      * Make a grid builder.
@@ -29,45 +69,22 @@ class ProductSpecController extends AdminController
      */
      */
     protected function grid()
     protected function grid()
     {
     {
-        return Grid::make(ProductSpec::with('group.product'), function (Grid $grid) {
-            $grid->model()->orderByDesc('group_id')->orderByDesc('sort');
+        return Grid::make(new ProductSpec(), function (Grid $grid) {
+            $grid->model()->orderByDesc('sort');
             $grid->column('id')->sortable();
             $grid->column('id')->sortable();
-            $grid->column('product','产品')->display(function (){
-                return $this->group->product->name;
-            })->label('info');
-            $grid->column('group_id')->display(function (){
-                return $this->group->name;
-            })->label('success');
             $grid->column('name')->label('primary');
             $grid->column('name')->label('primary');
-            $grid->column('sale_price')->editable();
             $grid->column('is_opened')->switch();
             $grid->column('is_opened')->switch();
             $grid->column('sort')->editable();
             $grid->column('sort')->editable();
-            $grid->column('created_at');
-
-            $url = admin_url('/product/'.$this->product_id.'/specGroup');
-            $grid->tools('<a href="'.$url.'" class="btn btn-primary btn-outline" style="position: absolute;right: 118px;">
-<i class="fa fa-align-justify"></i> 规格组管理</a>');
-        });
-    }
+            $grid->column('specs')->display(function (){
+                $html = '';
+                foreach ($this->specs as $key => $specs){
+                    $br = $key % 3 == 0 && $key != 0 ? '<br>': '';
+                    $html .= "<span class='label' style='background:#586cb1;margin: 0 5px 5px 0;display: inline-block;'> {$specs['name']}(&yen;{$specs['price']})</span>{$br}";
+                }
+                return $html;
+            });
 
 
-    /**
-     * Make a show builder.
-     *
-     * @param mixed $id
-     *
-     * @return Show
-     */
-    protected function detail($id)
-    {
-        return Show::make($id, new ProductSpec(), function (Show $show) {
-            $show->field('id');
-            $show->field('group_id');
-            $show->field('name');
-            $show->field('cover_img');
-            $show->field('origin_price');
-            $show->field('sale_price');
-            $show->field('created_at');
-            $show->field('updated_at');
+            $grid->disableViewButton();
         });
         });
     }
     }
 
 
@@ -80,16 +97,18 @@ class ProductSpecController extends AdminController
     {
     {
         return Form::make(new ProductSpec(), function (Form $form) {
         return Form::make(new ProductSpec(), function (Form $form) {
             $form->display('id');
             $form->display('id');
-            $cates = ProductSpecGroup::select(['id','name'])->where('is_opened',1)->get()->toArray();
-            $form->radio('group_id')
-                ->options(array_column($cates,'name','id'))
-                ->required();
 
 
+            $form->hidden('product_id')->value($this->product_id);
             $form->text('name')->required();
             $form->text('name')->required();
-            $form->decimal('sale_price')->required();
             $form->radio('is_opened')->options(config('global.bool_status'))->default(1);
             $form->radio('is_opened')->options(config('global.bool_status'))->default(1);
             $form->number('sort');
             $form->number('sort');
 
 
+            $form->table('specs', '规格', function (Form\NestedForm $table){
+                $table->text('name','规格名称')->required();
+                $table->decimal('price','规格价格')->required();
+            });
+
+
             $form->disableViewButton();
             $form->disableViewButton();
             $form->disableDeleteButton();
             $form->disableDeleteButton();
             $form->disableListButton();
             $form->disableListButton();

+ 0 - 71
server/app/Admin/Controllers/ProductSpecGroupController.php

@@ -1,71 +0,0 @@
-<?php
-
-namespace App\Admin\Controllers;
-
-use App\Models\ProductSpecGroup;
-use Dcat\Admin\Form;
-use Dcat\Admin\Grid;
-use Dcat\Admin\Show;
-use Dcat\Admin\Http\Controllers\AdminController;
-
-class ProductSpecGroupController extends AdminController
-{
-    protected $product_id;
-
-    public function __construct()
-    {
-        $route = \request()->route();
-        $this->product_id = $route->parameters['id'];
-    }
-
-    /**
-     * Make a grid builder.
-     *
-     * @return Grid
-     */
-    protected function grid()
-    {
-        return Grid::make(ProductSpecGroup::with('product'), function (Grid $grid) {
-            $grid->model()->orderByDesc('sort');
-            $grid->column('id')->sortable();
-            $grid->column('product_id')->display(function (){
-                return $this->product->name;
-            })->label('info');
-            $grid->column('name')->label('success');
-            $grid->column('is_opened')->switch();
-            $grid->column('sort')->editable();
-            $grid->column('created_at');
-
-            $url = admin_url('/product/'.$this->product_id.'/spec');
-            $grid->tools('<a href="'.$url.'" class="btn btn-primary btn-outline" style="position: absolute;right: 118px;">
-<i class="fa fa-align-left"></i> 规格管理</a>');
-
-            $grid->disableViewButton();
-        });
-    }
-
-
-    /**
-     * Make a form builder.
-     *
-     * @return Form
-     */
-    protected function form()
-    {
-        return Form::make(new ProductSpecGroup(), function (Form $form) {
-            $form->display('id');
-            $form->hidden('product_id')->value($this->product_id);
-            $form->text('name')->required();
-
-            $form->radio('is_opened')->options(config('global.bool_status'))->default(1);
-            $form->number('sort');
-
-            $form->disableViewButton();
-            $form->disableDeleteButton();
-            $form->disableListButton();
-            $form->disableEditingCheck();
-            $form->disableViewCheck();
-            $form->disableCreatingCheck();
-        });
-    }
-}

+ 20 - 11
server/app/Admin/Controllers/SettingController.php

@@ -20,14 +20,13 @@ class SettingController extends AdminController
         return Grid::make(new Setting(), function (Grid $grid) {
         return Grid::make(new Setting(), function (Grid $grid) {
             $grid->column('id')->sortable();
             $grid->column('id')->sortable();
             $grid->column('name');
             $grid->column('name');
-            $grid->column('logo');
+            $grid->column('logo')->image('',100);
             $grid->column('created_at');
             $grid->column('created_at');
-            $grid->column('updated_at')->sortable();
-        
-            $grid->filter(function (Grid\Filter $filter) {
-                $filter->equal('id');
-        
-            });
+
+            $grid->disableCreateButton();
+            $grid->disableDeleteButton();
+            $grid->disableRowSelector();
+            $grid->disableViewButton();
         });
         });
     }
     }
 
 
@@ -59,10 +58,20 @@ class SettingController extends AdminController
         return Form::make(new Setting(), function (Form $form) {
         return Form::make(new Setting(), function (Form $form) {
             $form->display('id');
             $form->display('id');
             $form->text('name');
             $form->text('name');
-            $form->text('logo');
-        
-            $form->display('created_at');
-            $form->display('updated_at');
+            $form->image('logo')->saveFullUrl()
+                ->uniqueName()->autoUpload()
+                ->autoSave(false)
+                ->removable(false)
+                ->width(4)
+                ->required();
+
+            $form->disableViewButton();
+            $form->disableDeleteButton();
+            $form->disableListButton();
+            $form->disableEditingCheck();
+            $form->disableViewCheck();
+            $form->disableCreatingCheck();
+
         });
         });
     }
     }
 }
 }

+ 9 - 18
server/app/Admin/Controllers/ShowroomController.php

@@ -21,6 +21,7 @@ class ShowroomController extends AdminController
             $grid->model()->orderByDesc('sort');
             $grid->model()->orderByDesc('sort');
             $grid->column('id')->sortable();
             $grid->column('id')->sortable();
             $grid->column('name');
             $grid->column('name');
+            $grid->column('cover_img')->image('',100);
             $grid->column('sort')->editable();
             $grid->column('sort')->editable();
             $grid->column('is_opened')->switch();
             $grid->column('is_opened')->switch();
 
 
@@ -29,28 +30,12 @@ class ShowroomController extends AdminController
                 $filter->like('name')->width(2);
                 $filter->like('name')->width(2);
 
 
             });
             });
-        });
-    }
 
 
-    /**
-     * Make a show builder.
-     *
-     * @param mixed $id
-     *
-     * @return Show
-     */
-    protected function detail($id)
-    {
-        return Show::make($id, new Showroom(), function (Show $show) {
-            $show->field('id');
-            $show->field('name');
-            $show->field('sort');
-            $show->field('is_opened');
-            $show->field('created_at');
-            $show->field('updated_at');
+            $grid->disableViewButton();
         });
         });
     }
     }
 
 
+
     /**
     /**
      * Make a form builder.
      * Make a form builder.
      *
      *
@@ -61,10 +46,16 @@ class ShowroomController extends AdminController
         return Form::make(new Showroom(), function (Form $form) {
         return Form::make(new Showroom(), function (Form $form) {
             $form->display('id');
             $form->display('id');
             $form->text('name')->required();
             $form->text('name')->required();
+            $form->image('cover_img','封面图(宽高比1.4:1)')->saveFullUrl()
+                ->uniqueName()->autoUpload()
+                ->autoSave(false)
+                ->removable(false)
+                ->width(4);
             $form->number('sort');
             $form->number('sort');
             $form->radio('is_opened')
             $form->radio('is_opened')
                 ->options(config('global.bool_status'))
                 ->options(config('global.bool_status'))
                 ->default(1);
                 ->default(1);
+            $form->editor('detail','案例(图片宽高比7.5:x)')->height(3000);
 
 
             $form->disableViewButton();
             $form->disableViewButton();
             $form->disableDeleteButton();
             $form->disableDeleteButton();

+ 0 - 2
server/app/Admin/routes.php

@@ -29,8 +29,6 @@ Route::group([
         $router->resource('categories','ProductCategoryController');
         $router->resource('categories','ProductCategoryController');
         // 规格
         // 规格
         $router->resource('{id}/spec','ProductSpecController');
         $router->resource('{id}/spec','ProductSpecController');
-        // 规格组
-        $router->resource('{id}/specGroup','ProductSpecGroupController');
     });
     });
     // 案例管理
     // 案例管理
     $router->group(['prefix' => 'cases'], function (Router $router){
     $router->group(['prefix' => 'cases'], function (Router $router){

+ 71 - 259
server/app/Http/Controllers/V1/AuthController.php

@@ -2,15 +2,11 @@
 
 
 namespace App\Http\Controllers\V1;
 namespace App\Http\Controllers\V1;
 
 
-use App\Models\Job;
+use App\Models\Account;
 use App\Models\User;
 use App\Models\User;
-use App\Models\UserInfo;
-use App\Services\Api\ErrorMsgServive;
-use App\Services\Api\UserService;
-use App\Services\JPushService;
-use App\Services\SmsServer;
 use Cache;
 use Cache;
 use EasyWeChat\Factory;
 use EasyWeChat\Factory;
+use Illuminate\Http\JsonResponse;
 use Illuminate\Http\Request;
 use Illuminate\Http\Request;
 use Illuminate\Support\Facades\Auth;
 use Illuminate\Support\Facades\Auth;
 use Illuminate\Support\Facades\DB;
 use Illuminate\Support\Facades\DB;
@@ -20,294 +16,110 @@ use Validator;
 
 
 class AuthController extends Controller
 class AuthController extends Controller
 {
 {
-    public function __construct()
-    {
-        $this->wxConfig = ['app_id' => env("WECHAT_MINI_PROGRAM_APPID"), 'secret' => env("WECHAT_MINI_PROGRAM_SECRET"), 'response_type' => 'array'];
-    }
-
-    //注册
-    public function register(Request $request)
-    {
-        $mobile   = $request->input('mobile', '');
-        $password = $request->input('password', '');
-
-        $validator = Validator::make($request->all(), [
-            'mobile' => 'required',
-            'password' => 'required|min:6',
-        ]);
-        if ($validator->fails()) {
-            return $this->error($validator->errors()->first());
-        }
-        if (UserService::checkUserByMobile($mobile)) {
-            return $this->error('手机号已被占用');
-        }
-
-        try { //手机验证码验证
-            SmsServer::checkSmsCodeByVerifyKey($request->verifyKey, $request->code);
-        } catch (Exception $exception) {
-            return $this->error($exception->getMessage());
-        }
-
-        $user = App::make('getUserInstance'); //在 app/Providers/AppServiceProvider.php 里面可以创一个单例模式
-        $user->name = 'User' . mb_substr($mobile, 0, 6);
-        $user->avatar = '';
-        $user->mobile = $mobile;
-        $user->password = $password; //这个不是直接存密码,User模型中使用了修改器
-        $user->register_ip = request()->ip();
-
-        return $this->success('创建成功!');
-    }
-
-    //账号密码登录
     public function login(Request $request)
     public function login(Request $request)
-    {
-        $account = $request->input('account');
-        $password = $request->input('password');
-        $jpush_reg_id = $request->input('jpush_reg_id');
-
-        if (!$user = User::query()->where(['mobile' => $account])->orWhere(['email' => $account])->first()) {
-            return $this->error('账号不存在');
-        }
-        $credentials1 = ['mobile' => $account, 'password' => $password];
-        $credentials2 = ['email' => $account, 'password' => $password];
-        if (!auth('api')->attempt($credentials1) && !auth('api')->attempt($credentials2)) {
-            return $this->error('密码错误!');
-        }
-        $data = $this->doLogin($user, $jpush_reg_id);
-
-        return $this->success($data);
-    }
-
-    //APP第三方授权登录(微信)
-    public function authLogin(Request $request)
     {
     {
         try {
         try {
-            $socialite = Socialite::driver('weixin')->stateless()->user();
-            $user = User::query()->where('open_id', $socialite->getId())->first();
-            if (!$user) {
-                $data['open_id'] = $socialite->getId();
-                $data['user'] = [];
-            } else {
-                $account = $user->mobile ?: $user->email;
-                $data = $this->doLogin($account, $request->post('jpush_reg_id', ''));
+            $req = $request->post();
+            $this->validate($request, [
+                'account'      => 'required|digits:11',
+                'password'     => 'required',
+                'code'      => 'required',
+            ]);
+
+            $account = Account::where('account', $req['account'])
+                ->where('status', 1)
+                ->first();
+
+            if(!$account){
+                return $this->error('没有找到相关账号');
             }
             }
-        } catch (Exception $exception) {
-            ErrorMsgServive::write($exception, requst()->url());
-            return $this->error('微信授权登录出错~');
-        }
-        return $this->success($data);
-    }
 
 
-    //微信小程序登录(微信)
-    public function miniProgram(Request $request)
-    {
-        try {
-            $mini = Factory::miniProgram($this->wxConfig);
-            $newMini = $mini->auth->session($request->input('code'));
-
-            $iv = $request->input('iv');
-            $encryptData = $request->input('encryptData');
-            $decryptedData = $mini->encryptor->decryptData($newMini['session_key'], $iv, $encryptData);
-            $openId = $decryptedData['openid'];
-            $user = User::query()->where('open_id', $openId)->first();
-            if (!$user) {
-                $data['open_id'] = $openId;
-                $data['user'] = [];
-            } else {
-                $account = $user->mobile ?: $user->email;
-                $data = $this->doLogin($account, $request->post('jpush_reg_id', ''));
+            if(!\Hash::check($req['password'], $account->password)){
+                return $this->error('账号或密码错误');
             }
             }
-        } catch (Exception $exception) {
-            ErrorMsgServive::write($exception, requst()->url());
-            return $this->error('微信授权登录出错~');
-        }
-        return $this->success($data);
-    }
-
-    //微信小程序获取手机号
-    public function decryptPhone(Request $request)
-    {
-        $user = auth('api')->user();
-        try {
-            $mini = Factory::miniProgram($this->wxConfig);
-            $newMini = $mini->auth->session($request->input('code'));
-
-            $iv = $request->input('iv');
-            $encryptData = $request->input('encryptData');
-            $decryptedData = $mini->encryptor->decryptData($newMini['session_key'], $iv, $encryptData);
 
 
-            $user = User::query()->where('id', $user->id)->first();
-            $user->mobile = $decryptedData['purePhoneNumber'];
+            $app = Factory::miniProgram(config('wechat.mini_program.default'));
+            $data = $app->auth->session($req['code']);
+            $user = User::where('open_id',$data['openid'])->first();
+            $user->account_id = $account->id;
             $user->save();
             $user->save();
-        } catch (\Exception $exception) {
-            ErrorMsgServive::write($exception, requst()->url());
-            return $this->error('获取手机号出错~');
-        }
-        return $this->success();
-    }
-
-    //H5 应用进行微信授权登录
-    public function h5Oauth()
-    {
-
-    }
-
-    //微信小程序 code
-    public function miniCode()
-    {
-
-    }
-
-    // 字节跳动登陆 code
-    public function bytedance(Request $request)
-    {
-        try {
-            $code = $request->input('code');
-            $app = $this->getUniFactory();
-            $res = $app->login($code);
-
-            $openId = $res['openid'];
-            $user = User::where('open_id', $openId)->first();
-            if (!$user) {
-                $user = new User();
-                $user->open_id = $openId;
-                $user->union_id = $res['unionid'];
-                $user->remember_token = $res['session_key'];
-                $user->save();
-
-                $info = new UserInfo();
-                $info->user_id = $user->id;
-                $info->platform = 1;
-                $info->save();
-
-                $user = User::where('id', $user->id)->first();
-            }else{
-                $user->remember_token = $res['session_key'];
-                $user->save();
-            }
 
 
             $token = Auth::guard('api')->fromUser($user);
             $token = Auth::guard('api')->fromUser($user);
-            $user = User::with(['info'])->where('id', $user->id)->first();
+
+            $user = User::with(['account'])->where('id', $user->id)->first();
             $data = [
             $data = [
                 'token' => "Bearer " . $token,
                 'token' => "Bearer " . $token,
                 'user_info' => $user,
                 'user_info' => $user,
             ];
             ];
             return $this->success($data);
             return $this->success($data);
-        } catch (\Exception $e) {
-            ErrorMsgServive::write($e, \request()->url());
-            return $this->error('字节授权登陆出错');
+        }catch (\Exception $ex){
+            return $this->error('账号或密码不正确,请重新输入');
         }
         }
     }
     }
 
 
-    // 快手登陆 code
-    public function kuaishou(Request $request)
+    public function wechatMiniCode(Request $request): JsonResponse
     {
     {
         try {
         try {
-            $code = $request->input('code');
-            $app = $this->getUniFactory(2);
-            $res = $app->login($code);
-
-            $openId = $res['open_id'];
-            $user = User::where('open_id', $openId)->first();
-            if (!$user) {
+            $req = $request->post();
+            $this->validate($request, [
+                'code'      => 'required',
+            ]);
+            $app = Factory::miniProgram(config('wechat.mini_program.default'));
+            $data = $app->auth->session($req['code']);
+            $user = User::where('open_id',$data['openid'])->first();
+            if(!$user){
                 $user = new User();
                 $user = new User();
-                $user->open_id = $openId;
-                $user->union_id = ''; // 没有 union_id
-                $user->remember_token = $res['session_key'];
-                $user->save();
+                $user->open_id = $data['openid'];
+            }
+
+            $user->remember_token = $data['session_key'];
+            $user->save();
 
 
-                $info = new UserInfo();
-                $info->user_id = $user->id;
-                $info->platform = 2;
-                $info->save();
+            return $this->success();
+        }catch (\Exception $ex){
+            return $this->error($ex->getMessage());
+        }
+    }
+
+    public function wechatMiniPhone(Request $request)
+    {
+        try{
+            $req = $request->post();
+            $this->validate($request, [
+                'encryptedData' => 'required',
+                'iv'            => 'required',
+            ]);
+            $app = Factory::miniProgram(config('wechat.mini_program.default'));
+            $user = \user()->makeVisible('remember_token');
+            $decryptedData = $app->encryptor->decryptData($user['remember_token'], $req['iv'], $req['encryptedData']);
+
+            $account = Account::where('account', $decryptedData['phoneNumber'])
+                ->where('status', 1)
+                ->first();
+            if(!$account){
+                return $this->error('没有找到相关账号');
+            }
 
 
-                $user = User::where('id', $user->id)->first();
-            }else{
-                $user->remember_token = $res['session_key'];
+            $user = User::where('id', $user['id'])->first();
+            if($user->mobile != $decryptedData['phoneNumber']) {
+                $user->mobile = $decryptedData['phoneNumber'];
+                $user->account_id = $account->id;
                 $user->save();
                 $user->save();
             }
             }
 
 
             $token = Auth::guard('api')->fromUser($user);
             $token = Auth::guard('api')->fromUser($user);
-            $user = User::with(['info'])->where('id', $user->id)->first();
+
+            $user = User::with(['account'])->where('id', $user->id)->first();
             $data = [
             $data = [
                 'token' => "Bearer " . $token,
                 'token' => "Bearer " . $token,
                 'user_info' => $user,
                 'user_info' => $user,
             ];
             ];
             return $this->success($data);
             return $this->success($data);
-        } catch (\Exception $e) {
-            ErrorMsgServive::write($e, \request()->url());
-            return $this->error('快手授权登陆出错');
-        }
-    }
-
-    //执行登录
-    public function doLogin($user, $jpush_reg_id = null)
-    {
-        if (!empty($jpush_reg_id)) {
-            //清除登陆过本设备的账号设备id
-            User::query()->where('jpush_reg_id', $jpush_reg_id)->update(['jpush_reg_id' => '']);
-            //当前登录用户绑定设备
-            $user->jpush_reg_id = $jpush_reg_id;
-            //清除别名
-            JPushService::deleteAlias('user_id_' . $user->id);
-            //设置极光推送别名
-            JPushService::updateAlias($user->jpush_reg_id, 'user_id_' . $user->id);
-        }
-        $user->online = 1;
-        $user->last_login_time = date('Y-m-d H:i:s');
-        $user->last_login_ip = request()->ip();
-        if (!$user->save()) {
-            return $this->error('数据保存失败');
-        }
-        $token = Auth::guard('api')->fromUser($user);
-        $userInfo = UserService::getUserInfoById($user->id);
-        $data = [
-            'token' => "Bearer " . $token,
-            'user_info' => $userInfo,
-        ];
-
-        return $data;
-    }
 
 
-    //用户是否存在
-    public function isUserExist($account)
-    {
-        $user = User::where(['mobile' => $account])
-            ->orWhere(['email' => $account])
-            ->first();
-        if (!$user) {
-            return false;
-        }
-        return $user;
-    }
-
-    //忘记密码
-    public function forgetPassword(Request $request)
-    {
-        if ($request->new_password != $request->confirm_password) {
-            return $this->error('两次密码不一致');
+        }catch (\Exception $ex){
+            return $this->error($ex->getMessage());
         }
         }
-        try {
-            SmsServer::checkSmsCodeByVerifyKey($request->verifyKey, $request->code);
-        } catch (Exception $exception) {
-            return $this->error($exception->getMessage());
-        }
-        $user->password = $request->new_password;
-        $user->save();
-
-        return $this->success();
     }
     }
 
 
-    //退出
-    public function logout()
-    {
-        $user = auth('api')->user();
-        //清空极光别名
-        JPushService::updateAlias($user->jpush_reg_id, '');
-        $user->online = 0;
-        $user->save();
-        auth('api')->logout();
 
 
-        return $this->success();
-    }
 }
 }

+ 60 - 0
server/app/Http/Controllers/V1/CasesController.php

@@ -0,0 +1,60 @@
+<?php
+/**
+ * Created by PhpStorm
+ * DateTime: 2022/10/2 13:32
+ *
+ * @description
+ */
+
+namespace App\Http\Controllers\V1;
+
+use App\Models\Product;
+use App\Models\Showroom;
+use App\Models\StatShowroom;
+use Dingo\Api\Http\Request;
+use Illuminate\Database\Eloquent\Builder;
+use Illuminate\Http\JsonResponse;
+
+class CasesController extends Controller
+{
+    public function search(Request $request): JsonResponse
+    {
+        $limit = $request->input('limit');
+        $keywords = $request->input('keywords', '');
+        $page = request()->input('page',1);
+        $offset = ($page - 1) * $limit;
+
+
+        $lists = Showroom::withCount(['viewer'])
+            ->where('is_opened', 1)
+            ->when($keywords, function ($query, $keywords) {
+                /* @var Builder  $query*/
+                return $query->where('name','like', "%$keywords%");
+            })
+            ->orderByDesc('viewer_count')
+            ->orderByDesc('sort')
+            ->limit($limit)
+            ->offset($offset)
+            ->get();
+
+        return $this->success($lists);
+    }
+
+    public function detail($id): JsonResponse
+    {
+        $info = Showroom::where('id',$id)
+            ->first();
+
+        return $this->success($info);
+    }
+
+    public function viewer($id): JsonResponse
+    {
+        $stat = new StatShowroom();
+        $stat->showroom_id = $id;
+        $stat->user_id = \user()->id;
+        $stat->save();
+
+        return $this->success();
+    }
+}

+ 0 - 34
server/app/Http/Controllers/V1/Controller.php

@@ -61,38 +61,4 @@ class Controller extends BaseController
 
 
         return response()->json($result);
         return response()->json($result);
     }
     }
-
-    /**
-     * @param int $platform
-     * @return ByteDance|Kuaishou
-     */
-    protected function getUniFactory($platform = 1)
-    {
-        if($platform == 1){
-            return $this->getByteDanceFactory();
-        }
-
-        return $this->getKuishouFactory();
-    }
-
-    protected function getByteDanceFactory()
-    {
-        $setting = PayConfig::first();
-        return (new ByteDance(app(ByteDanceAPI::class)))->factory([
-            'app_id'     => $setting->douyin_app_id,
-            'app_secret' => $setting->douyin_app_secret,
-            'slat'       => $setting->douyin_salt,
-            'token'      => $setting->douyin_token,
-        ]);
-    }
-
-    protected function getKuishouFactory()
-    {
-        $setting = PayConfig::first();
-        return (new Kuaishou(app(KuaishouAPI::class)))->factory([
-            'app_id'     => $setting->kuaishou_app_id,
-            'app_secret' => $setting->kuaishou_app_secret,
-        ]);
-    }
-
 }
 }

+ 0 - 21
server/app/Http/Controllers/V1/PayController.php

@@ -1,21 +0,0 @@
-<?php
-
-namespace App\Http\Controllers\V1;
-
-
-use App\Models\Pay;
-
-class PayController extends Controller
-{
-
-    public function query($payId)
-    {
-        $pay = Pay::find($payId);
-        if(!$pay || $pay->status){
-            return $this->success();
-        }
-
-        return $this->error();
-
-    }
-}

+ 76 - 0
server/app/Http/Controllers/V1/ProductController.php

@@ -0,0 +1,76 @@
+<?php
+/**
+ * Created by PhpStorm
+ * DateTime: 2022/10/2 13:32
+ *
+ * @description
+ */
+
+namespace App\Http\Controllers\V1;
+
+use App\Models\Product;
+use App\Models\StatProduct;
+use App\Models\StatProductDownload;
+use Dingo\Api\Http\Request;
+use Illuminate\Database\Eloquent\Builder;
+use Illuminate\Http\JsonResponse;
+
+class ProductController extends Controller
+{
+    public function search(Request $request): JsonResponse
+    {
+        $limit = $request->input('limit');
+        $keywords = $request->input('keywords', '');
+        $page = request()->input('page',1);
+        $offset = ($page - 1) * $limit;
+
+
+        $lists = Product::withCount(['viewer'])->where('is_opened', 1)
+            ->when($keywords, function ($query, $keywords) {
+                /* @var Builder  $query*/
+                return $query->where('name','like', "%$keywords%");
+            })
+            ->orderByDesc('viewer_count')
+            ->orderByDesc('sort')
+            ->limit($limit)
+            ->offset($offset)
+            ->get();
+
+        return $this->success($lists);
+    }
+
+    public function detail($id): JsonResponse
+    {
+        $info = Product::with(['specs' => function($query) {
+            $query->select(['id','product_id','name','specs'])
+                ->where('is_opened', 1)
+                ->orderByDesc('sort');
+        }])
+            ->where('id',$id)
+            ->first();
+        $info->files = $info;
+
+        return $this->success($info);
+    }
+
+    public function viewer($id): JsonResponse
+    {
+        $stat = new StatProduct();
+        $stat->product_id = $id;
+        $stat->user_id = \user()->id;
+        $stat->save();
+
+        return $this->success();
+    }
+
+    public function download($id): JsonResponse
+    {
+        $stat = new StatProductDownload();
+        $stat->product_id = $id;
+        $stat->user_id = \user()->id;
+        $stat->save();
+
+        return $this->success();
+    }
+
+}

+ 36 - 0
server/app/Http/Controllers/V1/SettingController.php

@@ -0,0 +1,36 @@
+<?php
+
+namespace App\Http\Controllers\V1;
+
+use App\Models\Banner;
+use App\Models\Contact;
+use App\Models\User;
+use Cache;
+use EasyWeChat\Factory;
+use Illuminate\Http\JsonResponse;
+use Illuminate\Http\Request;
+use Illuminate\Support\Facades\Auth;
+use Illuminate\Support\Facades\DB;
+use Laravel\Socialite\Facades\Socialite;
+use PHPUnit\Util\Exception;
+use Validator;
+
+class SettingController extends Controller
+{
+    public function loginBanner(): JsonResponse
+    {
+        $lists = Banner::select(['name','image'])
+            ->where('is_opened' ,1)
+            ->orderByDesc('sort')
+            ->get();
+        return $this->success($lists);
+    }
+
+
+    public function contact(): JsonResponse
+    {
+        $lists = Contact::select(['name','phone_num','wechat_num'])->get();
+
+        return $this->success($lists);
+    }
+}

+ 1 - 9
server/app/Http/Controllers/V1/UserController.php

@@ -75,15 +75,7 @@ class UserController extends Controller
     public function info()
     public function info()
     {
     {
         $user = auth('api')->user();
         $user = auth('api')->user();
-        $info = UserInfo::find($user->id);
-
-        // 校验VIP 是否过期
-        if($info->is_vip && $info->end_at < Carbon::now()->toDateString()){
-            $info->is_vip = 0;
-            $info->save();
-        }
-
-        $user = User::with(['info'])->where('id', $user->id)->first();
+        $user = User::with(['account'])->where('id', $user->id)->first();
 
 
         return $this->success($user);
         return $this->success($user);
     }
     }

+ 30 - 0
server/app/Models/Account.php

@@ -6,9 +6,39 @@ use Dcat\Admin\Traits\HasDateTimeFormatter;
 
 
 use Illuminate\Database\Eloquent\Model;
 use Illuminate\Database\Eloquent\Model;
 
 
+/**
+ * App\Models\Account
+ *
+ * @property int $id
+ * @property string $user_name 用户名
+ * @property string $account 账号
+ * @property string $password 密码
+ * @property int $type 用户类型 1-普通用户 2-VIP 3-设计师
+ * @property string|null $remark 备注
+ * @property int $status 状态 1-正常 0-禁用
+ * @property \Illuminate\Support\Carbon|null $created_at
+ * @property \Illuminate\Support\Carbon|null $updated_at
+ * @method static \Illuminate\Database\Eloquent\Builder|Account newModelQuery()
+ * @method static \Illuminate\Database\Eloquent\Builder|Account newQuery()
+ * @method static \Illuminate\Database\Eloquent\Builder|Account query()
+ * @method static \Illuminate\Database\Eloquent\Builder|Account whereAccount($value)
+ * @method static \Illuminate\Database\Eloquent\Builder|Account whereCreatedAt($value)
+ * @method static \Illuminate\Database\Eloquent\Builder|Account whereId($value)
+ * @method static \Illuminate\Database\Eloquent\Builder|Account wherePassword($value)
+ * @method static \Illuminate\Database\Eloquent\Builder|Account whereRemark($value)
+ * @method static \Illuminate\Database\Eloquent\Builder|Account whereStatus($value)
+ * @method static \Illuminate\Database\Eloquent\Builder|Account whereType($value)
+ * @method static \Illuminate\Database\Eloquent\Builder|Account whereUpdatedAt($value)
+ * @method static \Illuminate\Database\Eloquent\Builder|Account whereUserName($value)
+ * @mixin \Eloquent
+ */
 class Account extends Model
 class Account extends Model
 {
 {
 	use HasDateTimeFormatter;
 	use HasDateTimeFormatter;
 
 
     protected $table = 'accounts';
     protected $table = 'accounts';
+
+    protected $hidden = [
+        'password','updated_at','id','created_at'
+    ];
 }
 }

+ 57 - 1
server/app/Models/Product.php

@@ -3,6 +3,8 @@
 namespace App\Models;
 namespace App\Models;
 
 
 use Dcat\Admin\Traits\HasDateTimeFormatter;
 use Dcat\Admin\Traits\HasDateTimeFormatter;
+use Illuminate\Database\Eloquent\Relations\BelongsTo;
+use Illuminate\Database\Eloquent\Relations\HasMany;
 use Illuminate\Database\Eloquent\SoftDeletes;
 use Illuminate\Database\Eloquent\SoftDeletes;
 use Illuminate\Database\Eloquent\Model;
 use Illuminate\Database\Eloquent\Model;
 
 
@@ -51,6 +53,13 @@ use Illuminate\Database\Eloquent\Model;
  * @property int $cate_id 分类ID
  * @property int $cate_id 分类ID
  * @property-read \App\Models\ProductCategory|null $cate
  * @property-read \App\Models\ProductCategory|null $cate
  * @method static \Illuminate\Database\Eloquent\Builder|Product whereCateId($value)
  * @method static \Illuminate\Database\Eloquent\Builder|Product whereCateId($value)
+ * @property-read \Illuminate\Database\Eloquent\Collection|\App\Models\ProductSpec[] $specs
+ * @property-read int|null $specs_count
+ * @property-write mixed $files
+ * @property-read \Illuminate\Database\Eloquent\Collection|\App\Models\StatProductDownload[] $download
+ * @property-read int|null $download_count
+ * @property-read \Illuminate\Database\Eloquent\Collection|\App\Models\StatProduct[] $viewer
+ * @property-read int|null $viewer_count
  */
  */
 class Product extends Model
 class Product extends Model
 {
 {
@@ -66,8 +75,55 @@ class Product extends Model
         'other' => 'json',
         'other' => 'json',
     ];
     ];
 
 
-    public function cate(): \Illuminate\Database\Eloquent\Relations\BelongsTo
+
+    public function cate(): BelongsTo
     {
     {
         return $this->belongsTo(ProductCategory::class,'cate_id','id');
         return $this->belongsTo(ProductCategory::class,'cate_id','id');
     }
     }
+
+    public function specs(): HasMany
+    {
+        return $this->hasMany(ProductSpec::class,'product_id');
+    }
+
+    public function setFilesAttribute(Product $product)
+    {
+        $files = [];
+        foreach ($product->tech_param as $item){
+            $files[] = [
+                'name' => '产品技术参数下载',
+                'url' => $item['url']
+            ];
+        }
+        foreach ($product->cad_model as $item){
+            $files[] = [
+                'name' => '产品CAD模型下载',
+                'url' => $item['url']
+            ];
+        }
+        foreach ($product->cad_design as $item){
+            $files[] = [
+                'name' => '产品CAD设计下载',
+                'url' => $item['url']
+            ];
+        }
+        foreach ($product->su_model as $item){
+            $files[] = [
+                'name' => '产品SU模型下载',
+                'url' => $item['url']
+            ];
+        }
+        $files = array_merge($files,$product->other);
+        $this->attributes['files'] = $files;
+    }
+
+    public function viewer(): HasMany
+    {
+        return $this->hasMany(StatProduct::class,'product_id','id');
+    }
+
+    public function download(): HasMany
+    {
+        return $this->hasMany(StatProductDownload::class,'product_id','id');
+    }
  }
  }

+ 2 - 0
server/app/Models/ProductCategory.php

@@ -33,6 +33,8 @@ use Illuminate\Database\Eloquent\Model;
  * @method static \Illuminate\Database\Query\Builder|ProductCategory withoutTrashed()
  * @method static \Illuminate\Database\Query\Builder|ProductCategory withoutTrashed()
  * @mixin \Eloquent
  * @mixin \Eloquent
  * @property-read ProductCategory|null $parent
  * @property-read ProductCategory|null $parent
+ * @property int $is_opened 是否启用
+ * @method static \Illuminate\Database\Eloquent\Builder|ProductCategory whereIsOpened($value)
  */
  */
 class ProductCategory extends Model
 class ProductCategory extends Model
 {
 {

+ 26 - 11
server/app/Models/ProductSpec.php

@@ -7,35 +7,37 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo;
 use Illuminate\Database\Eloquent\SoftDeletes;
 use Illuminate\Database\Eloquent\SoftDeletes;
 use Illuminate\Database\Eloquent\Model;
 use Illuminate\Database\Eloquent\Model;
 
 
+
 /**
 /**
  * App\Models\ProductSpec
  * App\Models\ProductSpec
  *
  *
  * @property int $id
  * @property int $id
- * @property int $group_id 分组ID
+ * @property int $product_id 产品ID
  * @property string $name 名称
  * @property string $name 名称
- * @property string $cover_img 封面图
- * @property string $origin_price 原价
- * @property string $sale_price 现价
+ * @property int $sort 排序越大越靠前
+ * @property int $is_opened 上架状态 0-下架 1-上架
+ * @property string|null $specs 规格详情
  * @property \Illuminate\Support\Carbon|null $updated_at
  * @property \Illuminate\Support\Carbon|null $updated_at
  * @property \Illuminate\Support\Carbon|null $deleted_at
  * @property \Illuminate\Support\Carbon|null $deleted_at
  * @property \Illuminate\Support\Carbon|null $created_at
  * @property \Illuminate\Support\Carbon|null $created_at
+ * @property-read \App\Models\ProductSpecGroup|null $group
  * @method static \Illuminate\Database\Eloquent\Builder|ProductSpec newModelQuery()
  * @method static \Illuminate\Database\Eloquent\Builder|ProductSpec newModelQuery()
  * @method static \Illuminate\Database\Eloquent\Builder|ProductSpec newQuery()
  * @method static \Illuminate\Database\Eloquent\Builder|ProductSpec newQuery()
  * @method static \Illuminate\Database\Query\Builder|ProductSpec onlyTrashed()
  * @method static \Illuminate\Database\Query\Builder|ProductSpec onlyTrashed()
  * @method static \Illuminate\Database\Eloquent\Builder|ProductSpec query()
  * @method static \Illuminate\Database\Eloquent\Builder|ProductSpec query()
- * @method static \Illuminate\Database\Eloquent\Builder|ProductSpec whereCoverImg($value)
  * @method static \Illuminate\Database\Eloquent\Builder|ProductSpec whereCreatedAt($value)
  * @method static \Illuminate\Database\Eloquent\Builder|ProductSpec whereCreatedAt($value)
  * @method static \Illuminate\Database\Eloquent\Builder|ProductSpec whereDeletedAt($value)
  * @method static \Illuminate\Database\Eloquent\Builder|ProductSpec whereDeletedAt($value)
- * @method static \Illuminate\Database\Eloquent\Builder|ProductSpec whereGroupId($value)
  * @method static \Illuminate\Database\Eloquent\Builder|ProductSpec whereId($value)
  * @method static \Illuminate\Database\Eloquent\Builder|ProductSpec whereId($value)
+ * @method static \Illuminate\Database\Eloquent\Builder|ProductSpec whereIsOpened($value)
  * @method static \Illuminate\Database\Eloquent\Builder|ProductSpec whereName($value)
  * @method static \Illuminate\Database\Eloquent\Builder|ProductSpec whereName($value)
- * @method static \Illuminate\Database\Eloquent\Builder|ProductSpec whereOriginPrice($value)
- * @method static \Illuminate\Database\Eloquent\Builder|ProductSpec whereSalePrice($value)
+ * @method static \Illuminate\Database\Eloquent\Builder|ProductSpec whereProductId($value)
+ * @method static \Illuminate\Database\Eloquent\Builder|ProductSpec whereSort($value)
+ * @method static \Illuminate\Database\Eloquent\Builder|ProductSpec whereSpecs($value)
  * @method static \Illuminate\Database\Eloquent\Builder|ProductSpec whereUpdatedAt($value)
  * @method static \Illuminate\Database\Eloquent\Builder|ProductSpec whereUpdatedAt($value)
  * @method static \Illuminate\Database\Query\Builder|ProductSpec withTrashed()
  * @method static \Illuminate\Database\Query\Builder|ProductSpec withTrashed()
  * @method static \Illuminate\Database\Query\Builder|ProductSpec withoutTrashed()
  * @method static \Illuminate\Database\Query\Builder|ProductSpec withoutTrashed()
  * @mixin \Eloquent
  * @mixin \Eloquent
- * @property-read \App\Models\ProductSpecGroup|null $group
+ * @property-read \App\Models\Product|null $product
  */
  */
 class ProductSpec extends Model
 class ProductSpec extends Model
 {
 {
@@ -44,9 +46,22 @@ class ProductSpec extends Model
 
 
     protected $table = 'product_specs';
     protected $table = 'product_specs';
 
 
-    public function group(): BelongsTo
+    protected $fillable = [
+        'product_id',
+        'name',
+        'sale_price',
+        'sort',
+        'is_opened',
+        'specs'
+    ];
+
+    protected $casts = [
+        'specs' => 'json'
+    ];
+
+    public function product(): BelongsTo
     {
     {
-        return $this->belongsTo(ProductSpecGroup::class,'group_id','id');
+        return $this->belongsTo(Product::class,'product_id','id');
     }
     }
 
 
 }
 }

+ 0 - 56
server/app/Models/ProductSpecGroup.php

@@ -1,56 +0,0 @@
-<?php
-
-namespace App\Models;
-
-use Dcat\Admin\Traits\HasDateTimeFormatter;
-use Illuminate\Database\Eloquent\Relations\BelongsTo;
-use Illuminate\Database\Eloquent\Relations\HasMany;
-use Illuminate\Database\Eloquent\SoftDeletes;
-use Illuminate\Database\Eloquent\Model;
-
-
-/**
- * App\Models\ProductSpecGroup
- *
- * @property int $id
- * @property int $product_id 产品ID
- * @property string $name 名称
- * @property \Illuminate\Support\Carbon|null $updated_at
- * @property \Illuminate\Support\Carbon|null $deleted_at
- * @property \Illuminate\Support\Carbon|null $created_at
- * @method static \Illuminate\Database\Eloquent\Builder|ProductSpecGroup newModelQuery()
- * @method static \Illuminate\Database\Eloquent\Builder|ProductSpecGroup newQuery()
- * @method static \Illuminate\Database\Query\Builder|ProductSpecGroup onlyTrashed()
- * @method static \Illuminate\Database\Eloquent\Builder|ProductSpecGroup query()
- * @method static \Illuminate\Database\Eloquent\Builder|ProductSpecGroup whereCreatedAt($value)
- * @method static \Illuminate\Database\Eloquent\Builder|ProductSpecGroup whereDeletedAt($value)
- * @method static \Illuminate\Database\Eloquent\Builder|ProductSpecGroup whereId($value)
- * @method static \Illuminate\Database\Eloquent\Builder|ProductSpecGroup whereName($value)
- * @method static \Illuminate\Database\Eloquent\Builder|ProductSpecGroup whereProductId($value)
- * @method static \Illuminate\Database\Eloquent\Builder|ProductSpecGroup whereUpdatedAt($value)
- * @method static \Illuminate\Database\Query\Builder|ProductSpecGroup withTrashed()
- * @method static \Illuminate\Database\Query\Builder|ProductSpecGroup withoutTrashed()
- * @mixin \Eloquent
- * @property-read \App\Models\Product|null $product
- * @property-read \Illuminate\Database\Eloquent\Collection|\App\Models\ProductSpec[] $specs
- * @property-read int|null $specs_count
- */
-class ProductSpecGroup extends Model
-{
-	use HasDateTimeFormatter;
-    use SoftDeletes;
-
-    protected $table = 'product_spec_groups';
-
-
-    public function product(): BelongsTo
-    {
-        return $this->belongsTo(Product::class,'product_id','id');
-    }
-
-    public function specs(): HasMany
-    {
-        return $this->hasMany(ProductSpec::class,'group_id','id');
-    }
-
-}

+ 12 - 0
server/app/Models/Showroom.php

@@ -3,6 +3,7 @@
 namespace App\Models;
 namespace App\Models;
 
 
 use Dcat\Admin\Traits\HasDateTimeFormatter;
 use Dcat\Admin\Traits\HasDateTimeFormatter;
+use Illuminate\Database\Eloquent\Relations\HasMany;
 use Illuminate\Database\Eloquent\SoftDeletes;
 use Illuminate\Database\Eloquent\SoftDeletes;
 use Illuminate\Database\Eloquent\Model;
 use Illuminate\Database\Eloquent\Model;
 
 
@@ -30,9 +31,20 @@ use Illuminate\Database\Eloquent\Model;
  * @method static \Illuminate\Database\Query\Builder|Showroom withTrashed()
  * @method static \Illuminate\Database\Query\Builder|Showroom withTrashed()
  * @method static \Illuminate\Database\Query\Builder|Showroom withoutTrashed()
  * @method static \Illuminate\Database\Query\Builder|Showroom withoutTrashed()
  * @mixin \Eloquent
  * @mixin \Eloquent
+ * @property string|null $cover_img 封面
+ * @property string|null $detail 详情
+ * @method static \Illuminate\Database\Eloquent\Builder|Showroom whereCoverImg($value)
+ * @method static \Illuminate\Database\Eloquent\Builder|Showroom whereDetail($value)
+ * @property-read \Illuminate\Database\Eloquent\Collection|\App\Models\StatShowroom[] $viewer
+ * @property-read int|null $viewer_count
  */
  */
 class Showroom extends Model
 class Showroom extends Model
 {
 {
 	use HasDateTimeFormatter;
 	use HasDateTimeFormatter;
     use SoftDeletes;
     use SoftDeletes;
+
+    public function viewer(): HasMany
+    {
+        return $this->hasMany(StatShowroom::class,'showroom_id','id');
     }
     }
+}

+ 39 - 0
server/app/Models/StatProduct.php

@@ -0,0 +1,39 @@
+<?php
+
+namespace App\Models;
+
+use Dcat\Admin\Traits\HasDateTimeFormatter;
+use Illuminate\Database\Eloquent\SoftDeletes;
+use Illuminate\Database\Eloquent\Model;
+
+/**
+ * App\Models\StatProduct
+ *
+ * @property int $id
+ * @property string $product_id 产品ID
+ * @property string $user_id 用户ID
+ * @property \Illuminate\Support\Carbon|null $updated_at
+ * @property \Illuminate\Support\Carbon|null $deleted_at
+ * @property \Illuminate\Support\Carbon|null $created_at
+ * @method static \Illuminate\Database\Eloquent\Builder|StatProduct newModelQuery()
+ * @method static \Illuminate\Database\Eloquent\Builder|StatProduct newQuery()
+ * @method static \Illuminate\Database\Query\Builder|StatProduct onlyTrashed()
+ * @method static \Illuminate\Database\Eloquent\Builder|StatProduct query()
+ * @method static \Illuminate\Database\Eloquent\Builder|StatProduct whereCreatedAt($value)
+ * @method static \Illuminate\Database\Eloquent\Builder|StatProduct whereDeletedAt($value)
+ * @method static \Illuminate\Database\Eloquent\Builder|StatProduct whereId($value)
+ * @method static \Illuminate\Database\Eloquent\Builder|StatProduct whereProductId($value)
+ * @method static \Illuminate\Database\Eloquent\Builder|StatProduct whereUpdatedAt($value)
+ * @method static \Illuminate\Database\Eloquent\Builder|StatProduct whereUserId($value)
+ * @method static \Illuminate\Database\Query\Builder|StatProduct withTrashed()
+ * @method static \Illuminate\Database\Query\Builder|StatProduct withoutTrashed()
+ * @mixin \Eloquent
+ */
+class StatProduct extends Model
+{
+	use HasDateTimeFormatter;
+    use SoftDeletes;
+
+    protected $table = 'stat_products';
+    
+}

+ 39 - 0
server/app/Models/StatProductDownload.php

@@ -0,0 +1,39 @@
+<?php
+
+namespace App\Models;
+
+use Dcat\Admin\Traits\HasDateTimeFormatter;
+use Illuminate\Database\Eloquent\SoftDeletes;
+use Illuminate\Database\Eloquent\Model;
+
+/**
+ * App\Models\StatProductDownload
+ *
+ * @property int $id
+ * @property string $product_id 产品ID
+ * @property string $user_id 用户ID
+ * @property \Illuminate\Support\Carbon|null $updated_at
+ * @property \Illuminate\Support\Carbon|null $deleted_at
+ * @property \Illuminate\Support\Carbon|null $created_at
+ * @method static \Illuminate\Database\Eloquent\Builder|StatProductDownload newModelQuery()
+ * @method static \Illuminate\Database\Eloquent\Builder|StatProductDownload newQuery()
+ * @method static \Illuminate\Database\Query\Builder|StatProductDownload onlyTrashed()
+ * @method static \Illuminate\Database\Eloquent\Builder|StatProductDownload query()
+ * @method static \Illuminate\Database\Eloquent\Builder|StatProductDownload whereCreatedAt($value)
+ * @method static \Illuminate\Database\Eloquent\Builder|StatProductDownload whereDeletedAt($value)
+ * @method static \Illuminate\Database\Eloquent\Builder|StatProductDownload whereId($value)
+ * @method static \Illuminate\Database\Eloquent\Builder|StatProductDownload whereProductId($value)
+ * @method static \Illuminate\Database\Eloquent\Builder|StatProductDownload whereUpdatedAt($value)
+ * @method static \Illuminate\Database\Eloquent\Builder|StatProductDownload whereUserId($value)
+ * @method static \Illuminate\Database\Query\Builder|StatProductDownload withTrashed()
+ * @method static \Illuminate\Database\Query\Builder|StatProductDownload withoutTrashed()
+ * @mixin \Eloquent
+ */
+class StatProductDownload extends Model
+{
+	use HasDateTimeFormatter;
+    use SoftDeletes;
+
+    protected $table = 'stat_product_download';
+    
+}

+ 39 - 0
server/app/Models/StatShowroom.php

@@ -0,0 +1,39 @@
+<?php
+
+namespace App\Models;
+
+use Dcat\Admin\Traits\HasDateTimeFormatter;
+use Illuminate\Database\Eloquent\SoftDeletes;
+use Illuminate\Database\Eloquent\Model;
+
+/**
+ * App\Models\StatShowroom
+ *
+ * @property int $id
+ * @property string $showroom_id 展厅ID
+ * @property string $user_id 用户ID
+ * @property \Illuminate\Support\Carbon|null $updated_at
+ * @property \Illuminate\Support\Carbon|null $deleted_at
+ * @property \Illuminate\Support\Carbon|null $created_at
+ * @method static \Illuminate\Database\Eloquent\Builder|StatShowroom newModelQuery()
+ * @method static \Illuminate\Database\Eloquent\Builder|StatShowroom newQuery()
+ * @method static \Illuminate\Database\Query\Builder|StatShowroom onlyTrashed()
+ * @method static \Illuminate\Database\Eloquent\Builder|StatShowroom query()
+ * @method static \Illuminate\Database\Eloquent\Builder|StatShowroom whereCreatedAt($value)
+ * @method static \Illuminate\Database\Eloquent\Builder|StatShowroom whereDeletedAt($value)
+ * @method static \Illuminate\Database\Eloquent\Builder|StatShowroom whereId($value)
+ * @method static \Illuminate\Database\Eloquent\Builder|StatShowroom whereShowroomId($value)
+ * @method static \Illuminate\Database\Eloquent\Builder|StatShowroom whereUpdatedAt($value)
+ * @method static \Illuminate\Database\Eloquent\Builder|StatShowroom whereUserId($value)
+ * @method static \Illuminate\Database\Query\Builder|StatShowroom withTrashed()
+ * @method static \Illuminate\Database\Query\Builder|StatShowroom withoutTrashed()
+ * @mixin \Eloquent
+ */
+class StatShowroom extends Model
+{
+	use HasDateTimeFormatter;
+    use SoftDeletes;
+
+    protected $table = 'stat_showrooms';
+    
+}

+ 13 - 1
server/app/Models/User.php

@@ -3,6 +3,7 @@
 namespace App\Models;
 namespace App\Models;
 
 
 use Dcat\Admin\Traits\HasDateTimeFormatter;
 use Dcat\Admin\Traits\HasDateTimeFormatter;
+use Illuminate\Database\Eloquent\Relations\BelongsTo;
 use Illuminate\Database\Eloquent\SoftDeletes;
 use Illuminate\Database\Eloquent\SoftDeletes;
 use Illuminate\Foundation\Auth\User as Authenticatable;
 use Illuminate\Foundation\Auth\User as Authenticatable;
 use Illuminate\Notifications\Notifiable;
 use Illuminate\Notifications\Notifiable;
@@ -27,7 +28,6 @@ use PHPOpenSourceSaver\JWTAuth\Contracts\JWTSubject;
  * @property int $type 用户类型 1-普通用户 2-VIP 3-设计师
  * @property int $type 用户类型 1-普通用户 2-VIP 3-设计师
  * @property \Illuminate\Support\Carbon|null $created_at
  * @property \Illuminate\Support\Carbon|null $created_at
  * @property \Illuminate\Support\Carbon|null $updated_at
  * @property \Illuminate\Support\Carbon|null $updated_at
- * @property-read \App\Models\UserInfo|null $info
  * @property-read \Illuminate\Notifications\DatabaseNotificationCollection|\Illuminate\Notifications\DatabaseNotification[] $notifications
  * @property-read \Illuminate\Notifications\DatabaseNotificationCollection|\Illuminate\Notifications\DatabaseNotification[] $notifications
  * @property-read int|null $notifications_count
  * @property-read int|null $notifications_count
  * @method static \Illuminate\Database\Eloquent\Builder|User newModelQuery()
  * @method static \Illuminate\Database\Eloquent\Builder|User newModelQuery()
@@ -49,6 +49,9 @@ use PHPOpenSourceSaver\JWTAuth\Contracts\JWTSubject;
  * @method static \Illuminate\Database\Eloquent\Builder|User whereUnionId($value)
  * @method static \Illuminate\Database\Eloquent\Builder|User whereUnionId($value)
  * @method static \Illuminate\Database\Eloquent\Builder|User whereUpdatedAt($value)
  * @method static \Illuminate\Database\Eloquent\Builder|User whereUpdatedAt($value)
  * @mixin \Eloquent
  * @mixin \Eloquent
+ * @property int $account_id 关联账号ID
+ * @property-read \App\Models\Account|null $account
+ * @method static \Illuminate\Database\Eloquent\Builder|User whereAccountId($value)
  */
  */
 class User extends Authenticatable implements JWTSubject
 class User extends Authenticatable implements JWTSubject
 {
 {
@@ -73,8 +76,11 @@ class User extends Authenticatable implements JWTSubject
     protected $hidden = [
     protected $hidden = [
         'password',
         'password',
         'remember_token',
         'remember_token',
+        'email_verified_at',
+        'created_at',
         'updated_at',
         'updated_at',
         'deleted_at',
         'deleted_at',
+        'mobile'
     ];
     ];
 
 
     public function getJWTIdentifier()
     public function getJWTIdentifier()
@@ -104,4 +110,10 @@ class User extends Authenticatable implements JWTSubject
     {
     {
         $this->attributes['password'] = bcrypt($value);
         $this->attributes['password'] = bcrypt($value);
     }
     }
+
+
+    public function account(): BelongsTo
+    {
+        return$this->belongsTo(Account::class,'account_id', 'id');
+    }
 }
 }

+ 0 - 54
server/app/Models/UserInfo.php

@@ -1,54 +0,0 @@
-<?php
-
-namespace App\Models;
-
-use Illuminate\Database\Eloquent\Factories\HasFactory;
-use Illuminate\Database\Eloquent\Model;
-
-/**
- * App\Models\UserInfo
- *
- * @property int $user_id user_id
- * @property int $integral 金币余额
- * @property int $total_integral 总金币
- * @property int $is_vip 是否VIP
- * @property string $start_at VIP生效时间
- * @property string $end_at VIP失效时间
- * @property string $opend_at 开卡时间
- * @property \Illuminate\Support\Carbon $updated_at 删除时间
- * @property string $deleted_at 删除时间
- * @property \Illuminate\Support\Carbon $created_at 创建时间
- * @method static \Illuminate\Database\Eloquent\Builder|UserInfo newModelQuery()
- * @method static \Illuminate\Database\Eloquent\Builder|UserInfo newQuery()
- * @method static \Illuminate\Database\Eloquent\Builder|UserInfo query()
- * @method static \Illuminate\Database\Eloquent\Builder|UserInfo whereCreatedAt($value)
- * @method static \Illuminate\Database\Eloquent\Builder|UserInfo whereDeletedAt($value)
- * @method static \Illuminate\Database\Eloquent\Builder|UserInfo whereEndAt($value)
- * @method static \Illuminate\Database\Eloquent\Builder|UserInfo whereIntegral($value)
- * @method static \Illuminate\Database\Eloquent\Builder|UserInfo whereIsVip($value)
- * @method static \Illuminate\Database\Eloquent\Builder|UserInfo whereOpendAt($value)
- * @method static \Illuminate\Database\Eloquent\Builder|UserInfo whereStartAt($value)
- * @method static \Illuminate\Database\Eloquent\Builder|UserInfo whereTotalIntegral($value)
- * @method static \Illuminate\Database\Eloquent\Builder|UserInfo whereUpdatedAt($value)
- * @method static \Illuminate\Database\Eloquent\Builder|UserInfo whereUserId($value)
- * @mixin \Eloquent
- * @property int $platform 用户平台 1-抖音 2-快手 ...
- * @method static \Illuminate\Database\Eloquent\Builder|UserInfo wherePlatform($value)
- */
-class UserInfo extends Model
-{
-    use HasFactory;
-    protected $table = 'user_info';
-
-    protected $primaryKey = 'user_id';
-
-    protected $hidden = [
-        'updated_at','deleted_at','created_at'
-    ];
-
-
-    protected function serializeDate(\DateTimeInterface $date)
-    {
-        return $date->format($this->dateFormat ?: 'Y-m-d H:i:s');
-    }
-}

+ 146 - 0
server/config/wechat.php

@@ -0,0 +1,146 @@
+<?php
+
+/*
+ * This file is part of the overtrue/laravel-wechat.
+ *
+ * (c) overtrue <i@overtrue.me>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+return [
+    /*
+     * 默认配置,将会合并到各模块中
+     */
+    'defaults'         => [
+        /*
+         * 指定 API 调用返回结果的类型:array(default)/collection/object/raw/自定义类名
+         */
+        'response_type'     => 'array',
+
+        /*
+         * 使用 Laravel 的缓存系统
+         */
+        'use_laravel_cache' => true,
+
+        /**
+         * 日志配置
+         *
+         * level: 日志级别, 可选为:
+         *         debug/info/notice/warning/error/critical/alert/emergency
+         * path:日志文件位置(绝对路径!!!),要求可写权限
+         */
+        'log' => [
+            'default' => env('APP_DEBUG', false) ? 'dev' : 'prod', // 默认使用的 channel,生产环境可以改为下面的 prod
+            'channels' => [
+                // 测试环境
+                'dev' => [
+                    'driver' => 'single',
+                    'path' => '/tmp/easywechat.log',
+                    'level' => 'debug',
+                ],
+                // 生产环境
+                'prod' => [
+                    'driver' => 'daily',
+                    'path' => '/tmp/easywechat.log',
+                    'level' => 'info',
+                ],
+            ],
+        ],
+    ],
+
+    /*
+     * 路由配置
+     */
+    'route'            => [
+        /*
+         * 开放平台第三方平台路由配置
+         */
+        // 'open_platform' => [
+        //     'uri' => 'serve',
+        //     'action' => Overtrue\LaravelWeChat\Controllers\OpenPlatformController::class,
+        //     'attributes' => [
+        //         'prefix' => 'open-platform',
+        //         'middleware' => null,
+        //     ],
+        // ],
+    ],
+
+    /*
+     * 公众号
+     */
+    'official_account' => [
+        'default' => [
+            'app_id'  => env('WECHAT_OFFICIAL_ACCOUNT_APPID', 'your-app-id'),         // AppID
+            'secret'  => env('WECHAT_OFFICIAL_ACCOUNT_SECRET', 'your-app-secret'),    // AppSecret
+            'token'   => env('WECHAT_OFFICIAL_ACCOUNT_TOKEN', 'your-token'),           // Token
+            'aes_key' => env('WECHAT_OFFICIAL_ACCOUNT_AES_KEY', ''),                 // EncodingAESKey
+
+            /*
+             * OAuth 配置
+             *
+             * scopes:公众平台(snsapi_userinfo / snsapi_base),开放平台:snsapi_login
+             * callback:OAuth授权完成后的回调页地址(如果使用中间件,则随便填写。。。)
+             * enforce_https:是否强制使用 HTTPS 跳转
+             */
+            // 'oauth'   => [
+            //     'scopes'        => array_map('trim', explode(',', env('WECHAT_OFFICIAL_ACCOUNT_OAUTH_SCOPES', 'snsapi_userinfo'))),
+            //     'callback'      => env('WECHAT_OFFICIAL_ACCOUNT_OAUTH_CALLBACK', '/examples/oauth_callback.php'),
+            //     'enforce_https' => true,
+            // ],
+        ],
+    ],
+
+    /*
+     * 开放平台第三方平台
+     */
+    // 'open_platform' => [
+    //     'default' => [
+    //         'app_id'  => env('WECHAT_OPEN_PLATFORM_APPID', ''),
+    //         'secret'  => env('WECHAT_OPEN_PLATFORM_SECRET', ''),
+    //         'token'   => env('WECHAT_OPEN_PLATFORM_TOKEN', ''),
+    //         'aes_key' => env('WECHAT_OPEN_PLATFORM_AES_KEY', ''),
+    //     ],
+    // ],
+
+    /*
+     * 小程序
+     */
+     'mini_program' => [
+         'default' => [
+             'app_id'  => env('WECHAT_MINI_PROGRAM_APPID', ''),
+             'secret'  => env('WECHAT_MINI_PROGRAM_SECRET', ''),
+             'token'   => env('WECHAT_MINI_PROGRAM_TOKEN', ''),
+             'aes_key' => env('WECHAT_MINI_PROGRAM_AES_KEY', ''),
+         ],
+     ],
+
+    /*
+     * 微信支付
+     */
+     'payment' => [
+         'default' => [
+             'sandbox'            => env('WECHAT_PAYMENT_SANDBOX', false),
+             'app_id'             => env('WECHAT_PAYMENT_APPID', ''),
+             'mch_id'             => env('WECHAT_PAYMENT_MCH_ID', 'your-mch-id'),
+             'key'                => env('WECHAT_PAYMENT_KEY', 'key-for-signature'),
+             'cert_path'          => env('WECHAT_PAYMENT_CERT_PATH', 'path/to/cert/apiclient_cert.pem'),    // XXX: 绝对路径!!!!
+             'key_path'           => env('WECHAT_PAYMENT_KEY_PATH', 'path/to/cert/apiclient_key.pem'),      // XXX: 绝对路径!!!!
+             'notify_url'         => env('WECHAT_PAYMENT_NOTIFY_URL','')                           // 默认支付结果通知地址
+         ],
+         // ...
+     ],
+
+    /*
+     * 企业微信
+     */
+    // 'work' => [
+    //     'default' => [
+    //         'corp_id' => 'xxxxxxxxxxxxxxxxx',
+    //         'agent_id' => 100020,
+    //         'secret'   => env('WECHAT_WORK_AGENT_CONTACTS_SECRET', ''),
+    //          //...
+    //      ],
+    // ],
+];

+ 1 - 2
server/resources/lang/zh/product-spec.php

@@ -10,8 +10,7 @@ return [
         'group_id' => '规格分组',
         'group_id' => '规格分组',
         'name' => '名称',
         'name' => '名称',
         'cover_img' => '封面图',
         'cover_img' => '封面图',
-        'origin_price' => '原价',
-        'sale_price' => '价格',
+        'specs' => '规格',
         'sort' => '排序(越大越靠前)',
         'sort' => '排序(越大越靠前)',
         'is_opened' => '是否启用',
         'is_opened' => '是否启用',
     ],
     ],

Some files were not shown because too many files changed in this diff