Compare commits

..

No commits in common. 'main' and 'fix/goods-0525' have entirely different histories.

@ -1,4 +1,4 @@
FROM node:14.17.0
FROM node:12.13.1
WORKDIR /workload
COPY nuxt.config.js /workload/nuxt.config.js

@ -5,59 +5,30 @@
* @LastEditTime: 2022-05-07 11:39:38
* @Description: file content
-->
# shop-pc
## 前置环境
由于项目依赖问题,开发 `node` 版本必须是 `14`
如果电脑安装 `Node 18`,那么怎么添多个 `node` 版本,可以使用 `nvm` 版本管理工具。具体安装可以参考下面教程
[window install nvm](https://juejin.cn/post/7074108351524634655)
[mac install nvm](https://juejin.cn/post/7206882855200047161)
## 修改前端接口请求地址
进入到 ` plugins/config/env.js` 中:
```js
// 修改对应值即可
const ENV = {
base_url: "https://k8s-horse-gateway.mashibing.cn",
imUrl: "wss://k8s-horse-gateway.mashibing.cn",
};
export default ENV;
```
> `注意 ⚠️`:通常本地开发访问线上接口地址时会出现跨越(协议、域名、端口不一样时就会出现跨越问题),此时后端服务需要配置跨域相关的内容。
## 运行&打包
- 运行直接执行 npm run dev 打包执行 npm run build 即可,会根据分支读取不同环境变量配置;
- 运行直接执行 npm run dev 打包执行npm run build 即可,会根据分支读取不同环境变量配置;
## 环境变量配置
- 环境变量配置文件 env.config;
- 配置与分支对应关系msb_prod -> prod msb_beta -> beta msb_test -> test msb-其他 -> dev
- 环境变量配置文件env.config;
- 配置与分支对应关系msb_prod -> prod msb_beta -> beta msb_test -> test msb-其他 -> dev
- 输出的环境变量文件plugins/config/env.js
- 修改环境变量配置后需要执行 “ node env.config " 输出的环境变量才会更新
```js
``` js
// 直接引入输出的配置文件即可
import ENV from "@/plugins/config/env.js";
import ENV from '@/plugins/config/env.js';
// 直接访问你在配置文件中定义的属性
console.log(ENV.baseUrl);
```
## 公共方法 utils
```
- 公共方法统一放置 utils 文件夹内,可以按分类建方法文件 如:验证类 verify.js 请求类 request.js
## 公共方法utils
- 公共方法统一放置utils文件夹内可以按分类建方法文件 如验证类verify.js 请求类request.js
- 所有公共方法采用大驼峰命名法
- 所有的方法都从 index.js 输出,引入时统一引入 index不允许直接引入方法文件
- 所有的方法都从index.js输出引入时统一引入index不允许直接引入方法文件
- 所有方法文件如果导出的是多个方法,不允许在定义方法时导出,必须在文件底部一一导出,并附上方法简单的注释
```js
``` js
// 正确
import {Req, IsPhone} from '@/common/utils';
@ -75,25 +46,21 @@ export {
IsEmail
}
```
## 组件
- 根目录的components 只放置真正的组件某个页面的业务模块应该在pages的相应目录下新建module目录放置
- 所有的自定义组件文件名以大驼峰命名且在templet中使用也用大驼峰形式使用(包括页面内的模块组件)
- 根目录的 components 只放置真正的组件,某个页面的业务模块应该在 pages 的相应目录下新建 module 目录放置
- 所有的自定义组件文件名以大驼峰命名,且在 templet 中使用也用大驼峰形式使用(包括页面内的模块组件)
## storage的使用
- 不要在页面内直接使用loaclStorage全都放置到vuex中做一次管理
## storage 的使用
- 不要在页面内直接使用 loaclStorage全都放置到 vuex 中做一次管理
## 请求
- 项目中有两个 axios 请求实例,一个事 nuxt 自带的(不需要 Token 请使用这个),一个是额外封装的(需要 Token 请使用这个);
- 需要 Token 的请求,不要写在 asyncData 中;
- 项目中有两个axios请求实例一个事nuxt自带的不需要Token请使用这个一个是额外封装的需要Token请使用这个
- 需要Token的请求不要写在asyncData中
- 所有请求方法命名以Api+请求类型+具体方) 法命名
- 所有请求使用 ToAsyncAwait 包裹
- 不允许使用 try catch 和 then 处理返回结果
```js
- 所有请求使用ToAsyncAwait 包裹
- 不允许使用try catch 和 then 处理返回结果
``` js
// 使用示例
// xxapi.js
import {ToAsyncAwait, ReqestTk} from '@/common/utils'
@ -104,7 +71,7 @@ export {
}
// user.vue
improt {ApiGetUserInfo} from '@/common/api/xxapi.js';
const getUserInfo = async () =>{
const {error, result} = await ApiGetUserInfo();
if(error){
@ -115,47 +82,46 @@ export {
}
```
## css
- 采用BEM命名法
- 采用 BEM 命名法
### 兼容 CSS
```css
### 兼容CSS
``` css
/* 以下兼容方式的样式请使用util.css中的adj方法 */
.my-class {
transform: translate3d(-50%, 0, 0);
-webkit-transform: translate3d(-50%, 0, 0);
-moz-transform: translate3d(-50%, 0, 0);
-o-transform: translate3d(-50%, 0, 0);
-ms-transform: translate3d(-50%, 0, 0);
.my-class{
transform: translate3d(-50%, 0, 0);
-webkit-transform: translate3d(-50%, 0, 0);
-moz-transform: translate3d(-50%, 0, 0);
-o-transform: translate3d(-50%, 0, 0);
-ms-transform: translate3d(-50%, 0, 0);
}
/* 使用以下方法 */
@import "~/assets/scss/util.scss";
.my-class {
.my-class{
@include adj(transform, translate3d(-50%, 0, 0));
}
```
## 登录相关
```javascript
// 访问token
this.$store.state.token;
this.$store.state.token
// 设置token
this.$store.commit("setToken");
this.$store.commit('setToken')
// 退出登录
this.$store.commit("setLoginOut");
this.$store.commit('setLoginOut')
// 获取登录的用户信息
this.$store.state.userInfo;
this.$store.state.userInfo
// 登录拦截
// 示例:点击购买课程前需要判断当前用户是否登录,未登录则弹出登录弹窗
function onPurchaseCourse() {
if (!this.$isLoginValidate()) {
return;
}
// 此处省略其他业务代码...
if (!this.$isLoginValidate()) {
return;
}
// 此处省略其他业务代码...
}
```

Binary file not shown.

Before

Width:  |  Height:  |  Size: 756 B

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 762 B

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 850 B

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 613 B

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 972 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 445 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 445 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 375 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 386 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 386 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 179 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 215 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 242 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.4 KiB

@ -1,10 +1,3 @@
<!--
* @Author: ch
* @Date: 2022-05-26 10:41:45
* @LastEditors: ch
* @LastEditTime: 2022-06-15 15:43:49
* @Description: file content
-->
<template>
<div class="chosen">
<div class="chosen-title flex flex-between flex-middle">
@ -52,6 +45,7 @@ export default {
categoryId: "",
order: "",
});
console.log(res.result);
vm.recommendedData = res.result.records;
vm.pages = res.result.pages;
},

@ -1,58 +0,0 @@
<!--
* @Author: ch
* @Date: 2022-06-23 10:40:04
* @LastEditors: ch
* @LastEditTime: 2022-06-30 22:11:19
* @Description: file content
-->
<template>
<div class="follow">
<b class="follow--title">{{day}}追评:</b>
<p class="follow--ctx">{{followComment.commentContent}}</p>
<UiImgs v-if="imgs.length" :list="imgs" class="follow--imgs" />
</div>
</template>
<script>
import UiImgs from './UiImgs.vue';
export default {
components: { UiImgs },
props : {
followComment : {
type : Object,
default : () => ({})
},
commentTime : {
type : String,
default : ''
}
},
computed:{
day(){
const followTime = (new Date(this.followComment.createTime)).getTime();
const commentTime = (new Date(this.commentTime)).getTime();
const day = Math.floor((followTime - commentTime) / (24 * 60 * 60 * 1000));
return day > 0 ? `${day}天后` : `当天`;
},
imgs (){
let urls = this.followComment.pictureUrl || '';
return urls ? urls.split(',') : [];
}
}
}
</script>
<style lang="scss" scoped>
.follow{
margin-top: 30px;
&--title{
color: #FF6A19;
font-weight: normal;
}
&--ctx{
line-height: 24px;
word-break: break-all;
}
&--imgs{
margin-top: 10px;
}
}
</style>

@ -1,408 +0,0 @@
<!--
* @Author: ch
* @Date: 2022-06-24 11:43:04
* @LastEditors: ch
* @LastEditTime: 2022-07-01 18:13:33
* @Description: file content
-->
<template>
<div class="comment-info">
<div class="side-user" v-if="source == 'detail'">
<el-avatar :size="55" :src="commentDetail.userAvatar" />
<p>{{commentDetail.userName}}</p>
</div>
<div class="side-product" v-else>
<img :src="commentDetail.productPicture"/>
<p>{{commentDetail.productName}}</p>
<span>{{commentDetail.skuName}}</span>
</div>
<main>
<BsCommentSubmit v-if="!commentDetail.id" :commentDetail="commentDetail" @submit="handleSubmit"
@editStatusChange="editStatusChange"/>
<template v-else>
<div class="top">
<el-rate :value="commentDetail.commentScore" disabled/>
<span class="time">{{FormatDate(commentDetail.createTime, 'yyyy-mm-dd hh:ii')}}</span>
</div>
<p class="sku" v-if="commentDetail.skuName">{{commentDetail.skuName}}</p>
<div class="ctx">{{commentDetail.commentContent}}</div>
<!-- 图片预览 -->
<UiImgs v-if="imgs.length" :list="imgs" class="imgs" />
<!-- 追评 -->
<UiButton class="follow-btn" type="yellow_line" radius="4px"
v-if="isCanFollowComment && !showFollowForm" @click="showFollowForm = true">发起追评</UiButton>
<BsCommentSubmit v-if="showFollowForm && !followComment" :type="COMMENT.TYPE.FOLLOW_COMMENT"
:commentDetail="commentDetail" @submit="handleSubmitFollow" @editStatusChange="editStatusChange"/>
<BsCommentFollowInfo v-if="followComment"
:followComment="followComment" :commentTime="commentDetail.commentTime"/>
<!-- 点赞评论Bar -->
<div class="operation">
<div>
<span class="operation--chat" @click="answerVisible = !answerVisible">
<template v-if="replyCount">{{replyCount}}</template>
</span>
<span class="operation--show" v-if="answerCommentList.length" @click="answerVisible = !answerVisible">
{{answerVisible ? '收起' : '展开'}}
</span>
</div>
<span class="operation--thumb" :class="{'operation--thumb__active':isLike}" @click="handleUseful">{{usefulCount || ''}}</span>
</div>
<!-- 评论内容 -->
<div class="answer" v-if="showAnswerBox">
<b class="answer--title">全部评论({{replyCount}})</b>
<ul>
<li class="answer--item" v-if="commentDetail.merchantComment">
<div class="answer--name"><b>{{commentDetail.merchantComment.userName}}</b><span>{{commentDetail.merchantComment.createTime}}</span></div>
<p class="answer--ctx">{{commentDetail.merchantComment.commentContent}}</p>
</li>
<template v-if="answerVisible">
<li class="answer--item" v-for="(item, idx) in answerCommentList" :key="idx">
<div class="answer--name">
<b>{{item.userName}} {{item.parentId !== commentDetail.id ? ` 回复 ${item.parentUserName}` : ''}}</b>
<span>{{FormatDate(item.createTime, 'yyyy-mm-dd hh:ii')}}</span>
</div>
<p class="answer--ctx">{{item.commentContent}}</p>
<span class="answer--answer" @click="handleAnswer(item)"></span>
</li>
</template>
</ul>
<div v-if="answerVisible" class="answer--form">
<input v-model="answerContent" class="answer--input" maxlength="500"
@keydown="handleClearAnswer" :placeholder="answerPlaceholder"/>
<UiButton :disabled="!answerContent.trim()" @click="handleSubmitAnswer"
radius="4px" class="answer--btn" type="red_panel">发表</UiButton>
</div>
</div>
</template>
</main>
</div>
</template>
<script>
import BsCommentFollowInfo from './BsCommentFollowInfo.vue';
import {ApiPostComment, ApiPutCommentUseful} from '@/plugins/api/comment';
import {Debounce, FormatDate } from '@/plugins/utils';
import UiImgs from './UiImgs.vue';
import UiButton from './UiButton.vue';
import BsCommentSubmit from './BsCommentSubmit.vue';
import {COMMENT} from '@/constants'
export default {
components: { BsCommentFollowInfo, UiImgs, UiButton, BsCommentSubmit },
props:{
// commentdetail
source:{
type : String,
default : 'comment'
},
//
isFollowForm : {
type : Boolean,
default : false,
require : true
},
commentDetail : {
type : Object,
default : () => ({})
}
},
data(){
return {
COMMENT,
answerCommentList : this.commentDetail.answerCommentList || [],
answerVisible : false,
answerContent : '',
answer : null,
isLike : this.commentDetail.isLike,
usefulCount : this.commentDetail.usefulCount,
replyCount : this.commentDetail.replyCount,
showFollowForm : this.isFollowForm,
followComment : this.commentDetail.followComment || null
}
},
computed:{
showAnswerBox(){
return (this.commentDetail.merchantComment || this.answerVisible) ? true : false
},
answerPlaceholder (){
return this.answer ? `回复:${this.answer.userName}` : '说点什么吧?'
},
imgs (){
let imgs = this.commentDetail.pictureUrl;
return imgs ? imgs.split(',') : [];
},
/**
* 是否需要显示追评按钮
* 如果是在订单里进来的且没有追评则显示
*/
isCanFollowComment(){
let status = (!this.followComment && this.source === 'comment')
return status;
}
},
watch:{
commentDetail:{
handler(){
this.followComment = this.commentDetail.followComment;
this.replyCount = this.commentDetail.replyCount || 0;
this.answerCommentList = this.commentDetail.answerCommentList || [];
this.isLike = this.commentDetail.isLike;
this.usefulCount = this.commentDetail.usefulCount;
},
deep : true
}
},
mounted(){
this.answerCommentList = this.commentDetail.answerCommentList || [];
},
methods : {
FormatDate,
/**
* 评价成功后抛出事件方便父组件做数据处理
*/
handleSubmit(result){
this.$emit('submit', result)
},
/**
* 追评提交成功抛出事件方便父组件做数据处理
*/
handleSubmitFollow(result){
this.followComment = result;
},
handleAnswer (item){
this.answer = item;
},
/**
* 有回复用户时取消回复对象
* 输入框没有内容后再按一次退格删除回复对象直接回复评价
*/
handleClearAnswer(e){
if(e.code === 'Backspace' && !this.answerContent && this.answer){
this.answer = null;
}
},
/**
* 评论回复请求成功往列表添加一条记录
*/
async handleSubmitAnswer(){
let data = {
commentContent : this.answerContent,
commentType : 3,
originId : this.commentDetail.id,
parentId : this.answer ? this.answer.id : this.commentDetail.id
}
const {error, result} = await ApiPostComment(data);
if(error){
this.$message.error(error.message);
return false
}
this.answerCommentList.unshift({
...result,
userName : this.$store.state.userInfo.nickname,
parentUserName: this.answer ? this.answer.userName : ''
});
this.answerContent = '';
this.replyCount++;
this.$message.success('评论成功!');
},
/**
* 点击点赞做防抖处理
*/
handleUseful(){
this.isLike = !this.isLike
if(this.isLike){
this.usefulCount++;
}else{
this.usefulCount--;
}
if(!this.debounce){
this.debounce = Debounce(this.updateUseFul, 500);
}
this.debounce();
},
/**
* 更新点赞请求
*/
async updateUseFul(){
if(this.isLike === this.commentDetail.isLike){
return false
}
const {error, result} = await ApiPutCommentUseful({
commentId : this.commentDetail.id,
isLike : this.isLike
});
this.commentDetail.isLike = this.isLike;
},
/**
* 动态监听是否有输入内容的评价或追评
*/
editStatusChange(val){
this.$emit('editStatusChange', val)
}
}
}
</script>
<style lang="scss" scoped>
.comment-info{
display: flex;
border-top: 1px solid #f2f2f2;
padding: 35px 20px 50px 0;
main{
flex: 1;
}
}
.side-user{
width: 170px;
text-align: center;
p{
margin-top: 10px;
@include ellipses(2)
}
}
.side-product{
width: 230px;
padding: 0 45px;
text-align: left;
img{
width: 140px;
height: 140px;
object-fit: contain;
}
p{
margin-top: 10px;
@include ellipses(2);
}
span{
margin-top: 5px;
display: block;
color: #999;
font-size: 12px;
}
}
.top{
display: flex;
justify-content: space-between;
margin-bottom: 10px;
/deep/.el-rate__icon{
margin-right: 0;
font-size: 24px;
}
}
.time, .sku{
color: #999;
font-size: 12px;
}
.ctx{
word-break: break-all;
line-height: 24px;
margin-top: 8px;
}
.imgs{
margin-top: 10px;
}
.follow-btn{
margin-top:30px
}
.follow{
margin-top: 30px;
&--title{
color: #FF6A19;
font-weight: normal;
}
}
.operation{
color: #999;
display: flex;
justify-content: space-between;
margin-top: 30px;
&--show{
color: #3083FF;
margin-left: 28px;
cursor: pointer;
}
&--chat,&--thumb{
background: url('@/assets/img/comment/chat.png') no-repeat left center;
background-size: 16px;
padding-left: 24px;
cursor: pointer;
&__active,&:hover{
background-image: url('@/assets/img/comment/chat_active.png');
color: #FF6A19;
}
}
&--thumb{
background-image: url('@/assets/img/comment/thumb.png');
&__active,&:hover{
background-image: url('@/assets/img/comment/thumb_active.png');
}
}
}
.answer{
background: #F8F8F8;
margin-top: 14px;
padding: 0 24px 20px;
&--item{
border-top: 1px solid #eee;
padding: 20px 0;
}
&--title{
height: 54px;
line-height: 54px;
font-weight: normal;
color: #666;
}
&--name{
color: #999;
font-size: 12px;
display: flex;
justify-content: space-between;
}
&--ctx{
color: #666;
margin: 15px 0 0;
word-break: break-all;
}
&--answer{
font-size: 12px;
color: #666;
text-align: right;
cursor: pointer;
display: block;
}
&--form{
display: flex;
}
&--input{
flex: 1;
height: 40px;
border: 1px solid #eee;
border-radius: 4px;
margin-right: 15px;
padding: 0 20px;
}
&--btn{
height: 40px;
font-size: 14px;
}
/deep/.ui-button__disabled{
background: #eee;
color: #999;
}
}
</style>

@ -1,205 +0,0 @@
<!--
* @Author: ch
* @Date: 2022-06-25 15:29:43
* @LastEditors: ch
* @LastEditTime: 2022-06-30 22:53:16
* @Description: file content
-->
<template>
<div class="submit">
<p class="rate-box" v-if="type === COMMENT.TYPE.COMMENT">
<b>满意度评分</b>
<el-rate v-model="rate"></el-rate>
<span>{{reteDesc}}</span>
</p>
<el-input type="textarea" class="textarea" placeholder="从多个维度评价,可以帮助更多想买的人哦~"
v-model="commentContent" show-word-limit :maxlength="500" :rows="6"/>
<div class="operation">
<el-upload list-type="picture-card"
:on-remove="handleRemove" :limit="6"
:action="uploadAction" :data="uploadData"
:before-upload="handleBeforeUpload"
:on-exceed="handleUploadExceed"
:on-error="handleUploadError">
<i class="el-icon-plus"></i>
<p class="upload-txt">我要晒图</p>
</el-upload>
<UiButton class="upload-btn" :disabled="isDisabled" @click="handleSubmit"></UiButton>
</div>
</div>
</template>
<script>
import {ApiPostComment} from '@/plugins/api/comment';
import {ApiPostGetOssConfig} from '@/plugins/api/oss';
import {COMMENT} from '@/constants';
export default {
props : {
type : {
type : String | Number,
default : COMMENT.TYPE.COMMENT
},
commentDetail : {
type : Object,
default : () => ({})
}
},
data(){
return {
COMMENT,
rate: 5,
commentContent : '',
uploadData : {},
uploadAction : '',
fileList : []
}
},
computed:{
isEdit(){
return (this.commentContent || this.fileList.length > 0) ? true : false
},
reteDesc(){
return COMMENT.RATE_LEVEL[this.rate];
},
isDisabled(){
let status = false
if(this.type === COMMENT.TYPE.COMMENT){
status = !this.rate || !this.commentContent.trim();
}
if(this.type === COMMENT.TYPE.FOLLOW_COMMENT){
status = !this.commentContent.trim();
}
return status;
}
},
watch : {
isEdit(val){
this.$emit('editStatusChange', val)
}
},
methods : {
async handleSubmit(){
let data = {
commentContent : this.commentContent,
commentType : this.type,
orderProductId : this.commentDetail.orderProductId,
pictureUrl : this.fileList.map(i => i.url).join(',')
}
if(this.type === COMMENT.TYPE.COMMENT){
data.productId = this.commentDetail.productId;
data.commentScore = this.rate;
}else if(this.type === COMMENT.TYPE.FOLLOW_COMMENT){
data.originId = data.parentId = this.commentDetail.id;
}
const {error, result} = await ApiPostComment(data);
if(error){
this.$message.error(error.message);
return false;
}
this.commentContent = '';
this.fileList = [];
this.$nextTick(()=>{
this.$emit('submit',result);
})
},
/**
* 获取OSS鉴权信息
* configId 自定义文件夹 图片存储的文件夹名称
* serviceName 服务名
*/
async getOssCon(){
const {error, result} = await ApiPostGetOssConfig({
configId : 'account-comment/',
serviceName : 'comment'
});
if(error){
this.$message.error(error.message);
return false
}
return result
},
async handleBeforeUpload(file) {
let result = await this.getOssCon();
if(result){
this.uploadAction = result.host;
this.uploadData = {
...this.uploadData,
policy : result.policy,
OSSAccessKeyId: result.accessId,
success_action_status: 200,
signature: result.signature,
}
}
Object.assign(this.uploadData, {
key: `${result.dir}${"${filename}"}`,
name: file.name,
});
this.fileList.push({
url : `${result.host}/${result.dir}${file.name}`,
uid : file.uid
})
},
handleUploadError(error, file) {
this.handleRemove(file)
},
handleRemove(file) {
this.fileList = this.fileList.filter(i => i.uid != file.uid );
},
handleUploadExceed(){
this.$message.warning('最多只能上传6张照片哦~')
}
}
}
</script>
<style lang="scss" scoped>
.rate-box{
display: flex;
b{
font-weight: normal;
margin-right: 10px;
color: #666;
}
span{
color: #FF6A19;
margin-left: 10px;
}
}
.textarea{
height: 138px;
margin-top: 30px;
}
.operation{
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 15px;
.upload-txt{
font-size: 12px;
color: #999;
}
.upload-btn{
height: 40px;
width: 100px;
border-radius: 4px;
font-size: 14px;
padding: 0;
}
}
/deep/{
.el-upload--picture-card,.el-upload-list__item{
height: 70px !important;
width: 70px !important;
line-height: 20px;
}
.el-upload--picture-card{
padding-top: 10px;
}
}
</style>

@ -64,7 +64,6 @@ import { mapState } from "vuex";
import { Message } from "element-ui";
import { ApiGetCode, ApiPostLogin } from "@/plugins/api/account";
import { IsPhone } from "/plugins/utils";
import {Im, ImInit} from '@/plugins/chat'
const COUNT_DOWN_TIME = 60; //
export default {
@ -168,12 +167,7 @@ export default {
this.dialogVisible = false;
this.$store.commit("setToken", result.token);
this.$store.dispatch("getUserInfo");
// this.$startWebSockets()
// IM
ImInit().then(() => {
//
Im.getSessionList();
});
this.$startWebSockets()
}
}
});

@ -2,45 +2,30 @@
* @Author: ch
* @Date: 2022-05-08 00:39:50
* @LastEditors: ch
* @LastEditTime: 2022-07-11 11:32:27
* @LastEditTime: 2022-05-12 11:44:04
* @Description: file content
-->
<template>
<el-dialog :title="title" width="380px" class="box" center
:visible.sync="myVisible" @open="open" @close="close" :modal="false">
<div class="pay-type" v-if="!myPayType">
<el-radio-group v-model="myPayType">
<el-radio label="wx" class="pay-type--item" >
<img class="pay-type--wx" src="@/assets/img/order/wx.png"/>
</el-radio>
<el-radio label="ali" class="pay-type--item" >
<img class="pay-type--ali" src="@/assets/img/order/zfb.png"/>
</el-radio>
</el-radio-group>
</div>
<div class="pay" v-else>
<el-dialog title="打开微信扫描付款" width="380px" class="box" center
:visible.sync="myVisible" @open="open" @close="close">
<div class="pay">
<span class="pay--timer">{{timerTxt}}</span>
<UiMoney class="money" sufSize="14px" preSize="14px" size="20px"
float suffix prefix :money="orderInfo.payAmount"/>
<div class="pay--code">
<img :src="imgUrl" v-if="imgUrl"/>
<span v-else>{{imgTip}}</span>
<!-- <p v-if="!timer"></p> -->
</div>
<p class="pay--tips">如支付后没有自动跳转请点击 <span class="pay--finish" @click="finish"></span></p>
<UiButton class="pay--btn" type='yellow_gradual' @click="back"></UiButton>
</div>
</el-dialog>
</template>
<script>
import {ApiPostWxPayCdoeImg, ApiPostAliPayCdoeImg} from '@/plugins/api/pay'
import {ApiPostPayCdoeImg} from '@/plugins/api/wx'
import {ApiGetOrderDetail} from '@/plugins/api/order'
import UiMoney from './UiMoney.vue';
import UiButton from './UiButton.vue';
import QRcode from 'qrcode';
import {CreateUUID} from '@/plugins/utils';
import UiMoney from './UiMoney.vue'
export default {
components: { UiMoney, UiButton },
components: { UiMoney },
props : {
visible : {
type : Boolean,
@ -49,30 +34,15 @@ export default {
orderId : {
type : Number | String,
default : ''
},
payType:{
type : String | null,
require : true
}
},
data(){
return {
orderInfo: {},
imgUrl : '',
imgTip : '二维码获取中',
timerTxt : '',
startSecondNum : 0,
timerStop : null,
myPayType : null,
payUUID : null
}
},
watch:{
myPayType(val){
// change使watch
if(val){
this.getQrCode();
}
}
},
computed:{
@ -83,25 +53,12 @@ export default {
set(val){
this.$emit('update:visible', val)
}
},
title(){
let str = '请选择支付方式';
if(this.myPayType === 'wx'){
str = '打开微信扫描付款';
}else if(this.myPayType === 'ali'){
str = '打开支付宝扫描付款';
};
return str;
}
},
methods : {
open(){
this.getOrderInfo();
this.myPayType = this.payType;
if(this.myPayType){
this.getQrCode();
}
this.getCodeImg();
},
/**
* 获取订单最新信息
@ -123,52 +80,12 @@ export default {
}
},
getQrCode(){
this.payUUID = CreateUUID();
if(this.myPayType == 'wx'){
this.getWxCodeImg(this.payUUID);
}else if(this.myPayType === 'ali'){
this.getAliCodeImg(this.payUUID);
}
},
/**
* 支付宝二维码
*/
async getAliCodeImg(uid){
const {error, result} = await ApiPostAliPayCdoeImg({orderId : this.orderId});
if(this.payUUID !== uid){
return false;
}
async getCodeImg(){
const {error, result} = await ApiPostPayCdoeImg({orderId : this.orderId});
if(error){
this.imgTip = error.message;
return false;
}
QRcode.toDataURL(result.payDataInfo.qrCodeData,{
width : 200
}, (err, url)=>{
this.imgUrl = url;
})
},
/**
* 微信二维码
*/
async getWxCodeImg(uid){
const {error, result} = await ApiPostWxPayCdoeImg({orderId : this.orderId});
if(this.payUUID !== uid){
return false;
}
if(error){
this.imgTip = error.message;
return false;
}
QRcode.toDataURL(result.payDataInfo.codeUrlData,{
width : 200
}, (err, url)=>{
this.imgUrl = url;
})
this.imgUrl = result.dataInfo.codeImgData;
},
/**
@ -199,62 +116,22 @@ export default {
close(){
clearInterval(this.timerStop);
this.timerStop = null;
this.myPayType = this.payType;
this.$emit('cancel');
},
finish(){
this.myVisible = false;
this.$emit('finish')
},
back(){
this.myPayType = null;
this.imgUrl = null;
this.imgTip = '二维码获取中'
}
}
}
</script>
<style lang="scss" scoped>
.box{
background: rgba(0,0,0, .5);
}
.pay-type{
width: 270px;
padding: 0 30px;
&--item{
width: 270px;
height: 56px;
display: flex;
align-items: center;
border: 1px solid #ccc;
padding-left: 55px;
margin-bottom: 20px;
}
&--wx{
width: 108px;
}
&--ali{
width: 78px;
}
}
.pay{
text-align: center;
&--code{
width: 160px;
height: 160px;
margin: 15px auto 20px;
position: relative;
overflow: hidden;
img{
width: 200px;
position: absolute;
left: -20px;
top: -20px;
}
span{
line-height: 100px;
color: #999;
}
}
&--timer{
color: #999;
@ -268,12 +145,6 @@ export default {
color: #FF512B;
cursor: pointer;
}
&--btn{
border-radius: 4px;
width: 143px;
height: 32px;
margin-top: 20px;
}
}
.money{
color: #FF512B;

@ -1,137 +0,0 @@
<!--
* @Author: ch
* @Date: 2022-06-24 19:07:45
* @LastEditors: ch
* @LastEditTime: 2022-06-30 19:40:36
* @Description: file content
-->
<template>
<div class="preview-imgs">
<ul>
<li v-for="(i, idx) in list" :key="idx" :class="{'active' : curIndex == idx}" >
<img :src="i" @click="curIndex = idx"/>
</li>
</ul>
<p v-if="curIndex > -1">
<img :src="list[curIndex]" @click="curIndex = -1"/>
<span class="prev" @click="prev" v-if="curIndex > 0"></span>
<span class="next" @click="next" v-if="curIndex < list.length-1"></span>
</p>
</div>
</template>
<script>
export default {
props : {
list : {
type : Array,
default : () => ([])
}
},
data(){
return {
curIndex : -1,
}
},
methods:{
next(){
this.curIndex++;
},
prev(){
this.curIndex--;
}
}
}
</script>
<style lang="scss" scoped>
.preview-imgs{
ul{
display: flex;
}
li{
width: 46px;
height : 46px;
border: 1px solid #eee;
margin-right: 10px;
padding: 1px;
cursor: pointer;
img{
width: 42px;
height: 42px;
object-fit: contain;
}
&.active{
border-color: #FF512B;
position: relative;
&::after{
content: '';
height: 3px;
width: 3px;
bottom: -7px;
left: 17px;
display: block;
position: absolute;
// transform: rotate(45deg);
border: 3px solid #FF512B;
border-left-color: transparent;
border-right-color: transparent;
border-bottom-color: transparent;
}
}
}
p{
width: 300px;
margin-top: 20px;
position: relative;
border: 1px solid #eee;
img{
width: 300px;
height: 300px;
object-fit: contain;
cursor: zoom-out;
}
&:hover span{
display: block;
}
span{
display: none;
position: absolute;
width: 40px;
height: 40px;
background: rgba(0,0,0, .5);
top : 130px;
cursor: pointer;
&::after,&::before{
position: absolute;
display: block;
content: '';
background: #fff;
height: 20px;
width: 4px;
border-radius: 4px;
left: 16px;
top: 4px;
transform: rotate(45deg);
}
&::after{
transform: rotate(-45deg);
top: 16px;
}
&.prev{
left: 10px;
}
&.next{
right: 10px;
&::after{
left: 20px;
transform: rotate(45deg);
}
&::before{
left: 20px;
transform: rotate(-45deg);
}
}
}
}
}
</style>

@ -1,26 +1,15 @@
/*
* @Author: ch
* @Date: 2022-05-17 18:17:00
* @LastEditors: ch
* @LastEditTime: 2022-06-27 16:20:31
* @Description: file content
*/
/**
* 全局常量请避免使用魔法数字
*/
// 用户凭证名
const TOKEN_KEY = "msbPcToken";
const UUID_KEY = "msbPcUuid";
// 订单状态
const ORDER_STATUS = {
WAIT_PAY: 1, // 待付款
WAIT_SEND: 3, //待发货
WAIT_RECEIVE: 4, // 待收货
WAIT_COMMENT: 5,//评价
FINISH: 6,
FINISH_FOLLOW_COMMENT : 7
};
// 性别
@ -41,23 +30,5 @@ const SECKILL_STATUS = {
NOT_START: 1, // 未开始
GOING: 2, // 进行中
};
const COMMENT = {
TYPE: {
// 评价
COMMENT: 1,
// 追评
FOLLOW_COMMENT: 2,
// 回复
ANSWER: 3,
},
RATE_LEVEL: {
1 : '很不满意',
2 : '不太满意',
3 : '一般',
4 : '满意',
5 : '非常满意'
}
}
export { TOKEN_KEY, UUID_KEY, ORDER_STATUS, SEX_TYPE, CATEGROY_LEVEL, SECKILL_STATUS, COMMENT };
export { TOKEN_KEY, ORDER_STATUS, SEX_TYPE, CATEGROY_LEVEL, SECKILL_STATUS };

@ -2,7 +2,7 @@
* @Author: ch
* @Date: 2022-05-05 14:40:00
* @LastEditors: ch
* @LastEditTime: 2022-06-24 14:21:20
* @LastEditTime: 2022-05-07 10:51:53
* @Description: 根据git分支生成对应环境的环境变量
* 开发时如果环境变量换了可以不用重启服务直接运行node env.config.js即可
*/
@ -12,22 +12,19 @@ const getRepoInfo = require('git-repo-info');
const envConfig = {
dev : {
base_url: 'https://k8s-horse-gateway.mashibing.cn',
imUrl : 'wss://k8s-horse-gateway.mashibing.cn'
base_url: 'https://k8s-horse-gateway.mashibing.cn'
},
test : {
base_url: 'https://k8s-horse-gateway.mashibing.cn',
imUrl : 'wss://k8s-horse-gateway.mashibing.cn'
base_url: 'https://k8s-horse-gateway.mashibing.cn'
},
beta : {
base_url: 'https://you-gateway.mashibing.com',
imUrl : 'wss://you-gateway.mashibing.com'
base_url: 'https://you-gateway.mashibing.com'
},
prod : {
base_url: 'https://you-gateway.mashibing.com',
imUrl : 'wss://you-gateway.mashibing.com'
base_url: 'https://you-gateway.mashibing.com'
}
}
const branch = getRepoInfo().branch; // 调用获取git信息
let curEnvConfig = null;
const argv = global.process.argv;
for(key in envConfig){

@ -2,7 +2,7 @@
* @Author: ch
* @Date: 2022-05-04 17:56:39
* @LastEditors: ch
* @LastEditTime: 2022-06-25 10:10:25
* @LastEditTime: 2022-05-08 15:53:49
* @Description: file content
-->
<template>

@ -1,60 +0,0 @@
<!--
* @Author: ch
* @Date: 2022-05-04 17:56:39
* @LastEditors: ch
* @LastEditTime: 2022-06-02 17:49:00
* @Description: file content
-->
<template>
<div class="layout">
<BsLogin :visible.sync="loginVisible" />
<Header :is-sticky="isSticky" />
<div class="layout-content">
<Nuxt />
</div>
</div>
</template>
<script>
import BsLogin from "@/components/BsLogin.vue";
import Header from "./module/header/index.vue";
import Footer from "./module/footer/index.vue";
export default {
name: "down",
components: { Header, Footer, BsLogin },
data() {
return {
isSticky: false,
};
},
computed: {
loginVisible: {
get() {
return this.$store.state.loginVisible;
},
set(val) {
this.$store.commit("setLoginVisible", val);
},
},
},
mounted() {
//
window.addEventListener("scroll", this.scrollEventMethod);
},
destroyed() {
window.removeEventListener("scroll", this.scrollEventMethod);
},
methods: {
scrollEventMethod() {
this.isSticky = window.scrollY > 300;
},
},
};
</script>
<style lang="scss" scoped>
.layout {
.layout-content {
min-height: calc(100vh - 400px);
}
}
</style>

@ -17,9 +17,7 @@
<div class="layout-footer-wrap__line"></div>
<div class="layout-footer-wrap__address">
© 2020 马士兵北京教育科技有限公司
地址北京市海淀区北三环中路44号4号楼1层114 京ICP备17012835号-1
<a href="https://oss-cdn.mashibing.com/teacher_attachment/user_00020172/yyzz.jpg" target="_blank">营业页执照</a>
<a href="https://oss-cdn.mashibing.com/default/cbwxkz.jpg" target="_blank">出版物经营许可证新出发京零字第海210140号</a>
地址北京市海淀区北三环中路44号4号楼1层114 京ICP备17012835号-1
</div>
</div>
</div>
@ -91,9 +89,6 @@ export default {
color: #999999;
text-align: center;
line-height: 20px;
a{
color: #999;
}
}
}
}

@ -42,7 +42,7 @@
</div>
<div class="products-wrap-info__bottom flex flex-between">
<span class="wrap-info-bottom__skuname">{{
item.productSku ? item.productSku.name : '--'
item.productSku.name
}}</span>
<span class="wrap-info-bottom__count">{{ item.number }}</span>
</div>
@ -75,7 +75,7 @@
</div>
<div class="products-wrap-info__bottom flex flex-between">
<span class="wrap-info-bottom__skuname">{{
item.productSku ? item.productSku.name : '--'
item.productSku.name
}}</span>
<span class="wrap-info-bottom__count">{{ item.number }}</span>
</div>

@ -2,6 +2,7 @@
<div class="header-category">
<!-- 热门分类 -->
<div
v-show="showCategroyTab"
class="header-box-tab__category"
@mouseenter="handleCategoryChange(true)"
@mouseleave="handleCategoryChange(false)"
@ -11,6 +12,7 @@
<span>热门分类</span>
</div> -->
<div
v-show="isCategroyOpen || categroyVisible"
class="tab-category__menu"
@mouseenter="handleCategoryTwoChange(true)"
@mouseleave="handleCategoryTwoChange(false)"
@ -66,7 +68,7 @@ export default {
return {
CATEGROY_LEVEL,
categroyTwoVisible: false, //
categroyVisible: true, //
categroyVisible: false, //
currentCategroyId: 0, // id
list: [],
};
@ -96,7 +98,7 @@ export default {
},
methods: {
getList(id) {
let list = this.list.filter((item) => item && item.id == id);
let list = this.list.filter((item) => item.id == id);
// console.log(`list[0]?.list`, list[0]?.list);
return list[0]?.list || [];
},

@ -84,19 +84,43 @@
<img v-show="!orderHover" src="~/assets/img/layout/icon-order.png" />
<span>我的订单</span>
</div>
<div class="header-wrap-content__common flex flex-middle down"
@click="$router.push('/down')">
<img class="down--icon" src="~/assets/img/layout/icon-phone.png"/>
<img class="down--icon__hover" src="~/assets/img/layout/icon-phone-light.png"/>
<span>下载app</span>
</div>
<div class="header-wrap-content--line"></div>
<!-- 下载app -->
<el-popover
popper-class="header-info-bar__popover"
placement="bottom"
width="230"
trigger="hover"
@show="phoneHover = true"
@hide="phoneHover = false"
>
<div
slot="reference"
class="header-wrap-content__common flex flex-middle"
>
<img
v-show="phoneHover"
src="~/assets/img/layout/icon-phone-light.png"
/>
<img
v-show="!phoneHover"
src="~/assets/img/layout/icon-phone.png"
/>
<span :class="{ 'wrap-content-common--light': phoneHover }"
>下载app</span
>
</div>
<div class="info-bar-popover__qrcode">
<img src="~/assets/img/common/app-qrcode.png" />
<span>扫一扫上方二维码 下载APP</span>
</div>
</el-popover>
</div>
</div>
</div>
</template>
<script>
import { mapState } from "vuex";
import {Im, ImInit} from '@/plugins/chat'
const MENU_VALUE = {
PERSONAL: 1,
ADDRESS: 2,
@ -109,6 +133,7 @@ export default {
return {
menuVisible: false,
orderHover: false,
phoneHover: false,
messageHover: false,
menuList: [
{
@ -135,23 +160,9 @@ export default {
},
},
mounted() {
this.initSocket();
this.$startWebSockets();
},
methods: {
initSocket(){
if(!this.userInfo.id){
setTimeout(()=>{
this.initSocket();
},1000)
return false;
}
// IM
ImInit().then(() => {
//
Im.getSessionList();
}).catch(e => {});
},
onLoginClick() {
this.$isLoginValidate();
},
@ -165,7 +176,6 @@ export default {
break;
case MENU_VALUE.LOGON_OUT:
this.$store.dispatch("logout");
Im.close();
}
},
@ -307,26 +317,6 @@ export default {
margin-right: 4px;
}
}
.down{
background:#474747;
padding: 0 10px;
height: 26px;
display: flex;
align-items: center;
border-radius: 4px;
.down--icon__hover{
display: none;
}
&:hover{
color: #ff875b;
.down--icon{
display: none;
}
.down--icon__hover{
display: inline-block;
}
}
}
}
}
}

@ -192,6 +192,7 @@ export default {
z-index: 10;
background: #ffffff;
box-shadow: 0px 4px 10px 1px rgba(0, 0, 0, 0.1);
z-index: 9999;
&--hide-shadow {
box-shadow: none;

@ -2,90 +2,95 @@
* @Author: ch
* @Date: 2022-05-03 22:14:16
* @LastEditors: ch
* @LastEditTime: 2023-04-21 16:14:15
* @LastEditTime: 2022-05-10 14:18:39
* @Description: file content
*/
export default {
// Global page headers: https://go.nuxtjs.dev/config-head
head: {
title: "马士兵严选",
htmlAttrs: {
lang: "zh",
},
meta: [
{ charset: "utf-8" },
{ name: "viewport", content: "width=device-width, initial-scale=1" },
{ hid: "description", name: "description", content: "" },
{ name: "format-detection", content: "telephone=no" },
],
link: [{ rel: "icon", type: "image/x-icon", href: "/logo.ico" }],
},
router: {
extendRoutes(routes, resolve) {
routes.push({
name: "custom",
path: "/",
component: resolve(__dirname, "pages/index/index.vue"),
});
},
middleware: ["redirect"],
},
// Global page headers: https://go.nuxtjs.dev/config-head
head: {
title: '马士兵严选',
htmlAttrs: {
lang: 'zh'
},
meta: [
{ charset: 'utf-8' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
{ hid: 'description', name: 'description', content: '' },
{ name: 'format-detection', content: 'telephone=no' }
],
link: [
{ rel: 'icon', type: 'image/x-icon', href: '/logo.ico' }
]
},
router: {
extendRoutes(routes, resolve) {
routes.push({
name: 'custom',
path: '/',
component: resolve(__dirname, 'pages/index/index.vue')
})
},
middleware: ['redirect']
},
// Global CSS: https://go.nuxtjs.dev/config-css
css: ["@assets/scss/global.scss", "element-ui/lib/theme-chalk/index.css"],
// Global CSS: https://go.nuxtjs.dev/config-css
css: [
'@assets/scss/global.scss',
'element-ui/lib/theme-chalk/index.css'
],
// Plugins to run before rendering page: https://go.nuxtjs.dev/config-plugins
plugins: [
"@/plugins/element-ui",
"@/plugins/axios",
"@/plugins/axiosTk.js",
"@plugins/vue-inject.js",
"@/plugins/v-distpicker",
"@/plugins/router",
"@/plugins/im",
"@/plugins/chat",
],
// Plugins to run before rendering page: https://go.nuxtjs.dev/config-plugins
plugins: [
'@/plugins/element-ui',
'@/plugins/axios',
'@plugins/axiosTk.js',
'@plugins/vue-inject.js',
'@/plugins/v-distpicker',
'@/plugins/router',
'@/plugins/im'
],
// Auto import components: https://go.nuxtjs.dev/config-components
components: true,
// Auto import components: https://go.nuxtjs.dev/config-components
components: true,
// Modules for dev and build (recommended): https://go.nuxtjs.dev/config-modules
buildModules: [],
// Modules for dev and build (recommended): https://go.nuxtjs.dev/config-modules
buildModules: [
],
build: {
vendor: ["v-distpicker"],
vendor: ['v-distpicker']
},
styleResources: {
scss: "@/assets/scss/global.scss",
},
styleResources: {
scss: '@/assets/scss/global.scss'
},
// Modules: https://go.nuxtjs.dev/config-modules
modules: [
"@nuxtjs/axios",
"cookie-universal-nuxt",
"@nuxtjs/style-resources",
],
// Modules: https://go.nuxtjs.dev/config-modules
modules: [
'@nuxtjs/axios',
'cookie-universal-nuxt',
'@nuxtjs/style-resources'
],
// Build Configuration: https://go.nuxtjs.dev/config-build
build: {
transpile: [/^element-ui/],
},
axios: {
// 表示开启代理
// proxy: true,
},
proxy: {
// '/mall/': {
// target: 'https://you-gateway.mashibing.com/', // 目标接口域名
// // target: 'https://k8s-horse-gateway.mashibing.cn/', // 目标接口域名
// pathRewrite: {
// changeOrigin: true, // 表示是否跨域
// },
// },
},
server: {
port: 3000,
host: "0.0.0.0",
},
};
// Build Configuration: https://go.nuxtjs.dev/config-build
build: {
transpile: [/^element-ui/],
},
axios: {
// 表示开启代理
proxy: true,
},
proxy: {
'/mall/': {
// target: 'http://114.55.64.39:3004', // 目标接口域名
target: 'https://you-gateway.mashibing.com/', // 目标接口域名
pathRewrite: {
changeOrigin: true, // 表示是否跨域
},
},
},
server: {
port: 3000, // default: 3000
host: '0.0.0.0' // default: localhost,
},
}

26559
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -3,9 +3,9 @@
"version": "1.0.0",
"private": true,
"scripts": {
"server": "node env.config.js & nuxt",
"server:beta": "node env.config.js --ENV:beta & nuxt",
"server:prod": "node env.config.js --ENV:prod & nuxt",
"dev": "node env.config.js & nuxt",
"dev:beta": "node env.config.js --ENV:beta & nuxt",
"dev:prod": "node env.config.js --ENV:prod & nuxt",
"build:test": "node env.config.js --ENV:test & nuxt build",
"build:beta": "node env.config.js --ENV:beta & nuxt build",
"build:prod": "node env.config.js --ENV:prod & nuxt build",
@ -21,26 +21,25 @@
"element-ui": "^2.15.8",
"js-util-all": "^1.0.6",
"lodash": "^4.17.21",
"nuxt": "2.15.8",
"qrcode": "^1.5.0",
"nuxt": "^2.15.8",
"v-distpicker": "^1.2.13",
"vue": "2.6.14",
"vue-server-renderer": "2.6.14",
"vue-template-compiler": "2.6.14"
"vue": "^2.6.14",
"vue-server-renderer": "^2.6.14",
"vue-template-compiler": "^2.6.14"
},
"devDependencies": {
"@vue/test-utils": "^1.3.0",
"babel-core": "7.0.0-bridge.0",
"webpack": "^4.46.0",
"node-sass": "^4.14.1",
"sass": "^1.32.13",
"sass-loader": "^7.3.1",
"babel-jest": "^27.4.4",
"eslint": "^8.15.0",
"eslint-plugin-prettier": "^4.0.0",
"git-repo-info": "^2.1.1",
"jest": "^27.4.4",
"node-sass": "^4.14.1",
"prettier": "^2.6.2",
"sass": "^1.32.13",
"sass-loader": "^7.3.1",
"vue-jest": "^3.0.4",
"webpack": "^4.46.0"
"vue-jest": "^3.0.4"
}
}

@ -1,153 +0,0 @@
<!--
* @Author: ch
* @Date: 2022-06-27 11:46:34
* @LastEditors: ch
* @LastEditTime: 2022-06-30 23:40:44
* @Description: file content
-->
<template>
<div class="main">
<div class="tab">
<router-link class="tab--link" to="/account/home">个人中心</router-link>
<router-link class="tab--link" to="/account/order/list">我的订单</router-link>
<span>商品评价</span>
</div>
<div class="title">商品评价</div>
<div class="ctx">
<UiLoading v-if="loading"></UiLoading>
<BsCommentInfo v-else source="comment" :isFollowForm="isFollow" v-for="(item, idx) in list" :key="idx"
:commentDetail="item" @submit="handleSubmitComment($event, idx)"
@submitFollow="handleSubmitFollow($event, idx)" @editStatusChange="editChange($event, idx)"/>
</div>
</div>
</template>
<script>
import {
ApiGetOrderCommentDetail,
ApiGetCommentTabCount,
ApiGetProductSatisfaction,
ApiGetCommentCount,
ApiGetCommentDetail
} from "@/plugins/api/comment";
import {COMMENT} from '@/constants'
import BsCommentInfo from '../../../components/BsCommentInfo.vue';
export default {
components: { BsCommentInfo },
data(){
let query = this.$route.query;
return {
isFollow : query.follow ? true : false, //
orderId: query.orderId,
commentId : query.commentId,
list:[],
loading : true
}
},
mounted(){
if(this.orderId){
this.getOrderCommentDetail();
}else if(this.commentId){
this.getCommentDetail();
}
},
beforeRouteLeave(to, from, next) {
if(this.list.findIndex(i => i.isEdit) > -1){
this.$confirm('你正在进行商品评价,退出后评价的内容将不保存!','提示').then(()=>{
next()
}).catch(()=>{
})
}else{
next()
}
},
methods:{
/**
* 按订单查询评价详情
*/
async getOrderCommentDetail() {
this.loading = true;
const { error, result } = await ApiGetOrderCommentDetail({
orderId: this.orderId,
});
this.loading = false;
if (error) {
this.$message.warning(error.message);
return false;
}
this.list = result;
},
/**
* 按评论查询追评时用
*/
async getCommentDetail(){
this.loading = true;
const {error, result} = await ApiGetCommentDetail({
commentId : this.commentId
});
this.loading = false;
if (error) {
this.$message.warning(error.message);
return false;
}
this.list = [result];
},
/**
* 提交评论成功事件
* 如果还有未评论的则弹窗提示并更新当前行的数据
* 如果是最好一条产品评论则跳到评论成功页
*/
handleSubmitComment(result, idx){
this.$set(this.list, idx, {...this.list[idx],...result});
if(this.list.findIndex(i => !i.id) > -1){
this.$message.success('评论成功~');
}else{
this.$router.replace('/account/comment/success');
}
},
editChange(isEdit, idx){
this.$set(this.list[idx], 'isEdit', isEdit);
},
}
}
</script>
<style lang="scss" scoped>
.main{
@include layout-box;
margin-bottom: 32px;
}
.tab{
margin: 10px 0;
height: 40px;
line-height: 40px;
color: #999;
&--link{
background: url('@/assets/img/comment/right.png') no-repeat right center;
padding-right: 15px;
margin-right: 10px;
background-size: 5px;
color: #666;
}
}
.title{
width: 100%;
height: 40px;
line-height: 40px;
background: #f7f7f7;
border: 1px solid #f2f2f2;
padding: 0 16px;
font-size: 14px;
font-family: Microsoft YaHei-Regular, Microsoft YaHei;
font-weight: 400;
color: #666666;
display: flex;
justify-content: space-between;
}
.ctx{
border: 1px solid #f2f2f2;
border-top: 0;
}
</style>

@ -1,114 +0,0 @@
<!--
* @Author: ch
* @Date: 2022-05-08 01:11:33
* @LastEditors: ch
* @LastEditTime: 2022-06-28 10:39:35
* @Description: file content
-->
<template>
<div>
<div class="main">
<img class="icon" src="@/assets/img/order/pay_success.png" />
<p>评价成功</p>
<div>
<UiButton type="grey" @click="$router.back()" :radius="true">返回</UiButton>
</div>
<div v-if="goodsList.length">
<b class="product-title">这些宝贝还在等你得评价哦~</b>
<ul class="product">
<li v-for="(item, idx) in goodsList" :key="idx">
<img :src="item.productPicture"/>
<p>{{item.productName}}</p>
<UiButton type="grey" @click="$router.replace(`/account/comment?orderId=${item.orderId}`)" >
{{item.commentStatus == 1 ? '立即评价' : '立即追评'}}
</UiButton>
</li>
</ul>
</div>
</div>
<BsChosen class="chosen"/>
</div>
</template>
<script>
import UiButton from '@/components/UiButton.vue';
import {ApiGetCommentOrderDetailList} from '@/plugins/api/order';
export default {
components: { UiButton },
data(){
return {
goodsList : []
}
},
mounted(){
this.getAwaitGoodsList();
},
methods:{
async getAwaitGoodsList (){
const {error, result} = await ApiGetCommentOrderDetailList({
length:6,
pageIndex : 1
});
if(error){
this.$message.warning(error.message)
}
this.goodsList = result.records.map(item => {
return {
productPicture : item.productImageUrl,
productName : item.productName,
commentStatus : item.commentStatus,
orderId : item.orderId
}
});
}
}
}
</script>
<style lang="scss" scoped>
.main{
@include layout-box;
text-align: center;
padding: 100px 0 40px;
}
.icon{
width: 239px;
}
b{
display: block;
}
p{
margin: 10px 0 25px 0;
color: #999;
}
button{
margin: 0 10px 30px;
}
.product-title{
height: 40px;
line-height: 40px;
text-align: left;
background: #F7F7F7;
font-weight: normal;
color: #666;
padding: 0 16px;
}
.product{
display: flex;
// justify-content: flex-start;
li{
margin:40px 0 0 48px;
width: 140px;
img{
width: 140px;
height: 140px;
object-fit: contain;
}
p{
@include ellipses(2);
height: 40px;
margin: 10px 0;
}
}
}
</style>

@ -2,7 +2,7 @@
* @Author: ch
* @Date: 2022-05-04 17:27:37
* @LastEditors: ch
* @LastEditTime: 2022-06-28 16:38:18
* @LastEditTime: 2022-05-04 17:27:39
* @Description: file content
-->
@ -79,24 +79,17 @@ export default {
path: `/account/order/list?type=${ORDER_STATUS.WAIT_RECEIVE}`,
count: 0,
},
{
key: "waitComment",
label: "待评价",
icon: getOrderTypeIcon("icon-order4"),
path: `/account/order/list?type=${ORDER_STATUS.WAIT_COMMENT}`,
count: 0,
},
{
key: "progressCount",
label: "退款/售后",
icon: getOrderTypeIcon("icon-order5"),
icon: getOrderTypeIcon("icon-order4"),
path: "/account/order/saleAfter/list",
count: 0,
},
{
key: "",
label: "全部订单",
icon: getOrderTypeIcon("icon-order6"),
icon: getOrderTypeIcon("icon-order5"),
path: "/account/order/list",
},
],
@ -187,8 +180,8 @@ export default {
border: 2px solid #ffffff;
}
img {
width: 50px;
height: 50px;
width: 54px;
height: 54px;
}
}
}

@ -2,7 +2,7 @@
* @Author: ch
* @Date: 2022-05-04 17:48:12
* @LastEditors: ch
* @LastEditTime: 2022-06-13 11:49:37
* @LastEditTime: 2022-05-04 17:53:11
* @Description: file content
-->
@ -13,32 +13,28 @@
<section class="section">
<div
class="section__item flex"
:class="item.payload.customType !== 'orderAutoDelivery' && 'section__pointer'"
v-for="item in msgData"
v-for="item in socketMsgData"
:key="item.id"
@click="handleDetail(item)"
>
<img
class="section__item-img"
:src="item.payload.productImageUrls ? item.payload.productImageUrls[0] : require('@/assets/img/account/im/system.png')"
src="https://img1.baidu.com/it/u=3384796346,381674655&fm=253&fmt=auto&app=138&f=JPEG?w=889&h=500"
alt="消息通知"
/>
<div class="section__item-content flex flex-column">
<div class="section__item-content--title flex">
<span>{{ item.payload.title || `` }}</span>
<span class="section__item-content--time">{{item.createTimeStamp}}</span>
<span>{{ JSON.parse(item.payload).title || `` }}</span>
<span class="section__item-content--time">{{
item.createTimeStamp
}}</span>
</div>
<div class="section__item-content--txt" >{{ item.payload.content }}</div>
<div class="section__item-content--txt" v-if="item.payload.customType === 'orderAutoDelivery'">
<template v-for="i in item.payload.virtualProductContentList" @click="openLink(i)">
<a v-if="i.shipType === 1" :key="i.shipContent" :href="i.shipContent" target="_blank">[]</a>
<span v-else :key="i.shipContent" >{{i.shipContent}}</span>
</template>
<div class="section__item-content--txt">
{{ JSON.parse(item.payload).content || `` }}
</div>
</div>
</div>
<UiEmpty
v-if="!msgData.length"
v-if="!socketMsgData.length"
desc="暂无消息通知"
:icon="require('@/assets/img/account/order/empty.png')"
>
@ -48,74 +44,20 @@
</div>
</template>
<script>
import { mapState } from "vuex";
import UiEmpty from "@/components/UiEmpty.vue";
import {FormatDate, CreateUUID} from '@/plugins/utils';
import {Im, ImInit} from '@/plugins/chat'
export default {
components: { UiEmpty },
data() {
return {};
},
watch:{
curSession(nv,ov){
if(nv.id != ov.id){
Im.getHistoryMsg();
}
}
},
computed: {
curSession(){
let data = this.$store.state.socketMsgData
Im.setCurSessionId(data.id);
return data;
},
msgData(){
let data = this.curSession.messageList || [];
return data.map(i => {
let item = {...i}
item.createTimeStamp = FormatDate(i.createTimeStamp, 'mm-dd hh:ii:ss')
return item;
}).reverse();
},
...mapState(["token", "socketMsgData"]),
},
mounted() {
setTimeout(()=>{
this.readMsg();
},5000)
console.log(`socketMsgData`, this.$store.state);
},
methods: {
/**
* 把当前会话消息置为已读
*/
readMsg(){
Im.setRead({
content: {
sessionId : this.curSession.id
}
});
},
/**
* 点击详情跳转
*/
handleDetail(item){
if(item.payload.customType === 'orderAutoDelivery'){
return false;
}else if(['orderPay','orderDelivery','orderCancel'].includes(item.payload.customType)){
this.$router.push(`/account/order/detail?id=${item.payload.primaryId}`)
}else if(['refundFail','refundSuccess'].includes(item.payload.customType)){
this.$message.warning('请在移动端查看售后详情~');
}else if(item.payload.linkJump){
window.location.href = item.payload.linkJump;
}
}
},
destroyed(){
Im.setCurSessionId(null);
}
methods: {},
};
</script>
<style lang="scss" scoped>
@ -140,23 +82,20 @@ export default {
margin-top: -1px;
max-height: 600px;
overflow-y: auto;
&__pointer{
cursor: pointer;
}
&__item {
margin-bottom: 20px;
&-img {
width: 50px;
height: 50px;
width: 110px;
height: 110px;
margin-right: 20px;
}
&-content {
width: 840px;
&--title {
margin: 8px 0;
margin-top: 8px;
font-size: 16px;
font-family: Microsoft YaHei-Regular, Microsoft YaHei;
font-weight: 400;
@ -176,9 +115,6 @@ export default {
font-family: Microsoft YaHei-Regular, Microsoft YaHei;
font-weight: 400;
color: #999999;
a{
color: #FF875B;
}
}
}
}

@ -2,7 +2,7 @@
* @Author: ch
* @Date: 2022-05-09 14:41:37
* @LastEditors: ch
* @LastEditTime: 2022-06-30 20:01:46
* @LastEditTime: 2022-05-16 18:27:52
* @Description: file content
-->
<template>
@ -13,8 +13,6 @@
<div class="operation--btns">
<!-- 已发货可以确认收货 -->
<UiButton v-if="orderInfo.orderStatus === 4" type="yellow_gradual" :radius="true" @click="receive"></UiButton>
<UiButton v-if="orderInfo.orderStatus === 5" type="yellow_gradual" :radius="true" @click="handelComment"></UiButton>
<UiButton v-if="orderInfo.orderStatus === 6" type="yellow_gradual" :radius="true" @click="handelComment"></UiButton>
<!-- 待支付可以取消支付订单 -->
<template v-if="orderInfo.orderStatus === 1">
<UiButton type="yellow_gradual" :radius="true" @click="payVisible = true">去支付</UiButton>
@ -53,7 +51,6 @@ export default {
'5' : {name:'已收货'},
//
'6' : {name:'交易成功'},
'7' : {name:'交易成功'},
},
ctxCon : {},
startSecondNum : 0,
@ -130,21 +127,11 @@ export default {
uni.$toast(error.message);
return false;
}
this.handelComment()
// this.$message({
// type: 'success',
// message: '!'
// });
// this.emitStatus()
});
},
handelComment(){
this.$router.push({
path : '/account/comment',
query : {
orderId : this.orderInfo.orderId
}
this.$message({
type: 'success',
message: '成功收货!'
});
this.emitStatus()
});
},
emitStatus(){

@ -2,7 +2,7 @@
* @Author: ch
* @Date: 2022-05-04 20:47:29
* @LastEditors: ch
* @LastEditTime: 2022-06-29 11:50:49
* @LastEditTime: 2022-05-16 17:51:43
* @Description: file content
-->
<template>
@ -55,13 +55,11 @@
<UiButton type="yellow_gradual" @click="pay(item)"></UiButton>
<span class="link-btn" @click="cancelPay(item)"></span>
</template>
<UiButton type="yellow_gradual" v-if="item.orderStatus === 4" @click="receive(item)"></UiButton>
<UiButton type="yellow_gradual" v-if="item.orderStatus === 5"
@click="$router.push(`/account/comment?orderId=${item.orderId}`)">去评价</UiButton>
<UiButton type="yellow_gradual" v-if="item.orderStatus === 6"
@click="$router.push(`/account/comment?orderId=${item.orderId}&follow=true`)">去追评</UiButton>
<router-link class="link-btn" :to="`./detail?id=${item.orderId}`" v-if="item.orderStatus >= 4"></router-link>
<router-link :to="`./detail?id=${item.orderId}`" v-if="item.orderStatus > 4"></router-link>
<template v-if="item.orderStatus === 4">
<UiButton type="yellow_gradual" @click="receive(item)"></UiButton>
<router-link class="link-btn" :to="`./detail?id=${item.orderId}`">查看物流</router-link>
</template>
</td>
</template>
</tr>
@ -167,17 +165,11 @@ export default {
this.$$message.error(error.message);
return false;
}
this.$router.push({
path : '/account/tradeSuccess',
query : {
orderId : item.orderId
}
this.$message({
type: 'success',
message: '成功收货!'
});
// this.$message({
// type: 'success',
// message: '!'
// });
// this.reloadData();
this.reloadData()
});
},
handleDetail(id){

@ -2,7 +2,7 @@
* @Author: ch
* @Date: 2019-01-01 08:04:09
* @LastEditors: ch
* @LastEditTime: 2022-06-27 11:38:50
* @LastEditTime: 2022-05-11 21:34:49
* @Description: file content
-->
<template>
@ -30,7 +30,6 @@ export default {
{label : '待付款', value : 1, code: 'unpaidCount'},
{label : '待发货', value : 3, code: 'waitDeliveryCount'},
{label : '待收货', value : 4, code: 'deliveredCount'},
{label : '待评价', value : 5, code: 'waitComment'},
]
}
},

@ -2,15 +2,15 @@
* @Author: ch
* @Date: 2022-05-04 17:48:12
* @LastEditors: ch
* @LastEditTime: 2022-05-20 10:19:35
* @LastEditTime: 2022-05-04 17:53:11
* @Description: file content
-->
<template>
<main class="main flex flex-column flex-middle">
<p class="main-title">售后订单</p>
<img class="main-img" src="@/assets/img/common/app-qrcode.jpg" alt="" />
<p class="main-txt">请前往移动端查看售后订单</p>
<img class="main-img" src="@/assets/img/common/app-qrcode.png" alt="" />
<p class="main-txt">请前往APP查看售后订单</p>
</main>
</template>
<script>

@ -1,10 +1,3 @@
<!--
* @Author: ch
* @Date: 2022-05-20 09:59:05
* @LastEditors: ch
* @LastEditTime: 2022-06-25 15:40:05
* @Description: file content
-->
<!--
* @Author: ch
* @Date: 2022-05-04 17:52:50

@ -1,59 +0,0 @@
<!--
* @Author: ch
* @Date: 2022-05-08 01:11:33
* @LastEditors: ch
* @LastEditTime: 2022-06-27 15:37:53
* @Description: file content
-->
<template>
<div>
<div class="main">
<img class="icon" src="@/assets/img/order/pay_success.png" />
<!-- <b>交易成功</b> -->
<p>交易成功</p>
<div>
<UiButton type="grey" @click="$router.replace(`/`)" :radius="true">返回首页</UiButton>
<UiButton type="yellow_gradual" @click="$router.replace(`./comment?orderId=${orderId}`)" :radius="true">去评价</UiButton>
</div>
</div>
<BsChosen class="chosen"/>
</div>
</template>
<script>
import UiButton from '@/components/UiButton.vue';
import {ApiGetOrderPaySatus} from '@/plugins/api/order';
export default {
components: { UiButton },
data(){
return {
orderId : this.$route.query.orderId
}
},
mounted(){
},
methods:{
}
}
</script>
<style lang="scss" scoped>
.main{
@include layout-box;
text-align: center;
padding: 100px 0 40px;
}
.icon{
width: 239px;
}
b{
display: block;
}
p{
margin: 10px 0 25px 0;
color: #999;
}
button{
margin: 0 10px 30px;
}
</style>

@ -1,231 +0,0 @@
<!--
* @Author: ch
* @Date: 2022-05-26 16:08:17
* @LastEditors: ch
* @LastEditTime: 2022-06-02 17:56:04
* @Description: file content
-->
<template>
<div class="main">
<h2 class="title">下载马士兵严选APP</h2>
<p class="desc"><b>精选好物</b><span></span><b>精致生活</b></p>
<ul class="group">
<li class="group--item">
<div class="qrcode">
<img class="qrcode--img" src="@/assets/img/down/app-qrcode.png"/>
<img class="qrcode--icon" src="@/assets/img/down/android.png"/>
</div>
<span class="group--item-title">Android</span>
</li>
<li class="group--item">
<div class="qrcode qrcode__disabled">
<img class="qrcode--img" src="@/assets/img/down/app-qrcode.png"/>
<img class="qrcode--icon" src="@/assets/img/down/ios.png"/>
</div>
<span class="group--item-title">iOS</span>
<span class="group--item-btn">规划中...</span>
</li>
<li class="group--item">
<div class="qrcode">
<img class="qrcode--img" src="@/assets/img/down/wx-qrcode.jpg"/>
<img class="qrcode--icon" src="@/assets/img/down/wx.png"/>
</div>
<span class="group--item-title">微信公众号</span>
</li>
<li class="group--item">
<div class="qrcode">
<img class="qrcode--img" src="@/assets/img/down/h5-qrcode.png"/>
<img class="qrcode--icon" src="@/assets/img/down/browser.png"/>
</div>
<span class="group--item-title">H5</span>
</li>
</ul>
<div class="bg">
<i class="bg--icon1"></i>
<i class="bg--icon2"></i>
<i class="bg--icon3"></i>
<i class="bg--icon4"></i>
<i class="bg--icon5"></i>
</div>
</div>
</template>
<script>
export default {
layout : 'down'
}
</script>
<style lang="scss" scoped>
.main{
height: calc(100vh - 132px);
overflow: hidden;
position: relative;
}
.title{
text-align: center;
font-size: 40px;
font-family: 'PingFang SC-粗体, PingFang SC';
font-weight: bold;
color: #191919;
margin-top: 115px;
}
.desc{
text-align: center;
font-size: 24px;
font-family: 'PingFang SC-常规体, PingFang SC';
color: #A8A8A8;
line-height: 21px;
display: flex;
align-items: center;
justify-content: center;
margin-top: 24px;
span{
margin: 0 15px;
display: inline-block;
width: 6px;
height: 6px;
background: #A8A8A8;
border-radius: 6px;
}
}
.group{
width: 930px;
margin: 73px auto;
display: flex;
justify-content: space-between;
z-index: 1;
position: relative;
&--item{
text-align: center;
&-title{
display: block;
font-size: 20px;
margin: 25px 0;
}
&-btn{
border: 1px solid #ddd;
color: #A7A7A7;
font-size: 16px;
width: 142px;
height: 40px;
line-height: 40px;
display: inline-block;
}
}
}
// .qrcode--img:hover + .qrcode--icon{
// opacity: 0;
// }
.qrcode{
width: 180px;
height: 180px;
background: url('@/assets/img/down/qrcode.png') no-repeat #F4F3F3 right top;
background-size: 52px;
cursor: pointer;
text-align: center;
position: relative;
&--icon{
position: absolute;
z-index: 1;
width: 57px;
height: 96px;
left: 62px;
top: 42px;
transition: opacity .8s;
opacity: 1;
}
&--img{
position: absolute;
z-index: 2;
left: 0;
top: 0;
transition: opacity 1s;
opacity: 0;
&:hover{
opacity: 1;
+ .qrcode--icon{
opacity: 0;
}
}
}
&__disabled{
cursor: no-drop;
.qrcode--img:hover{
opacity: 0;
+ .qrcode--icon{
opacity: 1;
}
}
}
}
.bg{
width: 1920px;
height: 100%;
@include adj(transform, translateX(-50%));
z-index: 0;
position: absolute;
left: 50%;
top: 0;
i{
position: absolute;
display: block;
background-size: 100%;
}
&--icon1{
bottom: 45px;
width: 152px;
height: 257px;
background-image: url('@/assets/img/down/bg1.png');
}
&--icon2{
top: 148px;
left: 140px;
width: 197px;
height: 214px;
background-image: url('@/assets/img/down/bg2.png');
animation: float 4s ease-in-out infinite;
}
&--icon3{
top: 549px;
left: 960px;
width: 116px;
height: 126px;
background-image: url('@/assets/img/down/bg3.png');
animation: float 5s ease-in-out infinite;
}
&--icon4{
top: 456px;
right: 261px;
width: 133px;
height: 137px;
background-image: url('@/assets/img/down/bg4.png');
animation: float 4s ease-in-out infinite;
}
&--icon5{
top: -72px;
right: -20px;
width: 200px;
height: 200px;
box-shadow: 0 0 50px #ffc9b6;
background: #FFF8F2;
border-radius: 50%;
filter: blur(50px);
// background-image: url('@/assets/img/down/bg5.png');
// background-size: 246px !important ;
// background-repeat: no-repeat;
// background-position: center;
}
}
@keyframes float {
0% {
transform: translateY(0px);
}
50% {
transform: translateY(-50px);
}
100% {
transform: translateY(0px);
}
}
</style>

@ -41,7 +41,6 @@
</main>
</div>
<div v-else>
<HeaderBar v-show="showHeaderBar" :tabKey="headerKey" @jump="handleJump" @addCart="addCart" />
<nav class="nav flex flex-middle flex-center">
<p class="nav__crumbs">
全部商品
@ -189,15 +188,17 @@
></UiGoodsItem>
</div>
<div class="section-details">
<p class="section-title" ref="detailRef">商品详情</p>
<div class="rich" v-html="detailData.detail" v-if="detailData.detail" ></div>
<p class="section-title">商品详情</p>
<div
class="rich"
v-html="detailData.detail"
v-if="detailData.detail"
></div>
<div class="section-details--none" v-else>
<img src="@/assets/img/goods/none.png" alt="" />
<p>暂无详情</p>
</div>
<Comment ref="commentRef" />
</div>
</section>
<BsChosen v-else></BsChosen>
@ -234,16 +235,14 @@ import UiMoney from "@/components/UiMoney.vue";
import UiButton from "@/components/UiButton.vue";
import UiGoodsItem from "@/components/UiGoodsItem.vue";
import BsChosen from "@/components/BsChosen.vue";
import Comment from './module/Comment.vue';
import { ApiPutAddCart } from "@/plugins/api/cart";
import {
ApiGetGoodsDetail,
ApiGetGoodsSkus,
ApiGetGoodsList,
} from "@/plugins/api/goods";
import HeaderBar from './module/HeaderBar.vue';
export default {
components: { UiMoney, UiButton, UiGoodsItem, BsChosen, Comment, HeaderBar },
componetns: { UiMoney, UiButton, UiGoodsItem, BsChosen },
data() {
return {
pageLoading: true,
@ -261,8 +260,6 @@ export default {
startTime: "",
endTime: "",
showService: false,
showHeaderBar : false,
headerKey: 'detail'
};
},
async created() {
@ -280,7 +277,7 @@ export default {
vm.detailData = res1.result;
vm.skuData = res2.result;
vm.recommendedData = res3.result ? res3.result.records : [];
vm.recommendedData = res3.result.records;
vm.pageLoading = false;
if (
vm.detailData.productActivityVO.isActivity &&
@ -344,37 +341,7 @@ export default {
}
},
},
mounted(){
window.addEventListener("scroll", this.setHeaderBar);
this.setHeaderBar()
},
destroyed() {
window.removeEventListener("scroll", this.setHeaderBar);
},
methods: {
setHeaderBar(){
const y = window.scrollY
this.showHeaderBar = y > 700;
if(this.showHeaderBar){
const dY = this.$refs.detailRef.offsetTop;
const cY = this.$refs.commentRef.$el.offsetTop;
if(y >= cY){
this.headerKey = 'comment';
}else if(y > dY){
this.headerKey = 'detail';
}
}
},
handleJump(val){
if(val === 'detail'){
this.$refs.detailRef.scrollIntoView();
}else{
this.$refs.commentRef.$el.scrollIntoView();
}
this.headerKey = val;
},
onShowService() {
this.showService = true;
},
@ -559,8 +526,6 @@ export default {
this.$store.dispatch("getCartProducts");
// this.$Router.push('/cart');
},
},
};
</script>

@ -1,318 +0,0 @@
<!--
* @Author: ch
* @Date: 2022-06-24 14:39:38
* @LastEditors: ch
* @LastEditTime: 2022-06-30 22:42:19
* @Description: file content
-->
<template>
<div>
<div class="title">
<b>商品评价</b>
<div>
<el-dropdown class="title--sort" @command="handleCommand">
<span>{{sortActive.label}}<i class="el-icon-arrow-down el-icon--right"></i></span>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item v-for="item in sortData" :key="item.val" :command="item">{{item.label}}</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
<el-checkbox v-model="isContent" @change="handledContent"></el-checkbox>
</div>
</div>
<div class="ctx">
<div class="ctx--top">
<div class="ctx--top-rate" v-if="productRate">
<b>商品满意度</b>
<p>{{productRate}}</p>
<el-rate :value="productRate" disabled size="16px"></el-rate>
</div>
<div class="ctx--top-tabs" v-if="tabs.length > 1">
<span v-for="i in tabs" :key="i.labelType" @click="handleTabChanage(i)"
:class="{'active':i.labelType == tabActive}">
{{i.labelName}}({{i.commentCount}})
</span>
</div>
</div>
<UiEmpty v-if="!list.length && loading=='finish'" :icon="require('@/assets/img/comment/empty.png')" desc="暂时没有评论"/>
<template v-else>
<BsCommentInfo v-for="item in list" :key="item.id" :commentDetail="item" source="detail"/>
</template>
<div v-if="loading === 'loading'" class="loading">....</div>
<div v-if="loading === 'finish'" class="more" @click="next">></div>
</div>
<!-- <el-pagination class="pagination flex flex-right" layout="prev, pager, next"
@current-change="handleCurrentChange" :current-page.sync="pageIndex"
:page-size="pageSize" :total="total"></el-pagination> -->
</div>
</template>
<script>
import {
ApiGetCommentList,
ApiGetCommentTabCount,
ApiGetProductSatisfaction,
ApiGetCommentCount
} from "@/plugins/api/comment";
import BsCommentInfo from '../../../../components/BsCommentInfo.vue';
export default {
components: { BsCommentInfo },
props : {
},
data(){
return {
productId : this.$route.params.id,
tabActive : -1,
sortData : [{
val : 1,
label : '默认排序'
},{
val : 2,
label : '按时间排序'
}],
sortActive : {
val : 1,
label : '默认排序'
},
productRate : 0,
isContent : true,
pageIndex : 1,
pageSize : 10,
total:0,
tabs : [
{
labelName : '全部',
labelType : -1,
commentCount : 0
}
],
pageSize : 10,
list : [],
loading : 'finish'
}
},
mounted(){
this.getList();
this.getTabCount();
this.getProductSatisfaction();
this.getCount();
},
methods:{
async getList(){
if(this.loading === 'loading'){
return false;
}
this.loading = 'loading';
const {error, result} = await ApiGetCommentList({
pageIndex : this.pageIndex,
length : this.pageSize,
productId : this.productId,
commentLabel : this.tabActive == -1 ? null : this.tabActive,
sortType : this.sortActive.val,
isContent : this.isContent || null
});
this.loading = 'finish';
if(error){
return false;
}
this.list = this.list.concat(result.records);
//
if(result.records.length == 0 && this.pageIndex < result.pages){
this.next();
return false;
}
if(this.pageIndex === result.pages){
this.loading = 'nomore'
}
// this.total = result.total
},
next(){
this.pageIndex++;
this.getList();
},
async getTabCount (){
const {error, result} = await ApiGetCommentTabCount({
productId : this.productId
});
if(error){
return false;
}
this.tabs = this.tabs.concat(result.filter(i => i.commentCount > 0));
},
async getCount(){
const {error, result} = await ApiGetCommentCount({
productId : this.productId
});
if(error){
return false;
}
this.$set(this.tabs[0],'commentCount', result.allCommentCount)
},
async getProductSatisfaction (){
const {error, result} = await ApiGetProductSatisfaction({
productId : this.productId
});
if(error){
return false;
}
this.productRate = Number(result.productSatisfaction);
},
handleCurrentChange(val) {
this.pageIndex = val;
this.getList();
},
handleTabChanage(i){
if(this.tabActive == i.labelType){
return false
}
this.tabActive = i.labelType;
this.pageIndex = 1;
this.list = [];
this.getList();
},
handleCommand(val){
this.sortActive = val;
this.pageIndex = 1;
this.list = [];
this.getList();
},
handledContent(){
this.pageIndex = 1;
this.list = [];
this.getList();
}
}
}
</script>
<style lang="scss" scoped>
.title {
margin-top: 30px;
width: 100%;
height: 40px;
line-height: 40px;
background: #f7f7f7;
border: 1px solid #f2f2f2;
padding: 0 16px;
font-size: 14px;
font-family: Microsoft YaHei-Regular, Microsoft YaHei;
font-weight: 400;
color: #666666;
display: flex;
justify-content: space-between;
&--sort{
margin-right: 30px;
cursor: pointer;
&:hover{
color: #FF512B;
}
}
}
.ctx{
border: 1px solid #f2f2f2;
border-top: 0;
overflow: hidden;
margin-bottom: 40px;
&--top{
display: flex;
&-rate{
width: 170px;
height: 136px;
text-align: center;
padding-top: 26px;
color: #666;
position: relative;
b{
font-weight: normal;
display: block;
height: 14px;
}
p{
font-size: 40px;
height: 40px;
line-height: 40px;
font-weight: bold;
margin-top: 6px;
}
&::after{
content: '';
display: block;
height: 90px;
width: 1px;
background: #eee;
position: absolute;
right: 0;
top: 26px;
}
/deep/.el-rate__icon{
margin-right: 0;
font-size: 24px;
}
}
&-tabs{
margin: 34px 0;
padding-left: 40px;
display: flex;
align-items: center;
span{
display: inline-block;
height: 24px;
line-height: 22px;
border: 1px solid #eee;
color: #999;
padding: 0 12px;
font-size: 12px;
border-radius: 4px;
margin-right: 16px;
cursor: pointer;
&.active{
background: #FF6A19;
border-color: #FF6A19;
color: #fff;
}
}
}
}
}
.loading,.more{
color: #999;
text-align: center;
margin-bottom: 40px;
}
.more{
cursor: pointer;
}
.pagination {
margin: 50px 0;
/deep/.el-pager {
margin-left: 8px;
}
/deep/button,
/deep/.number,
/deep/.btn-quicknext,
/deep/.btn-quickprev {
width: 32px;
height: 32px;
text-align: center;
line-height: 32px;
margin-left: 8px;
border-radius: 2px 2px 2px 2px;
border: 1px solid rgba(0, 0, 0, 0.15);
font-size: 14px;
font-family: Microsoft YaHei-Regular, Microsoft YaHei;
font-weight: 400;
color: rgba(0, 0, 0, 0.65);
}
/deep/.active {
background: #ff512b;
color: #fff;
}
}
</style>

@ -1,84 +0,0 @@
<!--
* @Author: ch
* @Date: 2022-06-25 07:24:32
* @LastEditors: ch
* @LastEditTime: 2022-06-29 14:19:36
* @Description: file content
-->
<template>
<div class="header">
<div class="header--layout">
<ul>
<li @click="handleJump('detail')" :class="{'active': tabKey =='detail'}">商品详情</li>
<li @click="handleJump('comment')" :class="{'active': tabKey =='comment'}">商品评价</li>
</ul>
<UiButton @click="handleAddCart"></UiButton>
</div>
</div>
</template>
<script>
import UiButton from '@/components/UiButton.vue'
export default {
components: { UiButton },
props:{
tabKey : {
type : String,
default : 'detail'
}
},
data(){
return {}
},
methods:{
handleJump(val){
this.$emit('jump', val)
},
handleAddCart(){
this.$emit('addCart')
}
}
}
</script>
<style lang="scss" scoped>
.header{
position: fixed;
top: 0;
left: 0;
right: 0;
height: 70px;
background: #fff;
z-index: 11;
&--layout{
@include layout-box;
padding-left: 256px;
display: flex;
justify-content: space-between;
align-items: center;
height: 100%;
ul{
display: flex;
font-size: 16px;
li{
margin-right: 50px;
height: 70px;
line-height: 70px;
&.active{
color: #FF6A19;
position: relative;
&::after{
display: block;
content: '';
position: absolute;
height: 2px;
width: 40px;
background: #FF6A19;
bottom: 0;
left: 50%;
transform: translateX(-50%);
}
}
}
}
}
}
</style>

@ -11,7 +11,7 @@
<el-skeleton
v-for="item in 5"
:key="item"
style="width: 230px; margin-right: 20px"
style="width: 230px;margin-right:20px;"
:loading="true"
animated
>
@ -194,8 +194,8 @@ export default {
if (vm.levelType == 1) {
vm.params.categoryId = vm.categoryOneList[vm.levelActive].id;
}
if (vm.params.name) {
vm.params.categoryId = "";
if(vm.params.name){
vm.params.categoryId = ''
}
let res = await ApiGetGoodsList(vm.params);
vm.pageloading = false;
@ -259,9 +259,6 @@ export default {
&-btn {
margin-right: 50px;
cursor: pointer;
&:hover {
color: #ff512b;
}
}
&-active {
color: #ff512b;

@ -2,12 +2,12 @@
* @Author: ch
* @Date: 2022-05-03 22:14:16
* @LastEditors: ch
* @LastEditTime: 2022-06-13 10:28:21
* @LastEditTime: 2022-05-04 22:21:04
* @Description: file content
-->
<template>
<div class="home">
<Banner :data="bannerList"/>
<Banner />
<div class="home-wrap">
<Seckil
v-if="seckillData.activityTimeVO"
@ -50,13 +50,9 @@ import {
ApiGetRecommendedGoodsList,
ApiGetGoodsList,
} from "@/plugins/api/goods";
import {
ApiGetAdList
} from "@/plugins/api/ad";
import Banner from "./module/Banner.vue";
import Seckil from "./module/Seckill.vue";
import Pick from "./module/Pick.vue";
import { AD_LOCATION } from '@/plugins/dicts/ad';
import TabbarFixed from "./module/TabbarFixed.vue";
const SECKILL_COUNT = 20; //
const NEW_COUNT = 5; //
@ -99,16 +95,9 @@ export default {
if (newDataResult) {
newData = newDataResult.records || [];
}
let {result:bannerList} = await ApiGetAdList({
location : AD_LOCATION.HOME_BANNER
});
bannerList = bannerList || [];
return {
currentTime,
bannerList,
newData,
sekillGoodsList,
seckillData,
@ -132,10 +121,7 @@ export default {
created() {
this.getSelectProducts();
},
async mounted() {
let {result:bannerList} = await ApiGetAdList({
location : AD_LOCATION.HOME_BANNER
});
mounted() {
//
window.addEventListener("scroll", this.scrollEventMethod);
},

@ -2,17 +2,32 @@
* @Author: ch
* @Date: 2022-05-04 17:50:33
* @LastEditors: ch
* @LastEditTime: 2022-06-01 11:40:32
* @LastEditTime: 2022-05-04 22:15:26
* @Description: file content
-->
<template>
<div class="home-banner">
<el-carousel height="360px" indicator-position="outside">
<el-carousel-item v-for="item in data" :key="item.id">
<el-carousel-item>
<el-image
@click="onBannerClick(item)"
:src="item.pictureUrl"
@click="onAdvBannerClick(0)"
:src="adv1"
fit="cover"
></el-image>
</el-carousel-item>
<el-carousel-item>
<el-image
@click="onAdvBannerClick(1)"
:src="adv2"
fit="cover"
></el-image>
</el-carousel-item>
<el-carousel-item v-for="item in bannerList" :key="item.id">
<el-image
@click="onBannerClick(item.id)"
:src="item.url"
fit="cover"
></el-image>
</el-carousel-item>
@ -20,35 +35,43 @@
</div>
</template>
<script>
import { AD_JUMP_TYPE } from '@/plugins/dicts/ad';
export default {
name: "HomtBanner",
props : {
data : {
type : Array,
default : () => ([])
}
},
data() {
return {
adv1: require("~/assets/img/banner/zbk.jpg"),
adv2: require("~/assets/img/banner/jlp.jpg"),
bannerList: [
{
url: require(`~/assets/img/banner/1.jpg`),
id: 13,
},
{
url: require(`~/assets/img/banner/2.jpg`),
id: 30,
},
{
url: require(`~/assets/img/banner/3.jpg`),
id: 15,
},
{
url: require(`~/assets/img/banner/4.jpg`),
id: 40,
},
],
};
},
methods: {
onBannerClick(item) {
switch (item.jumpType) {
case AD_JUMP_TYPE.GOODS:
window.open(`${location.origin}/goods/detail/${item.jumpUrl}`);
break;
case AD_JUMP_TYPE.CATEGORY:
window.open(`${location.origin}/goods/list?id=${item.jumpUrl}&levelType=2`);
break;
case AD_JUMP_TYPE.LINK:
window.open(item.jumpUrl, '_blank');
break;
default:
break
onBannerClick(goodsId) {
window.open(`${location.origin}/goods/detail/${goodsId}`);
},
onAdvBannerClick(i) {
if (i === 0) {
window.open(`https://www.mashibing.com/live/1530`);
} else if (i === 1) {
window.open(`https://www.mashibing.com/free/1373`);
}
}
},
},
};

@ -185,6 +185,7 @@ export default {
deep: true,
handler(val) {
if (val && val.length >= NEW_MIN_COUNT) {
console.log("新品上架", val);
this.showNew = true;
this.newGoodsOne = val[0];
this.newArray = val.slice(1, NEW_MIN_COUNT);

@ -2,12 +2,13 @@
<div
v-if="tabBarVisible"
class="home-tabbar-fixed flex"
:class="{ 'home-tabbar-fixed--change': appHover }"
:style="tabBarStyle"
>
<!-- <div v-show="appHover" class="home-tabbar-fixed__qrcode">
<div v-show="appHover" class="home-tabbar-fixed__qrcode">
<img src="~/assets/img/common/app-qrcode.png" />
<p>扫一扫上方二维码 下载APP</p>
</div> -->
</div>
<div
class="home-tabbar-fixed__box flex flex-column flex-middle"
:class="{ 'home-tabbar-fixed__box--height-short': !showToTop }"
@ -15,10 +16,9 @@
<div
@mouseenter="appHover = true"
@mouseleave="appHover = false"
@click="$router.push('/down')"
class="tabbar-fixed-box__common flex flex-middle"
>
<span v-show="appHover">APP</span>
<span v-show="appHover">app</span>
<img v-show="!appHover" src="~/assets/img/home/icon-app.png" />
</div>
<div class="tabbar-fixed-box__line"></div>

@ -2,7 +2,7 @@
* @Author: ch
* @Date: 2022-05-04 17:30:58
* @LastEditors: ch
* @LastEditTime: 2022-06-30 11:19:15
* @LastEditTime: 2022-05-27 19:54:39
* @Description: file content
-->
@ -12,17 +12,11 @@
<h3 class="title">收货地址</h3>
<BsAddress v-model="address"/>
</template>
<h3 class="title">支付方式</h3>
<!-- <h3 class="title">支付方式</h3>
<div class="pay-type">
<el-radio-group v-model="payType">
<el-radio label="wx" >
<img class="pay-type--wx" src="@/assets/img/order/wx.png"/>
</el-radio>
<el-radio label="ali" >
<img class="pay-type--ali" src="@/assets/img/order/zfb.png"/>
</el-radio>
</el-radio-group>
</div>
<el-radio label="微信支付" />
<el-radio label="支付宝支付" />
</div> -->
<h3 class="title">确认商品信息</h3>
<OrderInfo :products="orderInfo.products" />
<Message :orderInfo="orderInfo" :message.sync="userMessage"/>
@ -30,7 +24,7 @@
<div class="pay">
<UiButton radius type="red_panel" @click="submit"></UiButton>
</div>
<BsPay :visible.sync="payVisible" :payType="payType" :orderId="payOrder.orderId"
<BsPay :visible.sync="payVisible" :orderId="payOrder.orderId"
@cancel="cancelPay" @finish="finishPay" />
</div>
</template>
@ -51,7 +45,6 @@ export default {
orderInfo : {},
userMessage : '',
payOrder : {},
payType : 'wx',
payVisible : false,
payTimerTxt : '',
payTimerStop : null,
@ -109,7 +102,7 @@ export default {
return false;
}
const {error, result} = await ApiPostSubmitOrder({
orderSource : 6,
orderSource : 4,
recipientAddressId : this.address.id,
isVirtual :this.productType == 2 && true,
shoppingCartIds : query.ids ? query.ids.split(',') : [],
@ -172,12 +165,6 @@ export default {
.pay-type{
border: 1px solid #DDDDDD;
padding: 30px 70px;
&--wx{
width: 130px;
}
&--ali{
width:92px
}
}
.pay{
text-align: right;

@ -1,22 +0,0 @@
/*
* @Author: ch
* @Date: 2022-05-31 11:17:38
* @LastEditors: ch
* @LastEditTime: 2022-06-01 15:53:53
* @Description: file content
*/
import {ToAsyncAwait} from '@/plugins/utils';
import { AD_PLATFORM } from '../dicts/ad';
import {axios} from "../axios";
import ENV from '../config/env';
const BASE_URL = `${ENV.base_url}/mall/marketing`;
// 获取广告列表
export const ApiGetAdList = (params) => ToAsyncAwait(axios.get(`${BASE_URL}/app/advertisement`, {
params: {
platform : AD_PLATFORM.PC,
...params
}
}))

@ -1,86 +0,0 @@
/*
* @Author: ch
* @Date: 2022-06-20 11:38:48
* @LastEditors: ch
* @LastEditTime: 2022-06-30 11:48:53
* @Description: file content
*/
import {axiosTk} from "../axiosTk";
import {axios} from "../axios";
import { ToAsyncAwait } from "../utils";
import ENV from '../config/env';
const BASE_URL = `${ENV.base_url}/mall/comment`;
/**
* 根据商品获取评论列表未
* @param {*} param0
*/
export const ApiGetCommentList = (params) =>
ToAsyncAwait(axiosTk.get(`${BASE_URL}/app/comment`, {
params,
headers: {
notVerifyToken : true
}
}));
/**
* 根据商品获取评论总数
* @param {*} param0
*/
export const ApiGetCommentCount = ({productId}) =>
ToAsyncAwait(axiosTk.get(`${BASE_URL}/app/comment/getAllCommentCountByProductId/${productId}`,{
headers: {
notVerifyToken : true
}
}));
/**
* 根据商品获取标签评论总数
* @param {*} param0
*/
export const ApiGetCommentTabCount = ({productId}) =>
ToAsyncAwait(axiosTk.get(`${BASE_URL}/app/comment/listCommentLabel/${productId}`,{
headers: {
notVerifyToken : true
}
}));
/**
* 获取订单评论详情
* @param {*} param0
*/
export const ApiGetOrderCommentDetail = ({orderId}) =>
ToAsyncAwait(axiosTk.get(`${BASE_URL}/app/comment/listOrderCommentByOrderId/${orderId}`));
/**
* 获取商品满意度
* @param {*} param0
*/
export const ApiGetProductSatisfaction = ({productId}) =>
ToAsyncAwait(axios.get(`${BASE_URL}/app/comment/getProductSatisfaction/${productId}`));
/**
* 获取评论详情
* @param {*} param0
*/
export const ApiGetCommentDetail = ({commentId}) =>
ToAsyncAwait(axiosTk.get(`${BASE_URL}/app/comment/getCommentDetail/${commentId}`, {
headers: {
notVerifyToken : true
}
}));
/**
* 新增评论
* @param {*} param0
*/
export const ApiPostComment = (data) =>
ToAsyncAwait(axiosTk.post(`${BASE_URL}/app/comment`, data));
/**
* 更新评论有用数
* @param {*} param0
*/
export const ApiPutCommentUseful = (data) =>
ToAsyncAwait(axiosTk.put(`${BASE_URL}/app/comment/updateUsefulCount`, data));

@ -1,21 +0,0 @@
/*
* @Author: ch
* @Date: 2022-06-12 14:06:01
* @LastEditors: ch
* @LastEditTime: 2022-06-14 21:14:54
* @Description: file content
*/
import {axiosTk} from "../axiosTk";
import { ToAsyncAwait } from "../utils";
import ENV from '../config/env';
const BASE_URL = `${ENV.base_url}/mall/im`;
/**
* 获取soket登录秘钥
*/
export const ApiGetSoketTicket = () => ToAsyncAwait(axiosTk.get(`${BASE_URL}/ticket`, {
params: {
ticketType: 'CONNECT_TICKET'
}
}));

@ -4,7 +4,7 @@
* @Author: ch
* @Date: 2022-05-04 18:17:25
* @LastEditors: ch
* @LastEditTime: 2022-06-28 09:56:23
* @LastEditTime: 2022-05-09 11:07:28
* @Description: file content
*/
import {axiosTk} from "../axiosTk";
@ -49,12 +49,6 @@ export const ApiGetOrderDetail = (id) =>
export const ApiGetOrderProductDetail = ({orderProductId}) =>
ToAsyncAwait(axiosTk.get(`${BASE_URL}/app/tradeOrder/product/${orderProductId}`));
/**
* 获取待评价订单详请列表
*/
export const ApiGetCommentOrderDetailList = (params) =>
ToAsyncAwait(axiosTk.get(`${BASE_URL}/app/tradeOrder/listOrderProductWaitComment`,{params}))
/**
* 提交订单
* @param {*} data

@ -1,26 +0,0 @@
/*
* @Author: ch
* @Date: 2022-05-08 00:44:22
* @LastEditors: ch
* @LastEditTime: 2022-06-29 14:51:00
* @Description: file content
*/
import {axiosTk} from "../axiosTk";
import {ToAsyncAwait} from "@/plugins/utils";
import ENV from '../config/env';
const BASE_URL = `${ENV.base_url}/mall/trade`;
/**
* 获取微信支付二维码
* @param {*} data
*/
export const ApiPostWxPayCdoeImg = (data) =>
ToAsyncAwait(axiosTk.post(`${BASE_URL}/payCenter/wxPay/nativeData`, data));
/**
* 获取支付宝支付二维码
* @param {*} data
*/
export const ApiPostAliPayCdoeImg = (data) =>
ToAsyncAwait(axiosTk.post(`${BASE_URL}/payCenter/aliPay/qr`, data));

@ -0,0 +1,17 @@
/*
* @Author: ch
* @Date: 2022-05-08 00:44:22
* @LastEditors: ch
* @LastEditTime: 2022-05-08 00:47:55
* @Description: file content
*/
import {axiosTk} from "../axiosTk";
import {ToAsyncAwait} from "@/plugins/utils";
import ENV from '../config/env';
const BASE_URL = `${ENV.base_url}/mall/trade`;
export const ApiPostPayCdoeImg = (data) =>
ToAsyncAwait(axiosTk.post(`${BASE_URL}/pay/wxPay/nativeImage`, data));

@ -1,30 +1,24 @@
/*
* @Author: ch
* @Date: 2022-05-03 23:04:45
* @LastEditors: ch
* @LastEditTime: 2022-06-12 14:35:43
* @LastEditTime: 2022-05-25 17:48:31
* @Description: file content
*/
let axios = null;
import { UUID_KEY } from "@/constants";
import { CreateUUID } from "@/plugins/utils";
export default function ({ app, $axios, store, req }) {
let uuid = store.state.uuid;
if (!uuid && req.headers.cookie) {
uuid = req.headers.cookie.split(';').find(i => i.includes(UUID_KEY));
uuid = uuid && uuid.replace(`${UUID_KEY}=`,'')
}
if (!uuid) {
uuid = CreateUUID(16, 2);
store.commit('setUUID', uuid);
}
let axios = null
export default function({app, $axios, req}) {
// if(req){
// let str = 'Safari'
// if(req.headers['user-agent'].includes('Chrome')){
// str = 'Chrome'
// }
// console.log(str, req.headers['cookie'].split('token')[1]);
// }
// export default function ({app, $axios}) {
// $axios.onRequest(()=>{
$axios.onRequest( config =>{
config.headers.uid = uuid;
return config;
});
// })
$axios.onResponse(response => {
const result = response.data;

@ -2,33 +2,19 @@
* @Author: ch
* @Date: 2022-05-04 17:11:07
* @LastEditors: ch
* @LastEditTime: 2022-06-29 16:01:55
* @LastEditTime: 2022-05-25 17:48:35
* @Description: file content
*/
import { CreateUUID } from "@/plugins/utils";
import { UUID_KEY } from "@/constants";
let axiosTk = null;
export default function ({$axios, store, route, req}, inject) {
export default function ({$axios, store, route}, inject) {
const $axiosTk = $axios.create();
let uuid = store.state.uuid;
if (!uuid && req.headers.cookie) {
uuid = req.headers.cookie.split(';').find(i => i.includes(UUID_KEY));
uuid = uuid && uuid.replace(`${UUID_KEY}=`,'')
}
if (!uuid) {
uuid = CreateUUID(16, 2);
store.commit('setUUID', uuid);
}
$axiosTk.defaults.timeout = 3000;
$axiosTk.onRequest( config =>{
config.headers.uid = uuid;
if(!store.state.token && !config.headers.notVerifyToken){
if(!store.state.token){
location.href = '/';
return Promise.reject({message : '要先登录才能操作哦~'});
}
delete config.headers.notVerifyToken;
config.headers.Authorization = store.state.token;
return config;
});

@ -1,58 +0,0 @@
/*
* @Author: ch
* @Date: 2022-06-12 14:04:56
* @LastEditors: ch
* @LastEditTime: 2022-06-13 20:27:34
* @Description: file content
*/
import MsbIm from '@/plugins/msbIm' ;
import { ToAsyncAwait, FormatJsonSearch } from './utils';
import { ApiGetSoketTicket } from '@/plugins/api/im';
import ENV from '@/plugins/config/env';
const Im = new MsbIm({
reconnect: true,
});
let ImInit = null;
export default ({store }) => {
ImInit = async () => {
const { error, result } = await ApiGetSoketTicket();
if (error) {
return false;
}
const par = FormatJsonSearch({
client: result.client,
ticket: result.ticket,
// 1普通用户 2客服链接
connect: 1,
user: store.state.userInfo.id,
nickname: store.state.userInfo.nickname,
avatar : store.state.userInfo.avatar
})
return await ToAsyncAwait(Im.init({
url: `${ENV.imUrl}/ws${par}`
}))
};
Im.interceptors.dataChangeAfter = () => {
let data = Im.sessionData.find(i => {
return (i.type === 4 && (typeof i.payload === 'string' ? JSON.parse(i.payload).type === 'system' : false));
}) || {}
let msgCount = data.unreadCount || 0;
store.commit('setSocketMsgData', JSON.parse(JSON.stringify(data)));
store.commit('setUnreadCount', msgCount);
}
Im.interceptors.onClose = () => {
Im.setSessionData([]);
Im.setCurSessionId(null);
store.commit('setSocketMsgData', {});
store.commit('setUnreadCount', 0);
}
}
export {
Im,
ImInit
}

@ -1,42 +0,0 @@
/*
* @Author: ch
* @Date: 2022-05-31 11:05:08
* @LastEditors: ch
* @LastEditTime: 2022-05-31 17:23:02
* @Description: file content
*/
const AD_PLATFORM = {
PC: 2,
MOBILE : 1
}
const AD_LOCATION = {
// 首页轮播图
HOME_BANNER: 1,
// 首页精装区
HOME_HARDCOVER: 2,
// 分类banner
CATEGORY_BANNER : 3
}
const AD_JUMP_TYPE = {
// 商品详情
GOODS: 1,
// 分类
CATEGORY: 2,
// 链接
LINK: 3,
// 不跳转
NO_JUMP : 4
}
export {
// 广告所属平台
AD_PLATFORM,
// 广告位置
AD_LOCATION,
// 广告跳转类型
AD_JUMP_TYPE
}

@ -1,13 +1,5 @@
/*
* @Author: ch
* @Date: 2022-05-20 09:59:06
* @LastEditors: ch
* @LastEditTime: 2022-05-27 10:51:44
* @Description: file content
*/
import Vue from "vue";
import { TOKEN_KEY } from "@/constants";
import ENV from '@/plugins/config/env';
function guid() {
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function (c) {
@ -18,9 +10,10 @@ function guid() {
}
Vue.prototype.$startWebSockets = function () {
console.log();
let token = this.$cookies.get(TOKEN_KEY);
if (token && !Vue.prototype.Socket) {
let url = `${ENV.imUrl}/ws?client=${token}&type=1`;
let url = `wss://k8s-horse-gateway.mashibing.cn/ws?client=${token}&type=1`;
Vue.prototype.Socket = new WebSocket(url);
Vue.prototype.Socket.onopen = () => {
let traceId = guid();

@ -1,480 +0,0 @@
/*
* @Author: ch
* @Date: 2022-05-18 14:54:47
* @LastEditors: ch
* @LastEditTime: 2022-06-14 11:49:08
* @Description: file content
*/
import { CreateUUID, FormatDate, ToAsyncAwait } from "@/plugins/utils";
import './potoReq';
import './protoRsp';
const connect = Symbol('connect');
const send = Symbol('send');
const onMessage = Symbol('onMessage');
const fromatPotoReq = (traceId, traceType, content) => {
let messageModel = new proto.ReqModel();
messageModel.setTraceid(traceId);
messageModel.setTracetype(traceType);
content && messageModel.setContent(JSON.stringify(content));
return messageModel.serializeBinary();
},
fromatPotoRsp = (data) => {
const res = proto.RspModel.deserializeBinary(new Uint8Array(data));
let ctx = res.getContent();
ctx = ctx ? JSON.parse(ctx) : {};
if (ctx.payload) {
ctx.payload = JSON.parse(ctx.payload);
}
return {
content: ctx,
traceId: res.getTraceid(),
traceType: res.getTracetype(),
code: res.getCode(),
message: res.getMessage(),
};
};
class MsbIm {
defaultOption = {
ioKey: 'traceId',
reconnect: true,
};
socket = null;
isOpen = false;
queue = {};
interceptors = {
dataChangeBefore: null,
dataChangeAfter: null,
onClose: null,
onMessage: null,
};
sessionData = [];
curSessionId = null;
constructor(option) {
this.option = {
...this.defaultOption,
...option,
};
}
/**
* 创建连接返回一个Promise 创建成功并成功打开连接算连接成功
* @param {*} option
*/
[connect](option) {
return new Promise((resolve, reject) => {
const open = () => {
console.log('[im] open');
this.isOpen = true;
resolve(this.socket);
};
const message = async (res) => {
const result = fromatPotoRsp(res.data);
this.interceptors.onMessage && this.interceptors.onMessage(result);
// 处理服务端主动推送的消息
this[onMessage](result);
// 如果再消息堆里有此消息回调,则执行回调,并删除
const cbk = this.queue[result[this.option.ioKey]];
if (cbk) {
cbk(result.code !== 200 ? { error: result } : { result: result });
delete this.queue[result[this.option.ioKey]];
}
};
const close = () => {
console.log('[im] close');
this.interceptors.onClose && this.interceptors.onClose();
};
let isUni = false;
try {
isUni = uni;
} catch (e) {
isUni = false;
}
if (isUni) {
this.socket = uni.connectSocket({
...this.option,
fail(e) {
reject(e);
},
});
this.socket.onOpen(() => {
open();
this.socket.onMessage((res) => {
message(res);
});
});
this.socket.onClose(() => {
close();
});
} else if (WebSocket) {
try {
this.socket = new WebSocket(this.option.url);
this.socket.binaryType = 'arraybuffer';
this.socket.onopen = () => {
open();
};
this.socket.onmessage = (res) => {
message(res);
};
this.socket.onclose = () => {
close();
};
} catch (e) {
reject(e);
}
}
});
}
/**
* 向服务端发送消息||请求返回一个Promise对象收到ioKey对应的消息会算一个同步完成
* @param {*} data
*/
[send](data) {
return new Promise((resolve, reject) => {
if (!this.isOpen) {
return reject('连接未打开');
}
this.queue[data[this.option.ioKey]] = ({ result, error }) => {
if (result) {
resolve(result);
} else {
reject(error);
}
};
const par = fromatPotoReq(data.traceId, data.traceType, data.content);
let isUni = false;
try {
isUni = uni;
} catch (e) {
isUni = false;
}
if (isUni) {
this.socket.send({
data: par,
fail(e) {
reject({ error: e });
},
});
} else if (WebSocket) {
this.socket.send(par);
}
});
}
/**
* 服务端推送消息只处理服务端主动推送的消息
* @param {*} data
*/
[onMessage](data) {
// 判断非服务端主动推送消息不做处理
if (data[this.option.ioKey] || data.code !== 200) {
return false;
}
console.log('[im] 主动接收的消息', data);
let ctx = data.content;
let historyData = [...this.sessionData],
newData = [];
const hisIndex = historyData.findIndex((i) => i.id === ctx.sessionId);
if (hisIndex >= 0) {
// 存在会话往现有会话增加一条消息,并修改最后一条消息为当前消息
const curHisData = historyData[hisIndex];
curHisData.messageList.push(ctx);
curHisData.lastMessage = ctx;
// 不在当前会话窗口则向会话消息加1条未读
if (ctx.sessionId !== this.curSessionId) {
curHisData.unreadCount++;
} else {
this.setRead({
content: {
sessionId: this.curSessionId,
},
});
}
newData = historyData;
} else {
// 会话列表不存在,则创建一个会话
newData = [
...historyData,
{
fromAvatar: ctx.session.fromAvatar,
fromId: ctx.fromId,
fromNickname: ctx.session.fromNickname,
id: ctx.sessionId,
lastMessage: ctx,
messageList: [ctx],
updateTimeStamp: ctx.createTimeStamp,
unreadCount: 1,
},
];
}
this.setSessionData(newData);
}
init(config) {
return new Promise((resolve, reject) => {
const heart = () => {
// 要优化 心跳没回复需要重连
setTimeout(async () => {
if (this.isOpen) {
await this[send]({
traceId: CreateUUID(),
traceType: 0,
content: { text: 'ping' },
});
}
heart();
}, 1000);
};
this.option = {
...this.option,
...config,
};
this[connect]()
.then((res) => {
resolve(res);
heart();
})
.catch((e) => {
console.log('eeeee', e);
});
});
}
/**
* 设置数据
*/
setSessionData(data) {
let newData = JSON.parse(JSON.stringify(data));
this.interceptors.dataChangeBefore && this.interceptors.dataChangeBefore(newData, this.sessionData);
this.sessionData = newData;
this.interceptors.dataChangeAfter && this.interceptors.dataChangeAfter(this.sessionData);
}
/**
* 设置当前聊天窗口
* Data为Session数据
* @param {*} data
*/
setCurSessionId(id) {
this.curSessionId = id;
}
/**
* 获取会话列表
* @param {*} params
*/
async getSessionList(params) {
const par = {
traceId: CreateUUID(),
traceType: 1,
...params,
};
console.log('[im] 获取会话列表--start', par);
let { error, result } = await ToAsyncAwait(this[send](par));
console.log('[im] 获取会话列表--end', result, error);
if (error) {
return Promise.reject(error);
}
const { content } = result;
// let newData = [];
content.sessionVOS.forEach((item) => {
if (item.lastMessage) {
item.lastMessage.payload = JSON.parse(item.lastMessage.payload || {});
}
let historyData = this.sessionData;
let hisIndex = historyData.findIndex((i) => i.id === item.id);
if (hisIndex < 0) {
item.messageList = [];
}
// let historyData = this.sessionData;
// let hisIndex = historyData.findIndex((i) => i.id === item.id);
// if (hisIndex >= 0) {
// historyData[hisIndex].lastMessage = item.lastMessage;
// historyData[hisIndex].unreadCount++;
// newData.push(historyData[hisIndex]);
// } else {
// item.messageList = [];
// newData = [...newData, item];
// }
});
this.setSessionData(content.sessionVOS);
return Promise.resolve(result);
}
/**
* 获取会话的历史消息记录
* @param {*} params
*/
async getHistoryMsg() {
const curSessionIdx = this.sessionData.findIndex((i) => i.id === this.curSessionId);
const curSession = this.sessionData[curSessionIdx];
console.log(curSession, this.curSessionId,'this.curSessionId');
const msgList = curSession.messageList || [];
const par = {
traceId: CreateUUID(),
traceType: 2,
content: {
sessionId: this.curSessionId,
topMessageId: msgList.length ? msgList[0].id : null,
},
};
console.log('[im] 获取会话历史消息--start', par);
const { error, result } = await ToAsyncAwait(this[send](par));
console.log('[im] 获取会话历史消息--end', result, error);
if (error) {
return Promise.reject(error);
}
const { content } = result;
if (content.length) {
let newData = this.sessionData;
content.forEach((item) => {
item.payload = JSON.parse(item.payload);
});
newData[curSessionIdx].messageList = content.concat(newData[curSessionIdx].messageList);
this.setSessionData(newData);
}
return Promise.resolve(result);
}
/**
* 会话已读
* @param {*} params
*/
async setRead(params) {
const par = {
traceId: CreateUUID(),
traceType: '6',
...params,
};
console.log('[im] 会话已读--start', par);
const { error, result } = await this[send](par);
console.log('[im] 会话已读--end', result, error);
let newData = this.sessionData.map((item) => {
if (item.id == params.content.sessionId) {
item.unreadCount = 0;
}
return item;
});
this.setSessionData(newData);
}
/**
* 发送消息
* @param {*} params
*/
async sendMsg(params) {
const index = this.sessionData.findIndex((i) => i.id === this.curSessionId);
let curSession = this.sessionData[index];
// 临时消息体
let par = {
...params,
traceId: CreateUUID(),
traceType: 3,
};
let msgCtx = {
...params.content,
...par,
createTimeStamp: new Date().getTime(),
sendStatus: 'loading',
};
if (typeof msgCtx.payload === 'string') {
msgCtx.payload = JSON.parse(msgCtx.payload);
}
// 点发送,立即把消息加入消息列表,标记为发送中状态
curSession.messageList.push(msgCtx);
this.setSessionData(this.sessionData);
// 超过时间未返回视为发送失败
this.timerStatus(msgCtx);
console.log('[im] 发送消息--start', par);
const { error, result } = await ToAsyncAwait(this[send](par));
console.log('[im] 发送消息--end', result, error);
// 接到通知,标记消息是否发送成功
for (let i = curSession.messageList.length; i--; ) {
const item = curSession.messageList[i];
if (item[this.option.ioKey] === par[this.option.ioKey]) {
curSession.messageList[i].sendStatus = msgCtx.sendStatus = error ? 'fail' : 'success';
break;
}
}
let newData = [...this.sessionData];
newData[index] = curSession;
this.setSessionData(newData);
if (error) {
return Promise.reject(error);
}
return Promise.resolve(result);
}
/**
* 发送失败时重新发送
* @param {*} params
*/
async resend(params) {
params.sendStatus = 'loading';
this.timerStatus(params);
console.log('[im] 重新发送消息--start', params);
const { error, result } = await ToAsyncAwait(
this[send]({
traceId: params.traceId,
traceType: params.traceType,
content: params.content,
})
);
console.log('[im] 重新发送消息--end', result, error);
params.createTimeStamp = result.createTimeStamp;
if (error) {
params.sendStatus = 'fail';
return Promise.reject(error);
}
params.sendStatus = 'success';
return Promise.resolve(result);
}
timerStatus(msg) {
setTimeout(() => {
if (msg.sendStatus === 'loading') {
msg.sendStatus = 'fail';
delete this.queue[msg.traceId];
}
}, 3000);
}
/**
* 主动创建会话
* @param {*} params
*/
async createSession(params) {
const par = {
traceId: CreateUUID(),
traceType: 9,
...params,
};
console.log('[im] 主动创建会话--start', par);
const { result, error } = await ToAsyncAwait(this[send](par));
console.log('[im] 主动创建会话--end', result, error);
if (error) {
return Promise.reject(error);
}
const { content } = result;
let historyData = this.sessionData;
let curSession = historyData.find((i) => i.id === content.id);
if (!curSession) {
curSession = {
...content,
unreadCount: 0,
messageList: [],
};
const newData = [...historyData, curSession];
this.setSessionData(newData);
}
return Promise.resolve(result);
}
close() {
this.socket.close();
this.socket = null;
this.isOpen = false;
this.setSessionData([]);
}
}
export default MsbIm;

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -2,7 +2,7 @@
* @Author: ch
* @Date: 2022-05-04 21:57:26
* @LastEditors: ch
* @LastEditTime: 2022-06-12 14:40:19
* @LastEditTime: 2022-05-04 22:24:07
* @Description: file content
*/
@ -11,8 +11,8 @@ import {
toAsyncAwait as ToAsyncAwait,
isPhone as IsPhone,
formatDate as FormatDate,
formatJsonSearch as FormatJsonSearch,
creatUuid as CreateUUID
createUUID as CreateUUID,
toSearchJson as ToSearchJson
} from "js-util-all"
/**
@ -47,7 +47,7 @@ export {
// 创建UUID
CreateUUID,
// 请求Search参数转化为JSON格式
FormatJsonSearch,
ToSearchJson,
// 防抖函数
Debounce
}

@ -2,10 +2,10 @@
* @Author: ch
* @Date: 2022-05-04 20:35:20
* @LastEditors: ch
* @LastEditTime: 2022-06-10 10:56:09
* @LastEditTime: 2022-05-07 22:33:28
* @Description: file content
*/
import { TOKEN_KEY,UUID_KEY } from "@/constants";
import { TOKEN_KEY } from "@/constants";
import { ApiGetCurrentUser, ApiPostLogout } from "@/plugins/api/account";
import { ApiGetCartList } from "@/plugins/api/cart";
import { ApiGetCategoryOneList } from "@/plugins/api/goods";
@ -13,7 +13,6 @@ const ONE_DAY = 86400000; // 一天的毫秒数 24 * 60 * 60 * 1000;
const state = () => ({
token: "",
uuid : '',
userInfo: {},
loginVisible: false, // 是否展示登录弹窗
seckillTabVisible: false, // 公共头是否展示秒杀tab
@ -26,14 +25,6 @@ const mutations = {
setUserInfo(state, info) {
state.userInfo = info;
},
setUUID(state, uuid) {
state.uuid = uuid;
this.$cookies.set(UUID_KEY, uuid, {
path: "/",
maxAge: ONE_DAY,
});
},
setToken(state, token) {
state.token = token;
this.$cookies.set(TOKEN_KEY, token, {
@ -68,11 +59,6 @@ const mutations = {
};
const actions = {
async nuxtServerInit({ state, commit, dispatch }) {
// 设置uuid
const uuid = this.$cookies.get(UUID_KEY);
if (!state.uuid && uuid) {
commit("setUUID", uuid);
}
// 设置token数据
const token = this.$cookies.get(TOKEN_KEY);
if (!state.token && token) {

Loading…
Cancel
Save