xiongzhu 3 lat temu
rodzic
commit
4f83000177

+ 1 - 0
package.json

@@ -9,6 +9,7 @@
   },
   "dependencies": {
     "axios": "^0.27.2",
+    "crypto-js": "^4.1.1",
     "date-fns": "^2.28.0",
     "element-plus": "^2.2.5",
     "feather-icons": "^4.29.0",

+ 12 - 5
src/components/AssetItem.vue

@@ -4,9 +4,12 @@
         <div class="name">{{ name }}</div>
         <div class="number">{{ number ? '#' : '' }}{{ number }}</div>
         <div style="flex-grow: 1"></div>
-        <div class="opt" v-if="info.consignment">
-            <button>下架</button>
-            <span class="price">¥{{ info.price }}</span>
+        <div class="opt">
+            <button v-if="info.consignment">下架</button>
+            <button v-else>上架</button>
+            <span class="price">
+                <span v-if="info.price">¥{{ info.price }}</span>
+            </span>
         </div>
     </div>
 </template>
@@ -17,8 +20,13 @@ const props = defineProps({
     info: {
         type: Object,
         required: true
+    },
+    size: {
+        type: Number,
+        default: 0
     }
 })
+
 const info = props.info || {}
 const name = ref(info.name)
 const number = ref(info.number)
@@ -32,8 +40,7 @@ watchEffect(() => {
 </script>
 <style lang="less" scoped>
 .asset {
-    flex-basis: 130px;
-    flex-grow: 1;
+    width: calc(var(--item-size) - 20px);
     height: 210px;
     box-sizing: border-box;
     border-radius: 8px;

+ 82 - 8
src/components/ListItem.vue

@@ -1,30 +1,93 @@
 <template>
-    <div class="list-item">
+    <div class="list-item" v-loading="loading">
         <img :src="info.pic[0].thumb || info.pic[0].url" class="cover" />
         <div class="info">
             <div class="row">
                 <div class="name">{{ info.name }}</div>
-                <div class="price">¥{{ info.price }}</div>
+                <div class="price">
+                    ¥{{ info.price }}
+                    <el-button link class="buy" @click="buy" v-if="showBuyBtn">一键购买</el-button>
+                </div>
             </div>
             <div class="row">
                 <div class="number">{{ info.number ? '#' : '' }}{{ info.number }}</div>
-                <div class="time">{{ timeAgo(info.createdAt) }}</div>
+                <div class="time">{{ getTime(info.createdAt) }}</div>
             </div>
         </div>
     </div>
 </template>
 <script setup>
-import { formatDistance, subDays, parse } from 'date-fns'
-import { zhCN } from 'date-fns/locale'
+import { ref } from 'vue'
+import { parse, format } from 'date-fns'
+import { http } from '@/plugins/http'
+import { encrypt } from '@/utils/encrypt'
+import qs from 'qs'
+import { ElMessage } from 'element-plus'
+
 const props = defineProps({
     info: {
         type: Object,
         required: true
+    },
+    showBuyBtn: {
+        type: Boolean,
+        default: false
     }
 })
+const emit = defineEmits(['refresh'])
 const info = props.info || {}
-const timeAgo = date => {
-    return formatDistance(subDays(new Date(), 1), parse(date, 'yyyy-MM-dd HH:mm:ss', new Date()), { locale: zhCN })
+const getTime = date => {
+    // return formatDistance(new Date(), parse(date, 'yyyy-MM-dd HH:mm:ss', new Date()), { locale: zhCN })
+    return format(parse(date, 'yyyy-MM-dd HH:mm:ss', new Date()), 'yyyy-MM-dd HH:mm')
+}
+
+const loading = ref(false)
+const checkOrder = orderId => {
+    return new Promise((resolve, reject) => {
+        const ts = new Date().getTime()
+        let createOrderTimer = setInterval(async () => {
+            const createOrderRes = await http.get('/order/createResult', { id: orderId })
+            if (createOrderRes) {
+                clearInterval(createOrderTimer)
+                createOrderTimer = null
+                if (createOrderRes.success) {
+                    resolve(createOrderRes.data)
+                } else {
+                    reject(createOrderRes.data || '失败')
+                }
+            } else {
+                if (new Date().getTime() - ts > 60000) {
+                    reject('超时')
+                }
+            }
+        }, 500)
+    })
+}
+const buy = async () => {
+    try {
+        loading.value = true
+        let params = {
+            collectionId: info.id,
+            qty: 1,
+            couponId: '',
+            invitor: '',
+            vip: false
+        }
+        params.sign = encrypt(qs.stringify({ ...params, ts: new Date().getTime() }))
+        const { id: orderId } = await http.post('/order/mqCreate', params)
+        console.log(orderId)
+
+        const orderInfo = await checkOrder(orderId)
+        console.log(orderInfo)
+
+        await http.post('/payOrder/v2/balance', { id: orderId, tradeCode: sessionStorage.tradeCode })
+        loading.value = false
+        ElMessage.success('购买成功')
+        emit('refresh')
+    } catch (e) {
+        loading.value = false
+        ElMessage.showError(e)
+    }
 }
 </script>
 <style lang="less" scoped>
@@ -56,7 +119,7 @@ const timeAgo = date => {
             display: flex;
             align-items: center;
             &:last-child {
-                margin-top: 2px;
+                margin-top: 4px;
             }
             .name {
                 flex-basis: 0;
@@ -71,6 +134,17 @@ const timeAgo = date => {
                 color: var(--el-color-danger);
                 font-size: 14px;
                 font-weight: 500;
+                display: flex;
+                align-items: center;
+                .buy {
+                    font-size: 13px;
+                    display: flex;
+                    align-items: center;
+                    color: var(--el-color-primary);
+                    margin: 0 0 0 10px;
+                    padding: 0;
+                    height: auto;
+                }
             }
             .number {
                 flex-basis: 0;

+ 13 - 0
src/main.js

@@ -9,6 +9,7 @@ import ElementPlus from 'element-plus'
 import http from '@/plugins/http'
 import * as ElementPlusIconsVue from '@element-plus/icons-vue'
 import VueLazyload from 'vue-lazyload'
+import { ElMessage } from 'element-plus'
 
 const app = createApp(App)
 
@@ -22,3 +23,15 @@ app.use(http)
 app.use(VueLazyload)
 
 app.mount('#app')
+
+ElMessage.showError = e => {
+    if (e instanceof Error) {
+        ElMessage.error(e.message)
+    } else if (typeof e === 'string') {
+        ElMessage.error(e)
+    } else if (e.error) {
+        ElMessage.error(e.error)
+    } else {
+        ElMessage.error('发生错误')
+    }
+}

+ 5 - 1
src/plugins/http.js

@@ -34,8 +34,12 @@ axiosInstance.interceptors.response.use(
 
 const http = {
     setToken(_token) {
-        axiosInstance.defaults.headers.common['Authorization'] = 'Bearer ' + _token
         token.value = _token
+        if (_token) {
+            axiosInstance.defaults.headers.common['Authorization'] = 'Bearer ' + _token
+        } else {
+            axiosInstance.defaults.headers.common['Authorization'] = null
+        }
     },
     async login(phone, password) {
         let { data: token } = await axiosInstance.post('/auth/phonePwdLogin', qs.stringify({ phone, password }))

+ 26 - 0
src/utils/encrypt.js

@@ -0,0 +1,26 @@
+import CryptoJS from 'crypto-js'
+function decrypt(content) {
+    const key = CryptoJS.enc.Hex.parse('16308B814B3E3CEC160BDCD8B56E3FD3')
+    const cipherText = CryptoJS.enc.Hex.parse(content)
+    var decrypted = CryptoJS.AES.decrypt(
+        {
+            ciphertext: cipherText
+        },
+        key,
+        {
+            mode: CryptoJS.mode.ECB,
+            padding: CryptoJS.pad.Pkcs7
+        }
+    )
+    return decrypted.toString(CryptoJS.enc.Utf8)
+}
+
+function encrypt(content) {
+    const key = CryptoJS.enc.Hex.parse('16308B814B3E3CEC160BDCD8B56E3FD3')
+    var encrypted = CryptoJS.AES.encrypt(content, key, {
+        mode: CryptoJS.mode.ECB,
+        padding: CryptoJS.pad.Pkcs7
+    })
+    return encrypted.ciphertext.toString(CryptoJS.enc.Hex)
+}
+export { decrypt, encrypt }

+ 59 - 34
src/views/HomeView.vue

@@ -12,9 +12,11 @@
             <el-row :gutter="20" style="margin: 10px" class="card-headers">
                 <el-col :span="6">
                     <span class="head-title">上架</span>
-                    <el-icon :size="15" class="refresh-icon" v-if="listLoading"><RefreshRight /></el-icon>
+                    <el-icon :size="15" class="refresh-icon" :class="{ spin: listLoading }" @click="getList">
+                        <RefreshRight />
+                    </el-icon>
                     <div style="flex-grow: 1"></div>
-                    <el-select size="small" v-model="listSort">
+                    <el-select size="small" v-model="listSort" @change="getList">
                         <el-option
                             v-for="item in sortOptions"
                             :key="item.value"
@@ -25,9 +27,11 @@
                 </el-col>
                 <el-col :span="6">
                     <span class="head-title">交易</span>
-                    <el-icon :size="15" class="refresh-icon" v-if="orderLoading"><RefreshRight /></el-icon>
+                    <el-icon :size="15" class="refresh-icon" :class="{ spin: orderLoading }" @click="getOrder">
+                        <RefreshRight />
+                    </el-icon>
                     <div style="flex-grow: 1"></div>
-                    <el-select size="small" v-model="orderSort">
+                    <el-select size="small" v-model="orderSort" @change="getOrder">
                         <el-option
                             v-for="item in sortOptions"
                             :key="item.value"
@@ -43,13 +47,27 @@
             </el-row>
             <el-row :gutter="20" style="margin: 10px">
                 <el-col :span="6" key="list" class="list-container">
-                    <list-item :info="item" v-for="item in list" :key="item.id"></list-item>
+                    <list-item
+                        :info="item"
+                        v-for="item in list"
+                        :key="item.id"
+                        @refresh="getData"
+                        showBuyBtn
+                    ></list-item>
                 </el-col>
                 <el-col :span="6" key="order" class="list-container">
-                    <list-item :info="item" v-for="item in order" :key="item.id"></list-item>
+                    <list-item :info="item" v-for="item in order" :key="item.id" @refresh="getOrder"></list-item>
                 </el-col>
-                <el-col :span="12" class="list-container assets-container" ref="assetListEl">
-                    <AssetItem v-for="item in assets" :key="item.id" :info="item"></AssetItem>
+                <el-col :span="12" class="list-container" ref="assetListEl" style="padding: 0">
+                    <div class="assets-container">
+                        <AssetItem
+                            v-for="item in assets"
+                            :key="item.id"
+                            :info="item"
+                            :size="itemSize"
+                            @refresh="getAssets"
+                        ></AssetItem>
+                    </div>
                 </el-col>
             </el-row>
         </el-main>
@@ -59,16 +77,27 @@
 <script setup>
 import VueFeather from 'vue-feather'
 import DarkMode from '@/components/DarkMode.vue'
-import Account from '@/domain/account'
-import { ref } from 'vue'
+import { ref, watchEffect } from 'vue'
 import { onBeforeRouteLeave } from 'vue-router'
 import { http } from '@/plugins/http'
 import ListItem from '@/components/ListItem.vue'
 import AssetItem from '@/components/AssetItem.vue'
-import { useStorage } from '@vueuse/core'
+import { useStorage, useElementSize, useCssVar } from '@vueuse/core'
 import { useRouter } from 'vue-router'
 import { store } from '@/stores/store'
 
+const assetListEl = ref(null)
+const { width: assetListWidth } = useElementSize(assetListEl)
+
+const itemSize = ref(0)
+const cssSize = useCssVar('--item-size', assetListEl, { initialValue: '0px' })
+watchEffect(() => {
+    console.log(assetListWidth.value)
+    itemSize.value = assetListWidth.value / Math.round(assetListWidth.value / 150)
+    console.log(itemSize.value)
+    cssSize.value = `${itemSize.value}px`
+})
+
 const router = useRouter()
 console.log(router)
 const logout = () => {
@@ -79,7 +108,7 @@ const logout = () => {
 }
 
 const sortOptions = [
-    { label: '日期', value: 'createdAt,desc' },
+    { label: '日期', value: 'id,desc' },
     { label: '价格', value: 'price' }
 ]
 const tagId = useStorage('tagId', '')
@@ -89,17 +118,13 @@ http.post('/tag/all', { size: 10000 }, { body: 'json' }).then(res => {
     tags.value = res.content
 })
 
-// const account1 = new Account('15077886171', 'x1ongDrew')
-const account2 = new Account('17601545948', '123456')
-// account1.login()
-account2.login()
-
 const list = ref([])
 const listSort = ref(sortOptions[1].value)
 const listLoading = ref(false)
 const getList = () => {
+    if (listLoading.value) return
     listLoading.value = true
-    http.get('/collection/byTag', { tagId: tagId.value }).then(res => {
+    http.get('/collection/byTag', { size: 10000, sort: listSort.value, tagId: tagId.value }).then(res => {
         listLoading.value = false
         res.content.forEach(i => {
             const m = /^(.*?)\s*(#\d+)$/.exec(i.name)
@@ -119,8 +144,9 @@ const order = ref([])
 const orderSort = ref(sortOptions[0].value)
 const orderLoading = ref(false)
 const getOrder = () => {
+    if (orderLoading.value) return
     orderLoading.value = true
-    http.get('/order/byTag', { tagId: tagId.value }).then(res => {
+    http.get('/order/byTag', { size: 10000, sort: orderSort.value, tagId: tagId.value }).then(res => {
         orderLoading.value = false
         res.content.forEach(i => {
             const m = /^(.*?)\s*(#\d+)$/.exec(i.name)
@@ -139,14 +165,12 @@ const getOrder = () => {
 const assets = ref([])
 const assetsLoading = ref(false)
 const getAssets = () => {
-    http.post('/asset/all', { size: 100, query: { status: 'NORMAL' }, sort: 'createdAt,desc' }, { body: 'json' }).then(
-        res => {
-            if (res.first) {
-                assets.value = []
-            }
-            assets.value = [...assets.value, ...res.content]
+    http.get('/asset/byTag', { size: 10000, sort: 'id,desc', tagId: tagId.value }, { body: 'json' }).then(res => {
+        if (res.first) {
+            assets.value = []
         }
-    )
+        assets.value = [...assets.value, ...res.content]
+    })
 }
 
 const getData = () => {
@@ -191,8 +215,11 @@ onBeforeRouteLeave(() => {
     }
     .refresh-icon {
         margin-left: 6px;
-        animation: spin 1s linear infinite;
         font-size: 14px;
+        cursor: pointer;
+        &.spin {
+            animation: spin 1s linear infinite;
+        }
     }
     .el-select {
         width: 80px;
@@ -214,12 +241,10 @@ onBeforeRouteLeave(() => {
         display: none;
         opacity: 0;
     }
-    &.assets-container {
-        display: flex;
-        flex-wrap: wrap;
-        align-items: center;
-        justify-content: space-between;
-        padding: 0px !important;
-    }
+}
+.assets-container {
+    display: flex;
+    flex-wrap: wrap;
+    padding: 0px !important;
 }
 </style>

+ 27 - 20
src/views/LoginView.vue

@@ -26,10 +26,17 @@
                         </template>
                     </el-input>
                 </el-form-item>
+                <el-form-item label="交易密码" prop="tradeCode">
+                    <el-input v-model="loginForm.tradeCode" type="password" show-password>
+                        <template #prefix>
+                            <el-icon><Lock /></el-icon>
+                        </template>
+                    </el-input>
+                </el-form-item>
                 <el-form-item>
-                    <el-button type="primary" @click="login" :loading="loading" style="width: 100%; margin-top: 10px"
-                        >登录</el-button
-                    >
+                    <el-button type="primary" @click="login" :loading="loading" style="width: 100%; margin-top: 10px">
+                        登录
+                    </el-button>
                 </el-form-item>
             </el-form>
         </el-main>
@@ -41,29 +48,29 @@ import { inject, ref } from 'vue'
 import { ElMessage } from 'element-plus'
 import router from '@/router/index'
 import { store } from '@/stores/store'
-const loginForm = ref({ username: 'xiong', password: '123456' })
+const loginForm = ref({ username: '15077886171', password: 'x1ongDrew', tradeCode: '' })
 const form = ref(null)
 const rules = {
     username: [{ required: true, message: '请输入用户名' }],
-    password: [{ required: true, message: '请输入密码' }]
+    password: [{ required: true, message: '请输入密码' }],
+    tradeCode: [{ required: true, message: '请输入交易密码' }]
 }
 const http = inject('http')
 const loading = ref(false)
-const login = () => {
-    form.value
-        .validate()
-        .then(async () => {
-            try {
-                loading.value = true
-                await http.login('15077886171', 'x1ongDrew')
-                store.userInfo = await http.get('/user/my')
-                loading.value = false
-                router.replace({ name: 'home' })
-            } catch (e) {
-                ElMessage.error(e.error || '登录失败')
-            }
-        })
-        .catch(() => {})
+const login = async () => {
+    try {
+        await form.value.validate()
+        loading.value = true
+        await http.login(loginForm.value.username, loginForm.value.password)
+        store.userInfo = await http.get('/user/my')
+        loading.value = false
+        sessionStorage.setItem('tradeCode', loginForm.value.tradeCode)
+        router.replace({ name: 'home' })
+    } catch (e) {
+        console.log(e)
+        ElMessage.showError(e)
+        loading.value = false
+    }
 }
 </script>
 <style lang="less" scoped>

+ 5 - 0
yarn.lock

@@ -408,6 +408,11 @@ cross-spawn@^7.0.2:
     shebang-command "^2.0.0"
     which "^2.0.1"
 
+crypto-js@^4.1.1:
+  version "4.1.1"
+  resolved "https://registry.npmmirror.com/crypto-js/-/crypto-js-4.1.1.tgz#9e485bcf03521041bd85844786b83fb7619736cf"
+  integrity sha512-o2JlM7ydqd3Qk9CA0L4NL6mTzU2sdx96a+oOfPu8Mkl/PK51vSyoi8/rQ8NknZtk44vq15lmhAj9CIAGwgeWKw==
+
 cssesc@^3.0.0:
   version "3.0.0"
   resolved "https://registry.npmmirror.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee"