panhui 3 năm trước cách đây
mục cha
commit
756d90fab5

BIN
src/assets/icon-lijibaoming.png


BIN
src/assets/icon-liujuyuanyin.png


BIN
src/assets/png-1v1-bg.png


BIN
src/assets/png-2v2~5v5-bg.png


BIN
src/assets/png-pipeizhong.png


BIN
src/assets/png-shuoming@2x.png


BIN
src/assets/png-tiren.png


BIN
src/assets/png-vs.png


+ 38 - 8
src/components/RoomInfo.vue

@@ -5,31 +5,61 @@
             width="58"
             height="58"
             radius="4"
-            src="https://fastly.jsdelivr.net/npm/@vant/assets/cat.jpeg"
+            :src="host.avatar || require('@assets/img_default_photo.png')"
         />
 
         <div class="room-info">
             <div class="text1">
-                <span>1v1</span>
-                <span>不限英雄 墨家机关道</span>
+                <span>{{ info.name }}</span>
             </div>
             <div class="text2">
                 <span>人数</span>
-                <van-progress track-color="#ffffff10" :show-pivot="false" :percentage="50" />
-                <span>1/2</span>
+                <van-progress track-color="#ffffff10" :show-pivot="false" :percentage="percentage" />
+                <span>{{ players.length }}/{{ info.maxPlayerNum }}</span>
             </div>
             <div class="text3">
-                <span>QQ区</span>
-                <span>创建时间 07-05 14:56</span>
+                <span>{{ info.zone }}区</span>
+                <span>创建时间 {{ createdAt }}</span>
             </div>
         </div>
     </div>
 </template>
 <script>
 export default {
+    props: {
+        info: {
+            type: Object,
+            default: () => {
+                return {};
+            }
+        }
+    },
+    computed: {
+        host() {
+            return this.info.host || {};
+        },
+        players() {
+            return this.info.players || [];
+        },
+        percentage() {
+            if (this.info.maxPlayerNum) {
+                return Math.ceil((this.players.length * 100) / this.info.maxPlayerNum);
+            } else {
+                return 0;
+            }
+        },
+        createdAt() {
+            return this.dayjs(this.info.createdAt).format('MM-DD HH:mm');
+        }
+    },
     methods: {
         goRoom() {
-            this.$router.push('/room');
+            this.$router.push({
+                path: '/room',
+                query: {
+                    id: this.info.id
+                }
+            });
         }
     }
 };

+ 115 - 28
src/components/TicketBuy.vue

@@ -2,16 +2,32 @@
     <van-popup v-model:show="show" safe-area-inset-bottom closeable position="bottom">
         <div class="popup-content">
             <div class="title">购买详情</div>
-            <div class="price">
-                <div class="text1">金额(元)</div>
-                <div class="text2">{{ info.price }}</div>
+            <div class="info">
+                <van-image :src="ticketInfo.img" width="102" height="48" fit="cover" />
+                <div class="info-content">
+                    <div class="text1">{{ ticketInfo.label }}</div>
+                    <div class="text2">
+                        <div class="text2-l">
+                            <span>¥</span>
+                            <span>{{ info.price }}</span>
+                        </div>
+                        <van-stepper size="mini" :min="1" v-model="qty" input-width="40px" />
+                    </div>
+                </div>
             </div>
+
             <div class="title">购买方式</div>
             <div class="pay-list">
                 <pay-method-pick v-model="payType"></pay-method-pick>
+
+                <div class="tips">虚拟商品购买成功后不支持退换,有效果期30天</div>
             </div>
             <div class="btn">
-                <van-button type="primary" block @click="pay">立即支付</van-button>
+                <div class="price">
+                    <span>¥</span>
+                    <span>{{ money }}</span>
+                </div>
+                <van-button type="primary" @click="pay">立即支付</van-button>
             </div>
         </div>
     </van-popup>
@@ -19,6 +35,7 @@
 
 <script>
 import PayMethodPick from './PayMethodPick.vue';
+import room from '../mixins/room.js';
 export default {
     components: { PayMethodPick },
     props: {
@@ -29,12 +46,27 @@ export default {
             }
         }
     },
+    mixins: [room],
     data() {
         return {
             show: false,
-            payType: ''
+            payType: '',
+            qty: 1
         };
     },
+    computed: {
+        money() {
+            let price = this.info.price || 0;
+            price = this.accMul(price, this.qty);
+            return price.toFixed(2);
+        },
+        ticketInfo() {
+            let info = [...this.requireTicketOptions].find(item => {
+                return item.value === this.info.type;
+            });
+            return info ? info : {};
+        }
+    },
     methods: {
         init() {
             this.show = true;
@@ -44,11 +76,15 @@ export default {
                 this.$toast('请选择购买方式');
                 return;
             }
+            this.$toast.loading({
+                message: '加载中...',
+                forbidClick: true
+            });
 
             this.$http
                 .post('/ticketOrder/create', {
                     type: this.info.type,
-                    qty: 1
+                    qty: this.qty
                 })
                 .then(res => {
                     return this.$http.post('/ticketOrder/balancePay', {
@@ -56,7 +92,7 @@ export default {
                     });
                 })
                 .then(res => {
-                    this.$toast.success('支付成功');
+                    this.$toast.success('购买成功');
                     this.show = false;
                 })
                 .catch(e => {
@@ -79,30 +115,81 @@ export default {
     padding: 16px 16px 0;
 }
 
-.price {
-    background: #313346;
-    border-radius: 4px;
-    margin: 27px 16px 0;
-    .flex-col();
-    align-items: center;
-    padding: 18px;
-
-    .text1 {
-        font-size: 14px;
-        color: #ffffff;
-        line-height: 24px;
+.btn {
+    padding: 50px 18px 9px;
+    .flex();
+    justify-content: space-between;
+    .van-button {
+        width: 160px;
     }
-
-    .text2 {
-        font-size: 30px;
-        font-weight: bold;
-        color: #ffffff;
-        line-height: 30px;
-        margin-top: 10px;
+    .price {
+        span {
+            &:first-child {
+                font-size: 12px;
+                font-weight: bold;
+                color: #ffffff;
+                line-height: 20px;
+            }
+            &:nth-child(2) {
+                font-size: 28px;
+                font-weight: bold;
+                color: #ffffff;
+                line-height: 28px;
+            }
+        }
+    }
+}
+.info {
+    .flex();
+    padding: 16px;
+    align-items: stretch;
+    .info-content {
+        flex-grow: 1;
+        margin-left: 7px;
+        .flex-col();
+        justify-content: space-between;
+        .text1 {
+            font-size: 14px;
+            color: #ffffff;
+            line-height: 24px;
+        }
+        .text2 {
+            .flex();
+            align-items: flex-end;
+            .text2-l {
+                flex-grow: 1;
+                span {
+                    font-size: 12px;
+                    color: #ffffff;
+                    line-height: 17px;
+                    &:nth-child(2) {
+                        font-size: 14px;
+                    }
+                }
+            }
+        }
     }
 }
 
-.btn {
-    padding: 50px 18px 9px;
+.tips {
+    font-size: 12px;
+    color: #6a6d83;
+    line-height: 17px;
+    padding: 6px 0;
+}
+
+/deep/.van-stepper {
+    .van-stepper__minus,
+    .van-stepper__plus {
+        width: 20px;
+        height: 20px;
+        border-radius: 4px;
+    }
+
+    .van-stepper__input {
+        height: 20px;
+        margin: 0 6px;
+        border-radius: 4px;
+    }
 }
 </style>

+ 21 - 9
src/components/room/changeUrl.vue

@@ -1,5 +1,5 @@
 <template>
-    <van-dialog v-model:show="show" theme="round-button" show-cancel-button>
+    <van-dialog v-model:show="show" theme="round-button" show-cancel-button @confirm="changeUrl">
         <div class="dialog-title">
             <img src="@assets/icon-xiugaidizhi.png" alt="" />
             <span>修改地址</span>
@@ -19,20 +19,32 @@ export default {
     },
     data() {
         return {
-            show: true,
+            show: false,
             message: ''
         };
     },
     methods: {
+        init(url = '') {
+            this.show = true;
+            this.message = url;
+        },
         changeUrl() {
-            this.$http.post('/room/changeUrl', {
-                roomId: this.roomId,
-                url: this.message
-                
-            }).then(res=>{
-                 this.$toast.success('修改成功');
-
+            this.$toast.loading({
+                message: '加载中...',
+                forbidClick: true
             });
+            this.$http
+                .post('/room/changeUrl', {
+                    roomId: this.roomId,
+                    url: this.message
+                })
+                .then(res => {
+                    this.$toast.success('修改成功');
+                })
+                .catch(e => {
+                    console.log(e);
+                    this.$toast(e.error);
+                });
         }
     }
 };

+ 54 - 0
src/components/room/description.vue

@@ -0,0 +1,54 @@
+<template>
+    <van-dialog v-model:show="show" theme="round-button" confirm-button-text="知道了">
+        <div class="dialog-title">
+            <span>比赛说明</span>
+        </div>
+        <div class="dialog-tips">
+            <div>1. 房主选英雄及地图,报名成功即同意</div>
+            <div>2. 网络干扰属于个人原因,不接受退赛退票处理</div>
+            <div>3. 队友挂机或者无响应,一律流局处理,违规者不反悔门票</div>
+            <div>4. 违规者门票将用于补偿所以玩家</div>
+        </div>
+    </van-dialog>
+</template>
+
+<script>
+export default {
+    data() {
+        return {
+            show: false
+        };
+    },
+    methods: {
+        init(url = '') {
+            this.show = true;
+        }
+    }
+};
+</script>
+
+<style lang="less" scoped>
+.dialog-title {
+    .flex-col();
+    align-items: center;
+    padding: 20px 0 24px;
+    img {
+        width: 48px;
+        height: 48px;
+    }
+    span {
+        font-size: 18px;
+        font-weight: bold;
+        color: #ffffff;
+        line-height: 24px;
+        margin-top: 12px;
+    }
+}
+
+.dialog-tips {
+    font-size: 14px;
+    color: #ffffff;
+    line-height: 20px;
+    padding: 0 20px 30px;
+}
+</style>

+ 79 - 0
src/components/room/failed.vue

@@ -0,0 +1,79 @@
+<template>
+    <van-dialog v-model:show="show" theme="round-button" show-cancel-button @confirm="changeUrl">
+        <div class="dialog-title">
+            <img src="@assets/icon-liujuyuanyin.png" alt="" />
+            <span>请填写流局原因</span>
+        </div>
+        <div class="filed">
+            <van-field v-model="message" rows="3" autosize label-width="0" type="textarea" placeholder="请输入流局原因">
+            </van-field>
+        </div>
+    </van-dialog>
+</template>
+
+<script>
+export default {
+    props: {
+        roomId: {
+            type: String,
+            default: ''
+        }
+    },
+    data() {
+        return {
+            show: true,
+            message: ''
+        };
+    },
+    methods: {
+        init(url = '') {
+            this.show = true;
+            this.message = url;
+        },
+        changeUrl() {
+            this.$toast.loading({
+                message: '加载中...',
+                forbidClick: true
+            });
+            this.$http
+                .post('/room/changeUrl', {
+                    roomId: this.roomId,
+                    url: this.message
+                })
+                .then(res => {
+                    this.$toast.success('修改成功');
+                })
+                .catch(e => {
+                    console.log(e);
+                    this.$toast(e.error);
+                });
+        }
+    }
+};
+</script>
+
+<style lang="less" scoped>
+.dialog-title {
+    .flex-col();
+    align-items: center;
+    padding: 20px 0 24px;
+    img {
+        width: 48px;
+        height: 48px;
+    }
+    span {
+        font-size: 18px;
+        font-weight: bold;
+        color: #ffffff;
+        line-height: 24px;
+        margin-top: 12px;
+    }
+}
+.filed {
+    padding: 0 20px 30px;
+}
+.van-field {
+    background: rgba(255, 255, 255, 0.1);
+    padding: 12px;
+}
+</style>

+ 159 - 0
src/components/room/joinRoom.vue

@@ -0,0 +1,159 @@
+<template>
+    <van-dialog
+        v-model:show="show"
+        theme="round-button"
+        show-cancel-button
+        confirm-button-text="立即报名"
+        cancel-button-text="退出房间"
+        @confirm="confirm"
+        @cancel="cancel"
+    >
+        <div class="dialog-title">
+            <img src="@assets/icon-lijibaoming.png" alt="" />
+            <span>是否立即报名?</span>
+        </div>
+        <div class="dialog-tips">
+            <div>1. 立即报名,将扣除您的赛事门票,然后等待房主开始比赛</div>
+            <div>2. 退出房间,您将离开此赛事房间</div>
+        </div>
+    </van-dialog>
+</template>
+
+<script>
+import room from '../../mixins/room.js';
+export default {
+    mixins: [room],
+    props: {
+        roomInfo: {
+            type: Object,
+            default: () => {
+                return {};
+            }
+        }
+    },
+    computed: {
+        requireTicketInfo() {
+            let info = [...this.requireTicketOptions].find(item => {
+                return item.value === this.roomInfo.requireTicket;
+            });
+            return info ? info : {};
+        }
+    },
+    data() {
+        return {
+            show: false,
+            message: ''
+        };
+    },
+    methods: {
+        init(url = '') {
+            this.show = true;
+        },
+        confirm() {
+            this.checkTicket()
+                .then(ticketId => {
+                    this.$toast.loading({
+                        message: '加载中...',
+                        forbidClick: true
+                    });
+                    this.$http
+                        .post(
+                            '/joinRoom/save',
+                            {
+                                roomId: this.roomInfo.id,
+                                userId: this.$store.state.userInfo.id,
+                                ticketId: ticketId
+                            },
+                            { body: 'json' }
+                        )
+                        .then(res => {
+                            this.$toast.success('加入成功');
+                            this.$emit('update')
+                        })
+                        .catch(e => {
+                            console.log(e);
+                            this.$toast(e.error);
+                        });
+                })
+                .catch(() => {
+                    this.show = true;
+                });
+        },
+
+        checkTicket() {
+            this.$toast.loading({
+                message: '加载中...',
+                forbidClick: true
+            });
+            return this.$http
+                .post(
+                    '/userTicket/all',
+                    {
+                        query: {
+                            userId: this.$store.state.userInfo.id,
+                            type: this.roomInfo.requireTicket,
+                            used: false,
+                            expired: false
+                        },
+                        sort: 'id,asc'
+                    },
+                    { body: 'json' }
+                )
+                .then(res => {
+                    let info = res;
+                    this.$toast.clear();
+                    if (res.empty) {
+                        return this.$dialog
+                            .confirm({
+                                title: '提示',
+                                message: '您没有可用的' + this.requireTicketInfo.label,
+                                confirmButtonText: '立即购买'
+                            })
+                            .then(res => {
+                                this.$router.push('/shop');
+                                return Promise.reject();
+                            });
+                    } else {
+                        return this.$dialog
+                            .confirm({
+                                title: '门票确认',
+                                message: '此操作将扣除您【' + this.requireTicketInfo.label + '】门票一张'
+                            })
+                            .then(() => {
+                                return Promise.resolve(info.content[0].id);
+                            });
+                    }
+                });
+        },
+        cancel() {
+            this.$router.back();
+        }
+    }
+};
+</script>
+
+<style lang="less" scoped>
+.dialog-title {
+    .flex-col();
+    align-items: center;
+    padding: 20px 0 24px;
+    img {
+        width: 48px;
+        height: 48px;
+    }
+    span {
+        font-size: 18px;
+        font-weight: bold;
+        color: #ffffff;
+        line-height: 24px;
+        margin-top: 12px;
+    }
+}
+
+.dialog-tips {
+    font-size: 14px;
+    color: #6a6d83;
+    line-height: 20px;
+    padding: 0 20px 30px;
+}
+</style>

+ 62 - 0
src/mixins/list.js

@@ -0,0 +1,62 @@
+export default {
+    data() {
+        return {
+            empty: false,
+            loading: false,
+            finished: false,
+            page: 0,
+            totalElements: 0,
+            size: 20
+        };
+    },
+    methods: {
+        getData(isFirst = false, scrollTop = 0) {
+            if (isFirst) {
+                this.page = 0;
+                this.list = [];
+                this.$root.$el.scrollTop = scrollTop;
+            }
+
+            this.loading = true;
+            this.finished = false;
+            this.empty = false;
+            let data = { page: this.page, size: this.size, sort: 'createdAt,desc' };
+            if (this.beforeData) {
+                data = {
+                    ...data,
+                    ...this.beforeData()
+                };
+            }
+
+            if (this.httpType === 'get') {
+                return this.$http.get(this.url, data, { body: 'json' }).then(res => {
+                    if (res.first) {
+                        this.list = [];
+                    }
+                    this.list = [...this.list, ...res.content];
+                    this.empty = res.empty;
+                    this.loading = false;
+                    this.finished = res.last;
+                    if (!this.finished) {
+                        this.page = this.page + 1;
+                    }
+                    this.totalElements = Number(res.totalElements);
+                });
+            } else {
+                return this.$http.post(this.url, data, { body: 'json' }).then(res => {
+                    if (res.first) {
+                        this.list = [];
+                    }
+                    this.list = [...this.list, ...res.content];
+                    this.empty = res.empty;
+                    this.loading = false;
+                    this.finished = res.last;
+                    if (!this.finished) {
+                        this.page = this.page + 1;
+                    }
+                    this.totalElements = Number(res.totalElements);
+                });
+            }
+        }
+    }
+};

+ 24 - 0
src/router/index.js

@@ -271,6 +271,30 @@ const routes = [
             title: '藏品兑换记录'
         }
     },
+    {
+        path: '/exceptionLogs',
+        name: 'exceptionLogs',
+        component: () => import('../views/user/ExceptionLogs.vue'),
+        meta: {
+            title: '异常申报纪录'
+        }
+    },
+    {
+        path: '/exceptionLog',
+        name: 'exceptionLog',
+        component: () => import('../views/user/ExceptionLog.vue'),
+        meta: {
+            title: '反馈纪录'
+        }
+    },
+    {
+        path: '/exceptionLogAdd',
+        name: 'exceptionLogAdd',
+        component: () => import('../views/user/ExceptionLogAdd.vue'),
+        meta: {
+            title: '异常申报'
+        }
+    },
     {
         path: '/about',
         name: 'about',

+ 3 - 0
src/styles/app.less

@@ -332,6 +332,9 @@ input:-webkit-autofill {
     .van-action-bar-button--last {
         border-radius: 2px;
         margin-left: 20px;
+        &.van-action-bar-button--first {
+            margin-left: 0;
+        }
     }
     .van-action-bar-button--warning {
         background: #25283d;

+ 18 - 6
src/views/Home.vue

@@ -33,9 +33,16 @@
             <div class="tab-content">
                 <van-tabs swipeable sticky v-model:active="active" shrink line-width="34px" line-height="2px">
                     <van-tab title="对局大厅">
-                        <div class="van-list">
-                            <room-info v-for="i in 10" :key="i"></room-info>
-                        </div>
+                        <van-list
+                            class="list"
+                            v-model:loading="loading"
+                            :immediate-check="false"
+                            :finished="finished"
+                            finished-text=""
+                            @load="getData"
+                        >
+                            <room-info v-for="(item, index) in list" :key="index" :info="item"></room-info>
+                        </van-list>
                     </van-tab>
                     <van-tab title="我的房间">
                         <div class="van-list">
@@ -69,11 +76,12 @@ import banner from '../mixins/banner';
 import ProductSmall from '../components/product/productSmall.vue';
 import { mapState } from 'vuex';
 import RoomInfo from '../components/RoomInfo.vue';
+import list from '../mixins/list.js';
 
 export default {
     name: 'home',
     inject: ['bar', 'setKeeps', 'scrollWrapper', 'changeScroll'],
-    mixins: [banner],
+    mixins: [banner, list],
     components: {
         Swiper,
         SwiperSlide,
@@ -89,7 +97,10 @@ export default {
             scrollTop: 0,
             search: '',
             active: 0,
-            type: 0
+            type: 0,
+            url: '/room/roomList',
+            list: [],
+            httpType: 'get'
         };
     },
     mounted() {
@@ -106,13 +117,14 @@ export default {
                 forbidClick: true
             });
             this.$toast.clear();
-            return Promise.resolve();
+            return this.getData();
             // return Promise.all([this.getBanner()]).then(() => {
             //     this.$toast.clear();
             //     return Promise.resolve();
             // });
             // this.getNews();
         },
+        beforeData() {},
         getProduct() {
             return Promise.resolve();
         },

+ 172 - 4
src/views/Room.vue

@@ -22,15 +22,61 @@
                     <div class="tab">{{ getLabelName(info.requireTicket, requireTicketOptions) }}</div>
                     <div class="tab">{{ gameMap.name }}</div>
                 </div>
+
+                <div class="player-content">
+                    <div class="players">
+                        <div class="player-item">
+                            <div class="title">主队</div>
+                            <div class="list1" v-if="info.maxPlayerNum === 2">
+                                <van-image
+                                    width="58"
+                                    height="58"
+                                    radius="4"
+                                    :src="host.avatar || require('@assets/img_default_photo.png')"
+                                    fit="cover"
+                                />
+                                <div class="name">{{ host.nickname }}</div>
+                            </div>
+                            <div class="list" v-else></div>
+                        </div>
+                        <div class="player-item">
+                            <div class="title">客队</div>
+                            <div class="list1 not" v-if="info.maxPlayerNum === 2">
+                                <van-image
+                                    width="58"
+                                    height="58"
+                                    radius="4"
+                                    :src="require('@assets/png-pipeizhong.png')"
+                                    fit="cover"
+                                />
+                                <div class="name">匹配中…</div>
+                            </div>
+                            <div class="list" v-else></div>
+                        </div>
+                    </div>
+                </div>
+
+                <div class="bg-icon">
+ 
+                    <img src="@assets/png-1v1-bg.png" class="img1" alt="" />
+                    <img src="@assets/png-vs.png" alt="" class="img2" />
+                </div>
+            </div>
+        </div>
+
+        <div class="help">
+            <div class="help-btn" @click="$refs.description.init()">
+                <img src="@assets/png-shuoming.png" alt="" />
+                <span>说明</span>
             </div>
         </div>
 
         <div class="bottom">
-            <div class="btn">
+            <div class="btn" @click="changeUrl">
                 <img src="@assets/png-xiugaidizhi.png" alt="" />
                 <span>修改地址</span>
             </div>
-            <div class="btn">
+            <div class="btn" @click="$router.push('/exceptionLogAdd?roomId=' + roomId)">
                 <img src="@assets/png-yicangshenbao.png" alt="" />
                 <span>异常申报</span>
             </div>
@@ -39,13 +85,19 @@
                 <span>取消赛事</span>
             </div>
         </div>
-        <change-url></change-url>
+        <change-url ref="url" :roomId="roomId"></change-url>
+        <join-room ref="join" :roomInfo="info" @update="getInfo"></join-room>
+        <description ref="description"></description>
+        <failed ref="failed"></failed>
     </div>
 </template>
 
 <script>
 import room from '../mixins/room.js';
 import changeUrl from '../components/room/changeUrl.vue';
+import joinRoom from '../components/room/joinRoom.vue';
+import description from '../components/room/description.vue';
+import failed from '../components/room/failed.vue';
 export default {
     data() {
         return {
@@ -65,7 +117,7 @@ export default {
         }
     },
     mixins: [room],
-    components: { changeUrl },
+    components: { changeUrl, joinRoom, description, failed },
     mounted() {
         if (this.$route.query.id) {
             this.roomId = this.$route.query.id;
@@ -74,9 +126,18 @@ export default {
     },
     methods: {
         getInfo() {
+            this.$toast.loading({
+                message: '加载中...',
+                forbidClick: true
+            });
             this.$http.get('/room/detail/' + this.roomId).then(res => {
+                this.$toast.clear();
                 this.info = res;
+                // this.$refs.join.init();
             });
+        },
+        changeUrl() {
+            this.$refs.url.init(this.info.url);
         }
     }
 };
@@ -106,6 +167,10 @@ export default {
         position: fixed;
         z-index: 20;
     }
+    .help {
+        position: fixed;
+        z-index: 20;
+    }
 }
 .bg {
     position: absolute;
@@ -145,6 +210,7 @@ export default {
     background: linear-gradient(180deg, #541ec1, #1e0470);
     min-height: calc(var(--app-height) - 100px);
     border-radius: 10px;
+    .flex-col();
     .content-top {
         padding: 8px 0;
         .flex-col();
@@ -169,10 +235,16 @@ export default {
 @colorList: #ff988c, #c787ff, #ff87c3, #9587ff;
 
 .content-box {
+    .flex-col();
+    position: relative;
     padding: 16px;
+    flex-grow: 1;
+    background: linear-gradient(180deg, #6310b0 0%, #09005c 100%);
     .tabs {
         .flex();
         justify-content: center;
+        padding-bottom: 14px;
+        flex-shrink: 0;
 
         .tab {
             height: 18px;
@@ -193,8 +265,76 @@ export default {
             margin-left: 12px;
         }
     }
+
+    .bg-icon {
+        position: absolute;
+        left: 0;
+        right: 0;
+        top: 130px;
+        z-index: 1;
+        .img1 {
+            width: 100%;
+            display: block;
+        }
+        .img2 {
+            width: 74px;
+            display: block;
+            position: absolute;
+            top: 0;
+            left: 50%;
+            transform: translateX(-50%);
+        }
+    }
+}
+.player-content {
+    flex-grow: 1;
+    border-radius: 8px;
+    position: relative;
+    padding: 16px;
+    &::after {
+        content: '';
+        position: absolute;
+        background: linear-gradient(180deg, #6310b0 0%, #09005c 100%);
+        top: 0;
+        left: 0;
+        right: 0;
+        bottom: 0;
+        border-radius: 8px;
+        z-index: 0;
+    }
 }
+.players {
+    .flex();
+    justify-content: space-between;
+    align-items: stretch;
+    position: relative;
+    z-index: 2;
+    .player-item {
+        width: 110px;
+        .title {
+            font-size: 14px;
+            font-weight: bold;
+            color: #ffffff;
+            line-height: 18px;
+            text-align: center;
+            padding: 4px 0 16px;
+        }
 
+        .list1 {
+            .flex-col();
+            align-items: center;
+            padding: 4px 0;
+            .name {
+                margin-top: 6px;
+            }
+            &.not {
+                .name {
+                    color: rgba(255, 255, 255, 0.6);
+                }
+            }
+        }
+    }
+}
 .bottom {
     background-color: #0e0042;
     bottom: 0;
@@ -221,4 +361,32 @@ export default {
         }
     }
 }
+
+.help {
+    position: fixed;
+    right: 10px;
+    bottom: 30vh;
+    .flex-col();
+    .help-btn {
+        width: 44px;
+        height: 44px;
+        background: #0e0042;
+        box-shadow: 0px 2px 6px 0px #421292;
+        border-radius: 44px;
+        .flex-col();
+        align-items: center;
+        justify-content: center;
+        img {
+            width: 24px;
+            height: 24px;
+        }
+        span {
+            font-size: 10px;
+            font-family: PingFangSC-Medium, PingFang SC;
+            font-weight: 500;
+            color: rgba(255, 255, 255, 0.6);
+            line-height: 10px;
+        }
+    }
+}
 </style>

+ 9 - 0
src/views/user/ExceptionLog.vue

@@ -0,0 +1,9 @@
+<template>
+    <div></div>
+</template>
+
+<script>
+export default {};
+</script>
+
+<style lang="less" scoped></style>

+ 215 - 0
src/views/user/ExceptionLogAdd.vue

@@ -0,0 +1,215 @@
+<template>
+    <div>
+        <van-form @submit="submit">
+            <van-field
+                class="textarea"
+                name="问题描述"
+                label="问题描述"
+                placeholder="请详细说明,以便于我们解决问题,您最多可填300字。"
+                v-model="form.detail"
+                clearable
+                rows="4"
+                autosize
+                required
+                type="textarea"
+                maxlength="300"
+                show-word-limit
+                :rules="[{ required: true, message: '请输入问题描述' }]"
+            />
+            <van-field name="uploader" required label="上传凭证" :rules="[{ required: true, message: '请上传凭证' }]">
+                <template #label>
+                    <div class="title">
+                        <span>上传凭证</span>
+                        <span>最多上传3张</span>
+                    </div>
+                </template>
+                <template #input>
+                    <van-uploader :max-count="3" v-model="form.pic" :after-read="afterRead" />
+                </template>
+            </van-field>
+            <div class="bottom">
+                <van-button round block type="primary" native-type="submit">提交</van-button>
+            </div>
+        </van-form>
+    </div>
+</template>
+
+<script>
+import Compressor from 'compressorjs';
+export default {
+    setup() {
+        const beforeRead = file =>
+            new Promise(resolve => {
+                // compressorjs 默认开启 checkOrientation 选项
+                // 会将图片修正为正确方向
+                new Compressor(file, {
+                    success: resolve,
+                    error(err) {
+                        console.log(err.message);
+                    }
+                });
+            });
+
+        return {
+            beforeRead
+        };
+    },
+    data() {
+        return {
+            form: {
+                detail: '',
+                pic: []
+            },
+            show: false
+        };
+    },
+    methods: {
+        submit() {
+            this.$toast.loading({
+                message: '加载中...',
+                forbidClick: true
+            });
+            let form = { ...this.form };
+            form.pic = form.pic.map(item => {
+                return item.url;
+            });
+            this.$http
+                .post('/message/create', form, { body: 'json' })
+                .then(res => {
+                    this.$toast.clear();
+                    this.$dialog
+                        .alert({
+                            title: '提交成功',
+                            message:
+                                '感谢您的宝贵意见与建议,我们将于24小时内给您相关答案,感谢您的耐心,愿您有美好的一天!'
+                        })
+                        .then(e => {
+                            this.$router.back();
+                        });
+                })
+                .catch(e => {
+                    this.$toast(e.error);
+                });
+        },
+        afterRead(file, e) {
+            file.status = 'uploading';
+            this.updateFile(file, 'id', 1000).then(img => {
+                console.log(img);
+                file.url = img;
+                file.status = 'done';
+            });
+        }
+    }
+};
+</script>
+
+<style lang="less" scoped>
+/deep/.van-cell {
+    flex-direction: column;
+
+    .van-field__label {
+        color: #ffffff;
+        line-height: 24px;
+        margin-bottom: 10px;
+    }
+}
+/deep/.textarea {
+    .van-field__body {
+        background: @bg2;
+        border-radius: 8px 8px 0 0;
+        padding: 10px;
+    }
+    .van-field__control {
+        font-size: 12px;
+    }
+
+    .van-field__word-limit {
+        font-size: 12px;
+        color: #c8c9cc;
+        line-height: 17px;
+        background: @bg2;
+        margin-top: 0;
+        padding: 10px;
+        border-radius: 0 0 8px 8px;
+    }
+}
+
+/deep/ .van-uploader__upload {
+    border: 1px dashed #c8c9cc;
+    border-radius: 8px;
+    width: 100px;
+    height: 100px;
+}
+
+/deep/.van-uploader__preview-image {
+    border-radius: 8px;
+    width: 100px;
+    height: 100px;
+}
+
+.bottom {
+    padding: 9px 52px;
+    .bottom(9px);
+    position: fixed;
+    left: 0;
+    bottom: 0;
+    right: 0;
+    background-color: @bg2;
+}
+
+/deep/.van-cell__title {
+    width: auto;
+}
+.title {
+    display: inline-block;
+    span {
+        font-size: 14px;
+        font-weight: normal;
+        color: #ffffff;
+        line-height: 24px;
+
+        &:nth-child(2) {
+            font-size: 12px;
+            color: #aaabad;
+            margin-left: 2px;
+        }
+    }
+}
+
+.dialog-box {
+    background: linear-gradient(180deg, #ffeddd 0%, #fff9f4 100%);
+    .flex-col();
+    align-items: center;
+    padding: 24px 16px;
+    position: relative;
+    img {
+        width: 128px;
+        display: block;
+    }
+    .dialog-title {
+        font-size: 16px;
+        font-weight: bold;
+        color: #000000;
+        line-height: 22px;
+        padding: 12px;
+    }
+    .dialog-text {
+        font-size: 14px;
+        color: #939599;
+        line-height: 20px;
+        text-align: center;
+    }
+
+    .close {
+        width: 24px;
+        height: 24px;
+        position: absolute;
+        right: 16px;
+        top: 16px;
+    }
+}
+
+/deep/.van-dialog {
+    width: 260px;
+}
+</style>

+ 9 - 0
src/views/user/ExceptionLogs.vue

@@ -0,0 +1,9 @@
+<template>
+    <div></div>
+</template>
+
+<script>
+export default {};
+</script>
+
+<style lang="less" scoped></style>