Merge branch 'master' of https://gitee.com/zhijiantianya/ruoyi-vue-pro into feature/notify

# Conflicts:
#	sql/mysql/ruoyi-vue-pro.sql
#	yudao-ui-admin/src/utils/dict.js
This commit is contained in:
YunaiV
2023-01-27 23:25:08 +08:00
2233 changed files with 107040 additions and 43011 deletions

View File

@@ -6,19 +6,19 @@
<el-table v-loading="loading" :data="list">
<el-table-column label="定义编号" align="center" prop="id" width="400" />
<el-table-column label="定义名称" align="center" prop="name" width="100">
<template slot-scope="scope">
<template v-slot="scope">
<el-button type="text" @click="handleBpmnDetail(scope.row)">
<span>{{ scope.row.name }}</span>
</el-button>
</template>
</el-table-column>
<el-table-column label="定义分类" align="center" prop="category" width="100">
<template slot-scope="scope">
<template v-slot="scope">
<dict-tag :type="DICT_TYPE.BPM_MODEL_CATEGORY" :value="scope.row.category" />
</template>
</el-table-column>
<el-table-column label="表单信息" align="center" prop="formType" width="200">
<template slot-scope="scope">
<template v-slot="scope">
<el-button v-if="scope.row.formId" type="text" @click="handleFormDetail(scope.row)">
<span>{{ scope.row.formName }}</span>
</el-button>
@@ -29,25 +29,25 @@
</template>
</el-table-column>
<el-table-column label="流程版本" align="center" prop="processDefinition.version" width="80">
<template slot-scope="scope">
<template v-slot="scope">
<el-tag size="medium" v-if="scope.row">v{{ scope.row.version }}</el-tag>
<el-tag size="medium" type="warning" v-else>未部署</el-tag>
</template>
</el-table-column>
<el-table-column label="状态" align="center" prop="version" width="80">
<template slot-scope="scope">
<template v-slot="scope">
<el-tag type="success" v-if="scope.row.suspensionState === 1">激活</el-tag>
<el-tag type="warning" v-if="scope.row.suspensionState === 2">挂起</el-tag>
</template>
</el-table-column>
<el-table-column label="部署时间" align="center" prop="deploymentTime" width="180">
<template slot-scope="scope">
<template v-slot="scope">
<span>{{ parseTime(scope.row.deploymentTime) }}</span>
</template>
</el-table-column>
<el-table-column label="定义描述" align="center" prop="description" width="300" show-overflow-tooltip />
<el-table-column label="操作" align="center" width="150" fixed="right">
<template slot-scope="scope">
<template v-slot="scope">
<el-button size="mini" type="text" icon="el-icon-s-custom" @click="handleAssignRule(scope.row)"
v-hasPermi="['bpm:task-assign-rule:update']">分配规则</el-button>
</template>
@@ -76,7 +76,6 @@
<script>
import {getProcessDefinitionBpmnXML, getProcessDefinitionPage} from "@/api/bpm/definition";
import {DICT_TYPE, getDictDatas} from "@/utils/dict";
import {getForm} from "@/api/bpm/form";
import {decodeFields} from "@/utils/formGenerator";
import Parser from '@/components/parser/Parser'
import taskAssignRuleDialog from "../taskAssignRule/taskAssignRuleDialog";

View File

@@ -151,9 +151,7 @@ import RightPanel from '@/views/infra/build/RightPanel'
import {
inputComponents, selectComponents, layoutComponents, formConf
} from '@/components/generator/config'
import {
exportDefault, beautifierConf, isNumberStr, titleCase, deepClone, isObjectObject
} from '@/utils/index'
import {beautifierConf, titleCase, deepClone, isObjectObject} from '@/utils'
import {
makeUpHtml, vueTemplate, vueScript, cssStyle
} from '@/components/generator/html'

View File

@@ -27,18 +27,18 @@
<el-table-column label="编号" align="center" prop="id" />
<el-table-column label="表单名" align="center" prop="name" />
<el-table-column label="开启状态" align="center" prop="status">
<template slot-scope="scope">
<template v-slot="scope">
<dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status"/>
</template>
</el-table-column>
<el-table-column label="备注" align="center" prop="remark" />
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
<template slot-scope="scope">
<template v-slot="scope">
<span>{{ parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template slot-scope="scope">
<template v-slot="scope">
<el-button size="mini" type="text" icon="el-icon-edit" @click="handleDetail(scope.row)"
v-hasPermi="['bpm:form:query']">详情</el-button>
<el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)"

View File

@@ -38,24 +38,24 @@
<el-table-column label="组名" align="center" prop="name" />
<el-table-column label="描述" align="center" prop="description" />
<el-table-column label="成员" align="center">
<template slot-scope="scope">
<template v-slot="scope">
<span v-for="userId in scope.row.memberUserIds">
{{ getUserNickname(userId) }}
</span>
</template>
</el-table-column>
<el-table-column label="状态" align="center" prop="status">
<template slot-scope="scope">
<template v-slot="scope">
<dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" />
</template>
</el-table-column>
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
<template slot-scope="scope">
<template v-slot="scope">
<span>{{ parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template slot-scope="scope">
<template v-slot="scope">
<el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)"
v-hasPermi="['bpm:user-group:update']">修改</el-button>
<el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)"
@@ -127,7 +127,6 @@ export default {
pageSize: 10,
name: null,
status: null,
tenantId: null,
createTime: []
},
// 表单参数

View File

@@ -40,19 +40,19 @@
<el-table v-loading="loading" :data="list">
<el-table-column label="流程标识" align="center" prop="key" />
<el-table-column label="流程名称" align="center" prop="name" width="200">
<template slot-scope="scope">
<template v-slot="scope">
<el-button type="text" @click="handleBpmnDetail(scope.row)">
<span>{{ scope.row.name }}</span>
</el-button>
</template>
</el-table-column>
<el-table-column label="流程分类" align="center" prop="category" width="100">
<template slot-scope="scope">
<template v-slot="scope">
<dict-tag :type="DICT_TYPE.BPM_MODEL_CATEGORY" :value="scope.row.category" />
</template>
</el-table-column>
<el-table-column label="表单信息" align="center" prop="formType" width="200">
<template slot-scope="scope">
<template v-slot="scope">
<el-button v-if="scope.row.formId" type="text" @click="handleFormDetail(scope.row)">
<span>{{ scope.row.formName }}</span>
</el-button>
@@ -63,31 +63,31 @@
</template>
</el-table-column>
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
<template slot-scope="scope">
<template v-slot="scope">
<span>{{ parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
<el-table-column label="最新部署的流程定义" align="center">
<el-table-column label="流程版本" align="center" prop="processDefinition.version" width="80">
<template slot-scope="scope">
<template v-slot="scope">
<el-tag size="medium" v-if="scope.row.processDefinition">v{{ scope.row.processDefinition.version }}</el-tag>
<el-tag size="medium" type="warning" v-else>未部署</el-tag>
</template>
</el-table-column>
<el-table-column label="激活状态" align="center" prop="processDefinition.version" width="80">
<template slot-scope="scope">
<template v-slot="scope">
<el-switch v-if="scope.row.processDefinition" v-model="scope.row.processDefinition.suspensionState"
:active-value="1" :inactive-value="2" @change="handleChangeState(scope.row)" />
</template>
</el-table-column>
<el-table-column label="部署时间" align="center" prop="deploymentTime" width="180">
<template slot-scope="scope">
<template v-slot="scope">
<span v-if="scope.row.processDefinition">{{ parseTime(scope.row.processDefinition.deploymentTime) }}</span>
</template>
</el-table-column>
</el-table-column>
<el-table-column label="操作" align="center" width="450" fixed="right">
<template slot-scope="scope">
<template v-slot="scope">
<el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)"
v-hasPermi="['bpm:model:update']">修改流程</el-button>
<el-button size="mini" type="text" icon="el-icon-setting" @click="handleDesign(scope.row)"
@@ -489,7 +489,10 @@ export default {
}).then(() => {
this.getList();
this.$modal.msgSuccess(statusState + "成功");
}).catch(() => {});
}).catch(() => {
// 取消后,进行恢复按钮
row.processDefinition.suspensionState = (state === 1 ? 2 : 1);
});
},
/** 导入按钮操作 */
handleImport() {

View File

@@ -41,33 +41,33 @@
<el-table v-loading="loading" :data="list">
<el-table-column label="申请编号" align="center" prop="id" />
<el-table-column label="状态" align="center" prop="result">
<template slot-scope="scope">
<template v-slot="scope">
<dict-tag :type="DICT_TYPE.BPM_PROCESS_INSTANCE_RESULT" :value="scope.row.result"/>
</template>
</el-table-column>
<el-table-column label="开始时间" align="center" prop="startTime" width="180">
<template slot-scope="scope">
<template v-slot="scope">
<span>{{ parseTime(scope.row.startTime) }}</span>
</template>
</el-table-column>
<el-table-column label="结束时间" align="center" prop="endTime" width="180">
<template slot-scope="scope">
<template v-slot="scope">
<span>{{ parseTime(scope.row.endTime) }}</span>
</template>
</el-table-column>
<el-table-column label="请假类型" align="center" prop="type">
<template slot-scope="scope">
<template v-slot="scope">
<dict-tag :type="DICT_TYPE.BPM_OA_LEAVE_TYPE" :value="scope.row.type"/>
</template>
</el-table-column>
<el-table-column label="原因" align="center" prop="reason" />
<el-table-column label="申请时间" align="center" prop="applyTime" width="180">
<template slot-scope="scope">
<template v-slot="scope">
<span>{{ parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="200">
<template slot-scope="scope">
<template v-slot="scope">
<el-button size="mini" type="text" icon="el-icon-delete" @click="handleCancel(scope.row)"
v-hasPermi="['bpm:oa-leave:create']" v-if="scope.row.result === 1">取消请假</el-button>
<el-button size="mini" type="text" icon="el-icon-view" @click="handleDetail(scope.row)"
@@ -159,7 +159,7 @@ export default {
type: 'warning',
confirmButtonText: "确定",
cancelButtonText: "取消",
inputPattern: /^[\s\S]*.*[^\s][\s\S]*$/, // 判断非空,且非空格
inputPattern: /^[\s\S]*.*\S[\s\S]*$/, // 判断非空,且非空格
inputErrorMessage: "取消原因不能为空",
}).then(({ value }) => {
return cancelProcessInstance(id, value);

View File

@@ -4,25 +4,25 @@
<div v-if="!selectProcessInstance">
<el-table v-loading="loading" :data="list">
<el-table-column label="流程名称" align="center" prop="name" width="200">
<template slot-scope="scope">
<template v-slot="scope">
<el-button type="text" @click="handleBpmnDetail(scope.row)">
<span>{{ scope.row.name }}</span>
</el-button>
</template>
</el-table-column>
<el-table-column label="流程分类" align="center" prop="category" width="100">
<template slot-scope="scope">
<template v-slot="scope">
<dict-tag :type="DICT_TYPE.BPM_MODEL_CATEGORY" :value="scope.row.category" />
</template>
</el-table-column>
<el-table-column label="流程版本" align="center" prop="processDefinition.version" width="80">
<template slot-scope="scope">
<template v-slot="scope">
<el-tag size="medium" v-if="scope.row">v{{ scope.row.version }}</el-tag>
</template>
</el-table-column>
<el-table-column label="流程描述" align="center" prop="description" width="300" show-overflow-tooltip />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template slot-scope="scope">
<template v-slot="scope">
<el-button type="text" size="small" icon="el-icon-plus" @click="handleSelect(scope.row)">选择</el-button>
</template>
</el-table-column>

View File

@@ -110,8 +110,8 @@ import {DICT_TYPE, getDictDatas} from "@/utils/dict";
import store from "@/store";
import {decodeFields} from "@/utils/formGenerator";
import Parser from '@/components/parser/Parser'
import {createProcessInstance, getProcessInstance} from "@/api/bpm/processInstance";
import {approveTask, getTaskListByProcessInstanceId, rejectTask, updateTaskAssignee,backTask} from "@/api/bpm/task";
import {getProcessInstance} from "@/api/bpm/processInstance";
import {approveTask, getTaskListByProcessInstanceId, rejectTask, updateTaskAssignee} from "@/api/bpm/task";
import {getDate} from "@/utils/dateUtils";
import {listSimpleUsers} from "@/api/system/user";
import {getActivityList} from "@/api/bpm/activity";

View File

@@ -52,39 +52,39 @@
<el-table-column label="编号" align="center" prop="id" width="320" />
<el-table-column label="流程名" align="center" prop="name" />
<el-table-column label="流程分类" align="center" prop="category">
<template slot-scope="scope">
<template v-slot="scope">
<dict-tag :type="DICT_TYPE.BPM_MODEL_CATEGORY" :value="scope.row.category" />
</template>
</el-table-column>
<el-table-column label="当前审批任务" align="center" prop="tasks">
<template slot-scope="scope">
<template v-slot="scope">
<el-button v-for="task in scope.row.tasks" :key="task.id" type="text" @click="handleFormDetail(task.id)">
<span>{{ task.name }}</span>
</el-button>
</template>
</el-table-column>
<el-table-column label="状态" align="center" prop="status">
<template slot-scope="scope">
<template v-slot="scope">
<dict-tag :type="DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS" :value="scope.row.status" />
</template>
</el-table-column>
<el-table-column label="结果" align="center" prop="result">
<template slot-scope="scope">
<template v-slot="scope">
<dict-tag :type="DICT_TYPE.BPM_PROCESS_INSTANCE_RESULT" :value="scope.row.result"/>
</template>
</el-table-column>
<el-table-column label="提交时间" align="center" prop="createTime" width="180">
<template slot-scope="scope">
<template v-slot="scope">
<span>{{ parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
<el-table-column label="结束时间" align="center" prop="createTime" width="180">
<template slot-scope="scope">
<template v-slot="scope">
<span>{{ parseTime(scope.row.endTime) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template slot-scope="scope">
<template v-slot="scope">
<el-button type="text" size="small" icon="el-icon-delete" v-if="scope.row.result === 1"
v-hasPermi="['bpm:process-instance:cancel']" @click="handleCancel(scope.row)">取消</el-button>
<el-button size="mini" type="text" icon="el-icon-edit" @click="handleDetail(scope.row)"
@@ -164,7 +164,7 @@ export default {
type: 'warning',
confirmButtonText: "确定",
cancelButtonText: "取消",
inputPattern: /^[\s\S]*.*[^\s][\s\S]*$/, // 判断非空,且非空格
inputPattern: /^[\s\S]*.*\S[\s\S]*$/, // 判断非空,且非空格
inputErrorMessage: "取消原因不能为空",
}).then(({ value }) => {
return cancelProcessInstance(id, value);

View File

@@ -24,28 +24,28 @@
<el-table-column label="所属流程" align="center" prop="processInstance.name" width="200" />
<el-table-column label="流程发起人" align="center" prop="processInstance.startUserNickname" width="120" />
<el-table-column label="结果" align="center" prop="result">
<template slot-scope="scope">
<template v-slot="scope">
<dict-tag :type="DICT_TYPE.BPM_PROCESS_INSTANCE_RESULT" :value="scope.row.result"/>
</template>
</el-table-column>
<el-table-column label="审批意见" align="center" prop="reason" width="200" />
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
<template slot-scope="scope">
<template v-slot="scope">
<span>{{ parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
<el-table-column label="审批时间" align="center" prop="endTime" width="180">
<template slot-scope="scope">
<template v-slot="scope">
<span>{{ parseTime(scope.row.endTime) }}</span>
</template>
</el-table-column>
<el-table-column label="耗时" align="center" prop="durationInMillis" width="180">
<template slot-scope="scope">
<template v-slot="scope">
<span>{{ getDateStar(scope.row.durationInMillis) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" fixed="right" class-name="small-padding fixed-width">
<template slot-scope="scope">
<template v-slot="scope">
<el-button size="mini" type="text" icon="el-icon-edit" @click="handleAudit(scope.row)"
v-hasPermi="['bpm:task:query']">详情</el-button>
</template>

View File

@@ -24,18 +24,18 @@
<el-table-column label="所属流程" align="center" prop="processInstance.name" />
<el-table-column label="流程发起人" align="center" prop="processInstance.startUserNickname" />
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
<template slot-scope="scope">
<template v-slot="scope">
<span>{{ parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
<el-table-column label="状态" align="center" prop="version" width="80">
<template slot-scope="scope">
<template v-slot="scope">
<el-tag type="success" v-if="scope.row.suspensionState === 1">激活</el-tag>
<el-tag type="warning" v-if="scope.row.suspensionState === 2">挂起</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template slot-scope="scope">
<template v-slot="scope">
<el-button size="mini" type="text" icon="el-icon-edit" @click="handleAudit(scope.row)"
v-hasPermi="['bpm:task:update']">审批</el-button>
</template>
@@ -50,7 +50,6 @@
<script>
import {getTodoTaskPage} from '@/api/bpm/task'
import {listSimpleUsers} from "@/api/system/user";
export default {
name: "Todo",

View File

@@ -6,19 +6,19 @@
<el-table-column label="任务名" align="center" prop="taskDefinitionName" width="120" fixed />
<el-table-column label="任务标识" align="center" prop="taskDefinitionKey" width="120" show-tooltip-when-overflow />
<el-table-column label="规则类型" align="center" prop="type" width="120">
<template slot-scope="scope">
<template v-slot="scope">
<dict-tag :type="DICT_TYPE.BPM_TASK_ASSIGN_RULE_TYPE" :value="scope.row.type" />
</template>
</el-table-column>
<el-table-column label="规则范围" align="center" prop="options" width="440px">
<template slot-scope="scope">
<template v-slot="scope">
<el-tag size="medium" v-if="scope.row.options" :key="option" v-for="option in scope.row.options">
{{ getAssignRuleOptionName(scope.row.type, option) }}
</el-tag>
</template>
</el-table-column>
<el-table-column v-if="modelId" label="操作" align="center" width="80" fixed="right">
<template slot-scope="scope">
<template v-slot="scope">
<el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdateTaskAssignRule(scope.row)"
v-hasPermi="['bpm:task-assign-rule:update']">修改</el-button>
</template>

View File

@@ -3,7 +3,7 @@
</template>
<script>
import echarts from 'echarts'
import * as echarts from 'echarts'
require('echarts/theme/macarons') // echarts theme
import resize from './mixins/resize'

View File

@@ -3,7 +3,7 @@
</template>
<script>
import echarts from 'echarts'
import * as echarts from 'echarts'
require('echarts/theme/macarons') // echarts theme
import resize from './mixins/resize'

View File

@@ -143,8 +143,7 @@ export default {
.card-panel-description {
float: right;
font-weight: bold;
margin: 26px;
margin-left: 0px;
margin: 26px 26px 26px 0;
.card-panel-text {
line-height: 18px;

View File

@@ -3,7 +3,7 @@
</template>
<script>
import echarts from 'echarts'
import * as echarts from 'echarts'
require('echarts/theme/macarons') // echarts theme
import resize from './mixins/resize'

View File

@@ -3,7 +3,7 @@
</template>
<script>
import echarts from 'echarts'
import * as echarts from 'echarts'
require('echarts/theme/macarons') // echarts theme
import resize from './mixins/resize'

View File

@@ -1,570 +0,0 @@
<template>
<div class="app-container home">
<el-row :gutter="20">
<el-col :sm="24" :lg="24">
<blockquote class="text-warning" style="font-size: 14px">
领取阿里云通用云产品1888优惠券
<br />
<el-link
href="https://www.aliyun.com/minisite/goods?userCode=brki8iof"
type="primary"
target="_blank"
>https://www.aliyun.com/minisite/goods?userCode=brki8iof</el-link
>
<br />
领取腾讯云通用云产品2860优惠券
<br />
<el-link
href="https://cloud.tencent.com/redirect.php?redirect=1025&cps_key=198c8df2ed259157187173bc7f4f32fd&from=console"
type="primary"
target="_blank"
>https://cloud.tencent.com/redirect.php?redirect=1025&cps_key=198c8df2ed259157187173bc7f4f32fd&from=console</el-link
>
<br />
阿里云服务器折扣区
<el-link href="http://aly.ruoyi.vip" type="primary" target="_blank"
>>点我进入</el-link
>
&nbsp;&nbsp;&nbsp; 腾讯云服务器秒杀区
<el-link href="http://txy.ruoyi.vip" type="primary" target="_blank"
>>点我进入</el-link
><br />
<h4 class="text-danger">
云产品通用红包可叠加官网常规优惠使用(仅限新用户)
</h4>
</blockquote>
<hr />
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :sm="24" :lg="12" style="padding-left: 20px">
<h2>芋道后台管理框架</h2>
<p>
一直想做一款后台管理系统看了很多优秀的开源项目但是发现没有合适自己的于是利用空闲休息时间开始自己写一套后台系统如此有了芋道管理系统她可以用于所有的Web应用程序如网站管理后台网站会员中心CMSCRMOA等等当然您也可以对她进行深度定制以做出更强系统所有前端后台代码封装过后十分精简易上手出错概率低同时支持移动客户端访问系统会陆续更新一些实用功能
</p>
<p>
<b>当前版本:</b> <span>v{{ version }}</span>
</p>
<p>
<el-tag type="danger">&yen;免费开源</el-tag>
</p>
<p>
<el-button
type="primary"
size="mini"
icon="el-icon-cloudy"
plain
@click="goTarget('https://gitee.com/zhijiantianya/ruoyi-vue-pro')"
>访问码云</el-button
>
<el-button
size="mini"
icon="el-icon-s-home"
plain
@click="goTarget('http://ruoyi.vip')"
>访问主页</el-button
>
</p>
</el-col>
<el-col :sm="24" :lg="12" style="padding-left: 50px">
<el-row>
<el-col :span="12">
<h2>技术选型</h2>
</el-col>
</el-row>
<el-row>
<el-col :span="6">
<h4>后端技术</h4>
<ul>
<li>SpringBoot</li>
<li>Spring Security</li>
<li>JWT</li>
<li>MyBatis</li>
<li>Druid</li>
<li>Fastjson</li>
<li>...</li>
</ul>
</el-col>
<el-col :span="6">
<h4>前端技术</h4>
<ul>
<li>Vue</li>
<li>Vuex</li>
<li>Element-ui</li>
<li>Axios</li>
<li>Sass</li>
<li>Quill</li>
<li>...</li>
</ul>
</el-col>
</el-row>
</el-col>
</el-row>
<el-divider />
<el-row :gutter="20">
<el-col :xs="24" :sm="24" :md="12" :lg="8">
<el-card class="update-log">
<div slot="header" class="clearfix">
<span>联系信息</span>
</div>
<div class="body">
<p>
<i class="el-icon-s-promotion"></i> 官网<el-link
href="http://www.ruoyi.vip"
target="_blank"
>http://www.ruoyi.vip</el-link
>
</p>
<p>
<i class="el-icon-user-solid"></i> QQ群<s>满937441</s>
<s>满887144332</s> <s>满180251782</s>
<a href="https://jq.qq.com/?_wv=1027&k=4WWwqNxN" target="_blank"
> 104180207</a
>
</p>
<p>
<i class="el-icon-chat-dot-round"></i> 微信<a
href="javascript:;"
>/ *芋道</a
>
</p>
<p>
<i class="el-icon-money"></i> 支付宝<a
href="javascript:;"
class="支付宝信息"
>/ *芋道</a
>
</p>
</div>
</el-card>
</el-col>
<el-col :xs="24" :sm="24" :md="12" :lg="8">
<el-card class="update-log">
<div slot="header" class="clearfix">
<span>更新日志</span>
</div>
<el-collapse accordion>
<el-collapse-item title="v3.3.0 - 2020-12-14">
<ol>
<li>新增缓存监控功能</li>
<li>支持主题风格配置</li>
<li>修复多级菜单之间切换无法缓存的问题</li>
<li>多级菜单自动配置组件</li>
<li>代码生成预览支持高亮显示</li>
<li>支持Get请求映射Params参数</li>
<li>删除用户和角色解绑关联</li>
<li>去除用户手机邮箱部门必填验证</li>
<li>Excel支持注解align对齐方式</li>
<li>Excel支持导入Boolean型数据</li>
<li>优化头像样式鼠标移入悬停遮罩</li>
<li>代码生成预览提供滚动机制</li>
<li>代码生成删除多余的数字float类型</li>
<li>修正转换字符串的目标字符集属性</li>
<li>回显数据字典防止空值报错</li>
<li>日志记录增加过滤多文件场景</li>
<li>修改缓存Set方法可能导致嵌套的问题</li>
<li>移除前端一些多余的依赖</li>
<li>防止安全扫描YUI出现的风险提示</li>
<li>修改node-sass为dart-sass</li>
<li>升级SpringBoot到最新版本2.1.18</li>
<li>升级poi到最新版本4.1.2</li>
<li>升级oshi到最新版本v5.3.6</li>
<li>升级bitwalker到最新版本1.21</li>
<li>升级axios到最新版本0.21.0</li>
<li>升级element-ui到最新版本2.14.1</li>
<li>升级vue到最新版本2.6.12</li>
<li>升级vuex到最新版本3.6.0</li>
<li>升级vue-cli到版本4.5.9</li>
<li>升级vue-router到最新版本3.4.9</li>
<li>升级vue-cli到最新版本4.4.6</li>
<li>升级vue-cropper到最新版本0.5.5</li>
<li>升级clipboard到最新版本2.0.6</li>
<li>升级core-js到最新版本3.8.1</li>
<li>升级echarts到最新版本4.9.0</li>
<li>升级file-saver到最新版本2.0.4</li>
<li>升级fuse.js到最新版本6.4.3</li>
<li>升级js-beautify到最新版本1.13.0</li>
<li>升级js-cookie到最新版本2.2.1</li>
<li>升级path-to-regexp到最新版本6.2.0</li>
<li>升级quill到最新版本1.3.7</li>
<li>升级screenfull到最新版本5.0.2</li>
<li>升级sortablejs到最新版本1.10.2</li>
<li>升级vuedraggable到最新版本2.24.3</li>
<li>升级chalk到最新版本4.1.0</li>
<li>升级eslint到最新版本7.15.0</li>
<li>升级eslint-plugin-vue到最新版本7.2.0</li>
<li>升级lint-staged到最新版本10.5.3</li>
<li>升级runjs到最新版本4.4.2</li>
<li>升级sass-loader到最新版本10.1.0</li>
<li>升级script-ext-html-webpack-plugin到最新版本2.1.5</li>
<li>升级svg-sprite-loader到最新版本5.1.1</li>
<li>升级vue-template-compiler到最新版本2.6.12</li>
<li>其他细节优化</li>
</ol>
</el-collapse-item>
<el-collapse-item title="v3.2.1 - 2020-11-18">
<ol>
<li>阻止任意文件下载漏洞</li>
<li>代码生成支持上传控件</li>
<li>新增图片上传组件</li>
<li>调整默认首页</li>
<li>升级druid到最新版本v1.2.2</li>
<li>mapperLocations配置支持分隔符</li>
<li>权限信息调整</li>
<li>调整sql默认时间</li>
<li>解决代码生成没有bit类型的问题</li>
<li>升级pagehelper到最新版1.3.0</li>
</ol>
</el-collapse-item>
<el-collapse-item title="v3.2.0 - 2020-10-10">
<ol>
<li>升级springboot版本到2.1.17 提升安全性</li>
<li>升级oshi到最新版本v5.2.5</li>
<li>升级druid到最新版本v1.2.1</li>
<li>升级jjwt到版本0.9.1</li>
<li>升级fastjson到最新版1.2.74</li>
<li>修改sass为node-sass避免el-icon图标乱码</li>
<li>代码生成支持同步数据库</li>
<li>代码生成支持富文本控件</li>
<li>代码生成页面时不忽略remark属性</li>
<li>代码生成添加select必填选项</li>
<li>Excel导出类型NUMERIC支持精度浮点类型</li>
<li>Excel导出targetAttr优化获取值防止get方法不规范</li>
<li>Excel注解支持自动统计数据总和</li>
<li>Excel注解支持设置BigDecimal精度&舍入规则</li>
<li>菜单&数据权限新增展开/折叠 全选/全不选 父子联动</li>
<li>允许用户分配到部门父节点</li>
<li>菜单新增是否缓存keep-alive</li>
<li>表格操作列间距调整</li>
<li>限制系统内置参数不允许删除</li>
<li>富文本组件优化支持自定义高度&图片冲突问题</li>
<li>富文本工具栏样式对齐</li>
<li>导入excel整形值校验优化</li>
<li>修复页签关闭所有时固定标签路由不刷新问题</li>
<li>表单构建布局型组件新增按钮</li>
<li>左侧菜单文字过长显示省略号</li>
<li>修正根节点为子部门时树状结构显示问题</li>
<li>修正调用目标字符串最大长度</li>
<li>修正菜单提示信息错误</li>
<li>修正定时任务执行一次权限标识</li>
<li>修正数据库字符串类型nvarchar</li>
<li>优化递归子节点</li>
<li>优化数据权限判断</li>
<li>其他细节优化</li>
</ol>
</el-collapse-item>
<el-collapse-item title="v3.1.0 - 2020-08-13">
<ol>
<li>表格工具栏右侧添加刷新&显隐查询组件</li>
<li>后端支持CORS跨域请求</li>
<li>代码生成支持选择上级菜单</li>
<li>代码生成支持自定义路径</li>
<li>代码生成支持复选框</li>
<li>Excel导出导入支持dictType字典类型</li>
<li>Excel支持分割字符串组内容</li>
<li>验证码类型支持数组计算字符验证</li>
<li>升级vue-cli版本到4.4.4</li>
<li>修改 node-sass dart-sass</li>
<li>表单类型为Integer/Long设置整形默认值</li>
<li>代码生成器默认mapper路径与默认mapperScan路径不一致</li>
<li>优化防重复提交拦截器</li>
<li>优化上级菜单不能选择自己</li>
<li>修复角色的权限分配后未实时生效问题</li>
<li>修复在线用户日志记录类型</li>
<li>修复富文本空格和缩进保存后不生效问题</li>
<li>修复在线用户判断逻辑</li>
<li>唯一限制条件只返回单条数据</li>
<li>添加获取当前的环境配置方法</li>
<li>超时登录后页面跳转到首页</li>
<li>全局异常状态汉化拦截处理</li>
<li>HTML过滤器改为将html转义</li>
<li>检查字符支持小数点&降级改成异常提醒</li>
<li>其他细节优化</li>
</ol>
</el-collapse-item>
<el-collapse-item title="v3.0.0 - 2020-07-20">
<ol>
<li>单应用调整为多模块项目</li>
<li>升级element-ui版本到2.13.2</li>
<li>删除babel提高编译速度</li>
<li>新增菜单默认主类目</li>
<li>编码文件名修改为uuid方式</li>
<li>定时任务cron表达式验证</li>
<li>角色权限修改时已有权限未自动勾选异常修复</li>
<li>防止切换权限用户后登录出现404</li>
<li>Excel支持sort导出排序</li>
<li>创建用户不允许选择超级管理员角色</li>
<li>修复代码生成导入表结构出现异常页面不提醒问题</li>
<li>修复代码生成点击多次表修改数据不变化的问题</li>
<li>修复头像上传成功二次打开无法改变裁剪框大小和位置问题</li>
<li>修复布局为small者mini用户表单显示错位问题</li>
<li>修复热部署导致的强换异常问题</li>
<li>修改用户管理复选框宽度防止部分浏览器出现省略号</li>
<li>IpUtils工具清除Xss特殊字符防止Xff注入攻击</li>
<li>生成domain 如果是浮点型 统一用BigDecimal</li>
<li>定时任务调整label-width防止部署出现错位</li>
<li>调整表头固定列默认样式</li>
<li>代码生成模板调整字段为String并且必填则加空串条件</li>
<li>代码生成字典Integer/Long使用parseInt</li>
<li>
修复dict_sort不可update为0的问题&查询返回增加dict_sort升序排序
</li>
<li>修正岗位导出权限注解</li>
<li>禁止加密密文返回前端</li>
<li>修复代码生成页面中的查询条件创建时间未生效的问题</li>
<li>修复首页搜索菜单外链无法点击跳转问题</li>
<li>修复菜单管理选择图标backspace删除时不过滤数据</li>
<li>用户管理部门分支节点不可检查&显示计数</li>
<li>数据范围过滤属性调整</li>
<li>其他细节优化</li>
</ol>
</el-collapse-item>
<el-collapse-item title="v2.3.0 - 2020-06-01">
<ol>
<li>升级fastjson到最新版1.2.70 修复高危安全漏洞</li>
<li>dev启动默认打开浏览器</li>
<li>vue-cli使用默认source-map</li>
<li>slidebar eslint报错优化</li>
<li>当tags-view滚动关闭右键菜单</li>
<li>字典管理添加缓存读取</li>
<li>参数管理支持缓存操作</li>
<li>支持一级菜单和主页同级在main区域显示</li>
<li>限制外链地址必须以http(s)开头</li>
<li>tagview & sidebar 主题颜色与element ui(全局)同步</li>
<li>修改数据源类型优先级先根据方法再根据类</li>
<li>支持是否需要设置token属性自定义返回码消息</li>
<li>swagger请求前缀加入配置</li>
<li>登录地点设置内容过长则隐藏显示</li>
<li>修复定时任务执行一次按钮后不提示消息问题</li>
<li>修改上级部门选择项排除本身和下级</li>
<li>通用http发送方法增加参数 contentType 编码类型</li>
<li>更换IP地址查询接口</li>
<li>修复页签变量undefined</li>
<li>添加校验部门包含未停用的子部门</li>
<li>修改定时任务详情下次执行时间日期显示错误</li>
<li>角色管理查询设置默认排序字段</li>
<li>swagger添加enable参数控制是否启用</li>
<li>只对json类型请求构建可重复读取inputStream的request</li>
<li>修改代码生成字典字段int类型没有自动选中问题</li>
<li>vuex用户名取值修正</li>
<li>表格树模板去掉多余的)</li>
<li>代码生成序号修正</li>
<li>全屏情况下不调整上外边距</li>
<li>代码生成Date字段添加默认格式</li>
<li>用户管理角色选择权限控制</li>
<li>修复路由懒加载报错问题</li>
<li>模板sql.vm添加菜单状态</li>
<li>设置用户名称不能修改</li>
<li>dialog添加append-to-body属性防止ie遮罩</li>
<li>菜单区分状态和显示隐藏功能</li>
<li>升级fastjson到最新版1.2.68 修复安全加固</li>
<li>修复代码生成如果选择字典类型缺失逗号问题</li>
<li>登录请求params更换为data防止暴露url</li>
<li>日志返回时间格式处理</li>
<li>添加handle控制允许拖动的元素</li>
<li>布局设置点击扩大范围</li>
<li>代码生成列属性排序查询</li>
<li>代码生成列支持拖动排序</li>
<li>修复时间格式不支持ios问题</li>
<li>表单构建添加父级class防止冲突</li>
<li>定时任务并发属性修正</li>
<li>角色禁用&菜单隐藏不查询权限</li>
<li>其他细节优化</li>
</ol>
</el-collapse-item>
<el-collapse-item title="v2.2.0 - 2020-03-18">
<ol>
<li>系统监控新增定时任务功能</li>
<li>添加一个打包Web工程bat</li>
<li>修复页签鼠标滚轮按下的时候可以关闭不可关闭的tag</li>
<li>修复点击退出登录有时会无提示问题</li>
<li>修复防重复提交注解无效问题</li>
<li>修复通知公告批量删除异常问题</li>
<li>添加菜单时路由地址必填限制</li>
<li>代码生成字段描述可编辑</li>
<li>修复用户修改个人信息导致缓存不过期问题</li>
<li>个人信息创建时间获取正确属性值</li>
<li>操作日志详细显示正确类型</li>
<li>导入表单击行数据时选中对应的复选框</li>
<li>批量替换表前缀逻辑调整</li>
<li>固定重定向路径表达式</li>
<li>升级element-ui版本到2.13.0</li>
<li>操作日志排序调整</li>
<li>修复charts切换侧边栏或者缩放窗口显示bug</li>
<li>其他细节优化</li>
</ol>
</el-collapse-item>
<el-collapse-item title="v2.1.0 - 2020-02-24">
<ol>
<li>新增表单构建</li>
<li>代码生成支持树表结构</li>
<li>新增用户导入</li>
<li>修复动态加载路由页面刷新问题</li>
<li>修复地址开关无效问题</li>
<li>汉化错误提示页面</li>
<li>代码生成已知问题修改</li>
<li>修复多数据源下配置关闭出现异常处理</li>
<li>添加HTML过滤器用于去除XSS漏洞隐患</li>
<li>修复上传头像控制台出现异常</li>
<li>修改用户管理分页不正确的问题</li>
<li>修复验证码记录提示错误</li>
<li>修复request.js缺少Message引用</li>
<li>修复表格时间为空出现的异常</li>
<li>添加Jackson日期反序列化时区配置</li>
<li>调整根据用户权限加载菜单数据树形结构</li>
<li>调整成功登录不恢复按钮防止多次点击</li>
<li>修改用户个人资料同步缓存信息</li>
<li>修复页面同时出现el-upload和Editor不显示处理</li>
<li>修复在角色管理页修改菜单权限偶尔未选中问题</li>
<li>配置文件新增redis密码属性</li>
<li>设置mybatis全局的配置文件</li>
<li>其他细节优化</li>
</ol>
</el-collapse-item>
<el-collapse-item title="v2.0.0 - 2019-12-02">
<ol>
<li>新增代码生成</li>
<li>新增@RepeatSubmit注解防止重复提交</li>
<li>新增菜单主目录添加/删除操作</li>
<li>日志记录过滤特殊对象防止转换异常</li>
<li>修改代码生成路由脚本错误</li>
<li>用户上传头像实时同步缓存无需重新登录</li>
<li>调整切换页签后不重新加载数据</li>
<li>添加jsencrypt实现参数的前端加密</li>
<li>系统退出删除用户缓存记录</li>
<li>其他细节优化</li>
</ol>
</el-collapse-item>
<el-collapse-item title="v1.1.0 - 2019-11-11">
<ol>
<li>新增在线用户管理</li>
<li>新增按钮组功能实现批量删除导出清空</li>
<li>新增查询条件重置按钮</li>
<li>新增Swagger全局Token配置</li>
<li>新增后端参数校验</li>
<li>修复字典管理页面的日期查询异常</li>
<li>修改时间函数命名防止冲突</li>
<li>去除菜单上级校验默认为顶级</li>
<li>修复用户密码无法修改问题</li>
<li>修复菜单类型为按钮时不显示权限标识</li>
<li>其他细节优化</li>
</ol>
</el-collapse-item>
<el-collapse-item title="v1.0.0 - 2019-10-08">
<ol>
<li>芋道前后端分离系统正式发布</li>
</ol>
</el-collapse-item>
</el-collapse>
</el-card>
</el-col>
<el-col :xs="24" :sm="24" :md="12" :lg="8">
<el-card class="update-log">
<div slot="header" class="clearfix">
<span>捐赠支持</span>
</div>
<div class="body">
<img
src="https://oscimg.oschina.net/oscnet/up-d6695f82666e5018f715c41cb7ee60d3b73.png"
alt="donate"
width="100%"
/>
<span style="display: inline-block; height: 30px; line-height: 30px"
>你可以请作者喝杯咖啡表示鼓励</span
>
</div>
</el-card>
</el-col>
</el-row>
</div>
</template>
<script>
export default {
name: "Index",
data() {
return {
// 版本号
version: "3.3.0",
};
},
methods: {
goTarget(href) {
window.open(href, "_blank");
},
},
};
</script>
<style scoped lang="scss">
.home {
blockquote {
padding: 10px 20px;
margin: 0 0 20px;
font-size: 17.5px;
border-left: 5px solid #eee;
}
hr {
margin-top: 20px;
margin-bottom: 20px;
border: 0;
border-top: 1px solid #eee;
}
.col-item {
margin-bottom: 20px;
}
ul {
padding: 0;
margin: 0;
}
font-family: "open sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: 13px;
color: #676a6c;
overflow-x: hidden;
ul {
list-style-type: none;
}
h4 {
margin-top: 0px;
}
h2 {
margin-top: 10px;
font-size: 26px;
font-weight: 100;
}
p {
margin-top: 10px;
b {
font-weight: 700;
}
}
.update-log {
ol {
display: block;
list-style-type: decimal;
margin-block-start: 1em;
margin-block-end: 1em;
margin-inline-start: 0;
margin-inline-end: 0;
padding-inline-start: 40px;
}
}
}
</style>

View File

@@ -48,7 +48,7 @@
<el-table-column label="日志编号" align="center" prop="id" />
<el-table-column label="用户编号" align="center" prop="userId" />
<el-table-column label="用户类型" align="center" prop="userType">
<template slot-scope="scope">
<template v-slot="scope">
<dict-tag :type="DICT_TYPE.USER_TYPE" :value="scope.row.userType"/>
</template>
</el-table-column>>
@@ -56,22 +56,22 @@
<el-table-column label="请求方法名" align="center" prop="requestMethod" />
<el-table-column label="请求地址" align="center" prop="requestUrl" width="250" />
<el-table-column label="请求时间" align="center" prop="beginTime" width="180">
<template slot-scope="scope">
<template v-slot="scope">
<span>{{ parseTime(scope.row.beginTime) }}</span>
</template>
</el-table-column>
<el-table-column label="执行时长" align="center" prop="startTime">
<template slot-scope="scope">
<template v-slot="scope">
<span>{{ scope.row.duration }} ms</span>
</template>
</el-table-column>
<el-table-column label="操作结果" align="center" prop="status">
<template slot-scope="scope">
<template v-slot="scope">
<span>{{ scope.row.resultCode === 0 ? '成功' : '失败(' + scope.row.resultMsg + ')' }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template slot-scope="scope">
<template v-slot="scope">
<el-button size="mini" type="text" icon="el-icon-view" @click="handleView(scope.row,scope.index)"
v-hasPermi="['infra:api-access-log:query']">详细</el-button>
</template>

View File

@@ -48,7 +48,7 @@
<el-table-column label="日志编号" align="center" prop="id" />
<el-table-column label="用户编号" align="center" prop="userId" />
<el-table-column label="用户类型" align="center" prop="userType">
<template slot-scope="scope">
<template v-slot="scope">
<dict-tag :type="DICT_TYPE.USER_TYPE" :value="scope.row.userType"/>
</template>
</el-table-column>>
@@ -56,18 +56,18 @@
<el-table-column label="请求方法名" align="center" prop="requestMethod" />
<el-table-column label="请求地址" align="center" prop="requestUrl" width="250" />
<el-table-column label="异常发生时间" align="center" prop="exceptionTime" width="180">
<template slot-scope="scope">
<template v-slot="scope">
<span>{{ parseTime(scope.row.exceptionTime) }}</span>
</template>
</el-table-column>
<el-table-column label="异常名" align="center" prop="exceptionName" width="250" />
<el-table-column label="处理状态" align="center" prop="processStatus">
<template slot-scope="scope">
<template v-slot="scope">
<dict-tag :type="DICT_TYPE.INFRA_API_ERROR_LOG_PROCESS_STATUS" :value="scope.row.processStatus" />
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template slot-scope="scope">
<template v-slot="scope">
<el-button size="mini" type="text" icon="el-icon-view" @click="handleView(scope.row,scope.index)"
v-hasPermi="['infra:api-access-log:query']">详细</el-button>
<el-button type="text" size="mini" icon="el-icon-check"

View File

@@ -84,7 +84,7 @@ import {
} from '@/components/generator/html'
import { makeUpJs } from '@/components/generator/js'
import { makeUpCss } from '@/components/generator/css'
import { exportDefault, beautifierConf, titleCase } from '@/utils/index'
import { exportDefault, beautifierConf } from '@/utils'
import ResourceDialog from './ResourceDialog'
import loadMonaco from '@/utils/loadMonaco'
import loadBeautifier from '@/utils/loadBeautifier'
@@ -325,7 +325,7 @@ export default {
}
}
@include action-bar;
::v-deep .el-drawer__header {
:deep(.el-drawer__header) {
display: none;
}
</style>

View File

@@ -101,7 +101,7 @@ export default {
}
}
.icon-dialog {
::v-deep .el-dialog {
:deep(.el-dialog) {
border-radius: 8px;
margin-bottom: 0;
margin-top: 4vh !important;

View File

@@ -25,7 +25,7 @@
</template>
<script>
import { beautifierConf } from '@/utils/index'
import { beautifierConf } from '@/utils'
import ClipboardJS from 'clipboard'
import { saveAs } from 'file-saver'
import loadMonaco from '@/utils/loadMonaco'
@@ -133,7 +133,7 @@ export default {
<style lang="scss" scoped>
@import '@/styles/mixin.scss';
::v-deep .el-drawer__header {
:deep(.el-drawer__header) {
display: none;
}
@include action-bar;

View File

@@ -60,7 +60,7 @@
</div>
</template>
<script>
import { deepClone } from '@/utils/index'
import { deepClone } from '@/utils'
export default {
components: {},

View File

@@ -651,10 +651,10 @@
<script>
import { isArray } from 'util'
import TreeNodeDialog from './TreeNodeDialog'
import { isNumberStr } from '@/utils/index'
import { isNumberStr } from '@/utils'
import IconsDialog from './IconsDialog'
import {
inputComponents, selectComponents, layoutComponents
inputComponents, selectComponents
} from '@/components/generator/config'
import { saveFormConf } from '@/utils/db'
@@ -1021,7 +1021,7 @@ export default {
.el-date-editor {
width: 227px;
}
::v-deep .el-icon-time {
:deep(.el-icon-time) {
display: none;
}
}

View File

@@ -71,7 +71,7 @@
</div>
</template>
<script>
import { isNumberStr } from '@/utils/index'
import { isNumberStr } from '@/utils'
import { getTreeNodeId, saveTreeNodeId } from '@/utils/db'
const id = getTreeNodeId()

View File

@@ -133,8 +133,8 @@ import {
inputComponents, selectComponents, layoutComponents, formConf
} from '@/components/generator/config'
import {
exportDefault, beautifierConf, isNumberStr, titleCase, deepClone, isObjectObject
} from '@/utils/index'
beautifierConf, titleCase, deepClone
} from '@/utils'
import {
makeUpHtml, vueTemplate, vueScript, cssStyle
} from '@/components/generator/html'
@@ -272,7 +272,7 @@ export default {
arr.reduce((pre, item, i) => {
if (arr.length === i + 1) {
pre[item] = data
} else if (!isObjectObject(pre[item])) {
} else if (pre[item]===undefined) {
pre[item] = {}
}
return pre[item]
@@ -300,7 +300,7 @@ export default {
url
}).then(resp => {
this.setLoading(component, false)
this.setRespData(component, resp.data)
this.setRespData(component, resp)
})
}
},

View File

@@ -2,7 +2,7 @@ import Vue from 'vue'
import App from './App.vue'
import router from '@/router'
import '@/styles/index.scss'
import '@/icons'
import '@/assets/icons'
import axios from 'axios'
import Tinymce from '@/components/tinymce/index.vue'

View File

@@ -13,7 +13,7 @@
:show-overflow-tooltip="true"
/>
<el-table-column label="字段描述" min-width="10%">
<template slot-scope="scope">
<template v-slot="scope">
<el-input v-model="scope.row.columnComment"></el-input>
</template>
</el-table-column>
@@ -24,45 +24,45 @@
:show-overflow-tooltip="true"
/>
<el-table-column label="Java类型" min-width="11%">
<template slot-scope="scope">
<template v-slot="scope">
<el-select v-model="scope.row.javaType">
<el-option label="Long" value="Long" />
<el-option label="String" value="String" />
<el-option label="Integer" value="Integer" />
<el-option label="Double" value="Double" />
<el-option label="BigDecimal" value="BigDecimal" />
<el-option label="Date" value="Date" />
<el-option label="LocalDateTime" value="LocalDateTime" />
<el-option label="Boolean" value="Boolean" />
</el-select>
</template>
</el-table-column>
<el-table-column label="java属性" min-width="10%">
<template slot-scope="scope">
<template v-slot="scope">
<el-input v-model="scope.row.javaField"></el-input>
</template>
</el-table-column>
<el-table-column label="插入" min-width="4%">
<template slot-scope="scope">
<template v-slot="scope">
<el-checkbox true-label="true" false-label="false" v-model="scope.row.createOperation"></el-checkbox>
</template>
</el-table-column>
<el-table-column label="编辑" min-width="4%">
<template slot-scope="scope">
<template v-slot="scope">
<el-checkbox true-label="true" false-label="false" v-model="scope.row.updateOperation"></el-checkbox>
</template>
</el-table-column>
<el-table-column label="列表" min-width="4%">
<template slot-scope="scope">
<template v-slot="scope">
<el-checkbox true-label="true" false-label="false" v-model="scope.row.listOperationResult"></el-checkbox>
</template>
</el-table-column>
<el-table-column label="查询" min-width="4%">
<template slot-scope="scope">
<template v-slot="scope">
<el-checkbox true-label="true" false-label="false" v-model="scope.row.listOperation"></el-checkbox>
</template>
</el-table-column>
<el-table-column label="查询方式" min-width="10%">
<template slot-scope="scope">
<template v-slot="scope">
<el-select v-model="scope.row.listOperationCondition">
<el-option label="=" value="=" />
<el-option label="!=" value="!=" />
@@ -76,12 +76,12 @@
</template>
</el-table-column>
<el-table-column label="允许空" min-width="5%">
<template slot-scope="scope">
<template v-slot="scope">
<el-checkbox true-label="true" false-label="false" v-model="scope.row.nullable"></el-checkbox>
</template>
</el-table-column>
<el-table-column label="显示类型" min-width="12%">
<template slot-scope="scope">
<template v-slot="scope">
<el-select v-model="scope.row.htmlType">
<el-option label="文本框" value="input" />
<el-option label="文本域" value="textarea" />
@@ -96,7 +96,7 @@
</template>
</el-table-column>
<el-table-column label="字典类型" min-width="12%">
<template slot-scope="scope">
<template v-slot="scope">
<el-select v-model="scope.row.dictType" clearable filterable placeholder="请选择">
<el-option
v-for="dict in dictOptions"
@@ -108,7 +108,7 @@
</template>
</el-table-column>
<el-table-column label="示例" min-width="10%">
<template slot-scope="scope">
<template v-slot="scope">
<el-input v-model="scope.row.example"></el-input>
</template>
</el-table-column>

View File

@@ -105,7 +105,7 @@
</el-form-item>
</el-col>
<el-col :span="24" v-if="info.genType == '1'">
<el-col :span="24" v-if="info.genType === '1'">
<el-form-item prop="genPath">
<span slot="label">
自定义路径
@@ -128,7 +128,7 @@
</el-col>
</el-row>
<el-row v-show="info.tplCategory == 'tree'">
<el-row v-show="info.tplCategory === 'tree'">
<h4 class="form-header">其他信息</h4>
<el-col :span="12">
<el-form-item>
@@ -185,7 +185,7 @@
</el-form-item>
</el-col>
</el-row>
<el-row v-show="info.tplCategory == 'sub'">
<el-row v-show="info.tplCategory === 'sub'">
<h4 class="form-header">关联信息</h4>
<el-col :span="12">
<el-form-item>
@@ -314,7 +314,7 @@ export default {
},
/** 设置关联外键 */
setSubTableColumns(value) {
for (var item in this.tables) {
for (let item in this.tables) {
const name = this.tables[item].tableName;
if (value === name) {
this.subColumns = this.tables[item].columns;

View File

@@ -38,17 +38,17 @@
<el-table-column label="表描述" align="center" prop="tableComment" :show-overflow-tooltip="true" width="120"/>
<el-table-column label="实体" align="center" prop="className" width="200"/>
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
<template slot-scope="scope">
<template v-slot="scope">
<span>{{ parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
<el-table-column label="更新时间" align="center" prop="createTime" width="180">
<template slot-scope="scope">
<template v-slot="scope">
<span>{{ parseTime(scope.row.updateTime) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" width="300px" class-name="small-padding fixed-width">
<template slot-scope="scope">
<template v-slot="scope">
<el-button type="text" size="small" icon="el-icon-view" @click="handlePreview(scope.row)" v-hasPermi="['infra:codegen:preview']">预览</el-button>
<el-button type="text" size="small" icon="el-icon-edit" @click="handleEditTable(scope.row)" v-hasPermi="['infra:codegen:update']">编辑</el-button>
<el-button type="text" size="small" icon="el-icon-delete" @click="handleDelete(scope.row)" v-hasPermi="['infra:codegen:delete']">删除</el-button>
@@ -209,7 +209,7 @@ export default {
highlightedCode(item) {
// const vmName = key.substring(key.lastIndexOf("/") + 1, key.indexOf(".vm"));
// var language = vmName.substring(vmName.indexOf(".") + 1, vmName.length);
var language = item.filePath.substring(item.filePath.lastIndexOf(".") + 1);
const language = item.filePath.substring(item.filePath.lastIndexOf('.') + 1)
const result = hljs.highlight(language, item.code || "", true);
return result.value || '&nbsp;';
},

View File

@@ -41,28 +41,28 @@
<el-table v-loading="loading" :data="configList">
<el-table-column label="参数主键" align="center" prop="id" />
<el-table-column label="参数分类" align="center" prop="group" />
<el-table-column label="参数分类" align="center" prop="category" />
<el-table-column label="参数名称" align="center" prop="name" :show-overflow-tooltip="true" />
<el-table-column label="参数键名" align="center" prop="key" :show-overflow-tooltip="true" />
<el-table-column label="参数键值" align="center" prop="value" />
<el-table-column label="系统内置" align="center" prop="type">
<template slot-scope="scope">
<template v-slot="scope">
<dict-tag :type="DICT_TYPE.INFRA_CONFIG_TYPE" :value="scope.row.type" />
</template>
</el-table-column>
<el-table-column label="是否可见" align="center" prop="visible">
<template slot-scope="scope">
<template v-slot="scope">
<span>{{ scope.row.visible ? '是' : '否' }}</span>
</template>
</el-table-column>
<el-table-column label="备注" align="center" prop="remark" :show-overflow-tooltip="true" />
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
<template slot-scope="scope">
<template v-slot="scope">
<span>{{ parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template slot-scope="scope">
<template v-slot="scope">
<el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)"
v-hasPermi="['infra:config:update']">修改</el-button>
<el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)"

View File

@@ -15,12 +15,12 @@
<el-table-column label="数据源连接" align="center" prop="url" />
<el-table-column label="用户名" align="center" prop="username" />
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
<template slot-scope="scope">
<template v-slot="scope">
<span>{{ parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template slot-scope="scope">
<template v-slot="scope">
<el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)"
v-hasPermi="['infra:data-source-config:update']">修改</el-button>
<el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)"

View File

@@ -1,14 +1,16 @@
<template>
<div class="app-container">
<doc-alert title="上传下载" url="https://doc.iocoder.cn/file/" />
<doc-alert title="上传下载" url="https://doc.iocoder.cn/file/"/>
<!-- 搜索工作栏 -->
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="文件路径" prop="path">
<el-input v-model="queryParams.path" placeholder="请输入文件路径" clearable @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="创建时间" prop="createTime">
<el-date-picker v-model="queryParams.createTime" style="width: 240px" value-format="yyyy-MM-dd HH:mm:ss" type="daterange"
range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期" :default-time="['00:00:00', '23:59:59']" />
<el-date-picker v-model="queryParams.createTime" style="width: 240px" value-format="yyyy-MM-dd HH:mm:ss"
type="daterange"
range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期"
:default-time="['00:00:00', '23:59:59']"/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" @click="handleQuery">搜索</el-button>
@@ -32,9 +34,12 @@
<el-table-column label="文件大小" align="center" prop="size" min-width="120px" :formatter="sizeFormat"/>
<el-table-column label="文件类型" :show-overflow-tooltip="true" align="center" prop="type" width="180px"/>
<el-table-column label="文件内容" align="center" prop="content" min-width="150px">
<template slot-scope="scope">
<template v-slot="scope">
<image-preview v-if="scope.row.type&&scope.row.type.indexOf('image/') === 0" :src="scope.row.url"
:width="'100px'"></image-preview>
<video v-else-if="scope.row.type&&scope.row.type.indexOf('video/') === 0" :width="'100px'">
<source :src="scope.row.url"/>
</video>
<i v-else>无法预览点击
<el-link type="primary" :underline="false" style="font-size:12px;vertical-align: baseline;" target="_blank"
:href="getFileUrl + scope.row.configId + '/get/' + scope.row.path">下载
@@ -43,12 +48,12 @@
</template>
</el-table-column>
<el-table-column label="上传时间" align="center" prop="createTime" min-width="170px">
<template slot-scope="scope">
<template v-slot="scope">
<span>{{ parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" min-width="100px">
<template slot-scope="scope">
<template v-slot="scope">
<el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)"
v-hasPermi="['infra:file:delete']">删除
</el-button>
@@ -118,7 +123,7 @@ export default {
title: "", // 弹出层标题
isUploading: false, // 是否禁用上传
url: process.env.VUE_APP_BASE_API + "/admin-api/infra/file/upload", // 请求地址
headers: { Authorization: "Bearer " + getAccessToken() }, // 设置上传的请求头部
headers: {Authorization: "Bearer " + getAccessToken()}, // 设置上传的请求头部
data: {} // 上传的额外数据,用于文件名
},
};
@@ -189,19 +194,20 @@ export default {
/** 删除按钮操作 */
handleDelete(row) {
const id = row.id;
this.$modal.confirm('是否确认删除文件编号为"' + id + '"的数据项?').then(function() {
this.$modal.confirm('是否确认删除文件编号为"' + id + '"的数据项?').then(function () {
return deleteFile(id);
}).then(() => {
this.getList();
this.$modal.msgSuccess("删除成功");
}).catch(() => {});
}).catch(() => {
});
},
// 用户昵称展示
sizeFormat(row, column) {
const unitArr = ["Bytes","KB","MB","GB","TB","PB","EB","ZB","YB"];
const unitArr = ["Bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
const srcSize = parseFloat(row.size);
const index = Math.floor(Math.log(srcSize) / Math.log(1024));
let size =srcSize/Math.pow(1024,index);
let size = srcSize / Math.pow(1024, index);
size = size.toFixed(2);//保留的小数位数
return size + ' ' + unitArr[index];
},

View File

@@ -36,23 +36,23 @@
<el-table-column label="编号" align="center" prop="id" />
<el-table-column label="配置名" align="center" prop="name" />
<el-table-column label="存储器" align="center" prop="storage">
<template slot-scope="scope">
<template v-slot="scope">
<dict-tag :type="DICT_TYPE.INFRA_FILE_STORAGE" :value="scope.row.storage" />
</template>
</el-table-column>
<el-table-column label="备注" align="center" prop="remark" />
<el-table-column label="主配置" align="center" prop="primary">
<template slot-scope="scope">
<template v-slot="scope">
<dict-tag :type="DICT_TYPE.INFRA_BOOLEAN_STRING" :value="scope.row.master" />
</template>
</el-table-column>
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
<template slot-scope="scope">
<template v-slot="scope">
<span>{{ parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="240">
<template slot-scope="scope">
<template v-slot="scope">
<el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)"
v-hasPermi="['infra:file-config:update']">修改</el-button>
<el-button size="mini" type="text" icon="el-icon-attract" @click="handleMaster(scope.row)"

View File

@@ -43,7 +43,7 @@
<el-table-column label="任务编号" align="center" prop="id" />
<el-table-column label="任务名称" align="center" prop="name" />
<el-table-column label="任务状态" align="center" prop="status">
<template slot-scope="scope">
<template v-slot="scope">
<dict-tag :type="DICT_TYPE.INFRA_JOB_STATUS" :value="scope.row.status" />
</template>
</el-table-column>>
@@ -51,7 +51,7 @@
<el-table-column label="处理器的参数" align="center" prop="handlerParam" />
<el-table-column label="CRON 表达式" align="center" prop="cronExpression" />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template slot-scope="scope">
<template v-slot="scope">
<el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)"
v-hasPermi="['infra:job:update']">修改</el-button>
<el-button size="mini" type="text" icon="el-icon-check" @click="handleChangeStatus(scope.row, true)"
@@ -62,9 +62,7 @@
v-hasPermi="['infra:job:delete']">删除</el-button>
<el-dropdown size="mini" @command="(command) => handleCommand(command, scope.row)"
v-hasPermi="['infra:job:trigger', 'infra:job:query']">
<span class="el-dropdown-link">
<i class="el-icon-d-arrow-right el-icon--right"></i>更多
</span>
<el-button size="mini" type="text" icon="el-icon-d-arrow-right">更多</el-button>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item command="handleRun" icon="el-icon-caret-right"
v-hasPermi="['infra:job:trigger']">执行一次</el-dropdown-item>

View File

@@ -41,22 +41,22 @@
<el-table-column label="处理器的参数" align="center" prop="handlerParam" />
<el-table-column label="第几次执行" align="center" prop="executeIndex" />
<el-table-column label="执行时间" align="center" width="180">
<template slot-scope="scope">
<template v-slot="scope">
<span>{{ parseTime(scope.row.beginTime) + ' ~ ' + parseTime(scope.row.endTime) }}</span>
</template>
</el-table-column>
<el-table-column label="执行时长" align="center" prop="duration">
<template slot-scope="scope">
<template v-slot="scope">
<span>{{ scope.row.duration + ' 毫秒' }}</span>
</template>
</el-table-column>
<el-table-column label="任务状态" align="center" prop="status">
<template slot-scope="scope">
<template v-slot="scope">
<dict-tag :type="DICT_TYPE.INFRA_JOB_LOG_STATUS" :value="scope.row.status" />
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template slot-scope="scope">
<template v-slot="scope">
<el-button size="mini" type="text" icon="el-icon-view" @click="handleView(scope.row)" :loading="exportLoading"
v-hasPermi="['infra:job:query']">详细</el-button>
</template>
@@ -77,7 +77,7 @@
<el-form-item label="处理器的参数:">{{ form.handlerParam }}</el-form-item>
<el-form-item label="第几次执行:">{{ form.executeIndex }}</el-form-item>
<el-form-item label="执行时间:">{{ parseTime(form.beginTime) + ' ~ ' + parseTime(form.endTime) }}</el-form-item>
<el-form-item label="执行时长:">{{ parseTime(form.duration) + ' 毫秒' }}</el-form-item>
<el-form-item label="执行时长:">{{ form.duration + ' 毫秒' }}</el-form-item>
<el-form-item label="任务状态:">
<dict-tag :type="DICT_TYPE.INFRA_JOB_LOG_STATUS" :value="form.status" />
</el-form-item>

View File

@@ -13,7 +13,7 @@
<td><div class="cell">Redis版本</div></td>
<td><div class="cell" v-if="cache.info">{{ cache.info.redis_version }}</div></td>
<td><div class="cell">运行模式</div></td>
<td><div class="cell" v-if="cache.info">{{ cache.info.redis_mode == "standalone" ? "单机" : "集群" }}</div></td>
<td><div class="cell" v-if="cache.info">{{ cache.info.redis_mode === "standalone" ? "单机" : "集群" }}</div></td>
<td><div class="cell">端口</div></td>
<td><div class="cell" v-if="cache.info">{{ cache.info.tcp_port }}</div></td>
<td><div class="cell">客户端数</div></td>
@@ -71,7 +71,7 @@
<el-table-column prop="keyType" label="Key 类型" width="100" />
<el-table-column prop="valueType" label="Value 类型" />
<el-table-column prop="timeoutType" label="超时时间" width="200">
<template slot-scope="scope">
<template v-slot="scope">
<dict-tag
:type="DICT_TYPE.INFRA_REDIS_TIMEOUT_TYPE"
:value="scope.row.timeoutType"
@@ -95,10 +95,10 @@
</div>
<el-table :data="cacheKeys" style="width: 100%" @row-click="handleKeyValue">
<el-table-column label="缓存键名" align="center" :show-overflow-tooltip="true">
<template slot-scope="scope">{{ scope.row }}</template>
<template v-slot="scope">{{ scope.row }}</template>
</el-table-column>
<el-table-column label="操作" width="60" align="center" class-name="small-padding fixed-width">
<template slot-scope="scope">
<template v-slot="scope">
<el-button size="mini" type="text" icon="el-icon-delete" @click="handleDeleteKey(scope.row)" />
</template>
</el-table-column>
@@ -136,8 +136,8 @@
<script>
import {getCache, getKeyDefineList, getKeyList, getKeyValue, deleteKey, deleteKeys} from "@/api/infra/redis";
import echarts from "echarts";
import * as echarts from 'echarts'
require('echarts/theme/macarons') // echarts theme
export default {
name: "Server",
data () {

View File

@@ -54,12 +54,12 @@
<el-table-column label="分类" align="center" prop="category" />
<el-table-column label="备注" align="center" prop="remark" />
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
<template slot-scope="scope">
<template v-slot="scope">
<span>{{ parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template slot-scope="scope">
<template v-slot="scope">
<el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)"
v-hasPermi="['infra:test-demo:update']">修改</el-button>
<el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)"

View File

@@ -0,0 +1,92 @@
<template>
<div class="app-container">
<el-form label-width="120px">
<el-row type="flex" :gutter="0">
<el-col :sm="12">
<el-form-item label="WebSocket地址" size="small">
<el-input v-model="url" type="text"/>
</el-form-item>
</el-col>
<el-col :offset="1">
<el-form-item label="" label-width="0px" size="small">
<el-button @click="connect" type="primary" :disabled="ws&&ws.readyState===1">
{{ ws && ws.readyState === 1 ? "已连接" : "连接" }}
</el-button>
<el-button @click="exit" type="danger">断开</el-button>
</el-form-item>
</el-col>
</el-row>
<el-form-item label="发送内容" size="small">
<el-input type="textarea" v-model="message" :rows="5"/>
</el-form-item>
<el-form-item label="" size="small">
<el-button type="success" @click="send">发送消息</el-button>
</el-form-item>
<el-form-item label="接收内容" size="small">
<el-input type="textarea" v-model="content" :rows="12" disabled/>
</el-form-item>
<el-form-item label="" size="small">
<el-button type="info" @click="content=''">清空消息</el-button>
</el-form-item>
</el-form>
</div>
</template>
<script>
import store from "@/store";
import {getNowDateTime} from "@/utils/ruoyi";
export default {
data() {
return {
url: process.env.VUE_APP_BASE_API + "/websocket/message",
message: "",
content: "",
ws: null,
};
},
created() {
this.url = this.url.replace("http", "ws")
},
methods: {
connect() {
if (!'WebSocket' in window) {
this.$modal.msgError("您的浏览器不支持WebSocket");
return;
}
const userId = store.getters.userId;
this.ws = new WebSocket(this.url + "?userId=" + userId);
const self = this;
this.ws.onopen = function (event) {
self.content = self.content + "\n**********************连接开始**********************\n";
};
this.ws.onmessage = function (event) {
self.content = self.content + "接收时间:" + getNowDateTime() + "\n" + event.data + "\n";
};
this.ws.onclose = function (event) {
self.content = self.content + "**********************连接关闭**********************\n";
};
this.ws.error = function (event) {
self.content = self.content + "**********************连接异常**********************\n";
};
},
exit() {
if (this.ws) {
this.ws.close();
this.ws = null;
}
},
send() {
if (!this.ws || this.ws.readyState !== 1) {
this.$modal.msgError("未连接到服务器");
return;
}
if (!this.message) {
this.$modal.msgError("请输入发送内容");
return;
}
this.ws.send(this.message);
}
},
};
</script>

View File

@@ -1,4 +1,4 @@
<template>
<template xmlns="">
<div class="container">
<div class="logo"></div>
<!-- 登录区域 -->
@@ -36,19 +36,10 @@
</el-form-item>
<el-form-item prop="password">
<el-input v-model="loginForm.password" type="password" auto-complete="off" placeholder="密码"
@keyup.enter.native="handleLogin">
@keyup.enter.native="getCode">
<svg-icon slot="prefix" icon-class="password" class="el-input__icon input-icon"/>
</el-input>
</el-form-item>
<el-form-item prop="code" v-if="captchaEnable">
<el-input v-model="loginForm.code" auto-complete="off" placeholder="验证码" style="width: 63%"
@keyup.enter.native="handleLogin">
<svg-icon slot="prefix" icon-class="validCode" class="el-input__icon input-icon"/>
</el-input>
<div class="login-code">
<img :src="codeUrl" @click="getCode" class="login-code-img"/>
</div>
</el-form-item>
<el-checkbox v-model="loginForm.rememberMe" style="margin:0 0 25px 0;">记住密码</el-checkbox>
</div>
@@ -62,10 +53,10 @@
<el-form-item prop="mobileCode">
<el-input v-model="loginForm.mobileCode" type="text" auto-complete="off" placeholder="短信验证码"
@keyup.enter.native="handleLogin">
<template slot="icon">
<template v-slot="icon">
<svg-icon slot="prefix" icon-class="password" class="el-input__icon input-icon"/>
</template>
<template slot="append">
<template v-slot="append">
<span v-if="mobileCodeTimer <= 0" class="getMobileCode" @click="getSmsCode" style="cursor: pointer;">获取验证码</span>
<span v-if="mobileCodeTimer > 0" class="getMobileCode">{{ mobileCodeTimer }}秒后可重新获取</span>
</template>
@@ -76,7 +67,7 @@
<!-- 下方的登录按钮 -->
<el-form-item style="width:100%;">
<el-button :loading="loading" size="medium" type="primary" style="width:100%;"
@click.native.prevent="handleLogin">
@click.native.prevent="getCode">
<span v-if="!loading"> </span>
<span v-else> 中...</span>
</el-button>
@@ -96,6 +87,11 @@
</div>
</div>
</div>
<!-- 图形验证码 -->
<Verify ref="verify" :captcha-type="'blockPuzzle'" :img-size="{width:'400px',height:'200px'}"
@success="handleLogin" />
<!-- footer -->
<div class="footer">
Copyright © 2020-2022 iocoder.cn All Rights Reserved.
@@ -104,10 +100,10 @@
</template>
<script>
import {getCodeImg, sendSmsCode, socialAuthRedirect} from "@/api/login";
import {sendSmsCode, socialAuthRedirect} from "@/api/login";
import {getTenantIdByName} from "@/api/system/tenant";
import {SystemUserSocialTypeEnum} from "@/utils/constants";
import {getTenantEnable} from "@/utils/ruoyi";
import {getCaptchaEnable, getTenantEnable} from "@/utils/ruoyi";
import {
getPassword,
getRememberMe, getTenantName,
@@ -118,8 +114,14 @@ import {
setUsername
} from "@/utils/auth";
import Verify from '@/components/Verifition/Verify';
import {resetUserPwd} from "@/api/system/user";
export default {
name: "Login",
components: {
Verify
},
data() {
return {
codeUrl: "",
@@ -130,11 +132,10 @@ export default {
loginType: "uname",
username: "admin",
password: "admin123",
captchaVerification: "",
mobile: "",
mobileCode: "",
rememberMe: false,
code: "",
uuid: "",
tenantName: "芋道源码",
},
scene: 21,
@@ -146,12 +147,11 @@ export default {
password: [
{required: true, trigger: "blur", message: "密码不能为空"}
],
code: [{required: true, trigger: "change", message: "验证码不能为空"}],
mobile: [
{required: true, trigger: "blur", message: "手机号不能为空"},
{
validator: function (rule, value, callback) {
if (/^(?:(?:\+|00)86)?1(?:(?:3[\d])|(?:4[5-79])|(?:5[0-35-9])|(?:6[5-7])|(?:7[0-8])|(?:8[\d])|(?:9[189]))\d{8}$/.test(value) === false) {
if (/^(?:(?:\+|00)86)?1(?:3[\d]|4[5-79]|5[0-35-9]|6[5-7]|7[0-8]|8[\d]|9[189])\d{8}$/.test(value) === false) {
callback(new Error("手机号格式错误"));
} else {
callback();
@@ -185,37 +185,34 @@ export default {
SysUserSocialTypeEnum: SystemUserSocialTypeEnum,
};
},
// watch: {
// $route: {
// handler: function(route) {
// this.redirect = route.query && route.query.redirect;
// },
// immediate: true
// }
// },
created() {
// 租户开关
this.tenantEnable = getTenantEnable();
if (this.tenantEnable) {
getTenantIdByName(this.loginForm.tenantName).then(res => { // 设置租户
const tenantId = res.data;
if (tenantId && tenantId >= 0) {
setTenantId(tenantId)
}
});
}
// 验证码开关
this.captchaEnable = getCaptchaEnable();
// 重定向地址
this.redirect = this.$route.query.redirect;
this.getCode();
this.redirect = this.$route.query.redirect ? decodeURIComponent(this.$route.query.redirect) : undefined;
this.getCookie();
},
methods: {
getCode() {
// 只有开启的状态,才加载验证码。默认开启
// 情况一,未开启:则直接登录
if (!this.captchaEnable) {
this.handleLogin({})
return;
}
// 请求远程,获得验证码
getCodeImg().then(res => {
res = res.data;
this.captchaEnable = res.enable;
if (this.captchaEnable) {
this.codeUrl = "data:image/gif;base64," + res.img;
this.loginForm.uuid = res.uuid;
}
});
// 情况二,已开启:则展示验证码;只有完成验证码的情况,才进行登录
// 弹出验证码
this.$refs.verify.show()
},
getCookie() {
const username = getUsername();
@@ -230,7 +227,7 @@ export default {
tenantName: tenantName ? tenantName : this.loginForm.tenantName,
};
},
handleLogin() {
handleLogin(captchaParams) {
this.$refs.loginForm.validate(valid => {
if (valid) {
this.loading = true;
@@ -246,6 +243,7 @@ export default {
removeRememberMe()
removeTenantName()
}
this.loginForm.captchaVerification = captchaParams.captchaVerification
// 发起登陆
// console.log("发起登录", this.loginForm);
this.$store.dispatch(this.loginForm.loginType === "sms" ? "SmsLogin" : "Login", this.loginForm).then(() => {
@@ -253,23 +251,44 @@ export default {
});
}).catch(() => {
this.loading = false;
this.getCode();
});
}
});
},
doSocialLogin(socialTypeEnum) {
async doSocialLogin(socialTypeEnum) {
// 设置登录中
this.loading = true;
// 计算 redirectUri
const redirectUri = location.origin + '/social-login?type=' + socialTypeEnum.type + '&redirect=' + (this.redirect || "/"); // 重定向不能丢
// const redirectUri = 'http://127.0.0.1:48080/api/gitee/callback';
// const redirectUri = 'http://127.0.0.1:48080/api/dingtalk/callback';
// 进行跳转
socialAuthRedirect(socialTypeEnum.type, encodeURIComponent(redirectUri)).then((res) => {
// console.log(res.url);
window.location.href = res.data;
});
let tenant = false;
if (this.tenantEnable) {
await this.$prompt('请输入租户名称', "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消"
}).then(({value}) => {
getTenantIdByName(value).then(res => {
const tenantId = res.data;
tenant = true
if (tenantId && tenantId >= 0) {
setTenantId(tenantId)
}
});
}).catch(() => {
return false
});
} else {
tenant = true
}
if(tenant){
// 计算 redirectUri
const redirectUri = location.origin + '/social-login?'
+ encodeURIComponent('type=' + socialTypeEnum.type + '&redirect=' + (this.redirect || "/")); // 重定向不能丢
// const redirectUri = 'http://127.0.0.1:48080/api/gitee/callback';
// const redirectUri = 'http://127.0.0.1:48080/api/dingtalk/callback';
// 进行跳转
socialAuthRedirect(socialTypeEnum.type, encodeURIComponent(redirectUri)).then((res) => {
// console.log(res.url);
window.location.href = res.data;
});
}
},
/** ========== 以下为升级短信登录 ========== */
getSmsCode() {
@@ -297,7 +316,7 @@ export default {
.oauth-login {
display: flex;
align-items: cen;
align-items: center;
cursor:pointer;
}
.oauth-login-item {

View File

@@ -37,7 +37,7 @@
<el-table-column label="标题" align="center" prop="title"/>
<el-table-column label="缩略图" align="center" prop="picUrl">
<template slot-scope="scope">
<template v-slot="scope">
<img v-if="scope.row.picUrl" :src="scope.row.picUrl" alt="缩略图片" class="img-height"/>
</template>
</el-table-column>
@@ -45,17 +45,17 @@
<el-table-column label="排序" align="center" prop="sort"/>
<el-table-column label="描述" align="center" prop="memo"/>
<el-table-column label="状态" align="center" prop="status">
<template slot-scope="scope">
<template v-slot="scope">
<dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status"/>
</template>
</el-table-column>
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
<template slot-scope="scope">
<template v-slot="scope">
<span>{{ parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template slot-scope="scope">
<template v-slot="scope">
<el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)"
v-hasPermi="['market:banner:update']">修改
</el-button>
@@ -107,7 +107,6 @@
import {
createBanner,
deleteBanner,
exportBannerExcel,
getBanner,
getBannerPage,
updateBanner

View File

@@ -1,8 +1,7 @@
<template>
<div class="app-container">
<!-- 搜索工作栏 -->
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
« <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="品牌名称" prop="name">
<el-input v-model="queryParams.name" placeholder="请输入品牌名称" clearable @keyup.enter.native="handleQuery"/>
</el-form-item>
@@ -26,41 +25,34 @@
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" plain icon="el-icon-plus" size="mini" @click="handleAdd"
v-hasPermi="['product:brand:create']">新增
</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="warning" plain icon="el-icon-download" size="mini" @click="handleExport"
:loading="exportLoading"
v-hasPermi="['product:brand:export']">导出
</el-button>
v-hasPermi="['product:brand:create']">新增</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<!-- 列表 -->
<el-table v-loading="loading" :data="list">
<el-table-column label="商品分类" align="center" prop="categoryId"/>
<el-table-column label="品牌编号" align="center" prop="id"/>
<el-table-column label="品牌名称" align="center" prop="name"/>
<el-table-column label="品牌图片" align="center" prop="bannerUrl">
<template slot-scope="scope">
<img v-if="scope.row.bannerUrl" :src="scope.row.bannerUrl" alt="分类图片" class="img-height"/>
<el-table-column label="品牌图片" align="center" prop="picUrl">
<template v-slot="scope">
<img v-if="scope.row.picUrl" :src="scope.row.picUrl" alt="分类图片" style="height: 100px;" />
</template>
</el-table-column>
<el-table-column label="品牌排序" align="center" prop="sort"/>
<el-table-column label="品牌描述" align="center" prop="description"/>
<el-table-column label="状态" align="center" prop="status">
<template slot-scope="scope">
<template v-slot="scope">
<dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status"/>
</template>
</el-table-column>
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
<template slot-scope="scope">
<template v-slot="scope">
<span>{{ parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template slot-scope="scope">
<template v-slot="scope">
<el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)"
v-hasPermi="['product:brand:update']">修改
</el-button>
@@ -77,15 +69,11 @@
<!-- 对话框(添加 / 修改) -->
<el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
<el-form ref="form" :model="form" :rules="rules" label-width="80px">
<el-form-item label="商品分类" prop="categoryId">
<Treeselect v-model="form.categoryId" :options="categoryOptions" :normalizer="normalizer" :show-count="true"
placeholder="请选择商品分类"/>
</el-form-item>
<el-form-item label="品牌名称" prop="name">
<el-input v-model="form.name" placeholder="请输入品牌名称"/>
</el-form-item>
<el-form-item label="品牌图片" prop="bannerUrl">
<imageUpload v-model="form.bannerUrl" :limit="1"/>
<el-form-item label="品牌图片" prop="picUrl">
<imageUpload v-model="form.picUrl" :limit="1"/>
</el-form-item>
<el-form-item label="品牌排序" prop="sort">
<el-input v-model="form.sort" placeholder="请输入品牌排序"/>
@@ -113,20 +101,18 @@
import {
createBrand,
deleteBrand,
exportBrandExcel,
getBrand,
getBrandPage,
updateBrand
} from "@/api/mall/product/brand";
import {listCategory} from "@/api/mall/product/category";
import ImageUpload from '@/components/ImageUpload';
import Treeselect from "@riophae/vue-treeselect";
import "@riophae/vue-treeselect/dist/vue-treeselect.css";
import {CommonStatusEnum} from "@/utils/constants";
export default {
name: "Brand",
name: "ProductBrand",
components: {
ImageUpload, Treeselect,
ImageUpload
},
data() {
return {
@@ -153,21 +139,18 @@ export default {
status: null,
createTime: []
},
// 商品分类树选项
categoryOptions: [],
// 表单参数
form: {},
// 表单校验
rules: {
categoryId: [{required: true, message: "分类编号不能为空", trigger: "blur"}],
name: [{required: true, message: "品牌名称不能为空", trigger: "blur"}],
bannerUrl: [{required: true, message: "品牌图片不能为空", trigger: "blur"}],
picUrl: [{required: true, message: "品牌图片不能为空", trigger: "blur"}],
sort: [{required: true, message: "品牌排序不能为空", trigger: "blur"}],
status: [{required: true, message: "状态不能为空", trigger: "change"}],
}
};
},
created() {
this.getTreeselect();
this.getList();
},
methods: {
@@ -181,26 +164,6 @@ export default {
this.loading = false;
});
},
/** 转换菜单数据结构 */
normalizer(node) {
if (node.children && !node.children.length) {
delete node.children;
}
return {
id: node.id,
label: node.name,
children: node.children
};
},
/** 查询分类下拉树结构 */
getTreeselect() {
listCategory().then(response => {
this.categoryOptions = [];
const menu = {id: 0, name: '商品分类', children: []};
menu.children = this.handleTree(response.data, "id", "pid");
this.categoryOptions.push(menu);
});
},
/** 取消按钮 */
cancel() {
this.open = false;
@@ -210,12 +173,11 @@ export default {
reset() {
this.form = {
id: undefined,
categoryId: undefined,
name: undefined,
bannerUrl: undefined,
sort: undefined,
picUrl: undefined,
sort: 0,
description: undefined,
status: undefined,
status: CommonStatusEnum.ENABLE,
};
this.resetForm("form");
},
@@ -232,14 +194,12 @@ export default {
/** 新增按钮操作 */
handleAdd() {
this.reset();
this.getTreeselect();
this.open = true;
this.title = "添加品牌";
},
/** 修改按钮操作 */
handleUpdate(row) {
this.reset();
this.getTreeselect();
const id = row.id;
getBrand(id).then(response => {
this.form = response.data;
@@ -280,30 +240,7 @@ export default {
this.$modal.msgSuccess("删除成功");
}).catch(() => {
});
},
/** 导出按钮操作 */
handleExport() {
// 处理查询参数
let params = {...this.queryParams};
params.pageNo = undefined;
params.pageSize = undefined;
// 执行导出
this.$modal.confirm('是否确认导出所有品牌数据项?').then(() => {
this.exportLoading = true;
return exportBrandExcel(params);
}).then(response => {
this.$download.excel(response, "品牌.xls");
this.exportLoading = false;
}).catch(() => {
});
}
}
};
</script>
<style scoped lang="scss">
//
.img-height {
height: 150px;
}
</style>

View File

@@ -6,12 +6,6 @@
<el-form-item label="分类名称" prop="name">
<el-input v-model="queryParams.name" placeholder="请输入分类名称" clearable @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="开启状态" prop="status">
<el-select v-model="queryParams.status" placeholder="请选择开启状态" clearable size="small">
<el-option v-for="dict in this.getDictDatas(DICT_TYPE.COMMON_STATUS)"
:key="dict.value" :label="dict.label" :value="dict.value"/>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" @click="resetQuery">重置</el-button>
@@ -28,42 +22,31 @@
<el-col :span="1.5">
<el-button type="info" plain icon="el-icon-sort" size="mini" @click="toggleExpandAll">展开/折叠</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="warning" plain icon="el-icon-download" size="mini" @click="handleExport"
:loading="exportLoading"
v-hasPermi="['product:category:export']">导出
</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<!-- 列表 -->
<el-table v-if="refreshTable" v-loading="loading" :data="list" row-key="id" :default-expand-all="isExpandAll"
:tree-props="{children: 'children', hasChildren: 'hasChildren'}">
<el-table-column label="分类名称" align="center" prop="name"/>
<el-table-column label="分类图" align="center" prop="icon">
<template slot-scope="scope">
<svg-icon :icon-class="scope.row.icon" />
</template>
</el-table-column>
<el-table-column label="分类图片" align="center" prop="bannerUrl">
<template slot-scope="scope">
<img v-if="scope.row.bannerUrl" :src="scope.row.bannerUrl" alt="分类图片" class="img-height"/>
<el-table-column label="分类名称" prop="name"/>
<el-table-column label="分类图" align="center" prop="picUrl">
<template v-slot="scope">
<img v-if="scope.row.picUrl" :src="scope.row.picUrl" alt="分类图片" style="height: 100px"/>
</template>
</el-table-column>
<el-table-column label="分类排序" align="center" prop="sort"/>
<el-table-column label="开启状态" align="center" prop="status">
<template slot-scope="scope">
<template v-slot="scope">
<dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status"/>
</template>
</el-table-column>
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
<template slot-scope="scope">
<template v-slot="scope">
<span>{{ parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template slot-scope="scope">
<template v-slot="scope">
<el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)"
v-hasPermi="['product:category:update']">修改
</el-button>
@@ -78,32 +61,20 @@
<el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
<el-form ref="form" :model="form" :rules="rules" label-width="80px">
<el-form-item label="上级分类" prop="parentId">
<Treeselect v-model="form.parentId" :options="parentCategoryOptions" :normalizer="normalizer"
:show-count="true"
placeholder="上级分类"/>
<Treeselect v-model="form.parentId" :options="parentCategoryOptions" :normalizer="normalizer" :show-count="true"
:defaultExpandLevel="1" placeholder="上级分类"/>
</el-form-item>
<el-form-item label="分类名称" prop="name">
<el-input v-model="form.name" placeholder="请输入分类名称"/>
</el-form-item>
<el-form-item label="分类图" prop="icon">
<el-popover placement="bottom-start" width="460" trigger="click" @show="$refs['iconSelect'].reset()">
<IconSelect ref="iconSelect" @selected="iconSelected"/>
<el-input slot="reference" v-model="form.icon" placeholder="点击选择分类图标" readonly>
<svg-icon v-if="form.icon" slot="prefix" :icon-class="form.icon" class="el-input__icon"
style="height: 32px;width: 16px;"/>
<i v-else slot="prefix" class="el-icon-search el-input__icon"/>
</el-input>
</el-popover>
</el-form-item>
<el-form-item label="分类图片" prop="bannerUrl">
<ImageUpload v-model="form.bannerUrl" :limit="1"/>
<el-form-item label="分类图" prop="picUrl">
<ImageUpload v-model="form.picUrl" :limit="1" :is-show-tip="false" />
<div v-if="form.parentId === 0" style="font-size: 10px">推荐 200x100 图片分辨率</div>
<div v-else style="font-size: 10px">推荐 100x100 图片分辨率</div>
</el-form-item>
<el-form-item label="分类排序" prop="sort">
<el-input-number v-model="form.sort" controls-position="right" :min="0" />
</el-form-item>
<el-form-item label="分类描述">
<editor v-model="form.description" :min-height="192"/>
</el-form-item>
<el-form-item label="开启状态" prop="status">
<el-radio-group v-model="form.status">
<el-radio v-for="dict in this.getDictDatas(DICT_TYPE.COMMON_STATUS)"
@@ -111,6 +82,9 @@
</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="分类描述">
<el-input v-model="form.description" type="textarea" placeholder="请输入分类描述" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
@@ -122,34 +96,29 @@
<script>
import {
createCategory,
deleteCategory,
exportCategoryExcel,
getCategory,
listCategory,
updateCategory
createProductCategory,
deleteProductCategory,
getProductCategory,
getProductCategoryList,
updateProductCategory
} from "@/api/mall/product/category";
import Editor from '@/components/Editor';
import Treeselect from "@riophae/vue-treeselect";
import "@riophae/vue-treeselect/dist/vue-treeselect.css";
import IconSelect from "@/components/IconSelect";
import ImageUpload from '@/components/ImageUpload';
import {CommonStatusEnum} from "@/utils/constants";
export default {
name: "Category",
name: "ProductCategory",
components: {
Editor, Treeselect, IconSelect, ImageUpload
Editor, Treeselect, ImageUpload
},
data() {
return {
// 遮罩层
loading: true,
// 导出遮罩层
exportLoading: false,
// 显示搜索条件
showSearch: true,
// 总条数
total: 0,
// 商品分类列表
list: [],
// 商品分类树选项
@@ -164,10 +133,7 @@ export default {
refreshTable: true,
// 查询参数
queryParams: {
pageNo: 1,
pageSize: 10,
name: null,
status: null,
},
// 表单参数
form: {},
@@ -175,8 +141,8 @@ export default {
rules: {
parentId: [{required: true, message: "请选择上级分类", trigger: "blur"}],
name: [{required: true, message: "分类名称不能为空", trigger: "blur"}],
icon: [{required: true, message: "分类图不能为空", trigger: "blur"}],
bannerUrl: [{required: true, message: "分类图片不能为空", trigger: "blur"}],
picUrl: [{required: true, message: "分类图不能为空", trigger: "blur"}],
sort: [{required: true, message: "分类排序不能为空", trigger: "blur"}],
status: [{required: true, message: "开启状态不能为空", trigger: "blur"}],
}
};
@@ -191,31 +157,12 @@ export default {
// 处理查询参数
let params = {...this.queryParams};
// 执行查询
listCategory(params).then(response => {
getProductCategoryList(params).then(response => {
this.list = this.handleTree(response.data, "id", "parentId");
this.loading = false;
});
},
// 选择图标
iconSelected(name) {
this.form.icon = name;
},
/** 转换菜单数据结构 */
normalizer(node) {
if (node.children && !node.children.length) {
delete node.children;
}
return {
id: node.id,
label: node.name,
children: node.children
};
},
/** 查询分类下拉树结构 */
getTreeselect() {
listCategory().then(response => {
// 属性下拉框
this.parentCategoryOptions = [];
const menu = {id: 0, name: '分类', children: []};
const menu = {id: 0, name: '顶级分类', children: []};
menu.children = this.handleTree(response.data, "id", "parentId");
this.parentCategoryOptions.push(menu);
});
@@ -231,11 +178,10 @@ export default {
id: undefined,
parentId: undefined,
name: undefined,
icon: undefined,
bannerUrl: undefined,
sort: undefined,
pirUrl: undefined,
sort: 0,
description: undefined,
status: undefined,
status: CommonStatusEnum.ENABLE,
};
this.resetForm("form");
},
@@ -257,19 +203,28 @@ export default {
this.refreshTable = true;
});
},
/** 转换菜单数据结构 */
normalizer(node) {
if (node.children && !node.children.length) {
delete node.children;
}
return {
id: node.id,
label: node.name,
children: node.children
};
},
/** 新增按钮操作 */
handleAdd() {
this.reset();
this.getTreeselect();
this.open = true;
this.title = "添加商品分类";
},
/** 修改按钮操作 */
handleUpdate(row) {
this.reset();
this.getTreeselect();
const id = row.id;
getCategory(id).then(response => {
getProductCategory(id).then(response => {
this.form = response.data;
this.open = true;
this.title = "修改商品分类";
@@ -283,7 +238,7 @@ export default {
}
// 修改的提交
if (this.form.id != null) {
updateCategory(this.form).then(response => {
updateProductCategory(this.form).then(response => {
this.$modal.msgSuccess("修改成功");
this.open = false;
this.getList();
@@ -291,7 +246,7 @@ export default {
return;
}
// 添加的提交
createCategory(this.form).then(response => {
createProductCategory(this.form).then(response => {
this.$modal.msgSuccess("新增成功");
this.open = false;
this.getList();
@@ -302,36 +257,13 @@ export default {
handleDelete(row) {
const id = row.id;
this.$modal.confirm('是否确认删除商品分类编号为"' + id + '"的数据项?').then(function () {
return deleteCategory(id);
return deleteProductCategory(id);
}).then(() => {
this.getList();
this.$modal.msgSuccess("删除成功");
}).catch(() => {
});
},
/** 导出按钮操作 */
handleExport() {
// 处理查询参数
let params = {...this.queryParams};
params.pageNo = undefined;
params.pageSize = undefined;
// 执行导出
this.$modal.confirm('是否确认导出所有商品分类数据项?').then(() => {
this.exportLoading = true;
return exportCategoryExcel(params);
}).then(response => {
this.$download.excel(response, '商品分类.xls');
this.exportLoading = false;
}).catch(() => {
});
}
}
};
</script>
<style scoped lang="scss">
//
.img-height {
height: 150px;
}
</style>

View File

@@ -3,14 +3,8 @@
<!-- 搜索工作栏 -->
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="规格名称" prop="name">
<el-input v-model="queryParams.name" placeholder="请输入规格名称" clearable @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="开启状态" prop="status">
<el-select v-model="queryParams.status" placeholder="请选择开启状态" clearable size="small">
<el-option v-for="dict in this.getDictDatas(DICT_TYPE.COMMON_STATUS)"
:key="dict.value" :label="dict.label" :value="dict.value"/>
</el-select>
<el-form-item label="名称" prop="name">
<el-input v-model="queryParams.name" placeholder="请输入名称" clearable @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="创建时间" prop="createTime">
<el-date-picker v-model="queryParams.createTime" style="width: 240px" value-format="yyyy-MM-dd HH:mm:ss" type="daterange"
@@ -33,17 +27,15 @@
<!-- 列表 -->
<el-table v-loading="loading" :data="list">
<el-table-column label="规格名称" align="center" prop="name" />
<el-table-column label="规格名称" align="center" prop="propertyValueList">
<el-table-column label="编号" align="center" prop="id" />
<el-table-column label="名称" align="center" :show-overflow-tooltip="true">
<template slot-scope="scope">
<span>{{formatList(scope.row.propertyValueList)}}</span>
</template>
</el-table-column>
<el-table-column label="开启状态" align="center" prop="status">
<template slot-scope="scope">
<dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status"/>
<router-link :to="'/property/value/' + scope.row.id" class="link-type">
<span>{{ scope.row.name }}</span>
</router-link>
</template>
</el-table-column>
<el-table-column label="备注" align="center" prop="remark" />
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.createTime) }}</span>
@@ -65,36 +57,12 @@
<!-- 对话框(添加 / 修改) -->
<el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
<el-form ref="form" :model="form" :rules="rules" label-width="80px">
<el-form-item label="规格名称" prop="name">
<el-input v-model="form.name" placeholder="请输入规格名称" />
<el-form-item label="名称" prop="name">
<el-input v-model="form.name" placeholder="请输入名称" />
</el-form-item>
<el-form-item label="开启状态" prop="status">
<el-radio-group v-model="form.status">
<el-radio v-for="dict in this.getDictDatas(DICT_TYPE.COMMON_STATUS)"
:key="dict.value" :label="parseInt(dict.value)">{{ dict.label }}
</el-radio>
</el-radio-group>
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" placeholder="备注" />
</el-form-item>
<el-form-item label="属性值">
<el-button type="primary" plain icon="el-icon-plus" size="mini" @click="addPropertyValue()">添加</el-button>
</el-form-item>
<el-form-item
v-for="(domain, index) in form.propertyValueList"
:key="domain.key"
:prop="'propertyValueList.' + index + '.name'"
:rules="{
required: true, message: '属性值不能为空', trigger: 'blur'
}"
>
<el-row>
<el-col :span="18">
<el-input v-model="domain.name" size="mini"></el-input>
</el-col>
<el-col :span="6">
<el-button style="margin-left: 20px;" size="mini" @click.prevent="removePropertyValue(domain)">删除</el-button>
</el-col>
</el-row>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
@@ -105,7 +73,7 @@
</template>
<script>
import { createProperty, updateProperty, deleteProperty, getProperty, getPropertyPage, exportPropertyExcel } from "@/api/mall/product/property";
import { createProperty, updateProperty, deleteProperty, getProperty, getPropertyPage } from "@/api/mall/product/property";
export default {
name: "Property",
@@ -115,13 +83,11 @@ export default {
return {
// 遮罩层
loading: true,
// 导出遮罩层
exportLoading: false,
// 显示搜索条件
showSearch: true,
// 总条数
total: 0,
// 规格名称列表
// 属性项列表
list: [],
// 弹出层标题
title: "",
@@ -132,19 +98,19 @@ export default {
pageNo: 1,
pageSize: 10,
name: null,
status: null,
createTime: []
},
// 表单参数
form: {
name:'',
status:'',
propertyValueList: [{
name: ''
}],
remark:"",
id: null,
},
// 表单校验
rules: {
name: [
{ required: true, message: "名称不能为空", trigger: "blur" }
]
}
};
},
@@ -170,12 +136,9 @@ export default {
/** 表单重置 */
reset() {
this.form = {
id: undefined,
name: undefined,
status: undefined,
propertyValueList: [{
name: ''
}]
name:'',
remark:"",
id: null,
};
this.resetForm("form");
},
@@ -193,7 +156,7 @@ export default {
handleAdd() {
this.reset();
this.open = true;
this.title = "添加规格";
this.title = "添加属性项";
},
/** 修改按钮操作 */
handleUpdate(row) {
@@ -202,7 +165,7 @@ export default {
getProperty(id).then(response => {
this.form = response.data;
this.open = true;
this.title = "修改规格";
this.title = "修改属性项";
});
},
/** 提交按钮 */
@@ -231,49 +194,13 @@ export default {
/** 删除按钮操作 */
handleDelete(row) {
const id = row.id;
this.$modal.confirm('是否确认删除规格名称为"' + row.name + '"的数据项?').then(function() {
this.$modal.confirm('是否确认删除名称为"' + row.name + '"的数据项?').then(function() {
return deleteProperty(id);
}).then(() => {
this.getList();
this.$modal.msgSuccess("删除成功");
}).catch(() => {});
},
/** 导出按钮操作 */
handleExport() {
// 处理查询参数
let params = {...this.queryParams};
params.pageNo = undefined;
params.pageSize = undefined;
// 执行导出
this.$modal.confirm('是否确认导出所有规格名称数据项?').then(() => {
this.exportLoading = true;
return exportPropertyExcel(params);
}).then(response => {
this.$download.excel(response, '规格名称.xls');
this.exportLoading = false;
}).catch(() => {});
},
removePropertyValue(item) {
var index = this.form.propertyValueList.indexOf(item)
if (index !== -1) {
this.form.propertyValueList.splice(index, 1)
}
},
addPropertyValue() {
this.form.propertyValueList.push({
name: ''
});
},
formatList(list) {
let str = ''
for (var i = 0; i < list.length; i++) {
str += list[i].name;
if(i != list.length-1){
str+="/";
}
}
return str
}
}
};
</script>

View File

@@ -0,0 +1,238 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="属性项" prop="propertyId">
<el-select v-model="queryParams.propertyId">
<el-option v-for="item in propertyOptions" :key="item.id" :label="item.name" :value="item.id"/>
</el-select>
</el-form-item>
<el-form-item label="名称" prop="name">
<el-input v-model="queryParams.name" placeholder="请输入名称" clearable @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" plain icon="el-icon-plus" size="mini" @click="handleAdd"
v-hasPermi="['system:dict:create']">新增
</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<el-table v-loading="loading" :data="dataList">
<el-table-column label="编号" align="center" prop="id"/>
<el-table-column label="名称" align="center" prop="name"/>
<el-table-column label="备注" align="center" prop="remark" :show-overflow-tooltip="true"/>
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template slot-scope="scope">
<el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)"
v-hasPermi="['system:dict:update']">修改
</el-button>
<el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)"
v-hasPermi="['system:dict:delete']">删除
</el-button>
</template>
</el-table-column>
</el-table>
<pagination v-show="total>0" :total="total" :page.sync="queryParams.pageNo" :limit.sync="queryParams.pageSize"
@pagination="getList"/>
<!-- 添加或修改参数配置对话框 -->
<el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
<el-form ref="form" :model="form" :rules="rules" label-width="90px">
<el-form-item label="属性项">
<el-input v-model="form.propertyId" :disabled="true"/>
</el-form-item>
<el-form-item label="名称" prop="name">
<el-input v-model="form.name" placeholder="请输入名称"/>
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" type="textarea" placeholder="请输入内容"></el-input>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import {
createPropertyValue,
deletePropertyValue,
getProperty,
getPropertyList,
getPropertyValue,
getPropertyValuePage,
updatePropertyValue
} from '@/api/mall/product/property'
export default {
name: "PropertyValue",
data() {
return {
// 遮罩层
loading: true,
// 显示搜索条件
showSearch: true,
// 总条数
total: 0,
// 字典表格数据
dataList: [],
// 默认字典类型
defaultPropertyId: "",
// 弹出层标题
title: "",
// 是否显示弹出层
open: false,
// 类型数据字典
propertyOptions: [],
// 查询参数
queryParams: {
pageNo: 1,
pageSize: 10,
propertyId: undefined,
name: undefined,
},
// 表单参数
form: {},
// 表单校验
rules: {
name: [
{required: true, message: "名称不能为空", trigger: "blur"}
]
},
};
},
created() {
const propertyId = this.$route.params && this.$route.params.propertyId;
this.getProperty(propertyId);
this.getPropertyList();
},
methods: {
/** 查询字典类型详细 */
getProperty(propertyId) {
getProperty(propertyId).then(response => {
this.queryParams.propertyId = response.data.id;
this.defaultPropertyId = response.data.id;
this.getList();
});
},
/** 查询字典类型列表 */
getPropertyList() {
getPropertyList().then(response => {
this.propertyOptions = response.data
});
},
/** 查询字典数据列表 */
getList() {
this.loading = true;
getPropertyValuePage(this.queryParams).then(response => {
this.dataList = response.data.list;
this.total = response.data.total;
this.loading = false;
});
},
// 取消按钮
cancel() {
this.open = false;
this.reset();
},
// 表单重置
reset() {
this.form = {
id: undefined,
propertyId: undefined,
name: undefined,
remark: undefined
};
this.resetForm("form");
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNo = 1;
this.getList();
},
/** 重置按钮操作 */
resetQuery() {
this.resetForm("queryForm");
this.queryParams.propertyId = this.defaultPropertyId;
this.handleQuery();
},
/** 新增按钮操作 */
handleAdd() {
this.reset();
this.open = true;
this.title = "添加属性值";
this.form.propertyId = this.queryParams.propertyId;
},
/** 修改按钮操作 */
handleUpdate(row) {
this.reset();
const id = row.id;
getPropertyValue(id).then(response => {
this.form = response.data;
this.open = true;
this.title = "修改属性值";
});
},
/** 提交按钮 */
submitForm: function () {
this.$refs["form"].validate(valid => {
if (valid) {
if (this.form.id !== undefined) {
updatePropertyValue(this.form).then(response => {
this.$modal.msgSuccess("修改成功");
this.open = false;
this.getList();
});
} else {
createPropertyValue(this.form).then(response => {
this.$modal.msgSuccess("新增成功");
this.open = false;
this.getList();
});
}
}
});
},
/** 删除按钮操作 */
handleDelete(row) {
const ids = row.id;
this.$modal.confirm('是否确认删除字典编码为"' + ids + '"的数据项?').then(function () {
return deletePropertyValue(ids);
}).then(() => {
this.getList();
this.$modal.msgSuccess("删除成功");
}).catch(() => {
});
},
/** 导出按钮操作 */
handleExport() {
const queryParams = this.queryParams;
this.$modal.confirm('是否确认导出所有数据项?').then(() => {
this.exportLoading = true;
return exportData(queryParams);
}).then(response => {
this.$download.excel(response, '字典数据.xls');
this.exportLoading = false;
}).catch(() => {
});
}
}
};
</script>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,580 @@
<template>
<div class="container">
<!-- TODO 样式优化表单宽度表单项对齐hr 粗细 -->
<el-tabs v-model="activeName" class="tabs">
<!-- 基础设置 -->
<!-- TODO @luowenfeng基础设置分成基础信息配送信息 -->
<el-tab-pane label="基础设置" name="basic">
<el-form ref="basic" :model="baseForm" :rules="rules" label-width="100px" style="width: 95%">
<el-form-item label="商品名称" prop="name">
<el-input v-model="baseForm.name" placeholder="请输入商品名称" />
</el-form-item>
<el-form-item label="促销语">
<el-input type="textarea" v-model="baseForm.sellPoint" placeholder="请输入促销语"/>
</el-form-item>
<el-form-item label="商品主图" prop="picUrls">
<ImageUpload v-model="baseForm.picUrls" :value="baseForm.picUrls" :limit="10" class="mall-image"/>
</el-form-item>
<el-form-item label="商品视频" prop="videoUrl">
<VideoUpload v-model="baseForm.videoUrl" :value="baseForm.videoUrl"/>
</el-form-item>
<el-form-item label="商品品牌" prop="brandId">
<el-select v-model="baseForm.brandId" placeholder="请选择商品品牌">
<el-option v-for="item in brandList" :key="item.id" :label="item.name" :value="item.id"/>
</el-select>
</el-form-item>
<el-form-item label="商品分类" prop="categoryIds">
<el-cascader v-model="baseForm.categoryIds" placeholder="商品分类" style="width: 100%"
:options="categoryList" :props="propName" clearable/>
</el-form-item>
<el-form-item label="是否上架" prop="status">
<el-radio-group v-model="baseForm.status">
<el-radio :label="1">立即上架</el-radio>
<el-radio :label="0">放入仓库</el-radio>
</el-radio-group>
</el-form-item>
</el-form>
</el-tab-pane>
<!-- 价格库存 -->
<!-- TODO @luowenfengrates=priceStack 会更好哈 -->
<el-tab-pane label="价格库存" name="rates" class="rates">
<el-form ref="rates" :model="ratesForm" :rules="rules">
<el-form-item label="启用多规格">
<el-switch v-model="specSwitch" @change="changeSpecSwitch"/>
</el-form-item>
<!-- 动态添加规格属性 -->
<div v-show="ratesForm.spec === 2">
<div v-for="(specs, index) in dynamicSpec" :key="index" class="dynamic-spec">
<!-- 删除按钮 -->
<el-button type="danger" icon="el-icon-delete" circle class="spec-delete" @click="removeSpec(index)"/>
<div class="spec-header">
规格项
<el-select v-model="specs.specId" filterable placeholder="请选择" @change="changeSpec">
<el-option v-for="item in propertyPageList" :key="item.id" :label="item.name" :value="item.id"/>
</el-select>
</div>
<div class="spec-values">
<template v-for="(v, i) in specs.specValue">
<el-input v-model="v.name" class="spec-value" :key="i" disabled/>
</template>
</div>
</div>
<el-button type="primary" @click="dynamicSpec.push({specValue: []}); ratesForm.rates = []">添加规格项目</el-button>
</div>
<!-- 规格明细 -->
<el-form-item label="规格明细">
<el-table :data="ratesForm.rates" border style="width: 100%" ref="ratesTable">
<template v-if="this.specSwitch">
<el-table-column :key="index" v-for="(item, index) in dynamicSpec.filter(v => v.specName !== undefined)"
:label="item.specName">
<template slot-scope="scope">
<el-input v-if="scope.row.spec" v-model="scope.row.spec[index]" disabled/>
</template>
</el-table-column>
</template>
<el-table-column label="规格图片" width="120px" :render-header="addRedStar" key="90">
<template slot-scope="scope">
<ImageUpload v-model="scope.row.picUrl" :limit="1" :isShowTip="false" style="width: 100px; height: 50px"/>
</template>
</el-table-column>
<el-table-column label="市场价(元)" :render-header="addRedStar" key="92">
<template slot-scope="scope">
<el-form-item :prop="'rates.'+ scope.$index + '.marketPrice'" :rules="[{required: true, trigger: 'change'}]">
<el-input v-model="scope.row.marketPrice"
oninput="value= value.match(/\d+(\.\d{0,2})?/) ? value.match(/\d+(\.\d{0,2})?/)[0] : ''"/>
</el-form-item>
</template>
</el-table-column>
<el-table-column label="销售价(元)" :render-header="addRedStar" key="93">
<template slot-scope="scope">
<el-form-item :prop="'rates.'+ scope.$index + '.price'"
:rules="[{required: true, trigger: 'change'}]">
<el-input v-model="scope.row.price"
oninput="value= value.match(/\d+(\.\d{0,2})?/) ? value.match(/\d+(\.\d{0,2})?/)[0] : ''" />
</el-form-item>
</template>
</el-table-column>
<el-table-column label="成本价" :render-header="addRedStar" key="94">
<template slot-scope="scope">
<el-form-item :prop="'rates.'+ scope.$index + '.costPrice'"
:rules="[{required: true, trigger: 'change'}]">
<el-input v-model="scope.row.costPrice"
oninput="value= value.match(/\d+(\.\d{0,2})?/) ? value.match(/\d+(\.\d{0,2})?/)[0] : ''" />
</el-form-item>
</template>
</el-table-column>
<el-table-column label="库存" :render-header="addRedStar" key="95">
<template slot-scope="scope">
<el-form-item :prop="'rates.'+ scope.$index + '.stock'" :rules="[{required: true, trigger: 'change'}]">
<el-input v-model="scope.row.stock" oninput="value=value.replace(/^(0+)|[^\d]+/g,'')"></el-input>
</el-form-item>
</template>
</el-table-column>
<el-table-column label="预警库存" key="96">
<template slot-scope="scope">
<el-input v-model="scope.row.warnStock" oninput="value=value.replace(/^(0+)|[^\d]+/g,'')"></el-input>
</template>
</el-table-column>
<el-table-column label="体积" key="97">
<template slot-scope="scope">
<el-input v-model="scope.row.volume" />
</template>
</el-table-column>
<el-table-column label="重量" key="98">
<template slot-scope="scope">
<el-input v-model="scope.row.weight" />
</template>
</el-table-column>
<el-table-column label="条码" key="99">
<template slot-scope="scope">
<el-input v-model="scope.row.barCode" />
</template>
</el-table-column>
<template v-if="this.specSwitch">
<el-table-column fixed="right" label="操作" width="50" key="100">
<template slot-scope="scope">
<el-button @click="scope.row.status = 1" type="text" size="small"
v-show="scope.row.status === undefined || scope.row.status === 0 ">禁用
</el-button>
<el-button @click="scope.row.status = 0" type="text" size="small" v-show="scope.row.status === 1">
启用
</el-button>
</template>
</el-table-column>
</template>
</el-table>
</el-form-item>
<el-form-item label="虚拟销量" prop="virtualSalesCount">
<el-input v-model="baseForm.virtualSalesCount" placeholder="请输入虚拟销量"
oninput="value=value.replace(/^(0+)|[^\d]+/g,'')"/>
</el-form-item>
</el-form>
</el-tab-pane>
<!-- 商品详情 -->
<el-tab-pane label="商品详情" name="detail">
<el-form ref="detail" :model="baseForm" :rules="rules">
<el-form-item prop="description">
<editor v-model="baseForm.description" :min-height="380"/>
</el-form-item>
</el-form>
</el-tab-pane>
<!-- 销售设置 -->
<el-tab-pane label="高级设置" name="senior">
<el-form ref="senior" :model="baseForm" :rules="rules" label-width="100px" style="width: 95%">
<el-form-item label="排序字段">
<el-input v-model="baseForm.sort" placeholder="请输入排序字段" oninput="value=value.replace(/^(0+)|[^\d]+/g,'')"/>
</el-form-item>
<el-form-item label="是否展示库存" prop="showStock">
<el-radio-group v-model="baseForm.showStock">
<el-radio :label="true"></el-radio>
<el-radio :label="false"></el-radio>
</el-radio-group>
</el-form-item>
</el-form>
</el-tab-pane>
</el-tabs>
<div class="buttons">
<el-button type="info" round @click="cancel">取消</el-button>
<el-button type="success" round @click="submit">确认</el-button>
</div>
</div>
</template>
<script>
import {getBrandList} from "@/api/mall/product/brand";
import {getProductCategoryList} from "@/api/mall/product/category";
import {createSpu, getSpuDetail, updateSpu} from "@/api/mall/product/spu";
import {getPropertyListAndValue,} from "@/api/mall/product/property";
import Editor from "@/components/Editor";
import ImageUpload from "@/components/ImageUpload";
import VideoUpload from "@/components/VideoUpload";
export default {
components: {
Editor,
ImageUpload,
VideoUpload
},
data() {
return {
specSwitch: false,
activeName: "basic",
propName: {
checkStrictly: true,
label: "name",
value: "id",
},
// 基础设置
baseForm: {
id: null,
name: null,
sellPoint: null,
categoryIds: null,
sort: null,
description: null,
picUrls: null,
videoUrl: null,
status: 0,
virtualSalesCount: 0,
showStock: true,
brandId: null,
},
categoryList: [],
// 价格库存
ratesForm: {
spec: 1,
// 规格明细
rates: [{}]
},
dynamicSpec: [
// {
// specId: 86,
// specName: "颜色",
// specValue:[{
// name: "红色",
// id: 225,
// }]
// },
],
propertyPageList: [],
brandList: [],
specValue: null,
// 表单校验
rules: {
name: [{required: true, message: "商品名称不能为空", trigger: "blur"},],
description: [{required: true, message: "描述不能为空", trigger: "blur"},],
categoryIds: [{required: true, message: "分类id不能为空", trigger: "blur"},],
status: [{required: true, message: "商品状态不能为空", trigger: "blur"}],
brandId: [{required: true, message: "商品品牌不能为空", trigger: "blur"}],
picUrls: [{required: true, message: "商品轮播图地址不能为空", trigger: "blur"}],
},
};
},
created() {
this.getListBrand();
this.getListCategory();
this.getPropertyPageList();
const spuId = this.$route.params && this.$route.params.spuId;
if (spuId != null) {
this.updateType(spuId)
}
},
methods: {
removeSpec(index) {
this.dynamicSpec.splice(index, 1);
this.changeSpecSwitch()
},
// 必选标识
addRedStar(h, {column}) {
return [
h('span', {style: 'color: #F56C6C'}, '*'),
h('span', ' ' + column.label)
];
},
changeSpecSwitch() {
this.specSwitch ? this.ratesForm.spec = 2 : this.ratesForm.spec = 1;
this.$refs.ratesTable.doLayout();
if (this.ratesForm.spec === 1) {
this.ratesForm.rates = [{}]
} else {
this.ratesForm.rates = []
if (this.dynamicSpec.length > 0) {
this.buildRatesFormRates()
}
}
},
// 构建规格明细笛卡尔积
buildRatesFormRates() {
let rates = [];
this.dynamicSpec.map(v => v.specValue.map(m => m.name))
.reduce((last, current) => {
const array = [];
last.forEach(par1 => {
current.forEach(par2 => {
let v
// 当两个对象合并时,需使用[1,2]方式生成数组而当数组和对象合并时需使用concat
if (par1 instanceof Array) {
v = par1.concat(par2)
} else {
v = [par1, par2];
}
array.push(v)
});
});
return array;
})
.forEach(v => {
let spec = v;
// 当v为单个规格项时会变成字符串。造成表格只截取第一个字符串而不是数组的第一个元素
if (typeof v == 'string') {
spec = Array.of(v)
}
rates.push({spec: spec, status: 0, name: Array.of(v).join()})
});
this.ratesForm.rates = rates
},
/** 查询分类 */
getListCategory() {
// 执行查询
getProductCategoryList().then((response) => {
this.categoryList = this.handleTree(response.data, "id", "parentId");
});
},
/** 查询品牌列表 */
getListBrand() {
// 执行查询
getBrandList().then((response) => {
this.brandList = response.data;
});
},
// 取消按钮
cancel() {
var currentView = this.$store.state.tagsView.visitedViews[0]
for (currentView of this.$store.state.tagsView.visitedViews) {
if (currentView.path === this.$route.path) {
break
}
}
this.$store.dispatch('tagsView/delView', currentView)
.then(() => {
this.$router.push("/product/spu")
})
},
submit() {
this.$refs[this.activeName].validate((valid) => {
if (!valid) {
return;
}
let rates = JSON.parse(JSON.stringify(this.ratesForm.rates));
// 价格元转分
rates.forEach(r => {
r.marketPrice = r.marketPrice * 100;
r.price = r.price * 100;
r.costPrice = r.costPrice * 100;
})
// 动态规格调整字段
if (this.specSwitch) {
rates.forEach(r => {
let properties = []
Array.of(r.spec).forEach(s => {
let obj;
if (s instanceof Array) {
obj = s;
} else {
obj = Array.of(s);
}
obj.forEach((v, i) => {
let specValue = this.dynamicSpec[i].specValue.find(o => o.name === v);
let propertie = {};
propertie.propertyId = this.dynamicSpec[i].specId;
propertie.valueId = specValue.id;
properties.push(propertie);
})
})
r.properties = properties;
})
} else {
rates[0].name = this.baseForm.name;
rates[0].status = this.baseForm.status;
}
let form = this.baseForm
if (form.picUrls instanceof Array) {
form.picUrls = form.picUrls.flatMap(m => m.split(','))
} else if (form.picUrls.split(',') instanceof Array) {
form.picUrls = form.picUrls.split(',').flatMap(m => m.split(','))
} else {
form.picUrls = Array.of(form.picUrls)
}
form.skus = rates;
form.specType = this.ratesForm.spec;
let category = form.categoryIds instanceof Array ? form.categoryIds: Array.of(form.categoryIds)
console.log(category)
form.categoryId = category[category.length - 1];
if (form.id == null) {
createSpu(form).then(() => {
this.$modal.msgSuccess("新增成功");
}).then(()=>{
this.cancel();
})
} else {
updateSpu(form).then(() => {
this.$modal.msgSuccess("修改成功");
}).then(()=>{
this.cancel();
})
}
});
},
/** 查询规格 */
getPropertyPageList() {
// 执行查询
getPropertyListAndValue().then((response) => {
this.propertyPageList = response.data;
});
},
// 添加规格项目
changeSpec(val) {
let obj = this.propertyPageList.find(o => o.id === val);
let spec = this.dynamicSpec.find(o => o.specId === val)
spec.specId = obj.id;
spec.specName = obj.name;
spec.specValue = obj.values;
this.buildRatesFormRates();
},
updateType(id) {
getSpuDetail(id).then((response) => {
let data = response.data;
this.baseForm.id = data.id;
this.baseForm.name = data.name;
this.baseForm.sellPoint = data.sellPoint;
this.baseForm.categoryIds = data.categoryId;
this.baseForm.videoUrl = data.videoUrl;
this.baseForm.sort = data.sort;
this.baseForm.description = data.description;
this.baseForm.picUrls = data.picUrls;
this.baseForm.status = data.status;
this.baseForm.virtualSalesCount = data.virtualSalesCount;
this.baseForm.showStock = data.showStock;
this.baseForm.brandId = data.brandId;
this.ratesForm.spec = data.specType;
data.skus.forEach(r => {
r.marketPrice = this.divide(r.marketPrice, 100)
r.price = this.divide(r.price, 100)
r.costPrice = this.divide(r.costPrice, 100)
})
if (this.ratesForm.spec === 2) {
this.specSwitch = true;
data.productPropertyViews.forEach(p => {
let obj = {};
obj.specId = p.propertyId;
obj.specName = p.name;
obj.specValue = p.propertyValues;
this.dynamicSpec.push(obj);
})
data.skus.forEach(s => {
s.spec = [];
s.properties.forEach(sp => {
let spec = data.productPropertyViews.find(o => o.propertyId === sp.propertyId).propertyValues.find(v => v.id === sp.valueId).name;
s.spec.push(spec)
})
})
}
this.ratesForm.rates = data.skus
})
},
},
};
</script>
<style lang="scss">
.container{
padding: 20px;
}
.dynamic-spec {
background-color: #f2f2f2;
width: 85%;
margin: auto;
margin-bottom: 10px;
.spec-header {
padding: 30px;
padding-bottom: 20px;
.spec-name {
display: inline;
input {
width: 30%;
}
}
}
.spec-values {
width: 84%;
padding: 25px;
margin: auto;
padding-top: 5px;
.spec-value {
display: inline-block;
margin-right: 10px;
margin-bottom: 10px;
width: 13%;
}
}
.spec-delete {
float: right;
margin-top: 10px;
margin-right: 10px;
}
}
.tabs {
border-bottom: 2px solid #f2f2f2;
.el-tab-pane {
overflow-y: auto;
}
}
// 库存价格图片样式修改
.rates {
.component-upload-image {
margin: auto;
}
.el-upload--picture-card {
width: 100px;
height: 50px;
line-height: 60px;
margin: auto;
}
.el-upload-list__item {
width: 100px !important;
height: 50px !important;
}
}
.buttons {
margin-top: 20px;
height: 36px;
button {
float: right;
margin-left: 15px;
}
}
.mall-image {
.el-upload--picture-card {
width: 80px;
height: 80px;
line-height: 90px;
}
.el-upload-list__item {
width: 80px;
height: 80px;
}
}
</style>

View File

@@ -0,0 +1,162 @@
<template>
<div class="app-container">
<!-- 搜索工作栏 -->
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="会员昵称" prop="nickname">
<el-input v-model="queryParams.nickname" placeholder="请输入会员昵称" clearable @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="创建时间" prop="createTime">
<el-date-picker v-model="queryParams.createTime" style="width: 240px" value-format="yyyy-MM-dd HH:mm:ss" type="daterange"
range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期" :default-time="['00:00:00', '23:59:59']" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<!-- 操作工具栏 -->
<el-row :gutter="10" class="mb8">
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<!-- Tab 选项真正的内容在 Lab -->
<el-tabs v-model="activeTab" type="card" @tab-click="tabClick" style="margin-top: -40px;">
<el-tab-pane v-for="tab in statusTabs" :key="tab.value" :label="tab.label" :name="tab.value" />
</el-tabs>
<!-- 列表 -->
<el-table v-loading="loading" :data="list">
<el-table-column label="会员信息" align="center" prop="nickname" /> <!-- TODO 芋艿以后支持头像支持跳转 -->
<el-table-column label="优惠劵" align="center" prop="name" />
<el-table-column label="优惠券类型" align="center" prop="discountType">
<template slot-scope="scope">
<dict-tag :type="DICT_TYPE.PROMOTION_DISCOUNT_TYPE" :value="scope.row.discountType" />
</template>
</el-table-column>
<el-table-column label="领取方式" align="center" prop="takeType">
<template slot-scope="scope">
<dict-tag :type="DICT_TYPE.PROMOTION_COUPON_TAKE_TYPE" :value="scope.row.takeType" />
</template>
</el-table-column>
<el-table-column label="状态" align="center" prop="status">
<template slot-scope="scope">
<dict-tag :type="DICT_TYPE.PROMOTION_COUPON_STATUS" :value="scope.row.status" />
</template>
</el-table-column>
<el-table-column label="领取时间" align="center" prop="createTime" width="180">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
<el-table-column label="使用时间" align="center" prop="useTime" width="180">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.useTime) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template slot-scope="scope">
<el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)"
v-hasPermi="['promotion:coupon:delete']">回收</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页组件 -->
<pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNo" :limit.sync="queryParams.pageSize"
@pagination="getList"/>
</div>
</template>
<script>
import { deleteCoupon, getCouponPage } from "@/api/mall/promotion/coupon";
import { DICT_TYPE, getDictDatas} from "@/utils/dict";
export default {
name: "Coupon",
components: {
},
data() {
return {
// 遮罩层
loading: true,
// 导出遮罩层
exportLoading: false,
// 显示搜索条件
showSearch: true,
// 总条数
total: 0,
// 优惠劵列表
list: [],
// 弹出层标题
title: "",
// 是否显示弹出层
open: false,
// 查询参数
queryParams: {
pageNo: 1,
pageSize: 10,
createTime: [],
status: undefined,
},
// Tab 筛选
activeTab: 'all',
statusTabs: [{
label: '全部',
value: 'all'
}],
};
},
created() {
this.getList();
// 设置 statuses 过滤
for (const dict of getDictDatas(DICT_TYPE.PROMOTION_COUPON_STATUS)) {
this.statusTabs.push({
label: dict.label,
value: dict.value
})
}
},
methods: {
/** 查询列表 */
getList() {
this.loading = true;
// 执行查询
getCouponPage(this.queryParams).then(response => {
this.list = response.data.list;
this.total = response.data.total;
this.loading = false;
});
},
/** 取消按钮 */
cancel() {
this.open = false;
this.reset();
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNo = 1;
this.getList();
},
/** 重置按钮操作 */
resetQuery() {
this.resetForm("queryForm");
this.handleQuery();
},
/** 删除按钮操作 */
handleDelete(row) {
const id = row.id;
this.$modal.confirm('回收将会收回会员领取的待使用的优惠券,已使用的将无法回收,确定要回收所选优惠券吗?').then(function() {
return deleteCoupon(id);
}).then(() => {
this.getList();
this.$modal.msgSuccess("回收成功");
}).catch(() => {});
},
/** tab 切换 */
tabClick(tab) {
this.queryParams.status = tab.name === 'all' ? undefined : tab.name;
this.getList();
}
}
};
</script>

View File

@@ -0,0 +1,403 @@
<template>
<div class="app-container">
<!-- 搜索工作栏 -->
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="82px">
<el-form-item label="优惠券名称" prop="name">
<el-input v-model="queryParams.name" placeholder="请输入优惠劵名" clearable @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="优惠券类型" prop="discountType">
<el-select v-model="queryParams.discountType" placeholder="请选择优惠券类型" clearable size="small">
<el-option v-for="dict in this.getDictDatas(DICT_TYPE.PROMOTION_DISCOUNT_TYPE)"
:key="dict.value" :label="dict.label" :value="dict.value"/>
</el-select>
</el-form-item>
<el-form-item label="优惠券状态" prop="status">
<el-select v-model="queryParams.status" placeholder="请选择优惠券状态" clearable size="small">
<el-option v-for="dict in this.getDictDatas(DICT_TYPE.COMMON_STATUS)"
:key="dict.value" :label="dict.label" :value="dict.value"/>
</el-select>
</el-form-item>
<el-form-item label="创建时间" prop="createTime">
<el-date-picker v-model="queryParams.createTime" style="width: 240px" value-format="yyyy-MM-dd HH:mm:ss" type="daterange"
range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期" :default-time="['00:00:00', '23:59:59']" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<!-- 操作工具栏 -->
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" plain icon="el-icon-plus" size="mini" @click="handleAdd"
v-hasPermi="['promotion:coupon-template:create']">新增</el-button>
<el-button type="info" plain icon="el-icon-s-operation" size="mini"
@click="() => this.$router.push('/promotion/coupon')"
v-hasPermi="['promotion:coupon:query']">会员优惠劵</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<!-- 列表 -->
<el-table v-loading="loading" :data="list">
<el-table-column label="优惠券名称" align="center" prop="name" />
<el-table-column label="优惠券类型" align="center" prop="discountType">
<template slot-scope="scope">
<dict-tag :type="DICT_TYPE.PROMOTION_DISCOUNT_TYPE" :value="scope.row.discountType" />
</template>
</el-table-column>
<el-table-column label="优惠金额 / 折扣" align="center" prop="discount" :formatter="discountFormat" />
<el-table-column label="发放数量" align="center" prop="totalCount" />
<el-table-column label="剩余数量" align="center" prop="totalCount" :formatter="row => (row.totalCount - row.takeCount)" />
<el-table-column label="领取上限" align="center" prop="takeLimitCount" :formatter="takeLimitCountFormat" />
<el-table-column label="有效期限" align="center" prop="validityType" width="180" :formatter="validityTypeFormat" />
<el-table-column label="状态" align="center" prop="status">
<template slot-scope="scope">
<el-switch v-model="scope.row.status" :active-value="0" :inactive-value="1" @change="handleStatusChange(scope.row)"/>
</template>
</el-table-column>
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template slot-scope="scope">
<el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)"
v-hasPermi="['promotion:coupon-template:update']">修改</el-button>
<el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)"
v-hasPermi="['promotion:coupon-template:delete']">删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页组件 -->
<pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNo" :limit.sync="queryParams.pageSize"
@pagination="getList"/>
<!-- 对话框(添加 / 修改) -->
<el-dialog :title="title" :visible.sync="open" width="600px" v-dialogDrag append-to-body>
<el-form ref="form" :model="form" :rules="rules" label-width="140px">
<el-form-item label="优惠券名称" prop="name">
<el-input v-model="form.name" placeholder="请输入优惠券名称" />
</el-form-item>
<el-form-item label="优惠券类型" prop="discountType">
<el-radio-group v-model="form.discountType">
<el-radio v-for="dict in this.getDictDatas(DICT_TYPE.PROMOTION_DISCOUNT_TYPE)"
:key="dict.value" :label="parseInt(dict.value)">{{dict.label}}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item v-if="form.discountType === PromotionDiscountTypeEnum.PRICE.type" label="优惠券面额" prop="discountPrice">
<el-input-number v-model="form.discountPrice" placeholder="请输入优惠金额,单位:元"
style="width: 400px" :precision="2" :min="0" />
</el-form-item>
<el-form-item v-if="form.discountType === PromotionDiscountTypeEnum.PERCENT.type" label="优惠券折扣" prop="discountPercent">
<el-input-number v-model="form.discountPercent" placeholder="优惠券折扣不能小于 1 折,且不可大于 9.9 折"
style="width: 400px" :precision="1" :min="1" :max="9.9" />
</el-form-item>
<el-form-item v-if="form.discountType === PromotionDiscountTypeEnum.PERCENT.type" label="最多优惠" prop="discountLimitPrice">
<el-input-number v-model="form.discountLimitPrice" placeholder="请输入最多优惠"
style="width: 400px" :precision="2" :min="0" />
</el-form-item>
<el-form-item label="满多少元可以使用" prop="usePrice">
<el-input-number v-model="form.usePrice" placeholder="无门槛请设为 0"
style="width: 400px" :precision="2" :min="0" />
</el-form-item>
<el-form-item label="领取方式" prop="takeType">
<el-radio-group v-model="form.takeType">
<el-radio :key="1" :label="1">直接领取</el-radio>
<el-radio :key="2" :label="2">指定发放</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item v-if="form.takeType === 1" label="发放数量" prop="totalCount">
<el-input-number v-model="form.totalCount" placeholder="发放数量,没有之后不能领取或发放,-1 为不限制"
style="width: 400px" :precision="0" :min="-1" />
</el-form-item>
<el-form-item v-if="form.takeType === 1" label="每人限领个数" prop="takeLimitCount">
<el-input-number v-model="form.takeLimitCount" placeholder="设置为 -1 时,可无限领取"
style="width: 400px" :precision="0" :min="-1" />
</el-form-item>
<el-form-item label="有效期类型" prop="validityType">
<el-radio-group v-model="form.validityType">
<el-radio v-for="dict in this.getDictDatas(DICT_TYPE.PROMOTION_COUPON_TEMPLATE_VALIDITY_TYPE)"
:key="dict.value" :label="parseInt(dict.value)">{{dict.label}}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item v-if="form.validityType === CouponTemplateValidityTypeEnum.DATE.type" label="固定日期" prop="validTimes">
<el-date-picker v-model="form.validTimes" style="width: 240px" value-format="yyyy-MM-dd HH:mm:ss" type="datetimerange"
:default-time="['00:00:00', '23:59:59']" />
</el-form-item>
<el-form-item v-if="form.validityType === CouponTemplateValidityTypeEnum.TERM.type" label="领取日期" prop="fixedStartTerm">
<el-input-number v-model="form.fixedStartTerm" placeholder="0 为今天生效"
style="width: 165px" :precision="0" :min="0"/>
<el-input-number v-model="form.fixedEndTerm" placeholder="请输入结束天数"
style="width: 165px" :precision="0" :min="0"/> 天有效
</el-form-item>
<el-form-item label="活动商品" prop="productScope">
<el-radio-group v-model="form.productScope">
<el-radio v-for="dict in this.getDictDatas(DICT_TYPE.PROMOTION_PRODUCT_SCOPE)"
:key="dict.value" :label="parseInt(dict.value)">{{dict.label}}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item v-if="form.productScope === PromotionProductScopeEnum.SPU.scope" prop="productSpuIds">
<el-select v-model="form.productSpuIds" placeholder="请选择活动商品" clearable size="small"
multiple filterable style="width: 400px">
<el-option v-for="item in productSpus" :key="item.id" :label="item.name" :value="item.id">
<span style="float: left">{{ item.name }}</span>
<span style="float: right; color: #8492a6; font-size: 13px">{{ (item.minPrice / 100.0).toFixed(2) }}</span>
</el-option>
</el-select>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import {
createCouponTemplate,
updateCouponTemplate,
deleteCouponTemplate,
getCouponTemplate,
getCouponTemplatePage,
updateCouponTemplateStatus
} from "@/api/mall/promotion/couponTemplate";
import {
CommonStatusEnum,
CouponTemplateValidityTypeEnum,
PromotionDiscountTypeEnum,
PromotionProductScopeEnum
} from "@/utils/constants";
import { getSpuSimpleList } from "@/api/mall/product/spu";
import { parseTime } from "@/utils/ruoyi";
import {changeRoleStatus} from "@/api/system/role";
export default {
name: "CouponTemplate",
components: {
},
data() {
return {
// 遮罩层
loading: true,
// 导出遮罩层
exportLoading: false,
// 显示搜索条件
showSearch: true,
// 总条数
total: 0,
// 优惠劵列表
list: [],
// 弹出层标题
title: "",
// 是否显示弹出层
open: false,
// 查询参数
queryParams: {
pageNo: 1,
pageSize: 10,
name: null,
status: null,
type: null,
createTime: [],
},
// 表单参数
form: {},
// 表单校验
rules: {
name: [{ required: true, message: "优惠券名称不能为空", trigger: "blur" }],
discountType: [{ required: true, message: "优惠券类型不能为空", trigger: "change" }],
discountPrice: [{ required: true, message: "优惠券面额不能为空", trigger: "blur" }],
discountPercent: [{ required: true, message: "优惠券折扣不能为空", trigger: "blur" }],
discountLimitPrice: [{ required: true, message: "最多优惠不能为空", trigger: "blur" }],
usePrice: [{ required: true, message: "满多少元可以使用不能为空", trigger: "blur" }],
takeType: [{ required: true, message: "领取方式不能为空", trigger: "change" }],
totalCount: [{ required: true, message: "发放数量不能为空", trigger: "blur" }],
takeLimitCount: [{ required: true, message: "每人限领个数不能为空", trigger: "blur" }],
validityType: [{ required: true, message: "有效期类型不能为空", trigger: "change" }],
validTimes: [{ required: true, message: "固定日期不能为空", trigger: "change" }],
fixedStartTerm: [{ required: true, message: "开始领取天数不能为空", trigger: "blur" }],
fixedEndTerm: [{ required: true, message: "开始领取天数不能为空", trigger: "blur" }],
productScope: [{ required: true, message: "商品范围不能为空", trigger: "blur" }],
productSpuIds: [{ required: true, message: "商品范围不能为空", trigger: "blur" }],
},
// 商品列表
productSpus: [],
// 如下的变量,主要为了 v-if 判断可以使用到
PromotionProductScopeEnum: PromotionProductScopeEnum,
CouponTemplateValidityTypeEnum: CouponTemplateValidityTypeEnum,
PromotionDiscountTypeEnum: PromotionDiscountTypeEnum,
};
},
created() {
this.getList();
},
methods: {
/** 查询列表 */
getList() {
this.loading = true;
// 执行查询
getCouponTemplatePage(this.queryParams).then(response => {
this.list = response.data.list;
this.total = response.data.total;
this.loading = false;
});
// 查询商品列表
getSpuSimpleList().then(response => {
this.productSpus = response.data
})
},
/** 取消按钮 */
cancel() {
this.open = false;
this.reset();
},
/** 表单重置 */
reset() {
this.form = {
id: undefined,
name: undefined,
discountType: PromotionDiscountTypeEnum.PRICE.type,
discountPrice: undefined,
discountPercent: undefined,
discountLimitPrice: undefined,
usePrice: undefined,
takeType: 1,
totalCount: undefined,
takeLimitCount: undefined,
validityType: CouponTemplateValidityTypeEnum.DATE.type,
validTimes: [],
validStartTime: undefined,
validEndTime: undefined,
fixedStartTerm: undefined,
fixedEndTerm: undefined,
productScope: PromotionProductScopeEnum.ALL.scope,
productSpuIds: [],
};
this.resetForm("form");
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNo = 1;
this.getList();
},
/** 重置按钮操作 */
resetQuery() {
this.resetForm("queryForm");
this.handleQuery();
},
/** 新增按钮操作 */
handleAdd() {
this.reset();
this.open = true;
this.title = "添加优惠劵";
},
/** 修改按钮操作 */
handleUpdate(row) {
this.reset();
const id = row.id;
getCouponTemplate(id).then(response => {
this.form = {
...response.data,
discountPrice: response.data.discountPrice !== undefined ? response.data.discountPrice / 100.0 : undefined,
discountPercent: response.data.discountPercent !== undefined ? response.data.discountPercent / 10.0 : undefined,
discountLimitPrice: response.data.discountLimitPrice !== undefined ? response.data.discountLimitPrice / 100.0 : undefined,
usePrice: response.data.usePrice !== undefined ? response.data.usePrice / 100.0 : undefined,
validTimes: [response.data.validStartTime, response.data.validEndTime]
}
this.open = true;
this.title = "修改优惠劵";
});
},
/** 提交按钮 */
submitForm() {
this.$refs["form"].validate(valid => {
if (!valid) {
return;
}
// 金额相关字段的缩放
let data = {
...this.form,
discountPrice: this.form.discountPrice !== undefined ? this.form.discountPrice * 100 : undefined,
discountPercent: this.form.discountPercent !== undefined ? this.form.discountPercent * 10 : undefined,
discountLimitPrice: this.form.discountLimitPrice !== undefined ? this.form.discountLimitPrice * 100 : undefined,
usePrice: this.form.usePrice !== undefined ? this.form.usePrice * 100 : undefined,
validStartTime: this.form.validTimes && this.form.validTimes.length === 2 ? this.form.validTimes[0] : undefined,
validEndTime: this.form.validTimes && this.form.validTimes.length === 2 ? this.form.validTimes[1] : undefined,
}
// 修改的提交
if (this.form.id != null) {
updateCouponTemplate(data).then(response => {
this.$modal.msgSuccess("修改成功");
this.open = false;
this.getList();
});
return;
}
// 添加的提交
createCouponTemplate(data).then(response => {
this.$modal.msgSuccess("新增成功");
this.open = false;
this.getList();
});
});
},
/** 优惠劵模板状态修改 */
handleStatusChange(row) {
// 此时row 已经变成目标状态了,所以可以直接提交请求和提示
let text = row.status === CommonStatusEnum.ENABLE ? "启用" : "停用";
this.$modal.confirm('确认要"' + text + '""' + row.name + '"优惠劵吗?').then(function() {
return updateCouponTemplateStatus(row.id, row.status);
}).then(() => {
this.$modal.msgSuccess(text + "成功");
}).catch(function() {
// 异常时,需要将 row.status 状态重置回之前的
row.status = row.status === CommonStatusEnum.ENABLE ? CommonStatusEnum.DISABLE
: CommonStatusEnum.ENABLE;
});
},
/** 删除按钮操作 */
handleDelete(row) {
const id = row.id;
this.$modal.confirm('是否确认删除优惠劵编号为"' + id + '"的数据项?').then(function() {
return deleteCouponTemplate(id);
}).then(() => {
this.getList();
this.$modal.msgSuccess("删除成功");
}).catch(() => {});
},
// 格式化【优惠金额/折扣】
discountFormat(row, column) {
if (row.discountType === PromotionDiscountTypeEnum.PRICE.type) {
return `${(row.discountPrice / 100.0).toFixed(2)}`;
}
if (row.discountType === PromotionDiscountTypeEnum.PERCENT.type) {
return `${(row.discountPrice / 100.0).toFixed(2)}`;
}
return '未知【' + row.discountType + '】';
},
// 格式化【领取上限】
takeLimitCountFormat(row, column) {
if (row.takeLimitCount === -1) {
return '无领取限制';
}
return `${row.takeLimitCount} 张/人`
},
// 格式化【有效期限】
validityTypeFormat(row, column) {
if (row.validityType === CouponTemplateValidityTypeEnum.DATE.type) {
return `${parseTime(row.validStartTime)}${parseTime(row.validEndTime)}`
}
if (row.validityType === CouponTemplateValidityTypeEnum.TERM.type) {
return `领取后第 ${row.fixedStartTerm} - ${row.fixedEndTerm} 天内可用`
}
return '未知【' + row.validityType + '】';
}
}
};
</script>

View File

@@ -0,0 +1,382 @@
<template>
<div class="app-container">
<!-- 搜索工作栏 -->
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="活动名称" prop="name">
<el-input v-model="queryParams.name" placeholder="请输入活动名称" clearable @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="活动状态" prop="status">
<el-select v-model="queryParams.status" placeholder="请选择活动状态" clearable size="small">
<el-option v-for="dict in this.getDictDatas(DICT_TYPE.PROMOTION_ACTIVITY_STATUS)"
:key="dict.value" :label="dict.label" :value="dict.value"/>
</el-select>
</el-form-item>
<el-form-item label="创建时间" prop="createTime">
<el-date-picker v-model="queryParams.createTime" style="width: 240px" value-format="yyyy-MM-dd HH:mm:ss" type="daterange"
range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期" :default-time="['00:00:00', '23:59:59']" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<!-- 操作工具栏 -->
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" plain icon="el-icon-plus" size="mini" @click="handleAdd"
v-hasPermi="['promotion:discount-activity:create']">新增</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<!-- 列表 -->
<el-table v-loading="loading" :data="list">
<el-table-column label="活动名称" align="center" prop="name" />
<el-table-column label="活动时间" align="center" prop="startTime" width="240">
<template slot-scope="scope">
<div>开始{{ parseTime(scope.row.startTime) }}</div>
<div>结束{{ parseTime(scope.row.endTime) }}</div>
</template>
</el-table-column>
<el-table-column label="状态" align="center" prop="status">
<template slot-scope="scope">
<dict-tag :type="DICT_TYPE.PROMOTION_ACTIVITY_STATUS" :value="scope.row.status" />
</template>
</el-table-column>
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template slot-scope="scope">
<el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)"
v-if="scope.row.status !== PromotionActivityStatusEnum.CLOSE.type"
v-hasPermi="['promotion:discount-activity:update']">修改</el-button>
<el-button size="mini" type="text" icon="el-icon-delete" @click="handleClose(scope.row)"
v-if="scope.row.status !== PromotionActivityStatusEnum.CLOSE.type &&
scope.row.status !== PromotionActivityStatusEnum.END.type"
v-hasPermi="['promotion:discount-activity:close']">关闭</el-button>
<el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)"
v-if="scope.row.status === PromotionActivityStatusEnum.CLOSE.type"
v-hasPermi="['promotion:discount-activity:delete']">删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页组件 -->
<pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNo" :limit.sync="queryParams.pageSize"
@pagination="getList"/>
<!-- 对话框(添加 / 修改) -->
<el-dialog :title="title" :visible.sync="open" width="1000px" v-dialogDrag append-to-body>
<el-form ref="form" :model="form" :rules="rules" label-width="80px">
<el-form-item label="活动名称" prop="name">
<el-input v-model="form.name" placeholder="请输入活动名称" />
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input type="textarea" v-model="form.remark" placeholder="请输入备注" />
</el-form-item>
<el-form-item label="活动时间" prop="startAndEndTime">
<el-date-picker clearable v-model="form.startAndEndTime" type="datetimerange" :default-time="['00:00:00', '23:59:59']"
value-format="timestamp" placeholder="选择开始时间" style="width: 880px" />
</el-form-item>
<el-form-item label="商品选择">
<el-select v-model="form.skuIds" placeholder="请选择活动商品" clearable size="small"
multiple filterable style="width: 880px" @change="changeFormSku">
<el-option v-for="item in productSkus" :key="item.id" :label="item.spuName + ' ' + item.name" :value="item.id">
<span style="float: left">{{ item.spuName }} &nbsp; {{ item.name}}</span>
<span style="float: right; color: #8492a6; font-size: 13px">¥{{ (item.price / 100.0).toFixed(2) }}</span>
</el-option>
</el-select>
<el-table v-loading="loading" :data="form.products">
<el-table-column label="商品名称" align="center" width="200">
<template slot-scope="scope">
{{ scope.row.spuName }} &nbsp; {{ scope.row.name}}
</template>
</el-table-column>
<el-table-column label="商品价格" align="center" prop="price">
<template slot-scope="scope">
¥{{ (scope.row.price / 100.0).toFixed(2) }}
</template>
</el-table-column>
<el-table-column label="库存" align="center" prop="stock" />
<el-table-column label="优惠类型" align="center" property="discountType">
<template slot-scope="scope">
<el-select v-model="scope.row.discountType" placeholder="请选择优惠类型">
<el-option v-for="dict in getDictDatas(DICT_TYPE.PROMOTION_DISCOUNT_TYPE)"
:key="dict.value" :label="dict.label" :value="parseInt(dict.value)"/>
</el-select>
</template>
</el-table-column>
<el-table-column label="优惠" align="center" prop="startTime" width="250">
<template slot-scope="scope">
<el-form-item v-if="scope.row.discountType === PromotionDiscountTypeEnum.PRICE.type" prop="discountPrice">
减 <el-input-number v-model="scope.row.discountPrice" placeholder="请输入优惠金额"
style="width: 190px" :precision="2" :min="0" :max="scope.row.price / 100.0 - 0.01" /> 元
</el-form-item>
<el-form-item v-if="scope.row.discountType === PromotionDiscountTypeEnum.PERCENT.type" prop="discountPercent">
打 <el-input-number v-model="scope.row.discountPercent" placeholder="请输入优惠折扣"
style="width: 190px" :precision="1" :min="1" :max="9.9" /> 折
</el-form-item>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template slot-scope="scope">
<el-button size="mini" type="text" icon="el-icon-delete" @click="removeFormSku(scope.row.skuId)">删除</el-button>
</template>
</el-table-column>
</el-table>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitForm">确 定</el-button>
<el-button @click="cancel">取 消</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import {
createDiscountActivity,
updateDiscountActivity,
deleteDiscountActivity,
getDiscountActivity,
getDiscountActivityPage,
closeDiscountActivity
} from "@/api/mall/promotion/discountActivity";
import {
PromotionActivityStatusEnum, PromotionDiscountTypeEnum,
PromotionProductScopeEnum
} from "@/utils/constants";
import { getSkuOptionList } from "@/api/mall/product/sku";
import { deepClone } from "@/utils";
export default {
name: "DiscountActivity",
components: {
},
data() {
return {
// 遮罩层
loading: true,
// 导出遮罩层
exportLoading: false,
// 显示搜索条件
showSearch: true,
// 总条数
total: 0,
// 限时折扣活动列表
list: [],
// 弹出层名称
title: "",
// 是否显示弹出层
open: false,
// 查询参数
queryParams: {
pageNo: 1,
pageSize: 10,
name: null,
status: null,
createTime: [],
},
// 表单参数
form: {
skuIds: [], // 选中的 SKU
products: [], // 商品信息
},
// 表单校验
rules: {
name: [{ required: true, message: "活动名称不能为空", trigger: "blur" }],
startAndEndTime: [{ required: true, message: "活动时间不能为空", trigger: "blur" }],
skuIds: [{ required: true, message: "选择商品不能为空", trigger: "blur" }],
},
// 商品 SKU 列表
productSkus: [],
// 如下的变量主要为了 v-if 判断可以使用到
PromotionProductScopeEnum: PromotionProductScopeEnum,
PromotionActivityStatusEnum: PromotionActivityStatusEnum,
PromotionDiscountTypeEnum: PromotionDiscountTypeEnum,
};
},
created() {
this.getList();
},
methods: {
/** 查询列表 */
getList() {
this.loading = true;
// 执行查询
getDiscountActivityPage(this.queryParams).then(response => {
this.list = response.data.list;
this.total = response.data.total;
this.loading = false;
});
// 获得 SKU 商品列表
getSkuOptionList().then(response => {
this.productSkus = response.data;
});
},
/** 取消按钮 */
cancel() {
this.open = false;
this.reset();
},
/** 表单重置 */
reset() {
this.form = {
id: undefined,
name: undefined,
startAndEndTime: undefined,
startTime: undefined,
endTime: undefined,
remark: undefined,
skuIds: [],
products: [],
};
this.resetForm("form");
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNo = 1;
this.getList();
},
/** 重置按钮操作 */
resetQuery() {
this.resetForm("queryForm");
this.handleQuery();
},
/** 新增按钮操作 */
handleAdd() {
this.reset();
this.open = true;
this.title = "添加限时折扣活动";
},
/** 修改按钮操作 */
handleUpdate(row) {
this.reset();
const id = row.id;
getDiscountActivity(id).then(response => {
this.form = response.data;
// 修改数据
this.form.startAndEndTime = [response.data.startTime, response.data.endTime];
this.form.skuIds = response.data.products.map(item => item.skuId);
this.form.products.forEach(product => {
// 获得对应的 SKU 信息
const sku = this.productSkus.find(item => item.id === product.skuId);
if (!sku) {
return;
}
// 设置商品信息
product.name = sku.name;
product.spuName = sku.spuName;
product.price = sku.price;
product.stock = sku.stock;
product.discountPrice = product.discountPrice !== undefined ? product.discountPrice / 100.0 : undefined;
product.discountPercent = product.discountPercent !== undefined ? product.discountPercent / 10.0 : undefined;
});
// 打开弹窗
this.open = true;
this.title = "修改限时折扣活动";
});
},
/** 提交按钮 */
submitForm() {
this.$refs["form"].validate(valid => {
if (!valid) {
return;
}
// 处理数据
const data = deepClone(this.form); // 必须深拷贝,不然后面的 products 操作会有影响
data.startTime = this.form.startAndEndTime[0];
data.endTime = this.form.startAndEndTime[1];
data.products.forEach(product => {
product.discountPrice = product.discountPrice !== undefined ? product.discountPrice * 100 : undefined;
product.discountPercent = product.discountPercent !== undefined ? product.discountPercent * 10 : undefined;
});
if (!valid) {
return;
}
// 修改的提交
if (this.form.id != null) {
updateDiscountActivity(data).then(response => {
this.$modal.msgSuccess("修改成功");
this.open = false;
this.getList();
});
return;
}
// 添加的提交
createDiscountActivity(data).then(response => {
this.$modal.msgSuccess("新增成功");
this.open = false;
this.getList();
});
});
},
/** 删除按钮操作 */
handleDelete(row) {
const id = row.id;
this.$modal.confirm('是否确认删除限时折扣活动编号为"' + id + '"的数据项?').then(function() {
return deleteDiscountActivity(id);
}).then(() => {
this.getList();
this.$modal.msgSuccess("删除成功");
}).catch(() => {});
},
/** 关闭按钮操作 */
handleClose(row) {
const id = row.id;
this.$modal.confirm('是否确认关闭限时折扣活动编号为"' + id + '"的数据项?').then(function() {
return closeDiscountActivity(id);
}).then(() => {
this.getList();
this.$modal.msgSuccess("关闭成功");
}).catch(() => {});
},
/** 当 Form 的 SKU 发生变化时 */
changeFormSku(skuIds) {
// 处理【新增】
skuIds.forEach(skuId => {
// 获得对应的 SKU 信息
const sku = this.productSkus.find(item => item.id === skuId);
if (!sku) {
return;
}
// 判断已存在,直接跳过
const product = this.form.products.find(item => item.skuId === skuId);
if (product) {
return;
}
this.form.products.push({
skuId: sku.id,
name: sku.name,
price: sku.price,
stock: sku.stock,
spuId: sku.spuId,
spuName: sku.spuName,
discountType: PromotionDiscountTypeEnum.PRICE.type,
});
});
// 处理【移除】
this.form.products.map((product, index) => {
if (!skuIds.includes(product.skuId)) {
this.form.products.splice(index, 1);
}
});
},
/** 移除 Form 的 SKU */
removeFormSku(skuId) {
this.form.skuIds.map((id, index) => {
if (skuId === id) {
this.form.skuIds.splice(index, 1);
}
});
this.changeFormSku(this.form.skuIds);
},
}
}
</script>

View File

@@ -0,0 +1,305 @@
<template>
<div class="app-container">
<!-- 搜索工作栏 -->
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="活动名称" prop="name">
<el-input v-model="queryParams.name" placeholder="请输入活动名称" clearable @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="活动状态" prop="status">
<el-select v-model="queryParams.status" placeholder="请选择活动状态" clearable size="small">
<el-option v-for="dict in this.getDictDatas(DICT_TYPE.PROMOTION_ACTIVITY_STATUS)"
:key="dict.value" :label="dict.label" :value="dict.value"/>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<!-- 操作工具栏 -->
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" plain icon="el-icon-plus" size="mini" @click="handleAdd"
v-hasPermi="['promotion:reward-activity:create']">新增</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<!-- 列表 -->
<el-table v-loading="loading" :data="list">
<el-table-column label="活动名称" align="center" prop="name" />
<el-table-column label="活动时间" align="center" prop="startTime" width="240">
<template slot-scope="scope">
<div>开始{{ parseTime(scope.row.startTime) }}</div>
<div>结束{{ parseTime(scope.row.endTime) }}</div>
</template>
</el-table-column>
<el-table-column label="状态" align="center" prop="status">
<template slot-scope="scope">
<dict-tag :type="DICT_TYPE.PROMOTION_ACTIVITY_STATUS" :value="scope.row.status" />
</template>
</el-table-column>
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template slot-scope="scope">
<el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)"
v-if="scope.row.status !== PromotionActivityStatusEnum.CLOSE.type"
v-hasPermi="['promotion:reward-activity:update']">修改</el-button>
<el-button size="mini" type="text" icon="el-icon-delete" @click="handleClose(scope.row)"
v-if="scope.row.status !== PromotionActivityStatusEnum.CLOSE.type &&
scope.row.status !== PromotionActivityStatusEnum.END.type"
v-hasPermi="['promotion:reward-activity:close']">关闭</el-button>
<el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)"
v-if="scope.row.status === PromotionActivityStatusEnum.CLOSE.type"
v-hasPermi="['promotion:reward-activity:delete']">删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页组件 -->
<pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNo" :limit.sync="queryParams.pageSize"
@pagination="getList"/>
<!-- 对话框(添加 / 修改) -->
<el-dialog :title="title" :visible.sync="open" width="600px" v-dialogDrag append-to-body>
<el-form ref="form" :model="form" :rules="rules" label-width="80px">
<el-form-item label="活动名称" prop="name">
<el-input v-model="form.name" placeholder="请输入活动名称" />
</el-form-item>
<el-form-item label="活动时间" prop="startAndEndTime">
<el-date-picker clearable v-model="form.startAndEndTime" type="datetimerange" :default-time="['00:00:00', '23:59:59']"
value-format="timestamp" placeholder="选择开始时间" style="width: 480px" />
</el-form-item>
<el-form-item label="条件类型" prop="conditionType">
<el-radio-group v-model="form.conditionType">
<el-radio v-for="dict in this.getDictDatas(DICT_TYPE.PROMOTION_CONDITION_TYPE)"
:key="dict.value" :label="parseInt(dict.value)">{{dict.label}}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="优惠设置" prop="conditionType">
<!-- TODO 芋艿:待实现! -->
</el-form-item>
<el-form-item label="活动商品" prop="productScope">
<el-radio-group v-model="form.productScope">
<el-radio v-for="dict in this.getDictDatas(DICT_TYPE.PROMOTION_PRODUCT_SCOPE)"
:key="dict.value" :label="parseInt(dict.value)">{{dict.label}}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item v-if="form.productScope === PromotionProductScopeEnum.SPU.scope" prop="productSpuIds">
<el-select v-model="form.productSpuIds" placeholder="请选择活动商品" clearable size="small"
multiple filterable style="width: 400px">
<el-option v-for="item in productSpus" :key="item.id" :label="item.name" :value="item.id">
<span style="float: left">{{ item.name }}</span>
<span style="float: right; color: #8492a6; font-size: 13px">¥{{ (item.minPrice / 100.0).toFixed(2) }}</span>
</el-option>
</el-select>
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" placeholder="请输入备注" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitForm">确 定</el-button>
<el-button @click="cancel">取 消</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import {
createRewardActivity,
updateRewardActivity,
deleteRewardActivity,
getRewardActivity,
getRewardActivityPage,
closeRewardActivity
} from "@/api/mall/promotion/rewardActivity";
import {
PromotionConditionTypeEnum,
PromotionProductScopeEnum,
PromotionActivityStatusEnum
} from "@/utils/constants";
import {getSpuSimpleList} from "@/api/mall/product/spu";
export default {
name: "RewardActivity",
components: {
},
data() {
return {
// 遮罩层
loading: true,
// 导出遮罩层
exportLoading: false,
// 显示搜索条件
showSearch: true,
// 总条数
total: 0,
// 满减送活动列表
list: [],
// 弹出层标题
title: "",
// 是否显示弹出层
open: false,
// 查询参数
queryParams: {
pageNo: 1,
pageSize: 10,
name: null,
status: null,
},
// 表单参数
form: {},
// 表单校验
rules: {
name: [{ required: true, message: "活动名称不能为空", trigger: "blur" }],
startAndEndTime: [{ required: true, message: "活动时间不能为空", trigger: "blur" }],
conditionType: [{ required: true, message: "条件类型不能为空", trigger: "change" }],
productScope: [{ required: true, message: "商品范围不能为空", trigger: "blur" }],
productSpuIds: [{ required: true, message: "商品范围不能为空", trigger: "blur" }],
},
// 商品列表
productSpus: [],
// 如下的变量主要为了 v-if 判断可以使用到
PromotionProductScopeEnum: PromotionProductScopeEnum,
PromotionActivityStatusEnum: PromotionActivityStatusEnum,
};
},
created() {
this.getList();
// 查询商品列表
getSpuSimpleList().then(response => {
this.productSpus = response.data
})
},
methods: {
/** 查询列表 */
getList() {
this.loading = true;
// 执行查询
getRewardActivityPage(this.queryParams).then(response => {
this.list = response.data.list;
this.total = response.data.total;
this.loading = false;
});
},
/** 取消按钮 */
cancel() {
this.open = false;
this.reset();
},
/** 表单重置 */
reset() {
this.form = {
id: undefined,
name: undefined,
startAndEndTime: undefined,
startTime: undefined,
endTime: undefined,
conditionType: PromotionConditionTypeEnum.PRICE.type,
remark: undefined,
productScope: PromotionProductScopeEnum.ALL.scope,
productSpuIds: undefined,
rules: undefined,
};
this.resetForm("form");
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNo = 1;
this.getList();
},
/** 重置按钮操作 */
resetQuery() {
this.resetForm("queryForm");
this.handleQuery();
},
/** 新增按钮操作 */
handleAdd() {
this.reset();
this.open = true;
this.title = "添加满减送活动";
},
/** 修改按钮操作 */
handleUpdate(row) {
this.reset();
const id = row.id;
getRewardActivity(id).then(response => {
this.form = response.data;
this.form.startAndEndTime = [response.data.startTime, response.data.endTime];
this.open = true;
this.title = "修改满减送活动";
});
},
/** 提交按钮 */
submitForm() {
this.$refs["form"].validate(valid => {
if (!valid) {
return;
}
this.form.startTime = this.form.startAndEndTime[0];
this.form.endTime = this.form.startAndEndTime[1];
// TODO 芋艿:临时实现
this.form.rules = [
{
limit: 1,
discountPrice: 10,
freeDelivery: true,
point: 10,
couponIds: [10, 20],
couponCounts: [1, 2]
}, {
limit: 2,
discountPrice: 20,
freeDelivery: false,
point: 20,
couponIds: [30, 40],
couponCounts: [3, 4]
}
];
// 修改的提交
if (this.form.id != null) {
updateRewardActivity(this.form).then(response => {
this.$modal.msgSuccess("修改成功");
this.open = false;
this.getList();
});
return;
}
// 添加的提交
createRewardActivity(this.form).then(response => {
this.$modal.msgSuccess("新增成功");
this.open = false;
this.getList();
});
});
},
/** 删除按钮操作 */
handleDelete(row) {
const id = row.id;
this.$modal.confirm('是否确认删除满减送活动编号为"' + id + '"的数据项?').then(function() {
return deleteRewardActivity(id);
}).then(() => {
this.getList();
this.$modal.msgSuccess("删除成功");
}).catch(() => {});
},
/** 关闭按钮操作 */
handleClose(row) {
const id = row.id;
this.$modal.confirm('是否确认关闭满减送活动编号为"' + id + '"的数据项?').then(function() {
return closeRewardActivity(id);
}).then(() => {
this.getList();
this.$modal.msgSuccess("关闭成功");
}).catch(() => {});
}
}
};
</script>

View File

@@ -0,0 +1,490 @@
<template>
<div class="app-container">
<!-- 搜索工作栏 -->
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch"
label-width="68px">
<el-form-item label="活动名称" prop="name">
<el-input v-model="queryParams.name" placeholder="请输入秒杀活动名称" clearable
@keyup.enter.native="handleQuery" />
</el-form-item>
<el-form-item label="活动状态" prop="status">
<el-select v-model="queryParams.status" placeholder="请选择活动状态" clearable size="small">
<el-option v-for="dict in this.getDictDatas(DICT_TYPE.PROMOTION_ACTIVITY_STATUS)" :key="dict.value"
:label="dict.label" :value="dict.value" />
</el-select>
</el-form-item>
<el-form-item label="参与场次" prop="timeId">
<el-select v-model="queryParams.timeId" placeholder="请选择参与场次" clearable size="small">
<el-option v-for="item in seckillTimeList" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</el-form-item>
<el-form-item label="创建时间" prop="createTime">
<el-date-picker v-model="queryParams.createTime" style="width: 240px" value-format="yyyy-MM-dd HH:mm:ss"
type="daterange" range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期"
:default-time="['00:00:00', '23:59:59']" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<!-- 操作工具栏 -->
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" plain icon="el-icon-plus" size="mini" @click="handleAdd"
v-hasPermi="['promotion:seckill-activity:create']">新增秒杀活动</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="primary" plain icon="el-icon-menu" size="mini" @click="openSeckillTime"
v-hasPermi="['promotion:seckill-activity:create']">管理参与场次</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<!-- 列表 -->
<el-table v-loading="loading" :data="list">
<el-table-column label="活动名称" align="center" prop="name" />
<el-table-column label="活动状态" align="center" prop="status">
<template slot-scope="scope">
<dict-tag :type="DICT_TYPE.PROMOTION_ACTIVITY_STATUS" :value="scope.row.status" />
</template>
</el-table-column>
<el-table-column label="参与场次" prop="timeIds" width="250">
<template slot-scope="scope">
<span v-for="item in seckillTimeList" :key="item.id"
v-if="scope.row.timeIds.includes(item.id)">
<el-tag style="margin:4px;" size="small">{{ item.name }}</el-tag>
</span>
</template>
</el-table-column>
<el-table-column label="活动开始时间" align="center" prop="startTime" width="190">
<template slot-scope="scope">
<span>{{ "开始: " + parseTime(scope.row.startTime) }}</span>
<span>{{ "结束: " + parseTime(scope.row.endTime) }}</span>
</template>
</el-table-column>
<el-table-column label="付款订单数" align="center" prop="orderCount" />
<el-table-column label="付款人数" align="center" prop="userCount" />
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template slot-scope="scope">
<el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)"
v-hasPermi="['promotion:seckill-activity:update']">修改</el-button>
<el-button size="mini" type="text" icon="el-icon-close" @click="handleClose(scope.row)"
v-hasPermi="['promotion:seckill-activity:delete']">关闭</el-button>
<el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)"
v-hasPermi="['promotion:seckill-activity:delete']">删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页组件 -->
<pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNo" :limit.sync="queryParams.pageSize"
@pagination="getList" />
<!-- 对话框(添加 / 修改) -->
<el-dialog :title="title" :visible.sync="open" width="1200px" v-dialogDrag append-to-body>
<el-form ref="form" :model="form" :rules="rules" label-width="80px">
<el-form-item label="活动名称" prop="name">
<el-input v-model="form.name" placeholder="请输入秒杀活动名称" />
</el-form-item>
<el-form-item label="活动时间" prop="startAndEndTime">
<el-date-picker clearable v-model="form.startAndEndTime" type="datetimerange"
value-format="timestamp" range-separator="" start-placeholder="开始日期" end-placeholder="结束日期"
style="width: 1080px" />
</el-form-item>
<el-form-item label="排序" prop="sort">
<el-input-number v-model="form.sort" controls-position="right" :min="0" :max="10000">
</el-input-number>
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input type="textarea" v-model="form.remark" placeholder="请输入备注" />
</el-form-item>
<el-form-item label="场次选择">
<el-select v-model="form.timeIds" placeholder="请选择参与场次" clearable size="small" multiple filterable
style="width: 880px">
<el-option v-for="item in seckillTimeList" :key="item.id" :label="item.name" :value="item.id">
<span style="float: left">{{ item.name + ': { ' }} {{ item.startTime }} -- {{ item.endTime +
' }'
}}</span>
<span style="float: right; color: #8492a6; font-size: 13px"></span>
</el-option>
</el-select>
</el-form-item>
<el-form-item label="商品选择">
<el-select v-model="form.skuIds" placeholder="请选择活动商品" clearable size="small" multiple filterable
style="width: 880px" @change="changeFormSku">
<el-option v-for="item in productSkus" :key="item.id" :label="item.spuName + ' ' + item.name"
:value="item.id">
<span style="float: left">{{ item.spuName }} &nbsp; {{ item.name }}</span>
<span style="float: right; color: #8492a6; font-size: 13px">{{ (item.price /
100.0).toFixed(2)
}}</span>
</el-option>
</el-select>
<el-row>
<el-button type="primary" size="mini" @click="batchEditProduct('limitBuyCount')">限购</el-button>
<el-button type="primary" size="mini" @click="batchEditProduct('seckillPrice')">秒杀价</el-button>
<el-button type="primary" size="mini" @click="batchEditProduct('seckillStock')">秒杀库存</el-button>
</el-row>
<el-table v-loading="loading" ref="productsTable" :data="form.products">
<el-table-column type="selection" width="55">
</el-table-column>
<el-table-column label="商品名称" align="center" width="200">
<template slot-scope="scope">
{{ scope.row.spuName }} &nbsp; {{ scope.row.name }}
</template>
</el-table-column>
<el-table-column label="商品价格" align="center" prop="price">
<template slot-scope="scope">
{{ (scope.row.price / 100.0).toFixed(2) }}
</template>
</el-table-column>
<el-table-column label="库存" align="center" prop="productStock" />
<el-table-column label="限购(0为不限购)" align="center" width="150">
<template slot-scope="scope">
<el-input-number v-model="scope.row.limitBuyCount" size="mini" :min="0" :max="10000">
</el-input-number>
</template>
</el-table-column>
<el-table-column label="秒杀价(元)" align="center" width="150">
<template slot-scope="scope">
<el-input-number v-model="scope.row.seckillPrice" size="mini" :precision="2" :min="0"
:max="10000">
</el-input-number>
</template>
</el-table-column>
<el-table-column label="秒杀库存" align="center" width="150" prop="seckillStock">
<template slot-scope="scope">
<el-input-number v-model="scope.row.seckillStock" size="mini" :min="0" :max="10000">
</el-input-number>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template slot-scope="scope">
<el-button size="mini" type="text" icon="el-icon-delete"
@click="removeFormSku(scope.row.skuId)">删除
</el-button>
</template>
</el-table-column>
</el-table>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { getSkuOptionList } from "@/api/mall/product/sku";
import { createSeckillActivity, updateSeckillActivity, closeSeckillActivity, deleteSeckillActivity, getSeckillActivity, getSeckillActivityPage, exportSeckillActivityExcel } from "@/api/mall/promotion/seckillActivity";
import { getSeckillTimeList } from "@/api/mall/promotion/seckillTime";
import { deepClone } from "@/utils";
export default {
name: "SeckillActivity",
components: {
},
data() {
return {
// 遮罩层
loading: true,
// 显示搜索条件
showSearch: true,
// 总条数
total: 0,
// 秒杀活动列表
list: [],
// 秒杀场次列表
seckillTimeList: [],
// 弹出层标题
title: "",
// 是否显示弹出层
open: false,
// 查询参数
queryParams: {
pageNo: 1,
pageSize: 10,
name: null,
status: null,
timeId: null,
createTime: [],
},
// 表单参数
form: {
skuIds: [], // 选中的 SKU
products: [], // 商品信息
timeIds: [], //选中的秒杀场次id
},
// 商品 SKU 列表
productSkus: [],
// 表单校验
rules: {
name: [{ required: true, message: "秒杀活动名称不能为空", trigger: "blur" }],
status: [{ required: true, message: "活动状态不能为空", trigger: "blur" }],
startAndEndTime: [{ required: true, message: "活动时间不能为空", trigger: "blur" }],
sort: [{ required: true, message: "排序不能为空", trigger: "blur" }],
timeIds: [{ required: true, message: "秒杀场次不能为空", trigger: "blur" }],
totalPrice: [{ required: true, message: "订单实付金额,单位:分不能为空", trigger: "blur" }],
}
};
},
created() {
this.getList();
},
watch: {
$route: 'getList'
},
methods: {
/** 查询列表 */
getList() {
// 从秒杀时段跳转过来并鞋带timeId参数进行查询
const timeId = this.$route.params && this.$route.params.timeId;
if (timeId) {
this.queryParams.timeId = timeId
}
this.loading = true;
// 执行查询
getSeckillActivityPage(this.queryParams).then(response => {
console.log(response.data.list, "查询出的值");
this.list = response.data.list;
this.total = response.data.total;
this.loading = false;
});
if (timeId) {
//查询完成后设置为空
this.$route.params.timeId = undefined
}
// 获得 SKU 商品列表
getSkuOptionList().then(response => {
this.productSkus = response.data;
});
// 获取参与场次列表
getSeckillTimeList().then(response => {
this.seckillTimeList = response.data;
});
},
/** 取消按钮 */
cancel() {
this.open = false;
this.reset();
},
/** 表单重置 */
reset() {
this.form = {
id: undefined,
name: undefined,
status: undefined,
remark: undefined,
startTime: undefined,
endTime: undefined,
sort: undefined,
timeIds: [],
totalPrice: undefined,
skuIds: [],
products: [],
};
this.resetForm("form");
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNo = 1;
this.getList();
},
/** 重置按钮操作 */
resetQuery() {
this.resetForm("queryForm");
this.handleQuery();
},
/**打开秒杀场次管理页面 */
openSeckillTime() {
this.$tab.openPage("秒杀场次管理", "/promotion/seckill-time");
},
/** 新增按钮操作 */
handleAdd() {
this.reset();
this.open = true;
this.title = "添加秒杀活动";
},
/** 修改按钮操作 */
handleUpdate(row) {
this.reset();
const id = row.id;
getSeckillActivity(id).then(response => {
this.form = response.data;
// 修改数据
this.form.startAndEndTime = [response.data.startTime, response.data.endTime];
this.form.skuIds = response.data.products.map(item => item.skuId);
this.form.products.forEach(product => {
// 获得对应的 SKU 信息
const sku = this.productSkus.find(item => item.id === product.skuId);
if (!sku) {
return;
}
// 设置商品信息
product.name = sku.name;
product.spuName = sku.spuName;
product.price = sku.price;
product.productStock = sku.stock;
this.$set(product, 'seckillStock', product.stock);
product.seckillPrice = product.seckillPrice !== undefined ? product.seckillPrice / 100 : undefined;
});
// 打开弹窗
this.open = true;
this.title = "修改限时折扣活动";
})
},
/** 提交按钮 */
submitForm() {
this.$refs["form"].validate(valid => {
if (!valid) {
return;
}
// 处理数据
const data = deepClone(this.form);
data.startTime = this.form.startAndEndTime[0];
data.endTime = this.form.startAndEndTime[1];
data.products.forEach(product => {
product.stock = product.seckillStock;
product.seckillPrice = product.seckillPrice !== undefined ? product.seckillPrice * 100 : undefined;
});
// 修改的提交
if (this.form.id != null) {
updateSeckillActivity(data).then(response => {
this.$modal.msgSuccess("修改成功");
this.open = false;
this.getList();
});
return;
}
// 添加的提交
createSeckillActivity(data).then(response => {
this.$modal.msgSuccess("新增成功");
this.open = false;
this.getList();
});
});
},
/** 关闭按钮操作 */
handleClose(row) {
const id = row.id;
this.$modal.confirm('是否确认关闭秒杀活动编号为"' + id + '"的数据项?').then(function () {
return closeSeckillActivity(id);
}).then(() => {
this.getList();
this.$modal.msgSuccess("关闭成功");
}).catch(() => { });
},
/** 删除按钮操作 */
handleDelete(row) {
const id = row.id;
this.$modal.confirm('是否确认删除秒杀活动编号为"' + id + '"的数据项?').then(function () {
return deleteSeckillActivity(id);
}).then(() => {
this.getList();
this.$modal.msgSuccess("删除成功");
}).catch(() => { });
},
/** 批量修改商品秒杀价,秒杀库存,每人限购数量 */
batchEditProduct(editType) {
const selectProducts = this.$refs.productsTable.selection;
if (selectProducts.length === 0) {
this.$modal.msgError("请选择需要修改的商品");
return;
}
let promptTitle = '请输入';
let regularPattern = /^[\s\S]*.*[^\s][\s\S]*$/; // 判断非空,且非空格
//限购数
if (editType === 'limitBuyCount') {
promptTitle = '限购数';
regularPattern = /^[0-9]*$/; //数字
}
//秒杀价
if (editType === 'seckillPrice') {
promptTitle = '秒杀价(元)';
regularPattern = /^[0-9]+(\.[0-9]{1,2})?$/; // 有一位或两位小数的正数
}
//秒杀库存
if (editType === 'seckillStock') {
promptTitle = '秒杀库存';
regularPattern = /^[0-9]*$/; //数字
}
this.$prompt(promptTitle, '提示', {
confirmButtonText: '保存',
cancelButtonText: '取消',
inputPattern: regularPattern,
inputErrorMessage: promptTitle + '格式不正确'
}).then(({ value }) => {
if (editType === 'limitBuyCount') {
selectProducts.forEach((item) => {
item.limitBuyCount = value;
})
}
if (editType === 'seckillPrice') {
selectProducts.forEach((item) => {
item.seckillPrice = value;
})
}
if (editType === 'seckillStock') {
selectProducts.forEach((item) => {
item.seckillStock = value;
})
}
}).catch();
},
/** 当 Form 的 SKU 发生变化时 */
changeFormSku(skuIds) {
// 处理【新增】
skuIds.forEach(skuId => {
// 获得对应的 SKU 信息
const sku = this.productSkus.find(item => item.id === skuId);
if (!sku) {
return;
}
// 判断已存在,直接跳过
const product = this.form.products.find(item => item.skuId === skuId);
if (product) {
return;
}
this.form.products.push({
skuId: sku.id,
name: sku.name,
price: sku.price,
productStock: sku.stock,
spuId: sku.spuId,
spuName: sku.spuName,
limitBuyCount: 1,
seckillStock: sku.stock,
seckillPrice: sku.price,
});
});
// 处理【移除】
this.form.products.map((product, index) => {
if (!skuIds.includes(product.skuId)) {
this.form.products.splice(index, 1);
}
});
},
/** 移除 Form 的 SKU */
removeFormSku(skuId) {
this.form.skuIds.map((id, index) => {
if (skuId === id) {
this.form.skuIds.splice(index, 1);
}
});
this.changeFormSku(this.form.skuIds);
},
}
};
</script>

View File

@@ -0,0 +1,197 @@
<template>
<div class="app-container">
<!-- 操作工具栏 -->
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" plain icon="el-icon-plus" size="mini" @click="handleAdd"
v-hasPermi="['promotion:seckill-time:create']">新增秒杀时段</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<!-- 列表 -->
<el-table v-loading="loading" :data="list">
<el-table-column label="秒杀时段名称" align="center" prop="name" />
<el-table-column label="开始时间点" align="center" prop="startTime" width="180">
<template slot-scope="scope">
<span>{{ scope.row.startTime }}</span>
</template>
</el-table-column>
<el-table-column label="结束时间点" align="center" prop="endTime" width="180">
<template slot-scope="scope">
<span>{{ scope.row.endTime }}</span>
</template>
</el-table-column>
<el-table-column label="秒杀活动数量" align="center" prop="seckillActivityCount" />
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template slot-scope="scope">
<el-button size="mini" type="text" icon="el-icon-view" @click="handleOpenSeckillActivity(scope.row)">
查看秒杀活动</el-button>
<el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)"
v-hasPermi="['promotion:seckill-time:update']">修改</el-button>
<el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)"
v-hasPermi="['promotion:seckill-time:delete']">删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 对话框(添加 / 修改) -->
<el-dialog :title="title" :visible.sync="open" width="600px" v-dialogDrag append-to-body>
<el-form ref="form" :model="form" :rules="rules" label-width="140px">
<el-form-item label="秒杀场次名称" prop="name">
<el-input v-model="form.name" placeholder="请输入秒杀时段名称" clearable />
</el-form-item>
<el-form-item label="秒杀时间段" prop="startAndEndTime">
<el-time-picker is-range v-model="form.startAndEndTime" range-separator="至" start-placeholder="开始时间"
end-placeholder="结束时间" placeholder="选择时间范围" value-format="HH:mm:ss">
</el-time-picker>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { createSeckillTime, updateSeckillTime, deleteSeckillTime, getSeckillTime, getSeckillTimePage, exportSeckillTimeExcel, getSeckillTimeList } from "@/api/mall/promotion/seckillTime";
import router from "@/router";
import { deepClone } from "@/utils";
export default {
name: "SeckillTime",
components: {
},
data() {
return {
// 遮罩层
loading: true,
// 导出遮罩层
exportLoading: false,
// 显示搜索条件
showSearch: true,
// 总条数
// total: 0,
// 秒杀时段列表
list: [],
// 弹出层标题
title: "",
// 是否显示弹出层
open: false,
// 表单参数
form: {},
// 表单校验
rules: {
name: [{ required: true, message: "秒杀时段名称不能为空", trigger: "blur" }],
startAndEndTime: [{ required: true, message: "秒杀时间段不能为空", trigger: "blur" }],
}
};
},
created() {
this.getList();
},
methods: {
/** 查询列表 */
getList() {
this.loading = true;
// 执行查询
getSeckillTimeList().then(response => {
this.list = response.data;
this.loading = false;
});
},
/** 取消按钮 */
cancel() {
this.open = false;
this.reset();
},
/** 表单重置 */
reset() {
this.form = {
id: undefined,
name: undefined,
startAndEndTime: undefined,
startTime: undefined,
endTime: undefined,
};
this.resetForm("form");
},
/** 搜索按钮操作 */
handleQuery() {
this.getList();
},
/** 重置按钮操作 */
resetQuery() {
this.resetForm("queryForm");
this.handleQuery();
},
/**查看当前秒杀时段的秒杀活动 */
handleOpenSeckillActivity(row) {
router.push({ name: 'SeckillActivity', params: { timeId: row.id } })
},
/** 新增按钮操作 */
handleAdd() {
this.reset();
this.open = true;
this.title = "添加秒杀时段";
},
/** 修改按钮操作 */
handleUpdate(row) {
this.reset();
const id = row.id;
getSeckillTime(id).then(response => {
response.data.startAndEndTime = [response.data.startTime, response.data.endTime]
this.form = response.data;
this.open = true;
this.title = "修改秒杀时段";
});
},
/** 提交按钮 */
submitForm() {
this.$refs["form"].validate(valid => {
console.log(valid, "是否通过");
if (!valid) {
return;
}
// 处理数据
const data = deepClone(this.form);
data.startTime = this.form.startAndEndTime[0];
data.endTime = this.form.startAndEndTime[1];
// 修改的提交
if (this.form.id != null) {
updateSeckillTime(data).then(response => {
this.$modal.msgSuccess("修改成功");
this.open = false;
this.getList();
});
return;
}
// 添加的提交
createSeckillTime(data).then(response => {
this.$modal.msgSuccess("新增成功");
this.open = false;
this.getList();
});
});
},
/** 删除按钮操作 */
handleDelete(row) {
const id = row.id;
this.$modal.confirm('是否确认删除秒杀时段编号为"' + id + '"的数据项?').then(function () {
return deleteSeckillTime(id);
}).then(() => {
this.getList();
this.$modal.msgSuccess("删除成功");
}).catch(() => { });
},
}
};
</script>

View File

@@ -0,0 +1,228 @@
<template>
<div class="app-container">
<!-- 搜索工作栏 -->
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="商品名称" prop="spuName">
<el-input v-model="queryParams.spuName" placeholder="请输入商品 SPU 名称" clearable @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="退款编号" prop="no">
<el-input v-model="queryParams.no" placeholder="请输入退款编号" clearable @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="订单编号" prop="orderNo">
<el-input v-model="queryParams.orderNo" placeholder="请输入订单编号" clearable @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="售后状态" prop="status">
<el-select v-model="queryParams.status" placeholder="请选择售后状态" clearable size="small">
<el-option v-for="dict in this.getDictDatas(DICT_TYPE.TRADE_AFTER_SALE_STATUS)"
:key="dict.value" :label="dict.label" :value="dict.value"/>
</el-select>
</el-form-item>
<el-form-item label="售后方式" prop="way">
<el-select v-model="queryParams.way" placeholder="请选择售后方式" clearable size="small">
<el-option v-for="dict in this.getDictDatas(DICT_TYPE.TRADE_AFTER_SALE_WAY)"
:key="dict.value" :label="dict.label" :value="dict.value"/>
</el-select>
</el-form-item>
<el-form-item label="售后类型" prop="type">
<el-select v-model="queryParams.type" placeholder="请选择售后类型" clearable size="small">
<el-option v-for="dict in this.getDictDatas(DICT_TYPE.TRADE_AFTER_SALE_TYPE)"
:key="dict.value" :label="dict.label" :value="dict.value"/>
</el-select>
</el-form-item>
<el-form-item label="创建时间" prop="createTime">
<el-date-picker v-model="queryParams.createTime" style="width: 240px" value-format="yyyy-MM-dd HH:mm:ss" type="daterange"
range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期"
:picker-options="datePickerOptions" :default-time="['00:00:00', '23:59:59']" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<!-- 操作工具栏 -->
<el-row :gutter="10" class="mb8">
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<!-- Tab 选项真正的内容在 Table -->
<el-tabs v-model="activeTab" type="card" @tab-click="tabClick" style="margin-top: -40px;">
<el-tab-pane v-for="tab in statusTabs" :key="tab.value" :label="tab.label" :name="tab.value" />
</el-tabs>
<!-- 列表 -->
<el-table v-loading="loading" :data="list">
<el-table-column label="退款编号" align="center" prop="no" />
<el-table-column label="订单编号" align="center" prop="orderNo" /> <!-- TODO 芋艿未来要加个订单链接 -->
<el-table-column label="商品信息" align="center" prop="spuName" width="auto" min-width="300">
<!-- TODO @小红样式不太对辛苦改改 -->
<!-- <div slot-scope="{ row }" class="goods-info">-->
<!-- <img :src="row.picUrl"/>-->
<!-- <span class="ellipsis-2" :title="row.name">{{row.name}}</span>-->
<!-- </div>-->
</el-table-column>
<el-table-column label="订单金额" align="center" prop="refundPrice">
<template v-slot="scope">
<span>{{ (scope.row.refundPrice / 100.0).toFixed(2) }}</span>
</template>
</el-table-column>
<el-table-column label="买家" align="center" prop="user.nickname" /> <!-- TODO 芋艿未来要加个会员链接 -->
<el-table-column label="申请时间" align="center" prop="createTime" width="180">
<template v-slot="scope">
<span>{{ parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
<el-table-column label="售后状态" align="center">
<template v-slot="scope">
<dict-tag :type="DICT_TYPE.TRADE_AFTER_SALE_STATUS" :value="scope.row.status" />
</template>
</el-table-column>
<el-table-column label="售后方式" align="center">
<template v-slot="scope">
<dict-tag :type="DICT_TYPE.TRADE_AFTER_SALE_WAY" :value="scope.row.way" />
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template v-slot="scope">
<el-button size="mini" type="text" icon="el-icon-thumb"
>处理退款</el-button>
<!-- @click="handleUpdate(scope.row)" v-hasPermi="['trade:after-sale:update']"-->
</template>
</el-table-column>
</el-table>
<!-- 分页组件 -->
<pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNo" :limit.sync="queryParams.pageSize"
@pagination="getList"/>
</div>
</template>
<script>
import { getAfterSalePage } from "@/api/mall/trade/afterSale";
import { datePickerOptions } from "@/utils/constants";
import { DICT_TYPE, getDictDatas } from "@/utils/dict";
export default {
name: "AfterSale",
components: {
},
data() {
return {
// 遮罩层
loading: true,
// 导出遮罩层
exportLoading: false,
// 显示搜索条件
showSearch: true,
// 总条数
total: 0,
// 交易售后列表
list: [],
// 弹出层标题
title: "",
// 是否显示弹出层
open: false,
// 查询参数
queryParams: {
pageNo: 1,
pageSize: 10,
no: null,
status: null,
orderNo: null,
spuName: null,
createTime: [],
way: null,
type: null,
},
// Tab 筛选
activeTab: 'all',
statusTabs: [{
label: '全部',
value: 'all'
}],
// 静态变量
datePickerOptions: datePickerOptions
};
},
created() {
this.getList();
// 设置 statuses 过滤
for (const dict of getDictDatas(DICT_TYPE.TRADE_AFTER_SALE_STATUS)) {
this.statusTabs.push({
label: dict.label,
value: dict.value
})
}
},
methods: {
/** 查询列表 */
getList() {
this.loading = true;
// 执行查询
getAfterSalePage(this.queryParams).then(response => {
this.list = response.data.list;
this.total = response.data.total;
this.loading = false;
});
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNo = 1;
this.activeTab = this.queryParams.status ? this.queryParams.status : 'all'; // 处理 tab
this.getList();
},
/** 重置按钮操作 */
resetQuery() {
this.resetForm("queryForm");
this.activeTab = 'all'; // 处理 tab
this.handleQuery();
},
/** tab 切换 */
tabClick(tab) {
this.queryParams.status = tab.name === 'all' ? undefined : tab.name;
this.getList();
},
goToDetail (row) {
this.$router.push({ path: '/mall/trade/order/detail', query: { orderNo: row.orderNo }})
}
}
};
</script>
<style lang="scss" scoped>
::v-deep .table-wrapper {
.el-table__row{
.el-table__cell {
border-bottom: none;
.cell{
.el-table {
.el-table__row {
>.el-table__cell {
.goods-info{
display: flex;
img{
margin-right: 10px;
width: 60px;
height: 60px;
border: 1px solid #e2e2e2;
}
}
.ellipsis-2 {
display: -webkit-box;
overflow: hidden;
text-overflow: ellipsis;
white-space: normal;
-webkit-line-clamp: 2; /* 要显示的行数 */
-webkit-box-orient: vertical;
word-break: break-all;
line-height: 22px !important;
max-height: 44px !important;
}
}
}
}
}
}
}
}
</style>

View File

@@ -0,0 +1,279 @@
<template>
<div class="app-container order-detail-page">
<!-- 订单信息 -->
<el-descriptions title="订单信息">
<el-descriptions-item label="订单号">{{ order.no }}</el-descriptions-item>
<el-descriptions-item label="配送方式">物流配送</el-descriptions-item> <!-- TODO 芋艿待实现 -->
<el-descriptions-item label="营销活动">物流配送</el-descriptions-item> <!-- TODO 芋艿待实现 -->
<el-descriptions-item label="订单类型">
<dict-tag :type="DICT_TYPE.TRADE_ORDER_TYPE" :value="order.type" />
</el-descriptions-item>
<el-descriptions-item label="收货人">{{ order.receiverName }}</el-descriptions-item>
<el-descriptions-item label="买家留言">{{ order.userRemark }}</el-descriptions-item>
<el-descriptions-item label="订单来源">
<dict-tag :type="DICT_TYPE.TERMINAL" :value="order.terminal" />
</el-descriptions-item>
<el-descriptions-item label="联系电话">{{ order.receiverMobile }}</el-descriptions-item>
<el-descriptions-item label="商家备注">{{ order.remark }}</el-descriptions-item>
<el-descriptions-item label="支付单号">{{ order.payOrderId }}</el-descriptions-item>
<el-descriptions-item label="付款方式">
<dict-tag :type="DICT_TYPE.PAY_CHANNEL_CODE_TYPE" :value="order.payChannelCode" />
</el-descriptions-item>
<el-descriptions-item label="买家">{{ order.user.nickname }}</el-descriptions-item> <!-- TODO 芋艿待实现跳转会员 -->
<el-descriptions-item label="收货地址">
{{ order.receiverAreaName }} &nbsp; {{ order.receiverDetailAddress }} &nbsp;
<el-link v-clipboard:copy="order.receiverAreaName + ' ' + order.receiverDetailAddress"
v-clipboard:success="clipboardSuccess" icon="el-icon-document-copy" type="primary"/>
</el-descriptions-item>
</el-descriptions>
<!-- 订单状态 -->
<el-descriptions title="订单状态" :column="1">
<el-descriptions-item label="订单状态">
<dict-tag :type="DICT_TYPE.TRADE_ORDER_STATUS" :value="order.status" />
</el-descriptions-item>
<el-descriptions-item label-class-name="no-colon">
<el-button type="primary" size="small">调整价格</el-button> <!-- TODO 芋艿待实现 -->
<el-button type="primary" size="small">备注</el-button> <!-- TODO 芋艿待实现 -->
<el-button type="primary" size="small">发货</el-button> <!-- TODO 芋艿待实现 -->
<el-button type="primary" size="small">关闭订单</el-button> <!-- TODO 芋艿待实现 -->
<el-button type="primary" size="small">修改地址</el-button> <!-- TODO 芋艿待实现 -->
<el-button type="primary" size="small">打印电子面单</el-button> <!-- TODO 芋艿待实现 -->
<el-button type="primary" size="small">打印发货单</el-button> <!-- TODO 芋艿待实现 -->
<el-button type="primary" size="small">确认收货</el-button> <!-- TODO 芋艿待实现 -->
</el-descriptions-item>
<el-descriptions-item label="提醒" label-style="color: red">
买家付款成功后货款将直接进入您的商户号微信支付宝<br />
请及时关注你发出的包裹状态确保可以配送至买家手中 <br />
如果买家表示没收到货或货物有问题请及时联系买家处理友好协商
</el-descriptions-item>
</el-descriptions>
<!-- 物流信息 TODO -->
<!-- 商品信息 -->
<el-descriptions title="商品信息" :column="6">
<el-descriptions-item labelClassName="no-colon">
<el-table :data="order.items" border>
<el-table-column prop="spuName" label="商品" width="700">
<template slot-scope="{ row }">
{{row.spuName}}
<el-tag size="medium" v-for="property in row.properties" :key="property.propertyId">
{{property.propertyName}}{{property.valueName}}</el-tag>
</template>
</el-table-column>
<el-table-column prop="originalUnitPrice" label="单价(元)" width="180">
<template slot-scope="{ row }">
{{ (row.originalUnitPrice / 100.0).toFixed(2) }}
</template>
</el-table-column>
<el-table-column prop="count" label="数量" width="180"/>
<el-table-column prop="originalPrice" label="小计(元)" width="180">
<template slot-scope="{ row }">
{{ (row.originalPrice / 100.0).toFixed(2) }}
</template>
</el-table-column>
<el-table-column prop="afterSaleStatus" label="退款状态">
<template slot-scope="{ row }">
<dict-tag :type="DICT_TYPE.TRADE_ORDER_ITEM_AFTER_SALE_STATUS" :value="row.afterSaleStatus" />
</template>
</el-table-column>
</el-table>
</el-descriptions-item>
<el-descriptions-item v-for="(item,index) in 5" label-class-name="no-colon" :key="item" /> <!-- 占位 -->
<el-descriptions-item label="商品总额">{{ (order.originalPrice / 100.0).toFixed(2) }}</el-descriptions-item>
<el-descriptions-item label="运费金额">{{ (order.deliveryPrice / 100.0).toFixed(2) }}</el-descriptions-item>
<el-descriptions-item label="订单调价">{{ (order.adjustPrice / 100.0).toFixed(2) }}</el-descriptions-item>
<el-descriptions-item label="商品优惠" label-style="color: red">
{{ ((order.originalPrice - order.originalPrice) / 100.0).toFixed(2) }}
</el-descriptions-item>
<el-descriptions-item label="订单优惠" label-style="color: red">
{{ (order.discountPrice / 100.0).toFixed(2) }}
</el-descriptions-item>
<el-descriptions-item label="积分抵扣" label-style="color: red">
{{ (order.pointPrice / 100.0).toFixed(2) }}
</el-descriptions-item>
<el-descriptions-item v-for="(item,index) in 5" label-class-name="no-colon" :key="item" /> <!-- 占位 -->
<el-descriptions-item label="应付金额">
{{ (order.payPrice / 100.0).toFixed(2) }}
</el-descriptions-item>
</el-descriptions>
<template v-for="(group, index) in detailGroups">
<el-descriptions v-bind="group.groupProps" :key="`group_${index}`" :title="group.title">
<!-- 订单操作日志 -->
<el-descriptions-item v-if="group.key === 'orderLog'" labelClassName="no-colon">
<el-timeline>
<el-timeline-item
v-for="(activity, index) in detailInfo[group.key]"
:key="index"
:timestamp="activity.timestamp"
>
{{activity.content}}
</el-timeline-item>
</el-timeline>
</el-descriptions-item>
<!-- 物流信息 -->
<el-descriptions-item v-if="group.key === 'expressInfo'" labelClassName="no-colon">
<el-tabs type="card">
<!-- 循环包裹物流信息 -->
<el-tab-pane v-for="(pkgInfo, pInIdx) in detailInfo[group.key]" :key="`pkgInfo_${pInIdx}`" :label="pkgInfo.label">
<!-- 包裹详情 -->
<el-descriptions>
<el-descriptions-item v-for="(pkgChild, pkgCIdx) in group.children" v-bind="pkgChild.childProps" :key="`pkgChild_${pkgCIdx}`" :label="pkgChild.label">
<!-- 包裹商品列表 -->
<template v-if="pkgChild.valueKey === 'goodsList' && pkgInfo[pkgChild.valueKey]">
<div v-for="(goodInfo, goodInfoIdx) in pkgInfo[pkgChild.valueKey]" :key="`goodInfo_${goodInfoIdx}`" style="display: flex;">
<el-image
style="width: 100px;height: 100px;flex: none"
:src="goodInfo.imgUrl">
</el-image>
<el-descriptions :column="1">
<el-descriptions-item labelClassName="no-colon">{{goodInfo.name}}</el-descriptions-item>
<el-descriptions-item label="数量">{{goodInfo.count}}</el-descriptions-item>
</el-descriptions>
</div>
</template>
<!-- 包裹物流详情 -->
<el-timeline v-else-if="pkgChild.valueKey==='wlxq'">
<el-timeline-item
v-for="(activity, index) in pkgInfo[pkgChild.valueKey]"
:key="index"
:timestamp="activity.timestamp"
>
{{activity.content}}
</el-timeline-item>
</el-timeline>
<template v-else>
{{pkgInfo[pkgChild.valueKey]}}
</template>
</el-descriptions-item>
</el-descriptions>
</el-tab-pane>
</el-tabs>
</el-descriptions-item>
</el-descriptions>
</template>
</div>
</template>
<script>
import { getOrderDetail } from "@/api/mall/trade/order";
export default {
name: "detail",
data () {
return {
detailGroups: [
{
title: '物流信息',
key: 'expressInfo',
children: [
{ label: '发货时间', valueKey: 'fhsj'},
{ label: '物流公司', valueKey: 'wlgs'},
{ label: '运单号', valueKey: 'ydh'},
{ label: '物流状态', valueKey: 'wlzt', childProps: { span: 3 }},
{ label: '物流详情', valueKey: 'wlxq'}
]
},
{
title: '订单操作日志',
key: 'orderLog'
}
],
detailInfo: {
expressInfo: [ // 物流信息
{
label: '包裹1',
name: 'bg1',
fhsj: '2022-11-03 16:50:45',
wlgs: '极兔',
ydh: '2132123',
wlzt: '不支持此快递公司',
wlxq: [
{
content: '正在派送途中,请您准备签收(派件人:王涛,电话:13854563814)',
timestamp: '2018-04-15 15:00:16'
},
{
content: '快件到达 【烟台龙口东江村委营业点】',
timestamp: '2018-04-13 14:54:19'
},
{
content: '快件已发车',
timestamp: '2018-04-11 12:55:52'
},
{
content: '快件已发车',
timestamp: '2018-04-11 12:55:52'
},
{
content: '快件已发车',
timestamp: '2018-04-11 12:55:52'
}
]
}
],
orderLog: [ // 订单操作日志
{
content: '买家【乌鸦】关闭了订单',
timestamp: '2018-04-15 15:00:16'
},
{
content: '买家【乌鸦】下单了',
timestamp: '2018-04-15 15:00:16'
}
],
goodsInfo: [] // 商品详情tableData
},
order: {
items: [],
user: {},
},
}
},
created() {
getOrderDetail(this.$route.query.id).then(res => {
this.order = res.data
})
},
methods: {
clipboardSuccess() {
this.$modal.msgSuccess("复制成功");
}
}
}
</script>
<style lang="scss" scoped>
:deep(.el-descriptions){
&:not(:nth-child(1)) {
margin-top: 20px;
}
.el-descriptions__title{
display: flex;
align-items: center;
&::before{
content: '';
display: inline-block;
margin-right: 10px;
width: 3px;
height: 20px;
background-color: #409EFF;
}
}
.el-descriptions-item__container{
margin: 0 10px;
.no-colon{
margin: 0;
&::after{
content: ''
}
}
}
}
</style>

View File

@@ -0,0 +1,279 @@
<template>
<div class="app-container">
<!-- 搜索工作栏 -->
<!-- TODO: inline 看看是不是需要; v-show= 那块逻辑还是要的 -->
<el-row :gutter="20">
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
<el-col :span="6" :xs="24">
<el-form-item label="搜索方式" prop="searchValue">
<el-input v-model="queryParams.searchValue" style="width: 240px">
<el-select v-model="queryParams.searchType" slot="prepend" style="width: 100px">
<el-option v-for="dict in searchTypes" :key="dict.value" :label="dict.label" :value="dict.value"/>
</el-select>
</el-input>
</el-form-item>
</el-col>
<el-col :span="6" :xs="24">
<el-form-item label="订单类型" prop="type">
<el-select v-model="queryParams.type" clearable style="width: 240px">
<el-option v-for="dict in this.getDictDatas(DICT_TYPE.TRADE_ORDER_TYPE)"
:key="dict.value" :label="dict.label" :value="dict.value"/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="6" :xs="24">
<el-form-item label="订单状态" prop="status">
<el-select v-model="queryParams.status" clearable style="width: 240px">
<el-option v-for="dict in this.getDictDatas(DICT_TYPE.TRADE_ORDER_STATUS)"
:key="dict.value" :label="dict.label" :value="dict.value"/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="6" :xs="24">
<el-form-item label="订单来源" prop="terminal">
<el-select v-model="queryParams.terminal" clearable style="width: 240px">
<el-option v-for="dict in this.getDictDatas(DICT_TYPE.TERMINAL)"
:key="dict.value" :label="dict.label" :value="dict.value"/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="6" :xs="24">
<el-form-item label="支付方式" prop="payChannelCode">
<el-select v-model="queryParams.payChannelCode" clearable style="width: 240px">
<el-option v-for="dict in this.getDictDatas(DICT_TYPE.PAY_CHANNEL_CODE_TYPE)"
:key="dict.value" :label="dict.label" :value="dict.value"/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="6" :xs="24">
<el-form-item label="下单时间" prop="createTime">
<el-date-picker v-model="queryParams.createTime" style="width: 240px" value-format="yyyy-MM-dd HH:mm:ss" type="daterange"
range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期"
:picker-options="datePickerOptions" :default-time="['00:00:00', '23:59:59']" />
</el-form-item>
</el-col>
<el-col :span="6" :xs="24" style="line-height: 32px">
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
</el-col>
</el-form>
</el-row>
<!-- 操作工具栏 -->
<el-row :gutter="10" class="mb8">
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<!-- tab切换 -->
<!-- TODO @小程看看能不能往上挪 -40px隐藏搜索刷新对齐 -->
<el-tabs v-model="activeTab" type="card" @tab-click="tabClick">
<el-tab-pane v-for="tab in statusTabs" :key="tab.value" :label="tab.label" :name="tab.value">
<!-- 列表 -->
<el-table v-loading="loading" :data="list" :show-header="false" class="order-table">
<el-table-column>
<template slot-scope="{ row }">
<el-row type="flex" align="middle">
<el-col :span="5">
订单号{{row.no}}
<el-popover title="支付单号:" :content="row.payOrderId + ''" placement="right" width="200" trigger="click">
<el-button slot="reference" type="text">更多</el-button>
</el-popover>
</el-col>
<el-col :span="5">下单时间{{ parseTime(row.createTime) }}</el-col>
<el-col :span="4">订单来源
<dict-tag :type="DICT_TYPE.TERMINAL" :value="row.terminal" />
</el-col>
<el-col :span="4">支付方式
<dict-tag v-if="row.payChannelCode" :type="DICT_TYPE.PAY_CHANNEL_CODE_TYPE" :value="row.payChannelCode" />
<span v-else>未支付</span>
</el-col>
<el-col :span="6" align="right">
<el-button type="text" @click="goToDetail(row)">详情</el-button>
</el-col>
</el-row>
<!-- 订单下的商品 -->
<el-table :data="row.items" border :show-header="true">
<el-table-column label="商品" prop="goods" header-align="center" width="auto" min-width="300">
<template slot-scope="{ row, $index }">
<div class="goods-info">
<img :src="row.picUrl"/>
<span class="ellipsis-2" :title="row.spuName">{{row.spuName}}</span>
<!-- TODO @小程下面是商品属性想当度一行放在商品名下面 -->
<el-tag size="medium" v-for="property in row.properties" :key="property.propertyId">
{{property.propertyName}}{{property.valueName}}</el-tag>
</div>
</template>
</el-table-column>
<el-table-column label="单价(元)/数量" prop="fee" align="center" width="115">
<template slot-scope="{ row }">
<div>{{ (row.originalUnitPrice / 100.0).toFixed(2) }}</div>
<div>{{row.count}} </div>
</template>
</el-table-column>
<!-- TODO @小程这里应该是一个订单下多个商品只展示订单上的总金额就是 order.payPrice -->
<el-table-column label="实付金额(元)" prop="amount" align="center" width="100"/>
<!-- TODO @小程这里应该是一个订单下多个商品只展示订单上的收件信息使用 order.receiverXXX 开头的字段 -->
<el-table-column label="买家/收货人" prop="buyer" header-align="center" width="auto" min-width="300">
<template slot-scope="{ row }">
<!-- TODO @芋艿以后增加一个会员详情界面 -->
<div>{{row.buyer}}</div>
<div>{{row.receiver}}{{row.tel}}</div>
<div class="ellipsis-2" :title="row.address">{{row.address}}</div>
</template>
</el-table-column>
<!-- TODO @小程这里应该是一个订单下多个商品交易状态是统一的使用 order.status 字段 -->
<el-table-column label="交易状态" prop="status" align="center" width="100"/>
</el-table>
</template>
</el-table-column>
</el-table>
</el-tab-pane>
</el-tabs>
<!-- 分页组件 -->
<pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNo" :limit.sync="queryParams.pageSize"
@pagination="getList"/>
</div>
</template>
<script>
import { getOrderPage } from "@/api/mall/trade/order";
import { datePickerOptions } from "@/utils/constants";
import { DICT_TYPE, getDictDatas } from "@/utils/dict";
export default {
name: "index",
data () {
return {
// 遮罩层
loading: true,
// 导出遮罩层
exportLoading: false,
// 显示搜索条件
showSearch: true,
// 总条数
total: 0,
// 交易售后列表
list: [],
queryParams: {
pageNo: 1,
pageSize: 10,
searchType: 'no',
searchValue: '',
type: null,
status: null,
payChannelCode: null,
createTime: [],
},
// Tab 筛选
activeTab: 'all',
statusTabs: [{
label: '全部',
value: 'all'
}],
// 静态变量
datePickerOptions: datePickerOptions,
searchTypes: [
{ label: '订单号', value: 'no' },
{ label: '会员编号', value: 'userId' },
{ label: '会员昵称', value: 'userNickname' },
{ label: '会员手机号', value: 'userMobile' },
{ label: '收货人姓名', value: 'receiverName' },
{ label: '收货人手机号码', value: 'receiverMobile' },
],
}
},
created() {
this.getList();
// 设置 statuses 过滤
for (const dict of getDictDatas(DICT_TYPE.TRADE_ORDER_STATUS)) {
this.statusTabs.push({
label: dict.label,
value: dict.value
})
}
},
methods: {
/** 查询列表 */
getList() {
this.loading = true;
// 执行查询
getOrderPage({
...this.queryParams,
searchType: undefined,
searchValue: undefined,
no: this.queryParams.searchType === 'no' ? this.queryParams.searchValue : undefined,
userId: this.queryParams.searchType === 'userId' ? this.queryParams.searchValue : undefined,
userNickname: this.queryParams.searchType === 'userNickname' ? this.queryParams.searchValue : undefined,
userMobile: this.queryParams.searchType === 'userMobile' ? this.queryParams.searchValue : undefined,
receiverName: this.queryParams.searchType === 'receiverName' ? this.queryParams.searchValue : undefined,
receiverMobile: this.queryParams.searchType === 'receiverMobile' ? this.queryParams.searchValue : undefined,
}).then(response => {
this.list = response.data.list;
this.total = response.data.total;
this.loading = false;
});
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNo = 1;
this.activeTab = this.queryParams.status ? this.queryParams.status : 'all'; // 处理 tab
this.getList();
},
/** 重置按钮操作 */
resetQuery() {
this.resetForm("queryForm");
this.handleQuery();
},
/** tab 切换 */
tabClick(tab) {
this.queryParams.status = tab.name === 'all' ? undefined : tab.name;
this.getList();
},
goToDetail (row) {
this.$router.push({ path: '/trade/order/detail', query: { id: row.id }})
}
}
}
</script>
<style lang="scss" scoped>
::v-deep .order-table{
border-bottom: none;
&::before{
height: 0;
}
.el-table__row{
.el-table__cell{
border-bottom: none;
.cell{
.el-table {
.el-table__row{
>.el-table__cell{
.goods-info{
display: flex;
img{
margin-right: 10px;
width: 60px;
height: 60px;
border: 1px solid #e2e2e2;
}
}
.ellipsis-2{
display: -webkit-box;
overflow: hidden;
text-overflow: ellipsis;
white-space: normal;
-webkit-line-clamp: 2; /* 要显示的行数 */
-webkit-box-orient: vertical;
word-break: break-all;
line-height: 22px !important;
max-height: 44px !important;
}
}
}
}
}
}
}
}
</style>

View File

@@ -0,0 +1,289 @@
<template>
<div class="app-container">
<!-- 搜索工作栏 -->
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="名称" prop="name">
<el-input v-model="queryParams.name" placeholder="请输入名称" clearable
@keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<!-- 操作工具栏 -->
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" plain icon="el-icon-plus" size="mini" @click="handleAdd"
v-hasPermi="['mp:account:create']">新增
</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<!-- 列表 -->
<el-table v-loading="loading" :data="list">
<el-table-column label="名称" align="center" prop="name"/>
<el-table-column label="微信号" align="center" prop="account" width="180"/>
<el-table-column label="appId" align="center" prop="appId" width="180"/>
<!-- <el-table-column label="appSecret" align="center" prop="appSecret" width="180"/>-->
<!-- <el-table-column label="token" align="center" prop="token"/>-->
<!-- <el-table-column label="消息加解密密钥" align="center" prop="aesKey"/>-->
<el-table-column label="服务器地址(URL)" align="center" prop="appId" width="360">
<template v-slot="scope">
{{ 'http://服务端地址/mp/open/' + scope.row.appId }}
</template>
</el-table-column>
<el-table-column label="二维码" align="center" prop="qrCodeUrl">
<template v-slot="scope">
<img v-if="scope.row.qrCodeUrl" :src="scope.row.qrCodeUrl" alt="二维码" style="height: 100px;" />
</template>
</el-table-column>
<el-table-column label="备注" align="center" prop="remark"/>
<!-- <el-table-column label="创建时间" align="center" prop="createTime" width="180">-->
<!-- <template slot-scope="scope">-->
<!-- <span>{{ parseTime(scope.row.createTime) }}</span>-->
<!-- </template>-->
<!-- </el-table-column>-->
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template slot-scope="scope">
<el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)"
v-hasPermi="['mp:account:update']">修改
</el-button>
<el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)"
v-hasPermi="['mp:account:delete']">删除
</el-button>
<el-button size="mini" type="text" icon="el-icon-refresh" @click="handleGenerateQrCode(scope.row)"
v-hasPermi="['mp:account:qr-code']">生成二维码
</el-button>
<el-button size="mini" type="text" icon="el-icon-share" @click="handleCleanQuota(scope.row)"
v-hasPermi="['mp:account:clear-quota']">清空 API 配额
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页组件 -->
<pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNo" :limit.sync="queryParams.pageSize"
@pagination="getList"/>
<!-- 对话框(添加 / 修改) -->
<el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
<el-form ref="form" :model="form" :rules="rules" label-width="120px">
<el-form-item label="名称" prop="name">
<el-input v-model="form.name" placeholder="请输入名称"/>
</el-form-item>
<el-form-item label="微信号" prop="account">
<span slot="label">
<el-tooltip content="在微信公众平台mp.weixin.qq.com的菜单 [设置与开发 - 公众号设置 - 账号详情] 中能找到「微信号」" placement="top">
<i class="el-icon-question" />
</el-tooltip>
微信号
</span>
<el-input v-model="form.account" placeholder="请输入微信号"/>
</el-form-item>
<el-form-item label="appId" prop="appId">
<span slot="label">
<el-tooltip content="在微信公众平台mp.weixin.qq.com的菜单 [设置与开发 - 公众号设置 - 基本设置] 中能找到「开发者ID(AppID)」" placement="top">
<i class="el-icon-question" />
</el-tooltip>
appId
</span>
<el-input v-model="form.appId" placeholder="请输入公众号 appId"/>
</el-form-item>
<el-form-item label="appSecret" prop="appSecret">
<span slot="label">
<el-tooltip content="在微信公众平台mp.weixin.qq.com的菜单 [设置与开发 - 公众号设置 - 基本设置] 中能找到「开发者密码(AppSecret)」" placement="top">
<i class="el-icon-question" />
</el-tooltip>
appSecret
</span>
<el-input v-model="form.appSecret" placeholder="请输入公众号 appSecret"/>
</el-form-item>
<el-form-item label="token" prop="token">
<el-input v-model="form.token" placeholder="请输入公众号token"/>
</el-form-item>
<el-form-item label="消息加解密密钥" prop="aesKey">
<el-input v-model="form.aesKey" placeholder="请输入消息加解密密钥"/>
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input type="textarea" v-model="form.remark" placeholder="请输入备注"/>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import {
clearAccountQuota,
createAccount,
deleteAccount,
generateAccountQrCode,
getAccount,
getAccountPage,
updateAccount
} from '@/api/mp/account'
export default {
name: 'mpAccount',
components: {},
data() {
return {
// 遮罩层
loading: true,
// 显示搜索条件
showSearch: true,
// 总条数
total: 0,
// 公众号账号列表
list: [],
// 弹出层标题
title: '',
// 是否显示弹出层
open: false,
// 查询参数
queryParams: {
pageNo: 1,
pageSize: 10,
name: null,
account: null,
appId: null,
},
// 表单参数
form: {},
// 表单校验
rules: {
name: [{required: true, message: '名称不能为空', trigger: 'blur'}],
account: [{required: true, message: '公众号账号不能为空', trigger: 'blur'}],
appId: [{required: true, message: '公众号 appId 不能为空', trigger: 'blur'}],
appSecret: [{required: true, message: '公众号密钥不能为空', trigger: 'blur'}],
token: [{required: true, message: '公众号 token 不能为空', trigger: 'blur'}],
},
}
},
created() {
this.getList()
},
methods: {
/** 查询列表 */
getList() {
this.loading = true
// 处理查询参数
let params = {...this.queryParams}
// 执行查询
getAccountPage(params).then(response => {
this.list = response.data.list
this.total = response.data.total
this.loading = false
})
},
/** 取消按钮 */
cancel() {
this.open = false
this.reset()
},
/** 表单重置 */
reset() {
this.form = {
id: undefined,
name: undefined,
account: undefined,
appId: undefined,
appSecret: undefined,
token: undefined,
aesKey: undefined,
remark: undefined,
}
this.resetForm('form')
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNo = 1
this.getList()
},
/** 重置按钮操作 */
resetQuery() {
this.dateRangeCreateTime = []
this.resetForm('queryForm')
this.handleQuery()
},
/** 新增按钮操作 */
handleAdd() {
this.reset()
this.open = true
this.title = '添加公众号账号'
},
/** 修改按钮操作 */
handleUpdate(row) {
this.reset()
const id = row.id
getAccount(id).then(response => {
this.form = response.data
this.open = true
this.title = '修改公众号账号'
})
},
/** 提交按钮 */
submitForm() {
this.$refs['form'].validate(valid => {
if (!valid) {
return
}
// 修改的提交
if (this.form.id != null) {
updateAccount(this.form).then(response => {
this.$modal.msgSuccess('修改成功')
this.open = false
this.getList()
})
return
}
// 添加的提交
createAccount(this.form).then(response => {
this.$modal.msgSuccess('新增成功')
this.open = false
this.getList()
})
})
},
/** 删除按钮操作 */
handleDelete(row) {
const id = row.id
this.$modal.confirm('是否确认删除公众号账号编号为"' + row.name + '"的数据项?').then(function () {
return deleteAccount(id)
}).then(() => {
this.getList()
this.$modal.msgSuccess('删除成功')
}).catch(() => {
})
},
/** 生成二维码的按钮操作 */
handleGenerateQrCode(row) {
const id = row.id
this.$modal.confirm('是否确认生成公众号账号编号为"' + row.name + '"的二维码?').then(function () {
return generateAccountQrCode(id)
}).then(() => {
this.getList()
this.$modal.msgSuccess('生成二维码成功')
}).catch(() => {
})
},
/** 清空二维码 API 配额的按钮操作 */
handleCleanQuota(row) {
const id = row.id
this.$modal.confirm('是否确认清空生成公众号账号编号为"' + row.name + '"的 API 配额?').then(function () {
return clearAccountQuota(id)
}).then(() => {
this.$modal.msgSuccess('清空 API 配额成功')
}).catch(() => {
})
},
}
}
</script>

View File

@@ -0,0 +1,382 @@
<!--
MIT License
Copyright (c) 2020 www.joolun.com
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
芋道源码
移除 avue 框架使用 element-ui 重写
重写代码保持和现有项目保持一致
-->
<template>
<div class="app-container">
<!-- 搜索工作栏 -->
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="公众号" prop="accountId">
<el-select v-model="queryParams.accountId" placeholder="请选择公众号">
<el-option v-for="item in accounts" :key="parseInt(item.id)" :label="item.name" :value="parseInt(item.id)" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<!-- tab 切换 -->
<el-tabs v-model="type" @tab-click="handleClick">
<!-- 操作工具栏 -->
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" plain icon="el-icon-plus" size="mini" @click="handleAdd"
v-hasPermi="['mp:auto-reply:create']" v-if="type !== '1' || list.length <= 0">新增
</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList" />
</el-row>
<!-- tab -->
<el-tab-pane name="1">
<span slot="label"><i class="el-icon-star-off"></i> 关注时回复</span>
</el-tab-pane>
<el-tab-pane name="2">
<span slot="label"><i class="el-icon-chat-line-round"></i> 消息回复</span>
</el-tab-pane>
<el-tab-pane name="3">
<span slot="label"><i class="el-icon-news"></i> 关键词回复</span>
</el-tab-pane>
</el-tabs>
<!-- 列表 -->
<el-table v-loading="loading" :data="list">
<el-table-column label="请求消息类型" align="center" prop="requestMessageType" v-if="type === '2'" />
<el-table-column label="关键词" align="center" prop="requestKeyword" v-if="type === '3'" />
<el-table-column label="匹配类型" align="center" prop="requestMatch" v-if="type === '3'">
<template v-slot="scope">
<dict-tag :type="DICT_TYPE.MP_AUTO_REPLY_REQUEST_MATCH" :value="scope.row.requestMatch"/>
</template>
</el-table-column>
<el-table-column label="回复消息类型" align="center">
<template v-slot="scope">
<dict-tag :type="DICT_TYPE.MP_MESSAGE_TYPE" :value="scope.row.responseMessageType"/>
</template>
</el-table-column>
<el-table-column label="回复内容" align="center">
<template slot-scope="scope">
<div v-if="scope.row.responseMessageType === 'text'">{{ scope.row.responseContent }}</div>
<div v-else-if="scope.row.responseMessageType === 'voice'">
<wx-voice-player :url="scope.row.responseMediaUrl" />
</div>
<div v-else-if="scope.row.responseMessageType === 'image'">
<a target="_blank" :href="scope.row.responseMediaUrl">
<img :src="scope.row.responseMediaUrl" style="width: 100px">
</a>
</div>
<div v-else-if="scope.row.responseMessageType === 'video' || scope.row.responseMessageType === 'shortvideo'">
<wx-video-player :url="scope.row.responseMediaUrl" style="margin-top: 10px" />
</div>
<div v-else-if="scope.row.responseMessageType === 'news'">
<wx-news :articles="scope.row.responseArticles" />
</div>
<div v-else-if="scope.row.responseMessageType === 'music'">
<wx-music :title="scope.row.responseTitle" :description="scope.row.responseDescription"
:thumb-media-url="scope.row.responseThumbMediaUrl"
:music-url="scope.row.responseMusicUrl" :hq-music-url="scope.row.responseHqMusicUrl" />
</div>
</template>
</el-table-column>
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template slot-scope="scope">
<el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)"
v-hasPermi="['mp:auto-reply:update']">修改
</el-button>
<el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)"
v-hasPermi="['mp:auto-reply:delete']">删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 添加或修改自动回复的对话框 -->
<el-dialog :title="title" :visible.sync="open" width="800px" append-to-body>
<el-form ref="form" :model="form" :rules="rules" label-width="80px">
<el-form-item label="消息类型" prop="requestMessageType" v-if="type === '2'">
<el-select v-model="form.requestMessageType" placeholder="请选择">
<el-option v-for="dict in this.getDictDatas(DICT_TYPE.MP_MESSAGE_TYPE)"
:key="dict.value" :label="dict.label" :value="dict.value"
v-if="requestMessageTypes.includes(dict.value)"/>
</el-select>
</el-form-item>
<el-form-item label="匹配类型" prop="requestMatch" v-if="type === '3'">
<el-select v-model="form.requestMatch" placeholder="请选择匹配类型" clearable size="small">
<el-option v-for="dict in this.getDictDatas(DICT_TYPE.MP_AUTO_REPLY_REQUEST_MATCH)"
:key="dict.value" :label="dict.label" :value="parseInt(dict.value)"/>
</el-select>
</el-form-item>
<el-form-item label="关键词" prop="requestKeyword" v-if="type === '3'">
<el-input v-model="form.requestKeyword" placeholder="请输入内容" clearable />
</el-form-item>
<el-form-item label="回复消息">
<wx-reply-select :objData="objData" v-if="hackResetWxReplySelect" />
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="cancel"> </el-button>
<el-button type="primary" @click="handleSubmit"> </el-button>
</span>
</el-dialog>
</div>
</template>
<script>
import WxVideoPlayer from '@/views/mp/components/wx-video-play/main.vue';
import WxVoicePlayer from '@/views/mp/components/wx-voice-play/main.vue';
import WxMsg from '@/views/mp/components/wx-msg/main.vue';
import WxLocation from '@/views/mp/components/wx-location/main.vue';
import WxMusic from '@/views/mp/components/wx-music/main.vue';
import WxNews from '@/views/mp/components/wx-news/main.vue';
import WxReplySelect from '@/views/mp/components/wx-reply/main.vue'
import { getSimpleAccounts } from "@/api/mp/account";
import { createAutoReply, deleteAutoReply, getAutoReply, getAutoReplyPage, updateAutoReply } from "@/api/mp/autoReply";
export default {
name: 'mpAutoReply',
components: {
WxVideoPlayer,
WxVoicePlayer,
WxMsg,
WxLocation,
WxMusic,
WxNews,
WxReplySelect
},
data() {
return {
// tab 类型1、关注时回复2、消息回复3、关键词回复
type: '3',
// 允许选择的请求消息类型
requestMessageTypes: ['text', 'image', 'voice', 'video', 'shortvideo', 'location', 'link'],
// 遮罩层
loading: true,
// 显示搜索条件
showSearch: true,
// 总条数
total: 0,
// 自动回复列表
list: [],
// 查询参数
queryParams: {
pageNo: 1,
pageSize: 10,
accountId: undefined,
},
// 弹出层标题
title: "",
// 是否显示弹出层
open: false,
// 表单参数
form: {},
// 回复消息
objData: {
type : 'text'
},
// 表单校验
rules: {
requestKeyword: [{ required: true, message: "请求的关键字不能为空", trigger: "blur" }],
requestMatch: [{ required: true, message: "请求的关键字的匹配不能为空", trigger: "blur" }],
},
hackResetWxReplySelect: false, // 重置 WxReplySelect 组件,解决无法清除的问题
// 公众号账号列表
accounts: []
}
},
created() {
getSimpleAccounts().then(response => {
this.accounts = response.data;
// 默认选中第一个
if (this.accounts.length > 0) {
this.queryParams.accountId = this.accounts[0].id;
}
// 加载数据
this.getList();
})
},
methods: {
/** 查询列表 */
getList() {
// 如果没有选中公众号账号,则进行提示。
if (!this.queryParams.accountId) {
this.$message.error('未选中公众号,无法查询自动回复')
return false
}
this.loading = false
// 处理查询参数
let params = {
...this.queryParams,
type: this.type
}
// 执行查询
getAutoReplyPage(params).then(response => {
this.list = response.data.list
this.total = response.data.total
this.loading = false
})
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNo = 1
this.getList()
},
/** 重置按钮操作 */
resetQuery() {
this.resetForm('queryForm')
// 默认选中第一个
if (this.accounts.length > 0) {
this.queryParams.accountId = this.accounts[0].id;
}
this.handleQuery()
},
handleClick(tab, event) {
this.type = tab.name
this.handleQuery()
},
/** 新增按钮操作 */
handleAdd(){
this.reset();
this.resetEditor();
// 打开表单,并设置初始化
this.open = true
this.title = '新增自动回复';
this.objData = {
type : 'text',
accountId: this.queryParams.accountId,
}
},
/** 修改按钮操作 */
handleUpdate(row) {
this.reset();
this.resetEditor();
const id = row.id;
getAutoReply(id).then(response => {
// 设置属性
this.form = {...response.data}
this.$delete(this.form, 'responseMessageType');
this.$delete(this.form, 'responseContent');
this.$delete(this.form, 'responseMediaId');
this.$delete(this.form, 'responseMediaUrl');
this.$delete(this.form, 'responseDescription');
this.$delete(this.form, 'responseArticles');
this.objData = {
type: response.data.responseMessageType,
accountId: this.queryParams.accountId,
content: response.data.responseContent,
mediaId: response.data.responseMediaId,
url: response.data.responseMediaUrl,
title: response.data.responseTitle,
description: response.data.responseDescription,
thumbMediaId: response.data.responseThumbMediaId,
thumbMediaUrl: response.data.responseThumbMediaUrl,
articles: response.data.responseArticles,
musicUrl: response.data.responseMusicUrl,
hqMusicUrl: response.data.responseHqMusicUrl,
}
// 打开表单
this.open = true
this.title = '修改自动回复';
})
},
handleSubmit() {
this.$refs["form"].validate(valid => {
if (!valid) {
return;
}
// 处理回复消息
const form = {...this.form};
form.responseMessageType = this.objData.type;
form.responseContent = this.objData.content;
form.responseMediaId = this.objData.mediaId;
form.responseMediaUrl = this.objData.url;
form.responseTitle = this.objData.title;
form.responseDescription = this.objData.description;
form.responseThumbMediaId = this.objData.thumbMediaId;
form.responseThumbMediaUrl = this.objData.thumbMediaUrl;
form.responseArticles = this.objData.articles;
form.responseMusicUrl = this.objData.musicUrl;
form.responseHqMusicUrl = this.objData.hqMusicUrl;
if (this.form.id !== undefined) {
updateAutoReply(form).then(response => {
this.$modal.msgSuccess("修改成功");
this.open = false;
this.getList();
});
} else {
createAutoReply(form).then(response => {
this.$modal.msgSuccess("新增成功");
this.open = false;
this.getList();
});
}
});
},
// 表单重置
reset() {
this.form = {
id: undefined,
accountId: this.queryParams.accountId,
type: this.type,
requestKeyword: undefined,
requestMatch: this.type === '3' ? 1 : undefined,
requestMessageType: undefined,
};
this.resetForm("form");
},
// 取消按钮
cancel() {
this.open = false;
this.reset();
},
// 表单 Editor 重置
resetEditor() {
this.hackResetWxReplySelect = false // 销毁组件
this.$nextTick(() => {
this.hackResetWxReplySelect = true // 重建组件
})
},
handleDelete: function(row) {
const ids = row.id;
this.$modal.confirm('是否确认删除此数据?').then(function() {
return deleteAutoReply(ids);
}).then(() => {
this.getList();
this.$modal.msgSuccess("删除成功");
}).catch(() => {});
},
}
}
</script>

View File

@@ -0,0 +1,230 @@
<!--
- Copyright (C) 2018-2019
- All rights reserved, Designed By www.joolun.com
芋道源码
调整 uploadData 属性只需要传入 accountId 即可
-->
<template>
<div id="wxEditor">
<div v-loading="quillUpdateImg" element-loading-text="请稍等,图片上传中">
<!-- 图片上传组件辅助-->
<el-upload class="avatar-uploader" name="file" :action="actionUrl" :headers="headers"
:show-file-list="false" :data="uploadData"
:on-success="uploadSuccess" :on-error="uploadError" :before-upload="beforeUpload">
</el-upload>
<quill-editor class="editor" v-model="content" ref="myQuillEditor" :options="editorOption"
@change="onEditorChange($event)">
</quill-editor>
</div>
</div>
</template>
<script>
// 工具栏配置
const toolbarOptions = [
["bold", "italic", "underline", "strike"], // 加粗 斜体 下划线 删除线
["blockquote", "code-block"], // 引用 代码块
[{ header: 1 }, { header: 2 }], // 1、2 级标题
[{ list: "ordered" }, { list: "bullet" }], // 有序、无序列表
[{ script: "sub" }, { script: "super" }], // 上标/下标
[{ indent: "-1" }, { indent: "+1" }], // 缩进
// [{'direction': 'rtl'}], // 文本方向
[{ size: ["small", false, "large", "huge"] }], // 字体大小
[{ header: [1, 2, 3, 4, 5, 6, false] }], // 标题
[{ color: [] }, { background: [] }], // 字体颜色、字体背景颜色
[{ font: [] }], // 字体种类
[{ align: [] }], // 对齐方式
["clean"], // 清除文本格式
["link", "image", "video"] // 链接、图片、视频
]
import { quillEditor } from "vue-quill-editor"
import "quill/dist/quill.core.css"
import "quill/dist/quill.snow.css"
import "quill/dist/quill.bubble.css"
import { getAccessToken } from "@/utils/auth";
export default {
props: {
/* 公众号账号编号 */
accountId: {
type: Number,
required: true
},
/* 编辑器的内容 */
value: {
type: String
},
/* 图片大小 */
maxSize: {
type: Number,
default: 4000 // kb
}
},
name: 'wxEditor',
components: {
quillEditor
},
data() {
return {
editorType: '1',
content: this.value.replace(/data-src/g, "src"),
quillUpdateImg: false, // 根据图片上传状态来确定是否显示loading动画刚开始是false,不显示
editorOption: {
theme: "snow", // or 'bubble'
placeholder: "请输入文章内容",
modules: {
toolbar: {
container: toolbarOptions,
// container: "#toolbar",
handlers: {
image: function(value) {
if (value) {
// 触发input框选择图片文件
document.querySelector(".avatar-uploader input").click();
} else {
this.quill.format("image", false);
}
},
link: function(value) {
if (value) {
const href = prompt('注意!只支持公众号图文链接');
this.quill.format("link", href);
} else {
this.quill.format("link", false);
}
},
}
}
}
},
actionUrl: process.env.VUE_APP_BASE_API +'/admin-api/mp/material/upload-news-image', // 这里写你要上传的图片服务器地址
headers: { Authorization: "Bearer " + getAccessToken() }, // 设置上传的请求头部
uploadData: {
"type": 'image', // TODO 芋艿:试试要不要换成 thumb
"accountId": this.accountId,
},
}
},
methods: {
onEditorChange(editor) {
//内容改变事件
this.$emit("input", this.content)
},
// 富文本图片上传前
beforeUpload() {
// 显示 loading 动画
this.quillUpdateImg = true
},
// 图片上传成功
// 注意!由于微信公众号的图片有访问限制,所以会显示“此图片来自微信公众号,未经允许不可引用”
uploadSuccess(res, file) {
// res为图片服务器返回的数据
// 获取富文本组件实例
let quill = this.$refs.myQuillEditor.quill
// 如果上传成功
const link = res.data
if (link){
// 获取光标所在位置
let length = quill.getSelection().index;
// 插入图片 res.info为服务器返回的图片地址
quill.insertEmbed(length, 'image', link)
// 调整光标到最后
quill.setSelection(length + 1)
} else {
this.$message.error('图片插入失败')
}
// loading 动画消失
this.quillUpdateImg = false;
},
// 富文本图片上传失败
uploadError() {
// loading 动画消失
this.quillUpdateImg = false;
this.$message.error("图片插入失败");
}
}
}
</script>
<style>
.editor {
line-height: normal !important;
height: 500px;
}
.ql-snow .ql-tooltip[data-mode=link]::before {
content: "请输入链接地址:";
}
.ql-snow .ql-tooltip.ql-editing a.ql-action::after {
border-right: 0;
content: '保存';
padding-right: 0;
}
.ql-snow .ql-tooltip[data-mode=video]::before {
content: "请输入视频地址:";
}
.ql-snow .ql-picker.ql-size .ql-picker-label::before,
.ql-snow .ql-picker.ql-size .ql-picker-item::before {
content: '14px';
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value=small]::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value=small]::before {
content: '10px';
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value=large]::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value=large]::before {
content: '18px';
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value=huge]::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value=huge]::before {
content: '32px';
}
.ql-snow .ql-picker.ql-header .ql-picker-label::before,
.ql-snow .ql-picker.ql-header .ql-picker-item::before {
content: '文本';
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="1"]::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="1"]::before {
content: '标题1';
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="2"]::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="2"]::before {
content: '标题2';
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="3"]::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="3"]::before {
content: '标题3';
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="4"]::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="4"]::before {
content: '标题4';
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="5"]::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="5"]::before {
content: '标题5';
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="6"]::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="6"]::before {
content: '标题6';
}
.ql-snow .ql-picker.ql-font .ql-picker-label::before,
.ql-snow .ql-picker.ql-font .ql-picker-item::before {
content: '标准字体';
}
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value=serif]::before,
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value=serif]::before {
content: '衬线字体';
}
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value=monospace]::before,
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value=monospace]::before {
content: '等宽字体';
}
</style>

View File

@@ -0,0 +1,36 @@
<!--
微信消息 - 定位
-->
<template>
<div>
<el-link type="primary" target="_blank" :href="'https://map.qq.com/?type=marker&isopeninfowin=1&markertype=1&pointx=' + locationY + '&pointy=' + locationX + '&name=' + label + '&ref=yudao'">
<img :src="'https://apis.map.qq.com/ws/staticmap/v2/?zoom=10&markers=color:blue|label:A|' + locationX + ',' + locationY + '&key=' + qqMapKey + '&size=250*180'">
<p/><i class="el-icon-map-location"></i>{{label}}
</el-link>
</div>
</template>
<script>
export default {
name: "wxLocation",
props: {
locationX: {
required: true,
type: Number
},
locationY: {
required: true,
type: Number
},
label: { // 地名
required: true,
type: String
},
qqMapKey: { // QQ 地图的密钥 https://lbs.qq.com/service/staticV2/staticGuide/staticDoc
required: false,
type: String,
default: 'TVDBZ-TDILD-4ON4B-PFDZA-RNLKH-VVF6E' // 需要自定义
}
}
};
</script>

View File

@@ -0,0 +1,247 @@
<!--
- Copyright (C) 2018-2019
- All rights reserved, Designed By www.joolun.com
芋道源码
移除 avue 组件使用 ElementUI 原生组件
-->
<template>
<!-- 类型图片 -->
<div v-if="objData.type === 'image'">
<div class="waterfall" v-loading="loading">
<div class="waterfall-item" v-for="item in list" :key="item.mediaId">
<img class="material-img" :src="item.url">
<p class="item-name">{{item.name}}</p>
<el-row class="ope-row">
<el-button size="mini" type="success" @click="selectMaterial(item)">选择
<i class="el-icon-circle-check el-icon--right"></i>
</el-button>
</el-row>
</div>
</div>
<!-- 分页组件 -->
<pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNo" :limit.sync="queryParams.pageSize"
@pagination="getMaterialPage"/>
</div>
<!-- 类型语音 -->
<div v-else-if="objData.type === 'voice'">
<!-- 列表 -->
<el-table v-loading="loading" :data="list">
<el-table-column label="编号" align="center" prop="mediaId" />
<el-table-column label="文件名" align="center" prop="name" />
<el-table-column label="语音" align="center">
<template v-slot="scope">
<wx-voice-player :url="scope.row.url" />
</template>
</el-table-column>
<el-table-column label="上传时间" align="center" prop="createTime" width="180">
<template v-slot="scope">
<span>{{ parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" fixed="right" class-name="small-padding fixed-width">
<template v-slot="scope">
<el-button size="mini" type="text" icon="el-icon-circle-plus"
@click="selectMaterial(scope.row)">选择</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页组件 -->
<pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNo" :limit.sync="queryParams.pageSize"
@pagination="getPage"/>
</div>
<div v-else-if="objData.type === 'video'">
<!-- 列表 -->
<el-table v-loading="loading" :data="list">
<el-table-column label="编号" align="center" prop="mediaId" />
<el-table-column label="文件名" align="center" prop="name" />
<el-table-column label="标题" align="center" prop="title" />
<el-table-column label="介绍" align="center" prop="introduction" />
<el-table-column label="视频" align="center">
<template v-slot="scope">
<wx-video-player :url="scope.row.url" />
</template>
</el-table-column>
<el-table-column label="上传时间" align="center" prop="createTime" width="180">
<template v-slot="scope">
<span>{{ parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" fixed="right" class-name="small-padding fixed-width">
<template v-slot="scope">
<el-button size="mini" type="text" icon="el-icon-circle-plus"
@click="selectMaterial(scope.row)">选择</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页组件 -->
<pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNo" :limit.sync="queryParams.pageSize"
@pagination="getMaterialPage"/>
</div>
<div v-else-if="objData.type === 'news'">
<div class="waterfall" v-loading="loading">
<div class="waterfall-item" v-for="item in list" :key="item.mediaId" v-if="item.content && item.content.newsItem">
<wx-news :articles="item.content.newsItem" />
<el-row class="ope-row">
<el-button size="mini" type="success" @click="selectMaterial(item)">
选择<i class="el-icon-circle-check el-icon--right"></i>
</el-button>
</el-row>
</div>
</div>
<!-- 分页组件 -->
<pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNo" :limit.sync="queryParams.pageSize"
@pagination="getMaterialPage"/>
</div>
</template>
<script>
import WxNews from '@/views/mp/components/wx-news/main.vue';
import WxVoicePlayer from '@/views/mp/components/wx-voice-play/main.vue';
import WxVideoPlayer from '@/views/mp/components/wx-video-play/main.vue';
import { getMaterialPage } from "@/api/mp/material";
import { getFreePublishPage } from "@/api/mp/freePublish";
import { getDraftPage } from "@/api/mp/draft";
export default {
name: "wxMaterialSelect",
components: {
WxNews,
WxVoicePlayer,
WxVideoPlayer
},
props: {
objData: {
type: Object, // type - 类型accountId - 公众号账号编号
required: true
},
newsType:{ // 图文类型1、已发布图文2、草稿箱图文
type: String,
default: "1"
},
},
data() {
return {
// 遮罩层
loading: false,
// 总条数
total: 0,
// 数据列表
list: [],
// 查询参数
queryParams: {
pageNo: 1,
pageSize: 10,
accountId: this.objData.accountId,
},
}
},
created() {
this.getPage()
},
methods:{
selectMaterial(item) {
this.$emit('selectMaterial', item)
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNo = 1
this.getPage()
},
getPage() {
this.loading = true
if (this.objData.type === 'news' && this.newsType === '1') { // 【图文】+ 【已发布】
this.getFreePublishPage();
} else if (this.objData.type === 'news' && this.newsType === '2') { // 【图文】+ 【草稿】
this.getDraftPage();
} else { // 【素材】
this.getMaterialPage();
}
},
getMaterialPage() {
getMaterialPage({
...this.queryParams,
type: this.objData.type
}).then(response => {
this.list = response.data.list
this.total = response.data.total
}).finally(() => {
this.loading = false
})
},
getFreePublishPage() {
getFreePublishPage(this.queryParams).then(response => {
// 将 thumbUrl 转成 picUrl保证 wx-news 组件可以预览封面
response.data.list.forEach(item => {
const newsItem = item.content.newsItem;
newsItem.forEach(article => {
article.picUrl = article.thumbUrl;
})
})
this.list = response.data.list
this.total = response.data.total
}).finally(() => {
this.loading = false
})
},
getDraftPage() {
getDraftPage((this.queryParams)).then(response => {
// 将 thumbUrl 转成 picUrl保证 wx-news 组件可以预览封面
response.data.list.forEach(item => {
const newsItem = item.content.newsItem;
newsItem.forEach(article => {
article.picUrl = article.thumbUrl;
})
})
this.list = response.data.list
this.total = response.data.total
}).finally(() => {
this.loading = false
})
}
}
};
</script>
<style lang="scss" scoped>
/*瀑布流样式*/
.waterfall {
width: 100%;
column-gap:10px;
column-count: 5;
margin: 0 auto;
}
.waterfall-item {
padding: 10px;
margin-bottom: 10px;
break-inside: avoid;
border: 1px solid #eaeaea;
}
.material-img {
width: 100%;
}
p {
line-height: 30px;
}
@media (min-width: 992px) and (max-width: 1300px) {
.waterfall {
column-count: 3;
}
p {
color:red;
}
}
@media (min-width: 768px) and (max-width: 991px) {
.waterfall {
column-count: 2;
}
p {
color: orange;
}
}
@media (max-width: 767px) {
.waterfall {
column-count: 1;
}
}
/*瀑布流样式*/
</style>

View File

@@ -0,0 +1,101 @@
.avue-card{
&__item{
margin-bottom: 16px;
border: 1px solid #e8e8e8;
background-color: #fff;
box-sizing: border-box;
color: rgba(0,0,0,.65);
font-size: 14px;
font-variant: tabular-nums;
line-height: 1.5;
list-style: none;
font-feature-settings: "tnum";
cursor: pointer;
height:200px;
&:hover{
border-color: rgba(0,0,0,.09);
box-shadow: 0 2px 8px rgba(0,0,0,.09);
}
&--add{
border:1px dashed #000;
width: 100%;
color: rgba(0,0,0,.45);
background-color: #fff;
border-color: #d9d9d9;
border-radius: 2px;
display: flex;
align-items: center;
justify-content: center;
font-size: 16px;
i{
margin-right: 10px;
}
&:hover{
color: #40a9ff;
background-color: #fff;
border-color: #40a9ff;
}
}
}
&__body{
display: flex;
padding: 24px;
}
&__detail{
flex:1
}
&__avatar{
width: 48px;
height: 48px;
border-radius: 48px;
overflow: hidden;
margin-right: 12px;
img{
width: 100%;
height: 100%;
}
}
&__title{
color: rgba(0,0,0,.85);
margin-bottom: 12px;
font-size: 16px;
&:hover{
color:#1890ff;
}
}
&__info{
color: rgba(0,0,0,.45);
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 3;
overflow: hidden;
height: 64px;
}
&__menu{
display: flex;
justify-content:space-around;
height: 50px;
background: #f7f9fa;
color: rgba(0,0,0,.45);
text-align: center;
line-height: 50px;
&:hover{
color:#1890ff;
}
}
}
/** joolun 额外加的 */
.avue-comment__main {
flex: unset!important;
border-radius: 5px!important;
margin: 0 8px!important;
}
.avue-comment__header {
border-top-left-radius: 5px;
border-top-right-radius: 5px;
}
.avue-comment__body {
border-bottom-right-radius: 5px;
border-bottom-left-radius: 5px;
}

View File

@@ -0,0 +1,88 @@
/* 来自 https://github.com/nmxiaowei/avue/blob/master/styles/src/element-ui/comment.scss */
.avue-comment{
margin-bottom: 30px;
display: flex;
align-items: flex-start;
&--reverse{
flex-direction:row-reverse;
.avue-comment__main{
&:before,&:after{
left: auto;
right: -8px;
border-width: 8px 0 8px 8px;
}
&:before{
border-left-color: #dedede;
}
&:after{
border-left-color: #f8f8f8;
margin-right: 1px;
margin-left: auto;
}
}
}
&__avatar{
width: 48px;
height: 48px;
border-radius: 50%;
border: 1px solid transparent;
box-sizing: border-box;
vertical-align: middle;
}
&__header{
padding: 5px 15px;
background: #f8f8f8;
border-bottom: 1px solid #eee;
display: flex;
align-items: center;
justify-content: space-between;
}
&__author{
font-weight: 700;
font-size: 14px;
color: #999;
}
&__main{
flex:1;
margin: 0 20px;
position: relative;
border: 1px solid #dedede;
border-radius: 2px;
&:before,&:after{
position: absolute;
top: 10px;
left: -8px;
right: 100%;
width: 0;
height: 0;
display: block;
content: " ";
border-color: transparent;
border-style: solid solid outset;
border-width: 8px 8px 8px 0;
pointer-events: none;
}
&:before {
border-right-color: #dedede;
z-index: 1;
}
&:after{
border-right-color: #f8f8f8;
margin-left: 1px;
z-index: 2;
}
}
&__body{
padding: 15px;
overflow: hidden;
background: #fff;
font-family: Segoe UI,Lucida Grande,Helvetica,Arial,Microsoft YaHei,FreeSans,Arimo,Droid Sans,wenquanyi micro hei,Hiragino Sans GB,Hiragino Sans GB W3,FontAwesome,sans-serif;color: #333;
font-size: 14px;
}
blockquote{
margin:0;
font-family: Georgia,Times New Roman,Times,Kai,Kaiti SC,KaiTi,BiauKai,FontAwesome,serif;
padding: 1px 0 1px 15px;
border-left: 4px solid #ddd;
}
}

View File

@@ -0,0 +1,294 @@
<!--
- Copyright (C) 2018-2019
- All rights reserved, Designed By www.joolun.com
芋道源码
移除暂时用不到的 websocket
代码优化补充注释提升阅读性
-->
<template>
<div class="msg-main">
<div class="msg-div" :id="'msg-div' + nowStr">
<!-- 加载更多 -->
<div v-loading="loading"></div>
<div v-if="!loading">
<div class="el-table__empty-block" v-if="loadMore" @click="loadingMore"><span class="el-table__empty-text">点击加载更多</span></div>
<div class="el-table__empty-block" v-if="!loadMore"><span class="el-table__empty-text">没有更多了</span></div>
</div>
<!-- 消息列表 -->
<div class="execution" v-for="item in list" :key='item.id'>
<div class="avue-comment" :class="item.sendFrom === 2 ? 'avue-comment--reverse' : ''">
<div class="avatar-div">
<img :src="item.sendFrom === 1 ? user.avatar : mp.avatar" class="avue-comment__avatar">
<div class="avue-comment__author">{{item.sendFrom === 1 ? user.nickname : mp.nickname }}</div>
</div>
<div class="avue-comment__main">
<div class="avue-comment__header">
<div class="avue-comment__create_time">{{ parseTime(item.createTime) }}</div>
</div>
<div class="avue-comment__body" :style="item.sendFrom === 2 ? 'background: #6BED72;' : ''">
<!-- 事件区域 -->
<div v-if="item.type === 'event' && item.event === 'subscribe'">
<el-tag type="success" size="mini">关注</el-tag>
</div>
<div v-else-if="item.type === 'event' && item.event === 'unsubscribe'">
<el-tag type="danger" size="mini">取消关注</el-tag>
</div>
<div v-else-if="item.type === 'event' && item.event === 'CLICK'">
<el-tag size="mini">点击菜单</el-tag>{{ item.eventKey }}
</div>
<div v-else-if="item.type === 'event' && item.event === 'VIEW'">
<el-tag size="mini">点击菜单链接</el-tag>{{ item.eventKey }}
</div>
<div v-else-if="item.type === 'event' && item.event === 'scancode_waitmsg'">
<el-tag size="mini">扫码结果</el-tag>{{ item.eventKey }}
</div>
<div v-else-if="item.type === 'event' && item.event === 'scancode_push'">
<el-tag size="mini">扫码结果</el-tag>{{ item.eventKey }}
</div>
<div v-else-if="item.type === 'event' && item.event === 'pic_sysphoto'">
<el-tag size="mini">系统拍照发图</el-tag>
</div>
<div v-else-if="item.type === 'event' && item.event === 'pic_photo_or_album'">
<el-tag size="mini">拍照或者相册</el-tag>
</div>
<div v-else-if="item.type === 'event' && item.event === 'pic_weixin'">
<el-tag size="mini">微信相册</el-tag>
</div>
<div v-else-if="item.type === 'event' && item.event === 'location_select'">
<el-tag size="mini">选择地理位置</el-tag>
</div>
<div v-else-if="item.type === 'event'">
<el-tag type="danger" size="mini">未知事件类型</el-tag>
</div>
<!-- 消息区域 -->
<div v-else-if="item.type === 'text'">{{ item.content }}</div>
<div v-else-if="item.type === 'voice'">
<wx-voice-player :url="item.mediaUrl" :content="item.recognition" />
</div>
<div v-else-if="item.type === 'image'">
<a target="_blank" :href="item.mediaUrl">
<img :src="item.mediaUrl" style="width: 100px">
</a>
</div>
<div v-else-if="item.type === 'video' || item.type === 'shortvideo'" style="text-align: center">
<wx-video-player :url="item.mediaUrl" />
</div>
<div v-else-if="item.type === 'link'" class="avue-card__detail">
<el-link type="success" :underline="false" target="_blank" :href="item.url">
<div class="avue-card__title"><i class="el-icon-link"></i>{{ item.title }}</div>
</el-link>
<div class="avue-card__info" style="height: unset">{{item.description}}</div>
</div>
<!-- TODO 芋艿待完善 -->
<div v-else-if="item.type === 'location'">
<wx-location :label="item.label" :location-y="item.locationY" :location-x="item.locationX" />
</div>
<div v-else-if="item.type === 'news'" style="width: 300px"> <!-- TODO 芋艿待测试详情页也存在类似的情况 -->
<wx-news :articles="item.articles" />
</div>
<div v-else-if="item.type === 'music'">
<wx-music :title="item.title" :description="item.description" :thumb-media-url="item.thumbMediaUrl"
:music-url="item.musicUrl" :hq-music-url="item.hqMusicUrl" />
</div>
</div>
</div>
</div>
</div>
</div>
<div class="msg-send" v-loading="sendLoading">
<wx-reply-select ref="replySelect" :objData="objData" />
<el-button type="success" size="small" class="send-but" @click="sendMsg">发送(S)</el-button>
</div>
</div>
</template>
<script>
import { getMessagePage, sendMessage } from '@/api/mp/message'
import WxReplySelect from '@/views/mp/components/wx-reply/main.vue'
import WxVideoPlayer from '@/views/mp/components/wx-video-play/main.vue';
import WxVoicePlayer from '@/views/mp/components/wx-voice-play/main.vue';
import WxNews from '@/views/mp/components/wx-news/main.vue';
import WxLocation from '@/views/mp/components/wx-location/main.vue';
import WxMusic from '@/views/mp/components/wx-music/main.vue';
import { getUser } from "@/api/mp/user";
export default {
name: "wxMsg",
components: {
WxReplySelect,
WxVideoPlayer,
WxVoicePlayer,
WxNews,
WxLocation,
WxMusic
},
props: {
userId: {
type: Number,
required: true
},
},
data() {
return {
nowStr: new Date().getTime(), // 当前的时间戳,用于每次消息加载后,回到原位置;具体见 :id="'msg-div' + nowStr" 处
loading: false, // 消息列表是否正在加载中
loadMore: true, // 是否可以加载更多
list: [], // 消息列表
queryParams: {
pageNo: 1, // 当前页数
pageSize: 14, // 每页显示多少条
accountId: undefined,
},
user: { // 由于微信不再提供昵称,直接使用“用户”展示
nickname: '用户',
avatar: require("@/assets/images/profile.jpg"),
accountId: 0, // 公众号账号编号
},
mp: {
nickname: '公众号',
avatar: require("@/assets/images/wechat.png"),
},
// ========= 消息发送 =========
sendLoading: false, // 发送消息是否加载中
objData: { // 微信发送消息
type: 'text',
},
}
},
created() {
// 获得用户信息
getUser(this.userId).then(response => {
this.user.nickname = response.data.nickname && response.data.nickname.length > 0 ?
response.data.nickname : this.user.nickname;
this.user.avatar = response.data.avatar && this.user.avatar.length > 0 ?
response.data.avatar : this.user.avatar;
this.user.accountId = response.data.accountId;
// 设置公众号账号编号
this.queryParams.accountId = response.data.accountId;
this.objData.accountId = response.data.accountId;
// 加载消息
console.log(this.queryParams)
this.refreshChange()
})
},
methods:{
sendMsg(){
if (!this.objData) {
return;
}
// 公众号限制:客服消息,公众号只允许发送一条
if (this.objData.type === 'news'
&& this.objData.articles.length > 1) {
this.objData.articles = [this.objData.articles[0]]
this.$message({
showClose: true,
message: '图文消息条数限制在 1 条以内,已默认发送第一条',
type: 'success'
})
}
// 执行发送
this.sendLoading = true
sendMessage(Object.assign({
userId: this.userId
}, {
...this.objData
})).then(response => {
this.sendLoading = false
// 添加到消息列表,并滚动
this.list = [...this.list , ...[response.data] ]
this.scrollToBottom()
// 重置 objData 状态
this.$refs['replySelect'].deleteObj(); // 重置,避免 tab 的数据未清理
}).catch(() => {
this.sendLoading = false
})
},
loadingMore() {
this.queryParams.pageNo++
this.getPage(this.queryParams)
},
getPage(page, params) {
this.loading = true
getMessagePage(Object.assign({
pageNo: page.pageNo,
pageSize: page.pageSize,
userId: this.userId,
accountId: page.accountId,
}, params)).then(response => {
// 计算当前的滚动高度
const msgDiv = document.getElementById('msg-div' + this.nowStr);
let scrollHeight = 0
if(msgDiv){
scrollHeight = msgDiv.scrollHeight
}
// 处理数据
const data = response.data.list.reverse();
this.list = [...data, ...this.list]
this.loading = false
if (data.length < this.queryParams.pageSize || data.length === 0){
this.loadMore = false
}
this.queryParams.pageNo = page.pageNo
this.queryParams.pageSize = page.pageSize
// 滚动到原来的位置
if(this.queryParams.pageNo === 1) { // 定位到消息底部
this.scrollToBottom()
} else if (data.length !== 0) { // 定位滚动条
this.$nextTick(() => {
if (scrollHeight !== 0){
msgDiv.scrollTop = document.getElementById('msg-div'+this.nowStr).scrollHeight - scrollHeight - 100
}
})
}
})
},
/**
* 刷新回调
*/
refreshChange() {
this.getPage(this.queryParams)
},
/** 定位到消息底部 */
scrollToBottom: function () {
this.$nextTick(() => {
let div = document.getElementById('msg-div' + this.nowStr)
div.scrollTop = div.scrollHeight
})
},
}
};
</script>
<style lang="scss" scoped>
/* 因为 joolun 实现依赖 avue 组件,该页面使用了 comment.scss、card.scc */
@import './comment.scss';
@import './card.scss';
.msg-main {
margin-top: -30px;
padding: 10px;
}
.msg-div {
height: 50vh;
overflow: auto;
background-color: #eaeaea;
margin-left: 10px;
margin-right: 10px;
}
.msg-send {
padding: 10px;
}
.avatar-div {
text-align: center;
width: 80px;
}
.send-but {
float: right;
margin-top: 8px!important;
}
</style>

View File

@@ -0,0 +1,52 @@
<!--
微信消息 - 音乐
-->
<template>
<div>
<el-link type="success" :underline="false" target="_blank" :href="hqMusicUrl ? hqMusicUrl : musicUrl">
<div class="avue-card__body" style="padding:10px;background-color: #fff;border-radius: 5px">
<div class="avue-card__avatar">
<img :src="thumbMediaUrl" alt=""/>
</div>
<div class="avue-card__detail">
<div class="avue-card__title" style="margin-bottom:unset">{{ title }}</div>
<div class="avue-card__info" style="height: unset">{{ description }}</div>
</div>
</div>
</el-link>
</div>
</template>
<script>
export default {
name: "wxMusic",
props: {
title: {
required: false,
type: String
},
description: {
required: false,
type: String
},
musicUrl: {
required: false,
type: String
},
hqMusicUrl: {
required: false,
type: String
},
thumbMediaUrl: {
required: true,
type: String
},
}
};
</script>
<style lang="scss" scoped>
/* 因为 joolun 实现依赖 avue 组件,该页面使用了 card.scc */
@import '../wx-msg/card.scss';
</style>

View File

@@ -0,0 +1,104 @@
<!--
- Copyright (C) 2018-2019
- All rights reserved, Designed By www.joolun.com
微信消息 - 图文
芋道源码
代码优化补充注释提升阅读性
-->
<template>
<div class="news-home">
<div v-for="(article, index) in articles" :key="index" class="news-div">
<!-- 头条 -->
<a target="_blank" :href="article.url" v-if="index === 0">
<div class="news-main">
<div class="news-content">
<img class="material-img" :src="article.picUrl" width="280px" height="120px"/>
<div class="news-content-title">
<span>{{article.title}}</span>
</div>
</div>
</div>
</a>
<!-- 二条/三条等等 -->
<a target="_blank" :href="article.url" v-else>
<div class="news-main-item">
<div class="news-content-item">
<div class="news-content-item-title">{{article.title}}</div>
<div class="news-content-item-img">
<img class="material-img" :src="article.picUrl" height="100%"/>
</div>
</div>
</div>
</a>
</div>
</div>
</template>
<script>
export default {
name: "wxNews",
props: {
articles: {
type: Array // title - 标题description - 描述picUrl - 图片连接url - 跳转链接
}
},
// created() {
// console.log(this.articles)
// },
};
</script>
<style lang="scss" scoped>
.news-home {
background-color: #FFFFFF;
width: 100%;
margin: auto;
}
.news-main {
width: 100%;
margin: auto;
}
.news-content {
background-color: #acadae;
width: 100%;
position: relative;
}
.news-content-title {
display: inline-block;
font-size: 12px;
color: #FFFFFF;
position: absolute;
left: 0;
bottom: 0;
background-color: black;
width: 98%;
padding: 1%;
opacity: 0.65;
white-space: normal;
box-sizing: unset!important
}
.news-main-item {
background-color: #FFFFFF;
padding: 5px 0;
border-top: 1px solid #eaeaea;
}
.news-content-item {
position: relative;
}
.news-content-item-title {
display: inline-block;
font-size: 10px;
width: 70%;
margin-left: 1%;
white-space: normal
}
.news-content-item-img {
display: inline-block;
width: 25%;
background-color: #acadae;
margin-right: 1%;
}
.material-img {
width: 100%;
}
</style>

View File

@@ -0,0 +1,547 @@
<!--
- Copyright (C) 2018-2019
- All rights reserved, Designed By www.joolun.com
芋道源码
移除多余的 rep 为前缀的变量 message 消息更简单
代码优化补充注释提升阅读性
优化消息的临时缓存策略发送消息时只清理被发送消息的 tab不会强制切回到 text 输入
支持发送视频消息时支持新建视频
-->
<template>
<el-tabs type="border-card" v-model="objData.type" @tab-click="handleClick">
<!-- 类型 1文本 -->
<el-tab-pane name="text">
<span slot="label"><i class="el-icon-document"></i> 文本</span>
<el-input type="textarea" :rows="5" placeholder="请输入内容" v-model="objData.content" @input="inputContent" />
</el-tab-pane>
<!-- 类型 2图片 -->
<el-tab-pane name="image">
<span slot="label"><i class="el-icon-picture"></i> 图片</span>
<el-row>
<!-- 情况一已经选择好素材或者上传好图片 -->
<div class="select-item" v-if="objData.url">
<img class="material-img" :src="objData.url">
<p class="item-name" v-if="objData.name">{{objData.name}}</p>
<el-row class="ope-row">
<el-button type="danger" icon="el-icon-delete" circle @click="deleteObj"></el-button>
</el-row>
</div>
<!-- 情况二未做完上述操作 -->
<div v-else>
<el-row style="text-align: center">
<!-- 选择素材 -->
<el-col :span="12" class="col-select">
<el-button type="success" @click="openMaterial">
素材库选择<i class="el-icon-circle-check el-icon--right"></i>
</el-button>
<el-dialog title="选择图片" :visible.sync="dialogImageVisible" width="90%" append-to-body>
<wx-material-select :obj-data="objData" @selectMaterial="selectMaterial" />
</el-dialog>
</el-col>
<!-- 文件上传 -->
<el-col :span="12" class="col-add">
<el-upload :action="actionUrl" :headers="headers" multiple :limit="1" :file-list="fileList" :data="uploadData"
:before-upload="beforeImageUpload" :on-success="handleUploadSuccess">
<el-button type="primary">上传图片</el-button>
<div slot="tip" class="el-upload__tip">支持 bmp/png/jpeg/jpg/gif 格式大小不超过 2M</div>
</el-upload>
</el-col>
</el-row>
</div>
</el-row>
</el-tab-pane>
<!-- 类型 3语音 -->
<el-tab-pane name="voice">
<span slot="label"><i class="el-icon-phone"></i> 语音</span>
<el-row>
<div class="select-item2" v-if="objData.url">
<p class="item-name">{{objData.name}}</p>
<div class="item-infos">
<wx-voice-player :url="objData.url" />
</div>
<el-row class="ope-row">
<el-button type="danger" icon="el-icon-delete" circle @click="deleteObj"></el-button>
</el-row>
</div>
<div v-else>
<el-row style="text-align: center">
<!-- 选择素材 -->
<el-col :span="12" class="col-select">
<el-button type="success" @click="openMaterial">
素材库选择<i class="el-icon-circle-check el-icon--right"></i>
</el-button>
<el-dialog title="选择语音" :visible.sync="dialogVoiceVisible" width="90%" append-to-body>
<WxMaterialSelect :objData="objData" @selectMaterial="selectMaterial"></WxMaterialSelect>
</el-dialog>
</el-col>
<!-- 文件上传 -->
<el-col :span="12" class="col-add">
<el-upload :action="actionUrl" :headers="headers" multiple :limit="1" :file-list="fileList" :data="uploadData"
:before-upload="beforeVoiceUpload" :on-success="handleUploadSuccess">
<el-button type="primary">点击上传</el-button>
<div slot="tip" class="el-upload__tip">格式支持 mp3/wma/wav/amr文件大小不超过 2M播放长度不超过 60s</div>
</el-upload>
</el-col>
</el-row>
</div>
</el-row>
</el-tab-pane>
<!-- 类型 4视频 -->
<el-tab-pane name="video">
<span slot="label"><i class="el-icon-share"></i> 视频</span>
<el-row>
<el-input v-model="objData.title" placeholder="请输入标题" @input="inputContent" />
<div style="margin: 20px 0;"></div>
<el-input v-model="objData.description" placeholder="请输入描述" @input="inputContent" />
<div style="margin: 20px 0;"></div>
<div style="text-align: center;">
<wx-video-player v-if="objData.url" :url="objData.url" />
</div>
<div style="margin: 20px 0;"></div>
<el-row style="text-align: center">
<!-- 选择素材 -->
<el-col :span="12">
<el-button type="success" @click="openMaterial">
素材库选择<i class="el-icon-circle-check el-icon--right"></i>
</el-button>
<el-dialog title="选择视频" :visible.sync="dialogVideoVisible" width="90%" append-to-body>
<wx-material-select :objData="objData" @selectMaterial="selectMaterial" />
</el-dialog>
</el-col>
<!-- 文件上传 -->
<el-col :span="12">
<el-upload :action="actionUrl" :headers="headers" multiple :limit="1" :file-list="fileList" :data="uploadData"
:before-upload="beforeVideoUpload" :on-success="handleUploadSuccess">
<el-button type="primary">新建视频<i class="el-icon-upload el-icon--right"></i></el-button>
</el-upload>
</el-col>
</el-row>
</el-row>
</el-tab-pane>
<!-- 类型 5图文 -->
<el-tab-pane name="news">
<span slot="label"><i class="el-icon-news"></i> 图文</span>
<el-row>
<div class="select-item" v-if="objData.articles">
<wx-news :articles="objData.articles" />
<el-row class="ope-row">
<el-button type="danger" icon="el-icon-delete" circle @click="deleteObj" />
</el-row>
</div>
<!-- 选择素材 -->
<div v-if="!objData.content">
<el-row style="text-align: center">
<el-col :span="24">
<el-button type="success" @click="openMaterial">{{newsType === '1' ? '选择已发布图文' : '选择草稿箱图文'}}<i class="el-icon-circle-check el-icon--right"></i></el-button>
</el-col>
</el-row>
</div>
<el-dialog title="选择图文" :visible.sync="dialogNewsVisible" width="90%" append-to-body>
<wx-material-select :objData="objData" @selectMaterial="selectMaterial" :newsType="newsType" />
</el-dialog>
</el-row>
</el-tab-pane>
<!-- 类型 6音乐 -->
<el-tab-pane name="music">
<span slot="label"><i class="el-icon-service"></i> 音乐</span>
<el-row>
<el-col :span="6">
<div class="thumb-div">
<img style="width: 100px" v-if="objData.thumbMediaUrl" :src="objData.thumbMediaUrl">
<i v-else class="el-icon-plus avatar-uploader-icon"></i>
<div class="thumb-but">
<el-upload :action="actionUrl" :headers="headers" multiple :limit="1" :file-list="fileList" :data="uploadData"
:before-upload="beforeThumbImageUpload" :on-success="handleUploadSuccess">
<el-button slot="trigger" size="mini" type="text">本地上传</el-button>
<el-button size="mini" type="text" @click="openMaterial" style="margin-left: 5px">素材库选择</el-button>
</el-upload>
</div>
</div>
<el-dialog title="选择图片" :visible.sync="dialogThumbVisible" width="80%" append-to-body>
<wx-material-select :objData="{type:'image', accountId: objData.accountId}" @selectMaterial="selectMaterial" />
</el-dialog>
</el-col>
<el-col :span="18">
<el-input v-model="objData.title" placeholder="请输入标题" @input="inputContent" />
<div style="margin: 20px 0;"></div>
<el-input v-model="objData.description" placeholder="请输入描述" @input="inputContent" />
</el-col>
</el-row>
<div style="margin: 20px 0;"></div>
<el-input v-model="objData.musicUrl" placeholder="请输入音乐链接" @input="inputContent" />
<div style="margin: 20px 0;"></div>
<el-input v-model="objData.hqMusicUrl" placeholder="请输入高质量音乐链接" @input="inputContent" />
</el-tab-pane>
</el-tabs>
</template>
<script>
import WxNews from '@/views/mp/components/wx-news/main.vue'
import WxMaterialSelect from '@/views/mp/components/wx-material-select/main.vue'
import WxVoicePlayer from '@/views/mp/components/wx-voice-play/main.vue';
import WxVideoPlayer from '@/views/mp/components/wx-video-play/main.vue';
import { getAccessToken } from '@/utils/auth'
export default {
name: "wxReplySelect",
components: {
WxNews,
WxMaterialSelect,
WxVoicePlayer,
WxVideoPlayer
},
props: {
objData: { // 消息对象。
type: Object, // 设置为 Object 的原因,方便属性的传递
required: true,
},
newsType:{ // 图文类型1、已发布图文2、草稿箱图文
type: String,
default: "1"
},
},
data() {
return {
tempPlayerObj: {
type: '2'
},
tempObj: new Map().set( // 临时缓存,切换消息类型的 tab 的时候,可以保存对应的数据;
this.objData.type, // 消息类型
Object.assign({}, this.objData)), // 消息内容
// ========== 素材选择的弹窗,是否可见 ==========
dialogNewsVisible: false, // 图文
dialogImageVisible: false, // 图片
dialogVoiceVisible: false, // 语音
dialogVideoVisible: false, // 视频
dialogThumbVisible: false, // 缩略图
// ========== 文件上传(图片、语音、视频) ==========
fileList: [], // 文件列表
uploadData: {
"accountId": undefined,
"type": this.objData.type,
"title":'',
"introduction":''
},
actionUrl: process.env.VUE_APP_BASE_API + '/admin-api/mp/material/upload-temporary',
headers: { Authorization: "Bearer " + getAccessToken() }, // 设置上传的请求头部
}
},
methods:{
beforeThumbImageUpload(file){
const isType = file.type === 'image/jpeg'
|| file.type === 'image/png'
|| file.type === 'image/gif'
|| file.type === 'image/bmp'
|| file.type === 'image/jpg';
if (!isType) {
this.$message.error('上传图片格式不对!');
return false;
}
const isLt = file.size / 1024 / 1024 < 2;
if (!isLt) {
this.$message.error('上传图片大小不能超过 2M!');
return false;
}
this.uploadData.accountId = this.objData.accountId;
return true;
},
beforeVoiceUpload(file){
// 校验格式
const isType = file.type === 'audio/mp3'
|| file.type === 'audio/mpeg'
|| file.type === 'audio/wma'
|| file.type === 'audio/wav'
|| file.type === 'audio/amr';
if (!isType) {
this.$message.error('上传语音格式不对!' + file.type);
return false;
}
// 校验大小
const isLt = file.size / 1024 / 1024 < 2;
if (!isLt) {
this.$message.error('上传语音大小不能超过 2M!');
return false;
}
this.uploadData.accountId = this.objData.accountId;
return true;
},
beforeImageUpload(file) {
// 校验格式
const isType = file.type === 'image/jpeg'
|| file.type === 'image/png'
|| file.type === 'image/gif'
|| file.type === 'image/bmp'
|| file.type === 'image/jpg';
if (!isType) {
this.$message.error('上传图片格式不对!');
return false;
}
// 校验大小
const isLt = file.size / 1024 / 1024 < 2;
if (!isLt) {
this.$message.error('上传图片大小不能超过 2M!');
return false;
}
this.uploadData.accountId = this.objData.accountId;
return true;
},
beforeVideoUpload(file){
// 校验格式
const isType = file.type === 'video/mp4';
if (!isType) {
this.$message.error('上传视频格式不对!');
return false;
}
// 校验大小
const isLt = file.size / 1024 / 1024 < 10;
if (!isLt) {
this.$message.error('上传视频大小不能超过 10M!');
return false;
}
this.uploadData.accountId = this.objData.accountId;
return true;
},
handleUploadSuccess(response, file, fileList) {
if (response.code !== 0) {
this.$message.error('上传出错:' + response.msg)
return false;
}
// 清空上传时的各种数据
this.fileList = []
this.uploadData.title = ''
this.uploadData.introduction = ''
// 上传好的文件,本质是个素材,所以可以进行选中
let item = response.data
this.selectMaterial(item)
},
/**
* 切换消息类型的 tab
*
* @param tab tab
*/
handleClick(tab) {
// 设置后续文件上传的文件类型
this.uploadData.type = this.objData.type;
if (this.uploadData.type === 'music') { // 【音乐】上传的是缩略图
this.uploadData.type = 'thumb';
}
// 从 tempObj 临时缓存中,获取对应的数据,并设置回 objData
let tempObjItem = this.tempObj.get(this.objData.type)
if (tempObjItem) {
this.objData.content = tempObjItem.content ? tempObjItem.content : null
this.objData.mediaId = tempObjItem.mediaId ? tempObjItem.mediaId : null
this.objData.url = tempObjItem.url ? tempObjItem.url : null
this.objData.name = tempObjItem.url ? tempObjItem.name : null
this.objData.title = tempObjItem.title ? tempObjItem.title : null
this.objData.description = tempObjItem.description ? tempObjItem.description : null
return;
}
// 如果获取不到,需要把 objData 复原
// 必须使用 $set 赋值,不然 input 无法输入内容
this.$set(this.objData, 'content', '');
this.$delete(this.objData, 'mediaId');
this.$delete(this.objData, 'url');
this.$set(this.objData, 'title', '');
this.$set(this.objData, 'description', '');
},
/**
* 选择素材,将设置设置到 objData 变量
*
* @param item 素材
*/
selectMaterial(item) {
// 选择好素材,所以隐藏弹窗
this.closeMaterial();
// 创建 tempObjItem 对象,并设置对应的值
let tempObjItem = {}
tempObjItem.type = this.objData.type;
if (this.objData.type === 'news') {
tempObjItem.articles = item.content.newsItem
this.objData.articles = item.content.newsItem
} else if (this.objData.type === 'music') { // 音乐需要特殊处理,因为选择的是图片的缩略图
tempObjItem.thumbMediaId = item.mediaId
this.objData.thumbMediaId = item.mediaId
tempObjItem.thumbMediaUrl = item.url
this.objData.thumbMediaUrl = item.url
// title、introduction、musicUrl、hqMusicUrl从 objData 到 tempObjItem避免上传素材后被覆盖掉
tempObjItem.title = this.objData.title || ''
tempObjItem.introduction = this.objData.introduction || ''
tempObjItem.musicUrl = this.objData.musicUrl || ''
tempObjItem.hqMusicUrl = this.objData.hqMusicUrl || ''
} else if (this.objData.type === 'image'
|| this.objData.type === 'voice') {
tempObjItem.mediaId = item.mediaId
this.objData.mediaId = item.mediaId
tempObjItem.url = item.url;
this.objData.url = item.url;
tempObjItem.name = item.name
this.objData.name = item.name
} else if (this.objData.type === 'video') {
tempObjItem.mediaId = item.mediaId
this.objData.mediaId = item.mediaId
tempObjItem.url = item.url;
this.objData.url = item.url;
tempObjItem.name = item.name
this.objData.name = item.name
// title、introduction从 item 到 tempObjItem因为素材里有 title、introduction
if (item.title) {
this.objData.title = item.title || ''
tempObjItem.title = item.title || ''
}
if (item.introduction) {
this.objData.description = item.introduction || '' // 消息使用的是 description素材使用的是 introduction所以转换下
tempObjItem.description = item.introduction || ''
}
} else if (this.objData.type === 'text') {
this.objData.content = item.content || ''
}
// 最终设置到临时缓存
this.tempObj.set(this.objData.type, tempObjItem)
},
openMaterial() {
if (this.objData.type === 'news') {
this.dialogNewsVisible = true
} else if(this.objData.type === 'image') {
this.dialogImageVisible = true
} else if(this.objData.type === 'voice') {
this.dialogVoiceVisible = true
} else if(this.objData.type === 'video') {
this.dialogVideoVisible = true
} else if(this.objData.type === 'music') {
this.dialogThumbVisible = true
}
},
closeMaterial() {
this.dialogNewsVisible = false
this.dialogImageVisible = false
this.dialogVoiceVisible = false
this.dialogVideoVisible = false
this.dialogThumbVisible = false
},
deleteObj() {
if (this.objData.type === 'news') {
this.$delete(this.objData, 'articles');
} else if(this.objData.type === 'image') {
this.objData.mediaId = null
this.$delete(this.objData, 'url');
this.objData.name = null
} else if(this.objData.type === 'voice') {
this.objData.mediaId = null
this.$delete(this.objData, 'url');
this.objData.name = null
} else if(this.objData.type === 'video') {
this.objData.mediaId = null
this.$delete(this.objData, 'url');
this.objData.name = null
this.objData.title = null
this.objData.description = null
} else if(this.objData.type === 'music') {
this.objData.thumbMediaId = null
this.objData.thumbMediaUrl = null
this.objData.title = null
this.objData.description = null
this.objData.musicUrl = null
this.objData.hqMusicUrl = null
} else if(this.objData.type === 'text') {
this.objData.content = null
}
// 覆盖缓存
this.tempObj.set(this.objData.type, Object.assign({}, this.objData));
},
/**
* 输入时,缓存每次 objData 到 tempObj 中
*
* why不确定为什么 v-model="objData.content" 不能自动缓存,所以通过这样的方式
*/
inputContent(str) {
// 覆盖缓存
this.tempObj.set(this.objData.type, Object.assign({}, this.objData));
}
}
};
</script>
<style lang="scss" scoped>
.public-account-management{
.el-input{
width: 70%;
margin-right: 2%;
}
}
.pagination{
text-align: right;
margin-right: 25px;
}
.select-item{
width: 280px;
padding: 10px;
margin: 0 auto 10px auto;
border: 1px solid #eaeaea;
}
.select-item2{
padding: 10px;
margin: 0 auto 10px auto;
border: 1px solid #eaeaea;
}
.ope-row{
padding-top: 10px;
text-align: center;
}
.item-name{
font-size: 12px;
overflow: hidden;
text-overflow:ellipsis;
white-space: nowrap;
text-align: center;
}
.el-form-item__content{
line-height:unset!important;
}
.col-select{
border: 1px solid rgb(234, 234, 234);
padding: 50px 0px;
height: 160px;
width: 49.5%;
}
.col-select2{
border: 1px solid rgb(234, 234, 234);
padding: 50px 0px;
height: 160px;
}
.col-add{
border: 1px solid rgb(234, 234, 234);
padding: 50px 0px;
height: 160px;
width: 49.5%;
float: right
}
.avatar-uploader-icon {
border: 1px solid #d9d9d9;
font-size: 28px;
color: #8c939d;
width: 100px!important;
height: 100px!important;
line-height: 100px!important;
text-align: center;
}
.material-img {
width: 100%;
}
.thumb-div{
display: inline-block;
text-align: center;
}
.item-infos{
width: 30%;
margin: auto
}
</style>

View File

@@ -0,0 +1,91 @@
<!--
- Copyright (C) 2018-2019
- All rights reserved, Designed By www.joolun.com
微信消息 - 视频
芋道源码
bug 修复
1joolun 的做法使用 mediaId 从微信公众号下载对应的 mp4 素材从而播放内容
存在的问题mediaId 有效期是 3 超过时间后无法播放
2重构后的做法后端接收到微信公众号的视频消息后将视频消息的 media_id 的文件内容保存到文件服务器中这样前端可以直接使用 URL 播放
体验优化弹窗关闭后自动暂停视频的播放
-->
<template>
<div>
<!-- 提示 -->
<div @click="playVideo()">
<i class="el-icon-video-play" style="font-size: 40px!important;" ></i>
<p>点击播放视频</p>
</div>
<!-- 弹窗播放 -->
<el-dialog title="视频播放" :visible.sync="dialogVideo" width="40%" append-to-body @close="closeDialog">
<video-player v-if="playerOptions.sources[0].src" class="video-player vjs-custom-skin" ref="videoPlayer"
:playsinline="true" :options="playerOptions"
@play="onPlayerPlay($event)" @pause="onPlayerPause($event)">
</video-player>
</el-dialog>
</div>
</template>
<script>
// 引入 videoPlayer 相关组件。教程https://juejin.cn/post/6923056942281654285
import { videoPlayer } from 'vue-video-player'
require('video.js/dist/video-js.css')
require('vue-video-player/src/custom-theme.css')
export default {
name: "wxVideoPlayer",
props: {
url: { // 视频地址例如说https://www.iocoder.cn/xxx.mp4
type: String,
required: true
},
},
components: {
videoPlayer
},
data() {
return {
dialogVideo:false,
playerOptions: {
playbackRates: [0.5, 1.0, 1.5, 2.0], // 播放速度
autoplay: false, // 如果 true,浏览器准备好时开始回放。
muted: false, // 默认情况下将会消除任何音频。
loop: false, // 导致视频一结束就重新开始。
preload: 'auto', // 建议浏览器在 <video> 加载元素后是否应该开始下载视频数据。auto 浏览器选择最佳行为,立即开始加载视频(如果浏览器支持)
language: 'zh-CN',
aspectRatio: '16:9', // 将播放器置于流畅模式,并在计算播放器的动态大小时使用该值。值应该代表一个比例 - 用冒号分隔的两个数字(例如"16:9"或"4:3"
fluid: true, // 当true时Video.js player 将拥有流体大小。换句话说,它将按比例缩放以适应其容器。
sources: [{
type: "video/mp4",
src: "" // 你的视频地址(必填)【重要】
}],
poster: "", // 你的封面地址
width: document.documentElement.clientWidth,
notSupportedMessage: '此视频暂无法播放,请稍后再试', //允许覆盖 Video.js 无法播放媒体源时显示的默认信息。
controlBar: {
timeDivider: true,
durationDisplay: true,
remainingTimeDisplay: false,
fullscreenToggle: true //全屏按钮
}
}
}
},
methods: {
playVideo(){
this.dialogVideo = true
// 设置地址
this.$set(this.playerOptions.sources[0], 'src', this.url)
},
closeDialog(){
// 暂停播放
this.$refs.videoPlayer.player.pause()
},
onPlayerPlay(player) {
},
onPlayerPause(player) {
},
}
};
</script>

View File

@@ -0,0 +1,98 @@
<!--
- Copyright (C) 2018-2019
- All rights reserved, Designed By www.joolun.com
微信消息 - 语音
芋道源码
bug 修复
1joolun 的做法使用 mediaId 从微信公众号下载对应的 mp4 素材从而播放内容
存在的问题mediaId 有效期是 3 超过时间后无法播放
2重构后的做法后端接收到微信公众号的视频消息后将视频消息的 media_id 的文件内容保存到文件服务器中这样前端可以直接使用 URL 播放
代码优化 props 中的 objData 调成为 data 中对应的属性并补充相关注释
-->
<template>
<div class="wx-voice-div" @click="playVoice">
<i :class="playing !== true ? 'el-icon-video-play': 'el-icon-video-pause'">
<span class="amr-duration" v-if="duration">{{ duration }} </span>
</i>
<div v-if="content">
<el-tag type="success" size="mini">语音识别</el-tag>
{{ content }}
</div>
</div>
</template>
<script>
// 因为微信语音是 amr 格式,所以需要用到 amr 解码器https://www.npmjs.com/package/benz-amr-recorder
const BenzAMRRecorder = require('benz-amr-recorder')
export default {
name: "wxVoicePlayer",
props: {
url: { // 语音地址例如说https://www.iocoder.cn/xxx.amr
type: String,
required: true
},
content: { // 语音文本
type: String,
required: false
}
},
data() {
return {
amr: undefined, // BenzAMRRecorder 对象
playing: false, // 是否在播放中
duration: undefined, // 播放时长
}
},
methods:{
playVoice() {
debugger
// 情况一:未初始化,则创建 BenzAMRRecorder
if (this.amr === undefined){
this.amrInit();
return;
}
if (this.amr.isPlaying()) {
this.amrStop();
} else {
this.amrPlay();
}
},
amrInit() {
const amr = new BenzAMRRecorder();
this.amr = amr;
// 设置播放
const that = this
amr.initWithUrl(this.url).then(function() {
that.amrPlay()
that.duration = amr.getDuration();
})
// 监听暂停
amr.onEnded(function() {
that.playing = false;
})
},
amrPlay() {
this.playing = true;
this.amr.play()
},
amrStop() {
this.playing = false;
this.amr.stop()
},
}
};
</script>
<style lang="scss" scoped>
.wx-voice-div {
padding: 5px;
background-color: #eaeaea;
border-radius: 10px;
}
.amr-duration {
font-size: 11px;
margin-left: 5px;
}
</style>

View File

@@ -0,0 +1,698 @@
<!--
MIT License
Copyright (c) 2020 www.joolun.com
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
芋道源码
优化代码和项目的代码保持一致
清理冗余代码保证代码整洁
增加注释提升可读性
-->
<template>
<div class="app-container">
<!-- 搜索工作栏 -->
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="公众号" prop="accountId">
<el-select v-model="queryParams.accountId" placeholder="请选择公众号">
<el-option v-for="item in accounts" :key="parseInt(item.id)" :label="item.name" :value="parseInt(item.id)" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<!-- 操作工具栏 -->
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" plain icon="el-icon-plus" size="mini" @click="handleAdd"
v-hasPermi="['mp:draft:create']">新增
</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<!-- 列表 -->
<div class="waterfall" v-loading="loading">
<div v-if="item.content && item.content.newsItem" class="waterfall-item" v-for="item in list"
:key='item.articleId'>
<wx-news :articles="item.content.newsItem" />
<!-- 操作按钮 -->
<el-row class="ope-row">
<el-button type="success" circle @click="handlePublish(item)" v-hasPermi="['mp:free-publish:submit']">发布</el-button>
<el-button type="primary" icon="el-icon-edit" circle @click="handleUpdate(item)" v-hasPermi="['mp:draft:update']" />
<el-button type="danger" icon="el-icon-delete" circle @click="handleDelete(item)" v-hasPermi="['mp:draft:delete']" />
</el-row>
</div>
</div>
<!-- 分页记录 -->
<pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNo" :limit.sync="queryParams.pageSize"
@pagination="getList"/>
<!-- 添加或修改草稿对话框 -->
<el-dialog :title="operateMaterial === 'add' ? '新建图文' : '修改图文'"
append-to-body width="80%" top="20px" :visible.sync="dialogNewsVisible"
:before-close="dialogNewsClose" :close-on-click-modal="false">
<div class="left">
<div class="select-item">
<div v-for="(news, index) in articlesAdd" :key='news.id'>
<div class="news-main father" v-if="index === 0" :class="{'activeAddNews': isActiveAddNews === index}"
@click="activeNews(index)">
<div class="news-content">
<img class="material-img" v-if="news.thumbUrl" :src="news.thumbUrl"/>
<div class="news-content-title">{{news.title}}</div>
</div>
<div class="child" v-if="articlesAdd.length>1">
<el-button type="mini" icon="el-icon-sort-down" @click="downNews(index)">下移</el-button>
<el-button v-if="operateMaterial === 'add'" type="mini" icon="el-icon-delete"
@click="minusNews(index)">删除
</el-button>
</div>
</div>
<div class="news-main-item father" v-if="index>0" :class="{'activeAddNews': isActiveAddNews === index}"
@click="activeNews(index)">
<div class="news-content-item">
<div class="news-content-item-title ">{{news.title}}</div>
<div class="news-content-item-img">
<img class="material-img" v-if="news.thumbUrl" :src="news.thumbUrl" height="100%"/>
</div>
</div>
<div class="child">
<el-button v-if="articlesAdd.length > index+1" type="mini" icon="el-icon-sort-down"
@click="downNews(index)">下移
</el-button>
<el-button type="mini" icon="el-icon-sort-up" @click="upNews(index)">上移</el-button>
<el-button v-if="operateMaterial=== 'add'" type="mini" icon="el-icon-delete"
@click="minusNews(index)">删除
</el-button>
</div>
</div>
</div>
<div class="news-main-plus" @click="plusNews" v-if="articlesAdd.length<8 && operateMaterial==='add'">
<i class="el-icon-circle-plus icon-plus"></i>
</div>
</div>
</div>
<div class="right" v-loading="addMaterialLoading" v-if="articlesAdd.length > 0">
<br /> <br /> <br /> <br />
<!-- 标题作者原文地址 -->
<el-input v-model="articlesAdd[isActiveAddNews].title" placeholder="请输入标题(必填)" />
<el-input v-model="articlesAdd[isActiveAddNews].author" placeholder="请输入作者" style="margin-top: 5px;" />
<el-input v-model="articlesAdd[isActiveAddNews].contentSourceUrl" placeholder="请输入原文地址" style="margin-top: 5px;" />
<!-- 封面和摘要 -->
<div class="input-tt">封面和摘要</div>
<div>
<div class="thumb-div">
<img class="material-img" v-if="articlesAdd[isActiveAddNews].thumbUrl"
:src="articlesAdd[isActiveAddNews].thumbUrl" :class="isActiveAddNews === 0 ? 'avatar':'avatar1'">
<i v-else class="el-icon-plus avatar-uploader-icon"
:class="isActiveAddNews === 0 ? 'avatar':'avatar1'"></i>
<div class="thumb-but">
<el-upload :action="actionUrl" :headers="headers" multiple :limit="1" :file-list="fileList" :data="uploadData"
:before-upload="beforeThumbImageUpload" :on-success="handleUploadSuccess">
<el-button slot="trigger" size="mini" type="primary">本地上传</el-button>
<el-button size="mini" type="primary" @click="openMaterial" style="margin-left: 5px">素材库选择</el-button>
<div slot="tip" class="el-upload__tip">支持 bmp/png/jpeg/jpg/gif 格式大小不超过 2M</div>
</el-upload>
</div>
<el-dialog title="选择图片" :visible.sync="dialogImageVisible" width="80%" append-to-body>
<wx-material-select ref="materialSelect" :objData="{type: 'image', accountId: this.queryParams.accountId}"
@selectMaterial="selectMaterial" />
</el-dialog>
</div>
<el-input :rows="8" type="textarea" v-model="articlesAdd[isActiveAddNews].digest" placeholder="请输入摘要"
class="digest" maxlength="120" style="float: right" />
</div>
<!--富文本编辑器组件-->
<el-row>
<wx-editor v-model="articlesAdd[isActiveAddNews].content" :account-id="this.uploadData.accountId"
v-if="hackResetEditor"/>
</el-row>
</div>
<div slot="footer" class="dialog-footer">
<el-button @click="dialogNewsVisible = false"> </el-button>
<el-button type="primary" @click="submitForm"> </el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import WxEditor from '@/views/mp/components/wx-editor/WxEditor.vue';
import WxNews from '@/views/mp/components/wx-news/main.vue';
import WxMaterialSelect from '@/views/mp/components/wx-material-select/main.vue'
import { getAccessToken } from '@/utils/auth'
import {createDraft, deleteDraft, getDraftPage, updateDraft} from "@/api/mp/draft";
import { getSimpleAccounts } from "@/api/mp/account";
import {deleteFreePublish, submitFreePublish} from "@/api/mp/freePublish";
export default {
name: 'mpDraft',
components: {
WxEditor,
WxNews,
WxMaterialSelect
},
data() {
return {
// 遮罩层
loading: false,
// 显示搜索条件
showSearch: true,
// 总条数
total: 0,
// 数据列表
list: [],
queryParams: {
pageNo: 1,
pageSize: 10,
accountId: undefined,
},
// ========== 文件上传 ==========
actionUrl: process.env.VUE_APP_BASE_API + "/admin-api/mp/material/upload-permanent", // 上传永久素材的地址
headers: { Authorization: "Bearer " + getAccessToken() }, // 设置上传的请求头部
fileList: [],
uploadData: {
"type": 'image',
// "accountId": 1,
},
// ========== 草稿新建 or 修改 ==========
dialogNewsVisible: false,
addMaterialLoading: false, // 添加草稿的 loading 标识
articlesAdd: [],
isActiveAddNews: 0,
dialogImageVisible: false,
operateMaterial: 'add',
articlesMediaId: '',
hackResetEditor: false,
// 公众号账号列表
accounts: [],
}
},
created() {
getSimpleAccounts().then(response => {
this.accounts = response.data;
// 默认选中第一个
if (this.accounts.length > 0) {
this.setAccountId(this.accounts[0].id);
}
// 加载数据
this.getList();
})
},
methods: {
// ======================== 列表查询 ========================
/** 设置账号编号 */
setAccountId(accountId) {
this.queryParams.accountId = accountId;
this.uploadData.accountId = accountId;
},
/** 查询列表 */
getList() {
// 如果没有选中公众号账号,则进行提示。
if (!this.queryParams.accountId) {
this.$message.error('未选中公众号,无法查询草稿箱')
return false
}
this.loading = true
getDraftPage(this.queryParams).then(response => {
// 将 thumbUrl 转成 picUrl保证 wx-news 组件可以预览封面
response.data.list.forEach(item => {
const newsItem = item.content.newsItem;
newsItem.forEach(article => {
article.picUrl = article.thumbUrl;
})
})
this.list = response.data.list
this.total = response.data.total
}).finally(() => {
this.loading = false
})
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNo = 1
// 默认选中第一个
if (this.queryParams.accountId) {
this.setAccountId(this.queryParams.accountId)
}
this.getList()
},
/** 重置按钮操作 */
resetQuery() {
this.resetForm('queryForm')
// 默认选中第一个
if (this.accounts.length > 0) {
this.setAccountId(this.accounts[0].id)
}
this.handleQuery()
},
// ======================== 新增/修改草稿 ========================
/** 新增按钮操作 */
handleAdd() {
this.resetEditor();
this.reset();
// 打开表单,并设置初始化
this.operateMaterial = 'add'
this.dialogNewsVisible = true
},
/** 更新按钮操作 */
handleUpdate(item){
this.resetEditor();
this.reset();
this.articlesMediaId = item.mediaId
this.articlesAdd = JSON.parse(JSON.stringify(item.content.newsItem))
// 打开表单,并设置初始化
this.operateMaterial = 'edit'
this.dialogNewsVisible = true
},
/** 提交按钮 */
submitForm() {
this.addMaterialLoading = true
if (this.operateMaterial === 'add') {
createDraft(this.queryParams.accountId, this.articlesAdd).then(response => {
this.$modal.msgSuccess("新增成功");
this.dialogNewsVisible = false;
this.getList()
}).finally(() => {
this.addMaterialLoading = false
})
} else {
updateDraft(this.queryParams.accountId, this.articlesMediaId, this.articlesAdd).then(response => {
this.$modal.msgSuccess("更新成功");
this.dialogNewsVisible = false;
this.getList()
}).finally(() => {
this.addMaterialLoading = false
})
}
},
// 关闭弹窗
dialogNewsClose(done) {
this.$modal.confirm('修改内容可能还未保存,确定关闭吗?').then(() => {
this.reset()
this.resetEditor()
done()
}).catch(() => {})
},
// 表单重置
reset() {
this.isActiveAddNews = 0
this.articlesAdd = [this.buildEmptyArticle()]
},
// 表单 Editor 重置
resetEditor() {
this.hackResetEditor = false // 销毁组件
this.$nextTick(() => {
this.hackResetEditor = true // 重建组件
})
},
// 将图文向下移动
downNews(index) {
let temp = this.articlesAdd[index]
this.articlesAdd[index] = this.articlesAdd[index+1]
this.articlesAdd[index + 1] = temp
this.isActiveAddNews = index + 1
},
// 将图文向上移动
upNews(index) {
let temp = this.articlesAdd[index]
this.articlesAdd[index] = this.articlesAdd[index - 1]
this.articlesAdd[index - 1] = temp
this.isActiveAddNews = index - 1
},
// 选中指定 index 的图文
activeNews(index) {
this.resetEditor();
this.isActiveAddNews = index
},
// 删除指定 index 的图文
minusNews(index) {
this.$modal.confirm('确定删除该图文吗?').then(() => {
this.articlesAdd.splice(index,1);
if (this.isActiveAddNews === index) {
this.isActiveAddNews = 0
}
}).catch(() => {})
},
// 添加一个图文
plusNews() {
this.articlesAdd.push(this.buildEmptyArticle())
this.isActiveAddNews = this.articlesAdd.length - 1
},
// 创建空的 article
buildEmptyArticle() {
return {
"title": '',
"thumbMediaId": '',
"author": '',
"digest": '',
"showCoverPic": '',
"content": '',
"contentSourceUrl": '',
"needOpenComment":'',
"onlyFansCanComment":'',
"thumbUrl":''
}
},
// ======================== 文件上传 ========================
beforeThumbImageUpload(file) {
this.addMaterialLoading = true
const isType = file.type === 'image/jpeg'
|| file.type === 'image/png'
|| file.type === 'image/gif'
|| file.type === 'image/bmp'
|| file.type === 'image/jpg';
if (!isType) {
this.$message.error('上传图片格式不对!')
this.addMaterialLoading = false
return false;
}
const isLt = file.size / 1024 / 1024 < 2
if (!isLt) {
this.$message.error('上传图片大小不能超过 2M!')
this.addMaterialLoading = false
return false;
}
// 校验通过
return true;
},
handleUploadSuccess(response, file, fileList) {
this.addMaterialLoading = false // 关闭 loading
if (response.code !== 0) {
this.$message.error('上传出错:' + response.msg)
return false;
}
// 重置上传文件的表单
this.fileList = []
// 设置草稿的封面字段
this.articlesAdd[this.isActiveAddNews].thumbMediaId = response.data.mediaId
this.articlesAdd[this.isActiveAddNews].thumbUrl = response.data.url
},
// 选择 or 上传完素材,设置回草稿
selectMaterial(item) {
this.dialogImageVisible = false
this.articlesAdd[this.isActiveAddNews].thumbMediaId = item.mediaId
this.articlesAdd[this.isActiveAddNews].thumbUrl = item.url
},
// 打开素材选择
openMaterial() {
this.dialogImageVisible = true
try {
this.$refs['materialSelect'].queryParams.accountId = this.queryParams.accountId // 强制设置下 accountId避免二次查询不对
this.$refs['materialSelect'].handleQuery(); // 刷新列表,失败也无所谓
} catch (e) {}
},
// ======================== 草稿箱发布 ========================
handlePublish(item) {
const accountId = this.queryParams.accountId;
const mediaId = item.mediaId;
const content = '你正在通过发布的方式发表内容。 发布不占用群发次数,一天可多次发布。已发布内容不会推送给用户,也不会展示在公众号主页中。 发布后,你可以前往发表记录获取链接,也可以将发布内容添加到自定义菜单、自动回复、话题和页面模板中。';
this.$modal.confirm(content).then(function() {
return submitFreePublish(accountId, mediaId);
}).then(() => {
this.getList();
this.$modal.msgSuccess("发布成功");
}).catch(() => {});
},
handleDelete(item) {
const accountId = this.queryParams.accountId;
const mediaId = item.mediaId;
this.$modal.confirm('此操作将永久删除该草稿, 是否继续?').then(function() {
return deleteDraft(accountId, mediaId);
}).then(() => {
this.getList();
this.$modal.msgSuccess("删除成功");
}).catch(() => {});
},
}
}
</script>
<style lang="scss" scoped>
.pagination {
float: right;
margin-right: 25px;
}
.add_but {
padding: 10px;
}
.ope-row {
margin-top: 5px;
text-align: center;
border-top: 1px solid #eaeaea;
padding-top: 5px;
}
.item-name {
font-size: 12px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
text-align: center;
}
.el-upload__tip {
margin-left: 5px;
}
/*新增图文*/
.left {
display: inline-block;
width: 35%;
vertical-align: top;
margin-top: 200px;
}
.right {
display: inline-block;
width: 60%;
margin-top: -40px;
}
.avatar-uploader {
width: 20%;
display: inline-block;
}
.avatar-uploader .el-upload {
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
text-align: unset !important;
}
.avatar-uploader .el-upload:hover {
border-color: #165dff;
}
.avatar-uploader-icon {
border: 1px solid #d9d9d9;
font-size: 28px;
color: #8c939d;
width: 120px;
height: 120px;
line-height: 120px;
text-align: center;
}
.avatar {
width: 230px;
height: 120px;
}
.avatar1 {
width: 120px;
height: 120px;
}
.digest {
width: 60%;
display: inline-block;
vertical-align: top;
}
/*新增图文*/
/*瀑布流样式*/
.waterfall {
width: 100%;
column-gap: 10px;
column-count: 5;
margin: 0 auto;
}
.waterfall-item {
padding: 10px;
margin-bottom: 10px;
break-inside: avoid;
border: 1px solid #eaeaea;
}
p {
line-height: 30px;
}
@media (min-width: 992px) and (max-width: 1300px) {
.waterfall {
column-count: 3;
}
p {
color: red;
}
}
@media (min-width: 768px) and (max-width: 991px) {
.waterfall {
column-count: 2;
}
p {
color: orange;
}
}
@media (max-width: 767px) {
.waterfall {
column-count: 1;
}
}
/*瀑布流样式*/
.news-main {
background-color: #FFFFFF;
width: 100%;
margin: auto;
height: 120px;
}
.news-content {
background-color: #acadae;
width: 100%;
height: 120px;
position: relative;
}
.news-content-title {
display: inline-block;
font-size: 15px;
color: #FFFFFF;
position: absolute;
left: 0px;
bottom: 0px;
background-color: black;
width: 98%;
padding: 1%;
opacity: 0.65;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
height: 25px;
}
.news-main-item {
background-color: #FFFFFF;
padding: 5px 0px;
border-top: 1px solid #eaeaea;
width: 100%;
margin: auto;
}
.news-content-item {
position: relative;
margin-left: -3px
}
.news-content-item-title {
display: inline-block;
font-size: 12px;
width: 70%;
}
.news-content-item-img {
display: inline-block;
width: 25%;
background-color: #acadae
}
.input-tt {
padding: 5px;
}
.activeAddNews {
border: 5px solid #2bb673;
}
.news-main-plus {
width: 280px;
text-align: center;
margin: auto;
height: 50px;
}
.icon-plus {
margin: 10px;
font-size: 25px;
}
.select-item {
width: 60%;
padding: 10px;
margin: 0 auto 10px auto;
border: 1px solid #eaeaea;
}
.father .child {
display: none;
text-align: center;
position: relative;
bottom: 25px;
}
.father:hover .child {
display: block;
}
.thumb-div {
display: inline-block;
width: 30%;
text-align: center;
}
.thumb-but {
margin: 5px;
}
.material-img {
width: 100%;
height: 100%;
}
</style>

View File

@@ -0,0 +1,393 @@
<!--
MIT License
Copyright (c) 2020 www.joolun.com
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
芋道源码
优化代码和项目的代码保持一致
-->
<template>
<div class="app-container">
<!-- 搜索工作栏 -->
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="公众号" prop="accountId">
<el-select v-model="queryParams.accountId" placeholder="请选择公众号">
<el-option v-for="item in accounts" :key="parseInt(item.id)" :label="item.name" :value="parseInt(item.id)" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<!-- 列表 -->
<div class="waterfall" v-loading="loading">
<div v-if="item.content && item.content.newsItem" class="waterfall-item" v-for="item in list"
:key='item.articleId'>
<wx-news :articles="item.content.newsItem" />
<!-- 操作 -->
<el-row class="ope-row">
<el-button type="danger" icon="el-icon-delete" circle @click="handleDelete(item)"
v-hasPermi="['mp:free-publish:delete']" />
</el-row>
</div>
</div>
<!-- 分页组件 -->
<pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNo" :limit.sync="queryParams.pageSize"
@pagination="getList"/>
</div>
</template>
<script>
import { getFreePublishPage, deleteFreePublish } from "@/api/mp/freePublish";
import { getSimpleAccounts } from "@/api/mp/account";
import WxNews from '@/views/mp/components/wx-news/main.vue';
export default {
name: 'mpDraft',
components: {
WxNews
},
data() {
return {
// 遮罩层
loading: false,
// 显示搜索条件
showSearch: true,
// 总条数
total: 0,
// 已发表列表
list: [],
// 查询参数
queryParams: {
total: 0, // 总页数
currentPage: 1, // 当前页数
queryParamsSize: 10 // 每页显示多少条
},
// 公众号账号列表
accounts: [],
}
},
created() {
getSimpleAccounts().then(response => {
this.accounts = response.data;
// 默认选中第一个
if (this.accounts.length > 0) {
this.queryParams.accountId = this.accounts[0].id;
}
// 加载数据
this.getList();
})
},
methods: {
/** 查询列表 */
getList() {
// 如果没有选中公众号账号,则进行提示。
if (!this.queryParams.accountId) {
this.$message.error('未选中公众号,无法查询已发表图文')
return false
}
this.loading = true;
getFreePublishPage(this.queryParams).then(response => {
// 将 thumbUrl 转成 picUrl保证 wx-news 组件可以预览封面
response.data.list.forEach(item => {
const newsItem = item.content.newsItem;
newsItem.forEach(article => {
article.picUrl = article.thumbUrl;
})
})
this.list = response.data.list
this.total = response.data.total
}).finally(() => {
this.loading = false
})
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNo = 1;
this.getList();
},
/** 重置按钮操作 */
resetQuery() {
this.resetForm("queryForm");
// 默认选中第一个
if (this.accounts.length > 0) {
this.queryParams.accountId = this.accounts[0].id;
}
this.handleQuery();
},
/** 删除按钮操作 */
handleDelete(item){
const articleId = item.articleId;
const accountId = this.queryParams.accountId;
this.$modal.confirm('删除后用户将无法访问此页面,确定删除?').then(function() {
return deleteFreePublish(accountId, articleId);
}).then(() => {
this.getList();
this.$modal.msgSuccess("删除成功");
}).catch(() => {});
},
}
}
</script>
<style lang="scss" scoped>
.pagination {
float: right;
margin-right: 25px;
}
.add_but {
padding: 10px;
}
.ope-row {
margin-top: 5px;
text-align: center;
border-top: 1px solid #eaeaea;
padding-top: 5px;
}
.item-name {
font-size: 12px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
text-align: center;
}
.el-upload__tip {
margin-left: 5px;
}
/*新增图文*/
.left {
display: inline-block;
width: 35%;
vertical-align: top;
margin-top: 200px;
}
.right {
display: inline-block;
width: 60%;
margin-top: -40px;
}
.avatar-uploader {
width: 20%;
display: inline-block;
}
.avatar-uploader .el-upload {
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
text-align: unset !important;
}
.avatar-uploader .el-upload:hover {
border-color: #165dff;
}
.avatar-uploader-icon {
border: 1px solid #d9d9d9;
font-size: 28px;
color: #8c939d;
width: 120px;
height: 120px;
line-height: 120px;
text-align: center;
}
.avatar {
width: 230px;
height: 120px;
}
.avatar1 {
width: 120px;
height: 120px;
}
.digest {
width: 60%;
display: inline-block;
vertical-align: top;
}
/*新增图文*/
/*瀑布流样式*/
.waterfall {
width: 100%;
column-gap: 10px;
column-count: 5;
margin: 0 auto;
}
.waterfall-item {
padding: 10px;
margin-bottom: 10px;
break-inside: avoid;
border: 1px solid #eaeaea;
}
p {
line-height: 30px;
}
@media (min-width: 992px) and (max-width: 1300px) {
.waterfall {
column-count: 3;
}
p {
color: red;
}
}
@media (min-width: 768px) and (max-width: 991px) {
.waterfall {
column-count: 2;
}
p {
color: orange;
}
}
@media (max-width: 767px) {
.waterfall {
column-count: 1;
}
}
/*瀑布流样式*/
.news-main {
background-color: #FFFFFF;
width: 100%;
margin: auto;
height: 120px;
}
.news-content {
background-color: #acadae;
width: 100%;
height: 120px;
position: relative;
}
.news-content-title {
display: inline-block;
font-size: 15px;
color: #FFFFFF;
position: absolute;
left: 0px;
bottom: 0px;
background-color: black;
width: 98%;
padding: 1%;
opacity: 0.65;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
height: 25px;
}
.news-main-item {
background-color: #FFFFFF;
padding: 5px 0px;
border-top: 1px solid #eaeaea;
width: 100%;
margin: auto;
}
.news-content-item {
position: relative;
margin-left: -3px
}
.news-content-item-title {
display: inline-block;
font-size: 12px;
width: 70%;
}
.news-content-item-img {
display: inline-block;
width: 25%;
background-color: #acadae
}
.input-tt {
padding: 5px;
}
.activeAddNews {
border: 5px solid #2bb673;
}
.news-main-plus {
width: 280px;
text-align: center;
margin: auto;
height: 50px;
}
.icon-plus {
margin: 10px;
font-size: 25px;
}
.select-item {
width: 60%;
padding: 10px;
margin: 0 auto 10px auto;
border: 1px solid #eaeaea;
}
.father .child {
display: none;
text-align: center;
position: relative;
bottom: 25px;
}
.father:hover .child {
display: block;
}
.thumb-div {
display: inline-block;
width: 30%;
text-align: center;
}
.thumb-but {
margin: 5px;
}
.material-img {
width: 100%;
height: 100%;
}
</style>

View File

@@ -0,0 +1,438 @@
<!--
MIT License
Copyright (c) 2020 www.joolun.com
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
芋道源码
清理冗余 css 内容清理冗余 data 变量
美化样式支持播放提升使用体验
优化代码特别是方法名和变量提升可读性
-->
<template>
<div class="app-container">
<!-- 搜索工作栏 -->
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="公众号" prop="accountId">
<el-select v-model="queryParams.accountId" placeholder="请选择公众号">
<el-option v-for="item in accounts" :key="parseInt(item.id)" :label="item.name" :value="parseInt(item.id)" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-tabs v-model="type" @tab-click="handleClick">
<!-- tab 1图片 -->
<el-tab-pane name="image">
<span slot="label"><i class="el-icon-picture"></i> 图片</span>
<div class="add_but" v-hasPermi="['mp:material:upload-permanent']">
<el-upload :action="actionUrl" :headers="headers" multiple :limit="1" :file-list="fileList" :data="uploadData"
:before-upload="beforeImageUpload" :on-success="handleUploadSuccess">
<el-button size="mini" type="primary">点击上传</el-button>
<sapn slot="tip" class="el-upload__tip" style="margin-left: 5px">支持 bmp/png/jpeg/jpg/gif 格式大小不超过 2M</sapn>
</el-upload>
</div>
<div class="waterfall" v-loading="loading">
<div class="waterfall-item" v-for="item in list" :key='item.id'>
<a target="_blank" :href="item.url">
<img class="material-img" :src="item.url">
<div class="item-name">{{item.name}}</div>
</a>
<el-row class="ope-row">
<el-button type="danger" icon="el-icon-delete" circle @click="handleDelete(item)"
v-hasPermi="['mp:material:delete']"/>
</el-row>
</div>
</div>
<!-- 分页组件 -->
<pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNo" :limit.sync="queryParams.pageSize"
@pagination="getList"/>
</el-tab-pane>
<!-- tab 2语音 -->
<el-tab-pane name="voice">
<span slot="label"><i class="el-icon-microphone"></i> 语音</span>
<div class="add_but" v-hasPermi="['mp:material:upload-permanent']">
<el-upload :action="actionUrl" :headers="headers" multiple :limit="1" :file-list="fileList" :data="uploadData"
:on-success="handleUploadSuccess" :before-upload="beforeVoiceUpload">
<el-button size="mini" type="primary">点击上传</el-button>
<span slot="tip" class="el-upload__tip" style="margin-left: 5px">格式支持 mp3/wma/wav/amr文件大小不超过 2M播放长度不超过 60s</span>
</el-upload>
</div>
<el-table :data="list" stripe border v-loading="loading" style="margin-top: 10px;">
<el-table-column label="编号" align="center" prop="mediaId" />
<el-table-column label="文件名" align="center" prop="name" />
<el-table-column label="语音" align="center">
<template v-slot="scope">
<wx-voice-player :url="scope.row.url" />
</template>
</el-table-column>
<el-table-column label="上传时间" align="center" prop="createTime" width="180">
<template v-slot="scope">
<span>{{ parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template slot-scope="scope">
<el-button type="text" icon="el-icon-download" size="small" plain @click="handleDownload(scope.row)">下载</el-button>
<el-button type="text" icon="el-icon-delete" size="small" plain @click="handleDelete(scope.row)"
v-hasPermi="['mp:material:delete']">删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页组件 -->
<pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNo" :limit.sync="queryParams.pageSize"
@pagination="getList"/>
</el-tab-pane>
<!-- tab 3视频 -->
<el-tab-pane name="video">
<span slot="label"><i class="el-icon-video-play"></i> 视频</span>
<div class="add_but" v-hasPermi="['mp:material:upload-permanent']">
<el-button size="mini" type="primary" @click="handleAddVideo">新建视频</el-button>
</div>
<!-- 新建视频的弹窗 -->
<el-dialog title="新建视频" :visible.sync="dialogVideoVisible" append-to-body width="600px"
v-loading="addMaterialLoading">
<el-upload :action="actionUrl" :headers="headers" multiple :limit="1" :file-list="fileList" :data="uploadData"
:before-upload="beforeVideoUpload" :on-success="handleUploadSuccess"
ref="uploadVideo" :auto-upload="false">
<el-button slot="trigger" size="mini" type="primary">选择视频</el-button>
<span class="el-upload__tip" style="margin-left: 10px;">格式支持 MP4文件大小不超过 10MB</span>
</el-upload>
<el-form :model="uploadData" :rules="uploadRules" ref="uploadForm" label-width="80px">
<el-row>
<el-form-item label="标题" prop="title">
<el-input v-model="uploadData.title" placeholder="标题将展示在相关播放页面,建议填写清晰、准确、生动的标题" />
</el-form-item>
</el-row>
<el-row>
<el-form-item label="描述" prop="introduction">
<el-input :rows="3" type="textarea" v-model="uploadData.introduction"
placeholder="介绍语将展示在相关播放页面,建议填写简洁明确、有信息量的内容" />
</el-form-item>
</el-row>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="cancelVideo"> </el-button>
<el-button type="primary" @click="submitVideo"> </el-button>
</div>
</el-dialog>
<el-table :data="list" stripe border v-loading="loading" style="margin-top: 10px;">
<el-table-column label="编号" align="center" prop="mediaId" />
<el-table-column label="文件名" align="center" prop="name" />
<el-table-column label="标题" align="center" prop="title" />
<el-table-column label="介绍" align="center" prop="introduction" />
<el-table-column label="视频" align="center">
<template v-slot="scope">
<wx-video-player :url="scope.row.url" />
</template>
</el-table-column>
<el-table-column label="上传时间" align="center" prop="createTime" width="180">
<template v-slot="scope">
<span>{{ parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" fixed="right" class-name="small-padding fixed-width">
<template v-slot="scope">
<el-button type="text" icon="el-icon-download" size="small" plain @click="handleDownload(scope.row)">下载</el-button>
<el-button type="text" icon="el-icon-delete" size="small" plain @click="handleDelete(scope.row)"
v-hasPermi="['mp:material:delete']">删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页组件 -->
<pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNo" :limit.sync="queryParams.pageSize"
@pagination="getList"/>
</el-tab-pane>
</el-tabs>
</div>
</template>
<script>
import WxVoicePlayer from '@/views/mp/components/wx-voice-play/main.vue';
import WxVideoPlayer from '@/views/mp/components/wx-video-play/main.vue';
import { getSimpleAccounts } from "@/api/mp/account";
import { getMaterialPage, deletePermanentMaterial } from "@/api/mp/material";
import { getAccessToken } from '@/utils/auth'
export default {
name: 'mpMaterial',
components: {
WxVoicePlayer,
WxVideoPlayer
},
data() {
return {
type: 'image',
// 遮罩层
loading: false,
// 显示搜索条件
showSearch: true,
// 总条数
total: 0,
// 数据列表
list: [],
// 查询参数
queryParams: {
pageNo: 1,
pageSize: 10,
accountId: undefined,
permanent: true,
},
actionUrl: process.env.VUE_APP_BASE_API + '/admin-api/mp/material/upload-permanent',
headers: { Authorization: "Bearer " + getAccessToken() }, // 设置上传的请求头部
fileList:[],
uploadData: {
"type": 'image',
"title":'',
"introduction":''
},
// === 视频上传,独有变量 ===
dialogVideoVisible: false,
addMaterialLoading: false,
uploadRules: { // 视频上传的校验规则
title: [{ required: true, message: '请输入标题', trigger: 'blur' }],
introduction: [{ required: true, message: '请输入描述', trigger: 'blur' }],
},
// 公众号账号列表
accounts: [],
}
},
created() {
getSimpleAccounts().then(response => {
this.accounts = response.data;
// 默认选中第一个
if (this.accounts.length > 0) {
this.setAccountId(this.accounts[0].id);
}
// 加载数据
this.getList();
})
},
methods: {
// ======================== 列表查询 ========================
/** 设置账号编号 */
setAccountId(accountId) {
this.queryParams.accountId = accountId;
this.uploadData.accountId = accountId;
},
/** 查询列表 */
getList() {
// 如果没有选中公众号账号,则进行提示。
if (!this.queryParams.accountId) {
this.$message.error('未选中公众号,无法查询草稿箱')
return false
}
this.loading = true
getMaterialPage({
...this.queryParams,
type: this.type
}).then(response => {
this.list = response.data.list
this.total = response.data.total
}).finally(() => {
this.loading = false
})
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNo = 1
// 默认选中第一个
if (this.queryParams.accountId) {
this.setAccountId(this.queryParams.accountId)
}
this.getList()
},
/** 重置按钮操作 */
resetQuery() {
this.resetForm('queryForm')
// 默认选中第一个
if (this.accounts.length > 0) {
this.setAccountId(this.accounts[0].id)
}
this.handleQuery()
},
handleClick(tab, event) {
// 设置 type
this.uploadData.type = tab.name
// 从第一页开始查询
this.handleQuery();
},
// ======================== 文件上传 ========================
beforeImageUpload(file) {
const isType = file.type === 'image/jpeg'
|| file.type === 'image/png'
|| file.type === 'image/gif'
|| file.type === 'image/bmp'
|| file.type === 'image/jpg';
if (!isType) {
this.$message.error('上传图片格式不对!')
return false;
}
const isLt = file.size / 1024 / 1024 < 2
if (!isLt) {
this.$message.error('上传图片大小不能超过 2M!')
return false;
}
this.loading = true
return true;
},
beforeVoiceUpload(file){
const isType = file.type === 'audio/mp3'
|| file.type === 'audio/wma'
|| file.type === 'audio/wav'
|| file.type === 'audio/amr';
const isLt = file.size / 1024 / 1024 < 2
if (!isType) {
this.$message.error('上传语音格式不对!')
return false;
}
if (!isLt) {
this.$message.error('上传语音大小不能超过 2M!')
return false;
}
this.loading = true
return true;
},
beforeVideoUpload(file){
const isType = file.type === 'video/mp4'
if (!isType) {
this.$message.error('上传视频格式不对!')
return false;
}
const isLt = file.size / 1024 / 1024 < 10
if (!isLt) {
this.$message.error('上传视频大小不能超过 10M!')
return false
}
this.addMaterialLoading = true
return true
},
handleUploadSuccess(response, file, fileList) {
this.loading = false
this.addMaterialLoading = false
if (response.code !== 0) {
this.$message.error('上传出错:' + response.msg)
return false;
}
// 清空上传时的各种数据
this.dialogVideoVisible = false
this.fileList = []
this.uploadData.title = ''
this.uploadData.introduction = ''
// 加载数据
this.getList()
},
// 下载文件
handleDownload(row) {
window.open(row.url,'_blank')
},
// 提交 video 新建的表单
submitVideo() {
this.$refs['uploadForm'].validate((valid) => {
if (!valid) {
return false;
}
this.$refs.uploadVideo.submit()
})
},
handleAddVideo() {
this.resetVideo();
this.dialogVideoVisible = true
},
/** 取消按钮 */
cancelVideo() {
this.dialogVideoVisible = false;
this.resetVideo();
},
/** 表单重置 */
resetVideo() {
this.fileList = []
this.uploadData.title = ''
this.uploadData.introduction = ''
this.resetForm("uploadForm");
},
// ======================== 其它操作 ========================
handleDelete(item) {
const id = item.id
this.$modal.confirm('此操作将永久删除该文件, 是否继续?').then(function() {
return deletePermanentMaterial(id);
}).then(() => {
this.getList();
this.$modal.msgSuccess("删除成功");
}).catch(() => {});
},
}
}
</script>
<style lang="scss" scoped>
/*瀑布流样式*/
.waterfall {
width: 100%;
column-gap:10px;
column-count: 5;
margin-top: 10px; /* 芋道源码:增加 10px避免顶着上面 */
}
.waterfall-item {
padding: 10px;
margin-bottom: 10px;
break-inside: avoid;
border: 1px solid #eaeaea;
}
.material-img {
width: 100%;
}
p {
line-height: 30px;
}
@media (min-width: 992px) and (max-width: 1300px) {
.waterfall {
column-count: 3;
}
p {
color:red;
}
}
@media (min-width: 768px) and (max-width: 991px) {
.waterfall {
column-count: 2;
}
p {
color: orange;
}
}
@media (max-width: 767px) {
.waterfall {
column-count: 1;
}
}
/*瀑布流样式*/
</style>

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@@ -0,0 +1,712 @@
<!--
MIT License
Copyright (c) 2020 www.joolun.com
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
芋道源码
less 切到 scss减少对 less less-loader 的依赖
-->
<template>
<div class="app-container">
<!-- 搜索工作栏 -->
<el-form ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="公众号" prop="accountId">
<el-select v-model="accountId" placeholder="请选择公众号">
<el-option v-for="item in accounts" :key="parseInt(item.id)" :label="item.name" :value="parseInt(item.id)" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<div class="public-account-management clearfix" v-loading="loading">
<!--左边配置菜单-->
<div class="left">
<div class="weixin-hd">
<div class="weixin-title">{{ name }}</div>
</div>
<div class="weixin-menu menu_main clearfix">
<div class="menu_bottom" v-for="(item, i) of menuList" :key="i" >
<!-- 一级菜单 -->
<div @click="menuClick(i, item)" class="menu_item el-icon-s-fold" :class="{'active': isActive === i}">{{item.name}}</div>
<!-- 以下为二级菜单-->
<div class="submenu" v-if="isSubMenuFlag === i">
<div class="subtitle menu_bottom" v-if="item.children" v-for="(subItem, k) in item.children" :key="k">
<div class="menu_subItem" :class="{'active': isSubMenuActive === i + '' + k}" @click="subMenuClick(subItem, i, k)">
{{subItem.name}}
</div>
</div>
<!-- 二级菜单加号 当长度 小于 5 才显示二级菜单的加号 -->
<div class="menu_bottom menu_addicon" v-if="!item.children || item.children.length < 5" @click="addSubMenu(i,item)">
<i class="el-icon-plus" />
</div>
</div>
</div>
<!-- 一级菜单加号 -->
<div class="menu_bottom menu_addicon" v-if="this.menuList.length < 3" @click="addMenu"><i class="el-icon-plus"></i></div>
</div>
<div class="save_div">
<el-button class="save_btn" type="success" size="small" @click="handleSave" v-hasPermi="['mp:menu:save']">保存并发布菜单</el-button>
<el-button class="save_btn" type="danger" size="small" @click="handleDelete" v-hasPermi="['mp:menu:delete']">清空菜单</el-button>
</div>
</div>
<!--右边配置-->
<div v-if="showRightFlag" class="right">
<div class="configure_page">
<div class="delete_btn">
<el-button size="mini" type="danger" icon="el-icon-delete" @click="deleteMenu(tempObj)">删除当前菜单</el-button>
</div>
<div>
<span>菜单名称</span>
<el-input class="input_width" v-model="tempObj.name" placeholder="请输入菜单名称" :maxlength="nameMaxLength" clearable />
</div>
<div v-if="showConfigureContent">
<div class="menu_content">
<span>菜单标识</span>
<el-input class="input_width" v-model="tempObj.menuKey" placeholder="请输入菜单 KEY" clearable />
</div>
<div class="menu_content">
<span>菜单内容</span>
<el-select v-model="tempObj.type" clearable placeholder="请选择" class="menu_option">
<el-option v-for="item in menuOptions" :label="item.label" :value="item.value" :key="item.value" />
</el-select>
</div>
<div class="configur_content" v-if="tempObj.type === 'view'">
<span>跳转链接</span>
<el-input class="input_width" v-model="tempObj.url" placeholder="请输入链接" clearable />
</div>
<div class="configur_content" v-if="tempObj.type === 'miniprogram'">
<div class="applet">
<span>小程序的 appid </span>
<el-input class="input_width" v-model="tempObj.miniProgramAppId" placeholder="请输入小程序的appid" clearable />
</div>
<div class="applet">
<span>小程序的页面路径</span>
<el-input class="input_width" v-model="tempObj.miniProgramPagePath"
placeholder="请输入小程序的页面路径pages/index" clearable />
</div>
<div class="applet">
<span>小程序的备用网页</span>
<el-input class="input_width" v-model="tempObj.url" placeholder="不支持小程序的老版本客户端将打开本网页" clearable />
</div>
<p class="blue">tips:需要和公众号进行关联才可以把小程序绑定带微信菜单上哟</p>
</div>
<div class="configur_content" v-if="tempObj.type === 'article_view_limited'">
<el-row>
<div class="select-item" v-if="tempObj && tempObj.replyArticles">
<wx-news :articles="tempObj.replyArticles" />
<el-row class="ope-row">
<el-button type="danger" icon="el-icon-delete" circle @click="deleteMaterial" />
</el-row>
</div>
<div v-else>
<el-row>
<el-col :span="24" style="text-align: center">
<el-button type="success" @click="openMaterial">
素材库选择<i class="el-icon-circle-check el-icon--right"></i>
</el-button>
</el-col>
</el-row>
</div>
<el-dialog title="选择图文" :visible.sync="dialogNewsVisible" width="90%">
<wx-material-select :objData="{type: 'news', accountId: this.accountId}" @selectMaterial="selectMaterial" />
</el-dialog>
</el-row>
</div>
<div class="configur_content" v-if="tempObj.type === 'click' || tempObj.type === 'scancode_waitmsg'">
<wx-reply-select :objData="tempObj.reply" v-if="hackResetWxReplySelect" />
</div>
</div>
</div>
</div>
<!-- 一进页面就显示的默认页面当点击左边按钮的时候就不显示了-->
<div v-else class="right">
<p>请选择菜单配置</p>
</div>
</div>
</div>
</template>
<script>
import WxReplySelect from '@/views/mp/components/wx-reply/main.vue'
import WxNews from '@/views/mp/components/wx-news/main.vue';
import WxMaterialSelect from '@/views/mp/components/wx-material-select/main.vue'
import { deleteMenu, getMenuList, saveMenu } from "@/api/mp/menu";
import { getSimpleAccounts } from "@/api/mp/account";
export default {
name: 'mpMenu',
components: {
WxReplySelect,
WxNews,
WxMaterialSelect
},
data(){
return {
// ======================== 列表查询 ========================
// 遮罩层
loading: true,
// 显示搜索条件
showSearch: true,
// 查询参数
accountId: undefined,
name:'', // 公众号名
menuList: {
children: [],
},
// ======================== 菜单操作 ========================
isActive: -1,// 一级菜单点中样式
isSubMenuActive: -1, // 一级菜单点中样式
isSubMenuFlag: -1, // 二级菜单显示标志
// ======================== 菜单编辑 ========================
showRightFlag: false, // 右边配置显示默认详情还是配置详情
nameMaxLength: 0, // 菜单名称最大长度1 级是 4 字符2 级是 7 字符;
showConfigureContent: true, // 是否展示配置内容;如果有子菜单,就不显示配置内容
hackResetWxReplySelect: false, // 重置 WxReplySelect 组件
tempObj: {}, // 右边临时变量,作为中间值牵引关系
tempSelfObj: { // 一些临时值放在这里进行判断,如果放在 tempObj由于引用关系menu 也会多了多余的参数
},
dialogNewsVisible: false, // 跳转图文时的素材选择弹窗
menuOptions: [{
value: 'view',
label: '跳转网页'
}, {
value: 'miniprogram',
label: '跳转小程序'
}, {
value: 'click',
label: '点击回复'
}, {
value: 'article_view_limited',
label: '跳转图文消息'
}, {
value: 'scancode_push',
label: '扫码直接返回结果'
}, {
value: 'scancode_waitmsg',
label: '扫码回复'
}, {
value: 'pic_sysphoto',
label: '系统拍照发图'
}, {
value: 'pic_photo_or_album',
label: '拍照或者相册'
}, {
value: 'pic_weixin',
label: '微信相册'
}, {
value: 'location_select',
label: '选择地理位置'
}],
// 公众号账号列表
accounts: [],
}
},
created() {
getSimpleAccounts().then(response => {
this.accounts = response.data;
// 默认选中第一个
if (this.accounts.length > 0) {
this.setAccountId(this.accounts[0].id);
}
// 加载数据
this.getList();
})
},
methods: {
// ======================== 列表查询 ========================
/** 设置账号编号 */
setAccountId(accountId) {
this.accountId = accountId;
this.name = this.accounts.find(item => item.id === accountId)?.name;
},
getList() {
this.loading = false;
getMenuList(this.accountId).then(response => {
response.data = this.convertMenuList(response.data);
this.menuList = this.handleTree(response.data, "id");
}).finally(() => {
this.loading = false;
})
},
/** 搜索按钮操作 */
handleQuery() {
this.resetForm();
// 默认选中第一个
if (this.accountId) {
this.setAccountId(this.accountId)
}
this.getList()
},
/** 重置按钮操作 */
resetQuery() {
this.resetForm();
// 默认选中第一个
if (this.accounts.length > 0) {
this.setAccountId(this.accounts[0].id)
}
this.handleQuery()
},
// 将后端返回的 menuList转换成前端的 menuList
convertMenuList(list) {
const menuList = [];
list.forEach(item => {
const menu = {
...item,
};
if (item.type === 'click' || item.type === 'scancode_waitmsg') {
this.$delete(menu, 'replyMessageType');
this.$delete(menu, 'replyContent');
this.$delete(menu, 'replyMediaId');
this.$delete(menu, 'replyMediaUrl');
this.$delete(menu, 'replyDescription');
this.$delete(menu, 'replyArticles');
menu.reply = {
type: item.replyMessageType,
accountId: item.accountId,
content: item.replyContent,
mediaId: item.replyMediaId,
url: item.replyMediaUrl,
title: item.replyTitle,
description: item.replyDescription,
thumbMediaId: item.replyThumbMediaId,
thumbMediaUrl: item.replyThumbMediaUrl,
articles: item.replyArticles,
musicUrl: item.replyMusicUrl,
hqMusicUrl: item.replyHqMusicUrl,
}
}
menuList.push(menu);
});
return menuList;
},
// 重置表单,清空表单数据
resetForm() {
// 菜单操作
this.isActive = -1;
this.isSubMenuActive = -1;
this.isSubMenuFlag = -1;
// 菜单编辑
this.showRightFlag = false;
this.nameMaxLength = 0;
this.showConfigureContent = 0;
this.hackResetWxReplySelect = true;
this.hackResetWxReplySelect = false;
this.tempObj = {};
this.tempSelfObj = {};
this.dialogNewsVisible = false;
},
// ======================== 菜单操作 ========================
// 一级菜单点击事件
menuClick(i, item) {
// 右侧的表单相关
this.resetEditor();
this.showRightFlag = true; // 右边菜单
this.tempObj = item; // 这个如果放在顶部flag 会没有。因为重新赋值了。
this.tempSelfObj.grand = "1"; // 表示一级菜单
this.tempSelfObj.index = i; // 表示一级菜单索引
this.nameMaxLength = 4
this.showConfigureContent = !(item.children && item.children.length > 0); // 有子菜单,就不显示配置内容
// 左侧的选中
this.isActive = i; // 一级菜单选中样式
this.isSubMenuFlag = i; // 二级菜单显示标志
this.isSubMenuActive = -1; // 二级菜单去除选中样式
},
// 二级菜单点击事件
subMenuClick(subItem, index, k) {
// 右侧的表单相关
this.resetEditor();
this.showRightFlag = true; // 右边菜单
this.tempObj = subItem; // 将点击的数据放到临时变量,对象有引用作用
this.tempSelfObj.grand = "2"; // 表示二级菜单
this.tempSelfObj.index = index; // 表示一级菜单索引
this.tempSelfObj.secondIndex = k; // 表示二级菜单索引
this.nameMaxLength = 7
this.showConfigureContent = true;
// 左侧的选中
this.isActive = -1; // 一级菜单去除样式
this.isSubMenuActive = index + "" + k; // 二级菜单选中样式
},
// 添加横向一级菜单
addMenu() {
const menuKeyLength = this.menuList.length;
const addButton = {
name: "菜单名称",
children: [],
reply: { // 用于存储回复内容
'type': 'text',
'accountId': this.accountId // 保证组件里,可以使用到对应的公众号
}
}
this.$set(this.menuList, menuKeyLength, addButton)
this.menuClick(this.menuKeyLength - 1, addButton)
},
// 添加横向二级菜单item 表示要操作的父菜单
addSubMenu(i, item) {
// 清空父菜单的属性,因为它只需要 name 属性即可
if (!item.children || item.children.length <= 0) {
this.$set( item, 'children',[])
this.$delete( item, 'type')
this.$delete( item, 'menuKey')
this.$delete( item, 'miniProgramAppId')
this.$delete( item, 'miniProgramPagePath')
this.$delete( item, 'url')
this.$delete( item, 'reply')
this.$delete( item, 'articleId')
this.$delete( item, 'replyArticles')
// 关闭配置面板
this.showConfigureContent = false
}
let subMenuKeyLength = item.children.length; // 获取二级菜单key长度
let addButton = {
name: "子菜单名称",
reply: { // 用于存储回复内容
'type': 'text',
'accountId': this.accountId // 保证组件里,可以使用到对应的公众号
}
}
this.$set(item.children, subMenuKeyLength, addButton);
this.subMenuClick(item.children[subMenuKeyLength], i, subMenuKeyLength)
},
// 删除当前菜单
deleteMenu(item) {
this.$modal.confirm('确定要删除吗?').then(() => {
// 删除数据
if (this.tempSelfObj.grand === "1") { // 一级菜单的删除方法
this.menuList.splice(this.tempSelfObj.index, 1);
} else if (this.tempSelfObj.grand === "2") { // 二级菜单的删除方法
this.menuList[this.tempSelfObj.index].children.splice(this.tempSelfObj.secondIndex, 1);
}
// 提示
this.$modal.msgSuccess("删除成功");
// 处理菜单的选中
this.tempObj = {};
this.showRightFlag = false;
this.isActive = -1;
this.isSubMenuActive = -1;
}).catch(() => {});
},
// ======================== 菜单编辑 ========================
handleSave() {
this.$modal.confirm('确定要保证并发布该菜单吗?').then(() => {
this.loading = true
return saveMenu(this.accountId, this.convertMenuFormList());
}).then(() => {
this.getList();
this.$modal.msgSuccess("发布成功");
}).finally(() => {
this.loading = false
});
},
// 表单 Editor 重置
resetEditor() {
this.hackResetWxReplySelect = false // 销毁组件
this.$nextTick(() => {
this.hackResetWxReplySelect = true // 重建组件
})
},
handleDelete() {
this.$modal.confirm('确定要清空所有菜单吗?').then(() => {
this.loading = true
return deleteMenu(this.accountId);
}).then(() => {
this.handleQuery();
this.$modal.msgSuccess("清空成功");
}).catch(() => {}).finally(() => {
this.loading = false
});
},
// 将前端的 menuList转换成后端接收的 menuList
convertMenuFormList() {
const menuList = [];
this.menuList.forEach(item => {
let menu = this.convertMenuForm(item);
menuList.push(menu);
// 处理子菜单
if (!item.children || item.children.length <= 0) {
return;
}
menu.children = [];
item.children.forEach(subItem => {
menu.children.push(this.convertMenuForm(subItem))
})
})
return menuList;
},
// 将前端的 menu转换成后端接收的 menu
convertMenuForm(menu) {
let result = {
...menu,
children: undefined, // 不处理子节点
reply: undefined, // 稍后复制
}
if (menu.type === 'click' || menu.type === 'scancode_waitmsg') {
result.replyMessageType = menu.reply.type;
result.replyContent = menu.reply.content;
result.replyMediaId = menu.reply.mediaId;
result.replyMediaUrl = menu.reply.url;
result.replyTitle = menu.reply.title;
result.replyDescription = menu.reply.description;
result.replyThumbMediaId = menu.reply.thumbMediaId;
result.replyThumbMediaUrl = menu.reply.thumbMediaUrl;
result.replyArticles = menu.reply.articles;
result.replyMusicUrl = menu.reply.musicUrl;
result.replyHqMusicUrl = menu.reply.hqMusicUrl;
}
return result;
},
// ======================== 菜单编辑(素材选择) ========================
openMaterial() {
this.dialogNewsVisible = true
},
selectMaterial(item) {
const articleId = item.articleId;
const articles = item.content.newsItem;
// 提示,针对多图文
if (articles.length > 1) {
this.$alert('您选择的是多图文,将默认跳转第一篇', '提示', {
confirmButtonText: '确定'
})
}
this.dialogNewsVisible = false
// 设置菜单的回复
this.tempObj.articleId = articleId;
this.tempObj.replyArticles = [];
articles.forEach(article => {
this.tempObj.replyArticles.push({
title: article.title,
description: article.digest,
picUrl: article.picUrl,
url: article.url,
})
})
},
deleteMaterial() {
this.$delete(this.tempObj,'articleId')
this.$delete(this.tempObj,'replyArticles')
},
},
}
</script>
<!--本组件样式-->
<style lang="scss" scoped="scoped">
/* 公共颜色变量 */
.clearfix{*zoom:1;}
.clearfix::after{content: "";display: table; clear: both;}
div{
text-align: left;
}
.weixin-hd{
color: #fff;
text-align: center;
position: relative;
bottom: 426px;
left:0px;
width: 300px;
height:64px;
background: transparent url("assets/menu_head.png") no-repeat 0 0;
background-position: 0 0;
background-size: 100%
}
.weixin-title{
color:#fff;
font-size:14px;
width:100%;
text-align: center;
position:absolute;
top: 33px;
left: 0px;
}
.weixin-menu{
background: transparent url("assets/menu_foot.png") no-repeat 0 0;
padding-left: 43px;
font-size: 12px
}
.menu_option{
width: 40%!important;
}
.public-account-management{
min-width: 1200px;
width: 1200px;
margin: 0 auto;
.left{
float: left;
display: inline-block;
width: 350px;
height: 715px;
background: url("assets/iphone_backImg.png") no-repeat;
background-size: 100% auto;
padding: 518px 25px 88px;
position: relative;
box-sizing: border-box;
/*第一级菜单*/
.menu_main{
.menu_bottom{
position: relative;
float: left;
display: inline-block;
box-sizing: border-box;
width: 85.5px;
text-align: center;
border: 1px solid #ebedee;
background-color: #fff;
cursor: pointer;
&.menu_addicon{
height: 46px;
line-height: 46px;
}
.menu_item{
height: 44px;
line-height: 44px;
text-align: center;
box-sizing: border-box;
width: 100%;
&.active{
border: 1px solid #2bb673;
}
}
.menu_subItem{
height: 44px;
line-height: 44px;
text-align: center;
box-sizing: border-box;
&.active{
border: 1px solid #2bb673;
}
}
}
i{
color:#2bb673;
}
/*第二级菜单*/
.submenu{
position: absolute;
width: 85.5px;
bottom: 45px;
.subtitle{
background-color: #fff;
box-sizing: border-box;
}
}
}
.save_div{
margin-top: 15px;
text-align: center;
.save_btn{
bottom: 20px;
left: 100px;
}
}
}
/*右边菜单内容*/
.right {
float: left;
width: 63%;
background-color: #e8e7e7;
padding: 20px;
margin-left: 20px;
-webkit-box-sizing: border-box;
box-sizing: border-box;
.configure_page {
.delete_btn {
text-align: right;
margin-bottom: 15px;
}
.menu_content {
margin-top: 20px;
}
.configur_content {
margin-top: 20px;
background-color: #fff;
padding: 20px 10px;
border-radius: 5px
}
.blue {
color:#29b6f6;
margin-top: 10px;
}
.applet{
margin-bottom: 20px;
span{
width: 20%;
}
}
.input_width {
width: 40%;
}
.material{
.input_width{
width: 30%;
}
.el-textarea{
width: 80%
}
}
}
}
.el-input {
width: 70%;
margin-right: 2%;
}
}
</style>
<!--素材样式-->
<style lang="scss" scoped>
.pagination {
text-align: right;
margin-right: 25px;
}
.select-item {
width: 280px;
padding: 10px;
margin: 0 auto 10px auto;
border: 1px solid #eaeaea;
}
.select-item2 {
padding: 10px;
margin: 0 auto 10px auto;
border: 1px solid #eaeaea;
}
.ope-row {
padding-top: 10px;
text-align: center;
}
.item-name {
font-size: 12px;
overflow: hidden;
text-overflow:ellipsis;
white-space: nowrap;
text-align: center;
}
</style>

View File

@@ -0,0 +1,237 @@
<template>
<div class="app-container">
<!-- 搜索工作栏 -->
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="公众号" prop="accountId">
<el-select v-model="queryParams.accountId" placeholder="请选择公众号">
<el-option v-for="item in accounts" :key="parseInt(item.id)" :label="item.name" :value="parseInt(item.id)" />
</el-select>
</el-form-item>
<!-- TODO 等待处理 -->
<el-form-item label="消息类型" prop="type">
<el-select v-model="queryParams.type" placeholder="请选择消息类型" clearable size="small">
<el-option v-for="dict in this.getDictDatas(DICT_TYPE.MP_MESSAGE_TYPE)"
:key="dict.value" :label="dict.label" :value="dict.value"/>
</el-select>
</el-form-item>
<el-form-item label="用户标识" prop="openid">
<el-input v-model="queryParams.openid" placeholder="请输入用户标识" clearable @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="创建时间" prop="createTime">
<el-date-picker v-model="queryParams.createTime" style="width: 240px" value-format="yyyy-MM-dd HH:mm:ss" type="daterange"
range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期" :default-time="['00:00:00', '23:59:59']" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<!-- 操作工具栏 -->
<el-row :gutter="10" class="mb8">
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<!-- 列表 -->
<el-table v-loading="loading" :data="list">
<el-table-column label="发送时间" align="center" prop="createTime" width="180">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
<el-table-column label="消息类型" align="center" prop="type" width="80"/>
<el-table-column label="发送方" align="center" prop="sendFrom" width="80">
<template slot-scope="scope">
<el-tag v-if="scope.row.sendFrom === 1" type="success">粉丝</el-tag>
<el-tag v-else type="info">公众号</el-tag>
</template>
</el-table-column>
<el-table-column label="用户标识" align="center" prop="openid" width="300" />
<el-table-column label="内容" prop="content">
<template slot-scope="scope">
<!-- 事件区域 -->
<div v-if="scope.row.type === 'event' && scope.row.event === 'subscribe'">
<el-tag type="success" size="mini">关注</el-tag>
</div>
<div v-else-if="scope.row.type === 'event' && scope.row.event === 'unsubscribe'">
<el-tag type="danger" size="mini">取消关注</el-tag>
</div>
<div v-else-if="scope.row.type === 'event' && scope.row.event === 'CLICK'">
<el-tag size="mini">点击菜单</el-tag>{{ scope.row.eventKey }}
</div>
<div v-else-if="scope.row.type === 'event' && scope.row.event === 'VIEW'">
<el-tag size="mini">点击菜单链接</el-tag>{{ scope.row.eventKey }}
</div>
<div v-else-if="scope.row.type === 'event' && scope.row.event === 'scancode_waitmsg'">
<el-tag size="mini">扫码结果</el-tag>{{ scope.row.eventKey }}
</div>
<div v-else-if="scope.row.type === 'event' && scope.row.event === 'scancode_push'">
<el-tag size="mini">扫码结果</el-tag>{{ scope.row.eventKey }}
</div>
<div v-else-if="scope.row.type === 'event' && scope.row.event === 'pic_sysphoto'">
<el-tag size="mini">系统拍照发图</el-tag>
</div>
<div v-else-if="scope.row.type === 'event' && scope.row.event === 'pic_photo_or_album'">
<el-tag size="mini">拍照或者相册</el-tag>
</div>
<div v-else-if="scope.row.type === 'event' && scope.row.event === 'pic_weixin'">
<el-tag size="mini">微信相册</el-tag>
</div>
<div v-else-if="scope.row.type === 'event' && scope.row.event === 'location_select'">
<el-tag size="mini">选择地理位置</el-tag>
</div>
<div v-else-if="scope.row.type === 'event'">
<el-tag type="danger" size="mini">未知事件类型</el-tag>
</div>
<!-- 消息区域 -->
<div v-else-if="scope.row.type === 'text'">{{ scope.row.content }}</div>
<div v-else-if="scope.row.type === 'voice'">
<wx-voice-player :url="scope.row.mediaUrl" :content="scope.row.recognition" />
</div>
<div v-else-if="scope.row.type === 'image'">
<a target="_blank" :href="scope.row.mediaUrl">
<img :src="scope.row.mediaUrl" style="width: 100px">
</a>
</div>
<div v-else-if="scope.row.type === 'video' || scope.row.type === 'shortvideo'">
<wx-video-player :url="scope.row.mediaUrl" style="margin-top: 10px" />
</div>
<div v-else-if="scope.row.type === 'link'">
<el-tag size="mini">链接</el-tag>
<a :href="scope.row.url" target="_blank">{{scope.row.title}}</a>
</div>
<div v-else-if="scope.row.type === 'location'">
<wx-location :label="scope.row.label" :location-y="scope.row.locationY" :location-x="scope.row.locationX" />
</div>
<div v-else-if="scope.row.type === 'music'">
<wx-music :title="scope.row.title" :description="scope.row.description" :thumb-media-url="scope.row.thumbMediaUrl"
:music-url="scope.row.musicUrl" :hq-music-url="scope.row.hqMusicUrl" />
</div>
<div v-else-if="scope.row.type === 'news'">
<wx-news :articles="scope.row.articles" />
</div>
<div v-else>
<el-tag type="danger" size="mini">未知消息类型</el-tag>
</div>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template slot-scope="scope">
<el-button size="mini" type="text" icon="el-icon-edit" @click="handleSend(scope.row)"
v-hasPermi="['mp:message:send']">消息
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页组件 -->
<pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNo" :limit.sync="queryParams.pageSize"
@pagination="getList"/>
<!-- 发送消息的弹窗 -->
<el-dialog title="粉丝消息列表" :visible.sync="open" width="50%">
<wx-msg :user-id="userId" v-if="open" />
</el-dialog>
</div>
</template>
<script>
import WxVideoPlayer from '@/views/mp/components/wx-video-play/main.vue';
import WxVoicePlayer from '@/views/mp/components/wx-voice-play/main.vue';
import WxMsg from '@/views/mp/components/wx-msg/main.vue';
import WxLocation from '@/views/mp/components/wx-location/main.vue';
import WxMusic from '@/views/mp/components/wx-music/main.vue';
import WxNews from '@/views/mp/components/wx-news/main.vue';
import { getMessagePage } from "@/api/mp/message";
import { getSimpleAccounts } from "@/api/mp/account";
export default {
name: "MpMessage",
components: {
WxVideoPlayer,
WxVoicePlayer,
WxMsg,
WxLocation,
WxMusic,
WxNews
},
data() {
return {
// 遮罩层
loading: true,
// 导出遮罩层
exportLoading: false,
// 显示搜索条件
showSearch: true,
// 总条数
total: 0,
// 粉丝消息列表
list: [],
// 是否显示弹出层
open: false,
// 查询参数
queryParams: {
pageNo: 1,
pageSize: 10,
openid: null,
accountId: null,
type: null,
createTime: []
},
// 操作的用户编号
userId: 0,
// 公众号账号列表
accounts: []
};
},
created() {
getSimpleAccounts().then(response => {
this.accounts = response.data;
// 默认选中第一个
if (this.accounts.length > 0) {
this.queryParams.accountId = this.accounts[0].id;
}
// 加载数据
this.getList();
})
},
methods: {
/** 查询列表 */
getList() {
// 如果没有选中公众号账号,则进行提示。
if (!this.queryParams.accountId) {
this.$message.error('未选中公众号,无法查询消息')
return false
}
this.loading = true;
// 执行查询
getMessagePage(this.queryParams).then(response => {
this.list = response.data.list;
this.total = response.data.total;
this.loading = false;
});
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNo = 1;
this.getList();
},
/** 重置按钮操作 */
resetQuery() {
this.resetForm("queryForm");
// 默认选中第一个
if (this.accounts.length > 0) {
this.queryParams.accountId = this.accounts[0].id;
}
this.handleQuery();
},
handleSend(row) {
this.userId = row.userId;
this.open = true;
},
}
};
</script>

View File

@@ -0,0 +1,362 @@
<template>
<div class="app-container">
<!-- 搜索工作栏 -->
<el-form ref="queryForm" size="small" :inline="true" label-width="68px">
<el-form-item label="公众号" prop="accountId">
<el-select v-model="accountId" @change="getSummary">
<el-option v-for="item in accounts" :key="parseInt(item.id)" :label="item.name" :value="parseInt(item.id)" />
</el-select>
</el-form-item>
<el-form-item label="时间范围" prop="date">
<el-date-picker v-model="date" style="width: 260px" value-format="yyyy-MM-dd HH:mm:ss" type="daterange"
range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期"
:picker-options="datePickerOptions" :default-time="['00:00:00', '23:59:59']"
@change="getSummary">
</el-date-picker>
</el-form-item>
</el-form>
<!-- 图表 -->
<el-row>
<el-col :span="12" class="card-box">
<el-card>
<div slot="header">
<span>用户增减数据</span>
</div>
<div class="el-table el-table--enable-row-hover el-table--medium">
<div ref="userSummaryChart" style="height: 420px" />
</div>
</el-card>
</el-col>
<el-col :span="12" class="card-box">
<el-card>
<div slot="header">
<span>累计用户数据</span>
</div>
<div class="el-table el-table--enable-row-hover el-table--medium">
<div ref="userCumulateChart" style="height: 420px" />
</div>
</el-card>
</el-col>
<el-col :span="12" class="card-box">
<el-card>
<div slot="header">
<span>消息概况数据</span>
</div>
<div class="el-table el-table--enable-row-hover el-table--medium">
<div ref="upstreamMessageChart" style="height: 420px" />
</div>
</el-card>
</el-col>
<el-col :span="12" class="card-box">
<el-card>
<div slot="header">
<span>接口分析数据</span>
</div>
<div class="el-table el-table--enable-row-hover el-table--medium">
<div ref="interfaceSummaryChart" style="height: 420px" />
</div>
</el-card>
</el-col>
</el-row>
</div>
</template>
<script>
// 引入基本模板
import * as echarts from 'echarts'
// 引入柱状图组件
require('echarts/lib/chart/bar')
// 引入柱拆线组件
require('echarts/lib/chart/line')
// 引入提示框和title组件
require('echarts/lib/component/tooltip')
require('echarts/lib/component/title')
require('echarts/lib/component/legend')
import { getInterfaceSummary, getUserSummary, getUserCumulate, getUpstreamMessage} from '@/api/mp/statistics'
import { datePickerOptions } from "@/utils/constants";
import {addTime, beginOfDay, betweenDay, endOfDay, formatDate} from "@/utils/dateUtils";
import { getSimpleAccounts } from "@/api/mp/account";
export default {
name: 'mpStatistics',
data() {
return {
date : [beginOfDay(new Date(new Date().getTime() - 3600 * 1000 * 24 * 7)), // -7 天
endOfDay(new Date(new Date().getTime() - 3600 * 1000 * 24))], // -1 天
accountId: undefined,
accounts: [],
xAxisDate: [], // X 轴的日期范围
userSummaryOption: { // 用户增减数据
color: ['#67C23A', '#e5323e'],
legend: {
data: ['新增用户','取消关注的用户']
},
tooltip: {},
xAxis: {
data: [] // X 轴的日期范围
},
yAxis: {
minInterval: 1
},
series: [{
name: '新增用户',
type: 'bar',
label: {
normal: {
show: true
}
},
barGap: 0,
data: [] // 新增用户的数据
}, {
name: '取消关注的用户',
type: 'bar',
label: {
normal: {
show: true
}
},
data: [] // 取消关注的用户的数据
}]
},
userCumulateOption: { // 累计用户数据
legend: {
data: ['累计用户量']
},
xAxis: {
type: 'category',
data: []
},
yAxis: {
minInterval: 1
},
series: [{
name:'累计用户量',
data: [], // 累计用户量的数据
type: 'line',
smooth: true,
label: {
normal: {
show: true
}
}
}]
},
upstreamMessageOption: { // 消息发送概况数据
color: ['#67C23A', '#e5323e'],
legend: {
data: ['用户发送人数', '用户发送条数']
},
tooltip: {},
xAxis: {
data: [] // X 轴的日期范围
},
yAxis: {
minInterval: 1
},
series: [{
name: '用户发送人数',
type: 'line',
smooth: true,
label: {
normal: {
show: true
}
},
data: [] // 用户发送人数的数据
}, {
name: '用户发送条数',
type: 'line',
smooth: true,
label: {
normal: {
show: true
}
},
data: [] // 用户发送条数的数据
}]
},
interfaceSummaryOption: { // 接口分析况数据
color: ['#67C23A', '#e5323e', '#E6A23C', '#409EFF'],
legend: {
data: ['被动回复用户消息的次数','失败次数', '最大耗时','总耗时']
},
tooltip: {},
xAxis: {
data: [] // X 轴的日期范围
},
yAxis: {},
series: [{
name: '被动回复用户消息的次数',
type: 'bar',
label: {
normal: {
show: true
}
},
barGap: 0,
data: [] // 被动回复用户消息的次数的数据
}, {
name: '失败次数',
type: 'bar',
label: {
normal: {
show: true
}
},
data: [] // 失败次数的数据
}, {
name: '最大耗时',
type: 'bar',
label: {
normal: {
show: true
}
},
data: [] // 最大耗时的数据
}, {
name: '总耗时',
type: 'bar',
label: {
normal: {
show: true
}
},
data: [] // 总耗时的数据
}]
},
// 静态变量
datePickerOptions: datePickerOptions,
}
},
created() {
getSimpleAccounts().then(response => {
this.accounts = response.data;
// 默认选中第一个
if (this.accounts.length > 0) {
this.accountId = this.accounts[0].id;
}
// 加载数据
this.getSummary();
})
},
methods: {
getSummary() {
// 如果没有选中公众号账号,则进行提示。
if (!this.accountId) {
this.$message.error('未选中公众号,无法统计数据')
return false
}
// 必须选择 7 天内,因为公众号有时间跨度限制为 7
if (betweenDay(this.date[0], this.date[1]) >= 7) {
this.$message.error('时间间隔 7 天以内,请重新选择')
return false
}
this.xAxisDate = []
const days = betweenDay(this.date[0], this.date[1]) // 相差天数
for(let i = 0; i <= days; i++){
this.xAxisDate.push(formatDate(addTime(this.date[0], 3600 * 1000 * 24 * i), 'yyyy-MM-dd'));
}
// 初始化图表
this.initUserSummaryChart();
this.initUserCumulateChart();
this.initUpstreamMessageChart();
this.interfaceSummaryChart();
},
initUserSummaryChart() {
this.userSummaryOption.xAxis.data = [];
this.userSummaryOption.series[0].data = [];
this.userSummaryOption.series[1].data = [];
getUserSummary({
accountId: this.accountId,
date: [formatDate(this.date[0], 'yyyy-MM-dd HH:mm:ss'), formatDate(this.date[1], 'yyyy-MM-dd HH:mm:ss'),]
}).then(response => {
this.userSummaryOption.xAxis.data = this.xAxisDate;
// 处理数据
this.xAxisDate.forEach((date, index) => {
response.data.forEach((item) => {
// 匹配日期
const refDate = formatDate(new Date(item.refDate), 'yyyy-MM-dd');
if (refDate.indexOf(date) === -1) {
return;
}
// 设置数据到对应的位置
this.userSummaryOption.series[0].data[index] = item.newUser;
this.userSummaryOption.series[1].data[index] = item.cancelUser;
})
})
// 绘制图表
const userSummaryChart = echarts.init(this.$refs.userSummaryChart);
userSummaryChart.setOption(this.userSummaryOption)
}).catch(() => {})
},
initUserCumulateChart() {
this.userCumulateOption.xAxis.data = [];
this.userCumulateOption.series[0].data = [];
// 发起请求
getUserCumulate({
accountId: this.accountId,
date: [formatDate(this.date[0], 'yyyy-MM-dd HH:mm:ss'), formatDate(this.date[1], 'yyyy-MM-dd HH:mm:ss'),]
}).then(response => {
this.userCumulateOption.xAxis.data = this.xAxisDate;
// 处理数据
response.data.forEach((item, index) => {
this.userCumulateOption.series[0].data[index] = item.cumulateUser;
})
// 绘制图表
const userCumulateChart = echarts.init(this.$refs.userCumulateChart);
userCumulateChart.setOption(this.userCumulateOption)
}).catch(() => {})
},
initUpstreamMessageChart() {
this.upstreamMessageOption.xAxis.data = [];
this.upstreamMessageOption.series[0].data = [];
this.upstreamMessageOption.series[1].data = [];
// 发起请求
getUpstreamMessage({
accountId: this.accountId,
date: [formatDate(this.date[0], 'yyyy-MM-dd HH:mm:ss'), formatDate(this.date[1], 'yyyy-MM-dd HH:mm:ss'),]
}).then(response => {
this.upstreamMessageOption.xAxis.data = this.xAxisDate;
// 处理数据
response.data.forEach((item, index) => {
this.upstreamMessageOption.series[0].data[index] = item.messageUser;
this.upstreamMessageOption.series[1].data[index] = item.messageCount;
})
// 绘制图表
const upstreamMessageChart = echarts.init(this.$refs.upstreamMessageChart);
upstreamMessageChart.setOption(this.upstreamMessageOption);
}).catch(() => {})
},
interfaceSummaryChart() {
this.interfaceSummaryOption.xAxis.data = [];
this.interfaceSummaryOption.series[0].data = [];
this.interfaceSummaryOption.series[1].data = [];
this.interfaceSummaryOption.series[2].data = [];
this.interfaceSummaryOption.series[3].data = [];
// 发起请求
getInterfaceSummary({
accountId: this.accountId,
date: [formatDate(this.date[0], 'yyyy-MM-dd HH:mm:ss'), formatDate(this.date[1], 'yyyy-MM-dd HH:mm:ss'),]
}).then(response => {
this.interfaceSummaryOption.xAxis.data = this.xAxisDate;
// 处理数据
response.data.forEach((item, index) => {
this.interfaceSummaryOption.series[0].data[index] = item.callbackCount;
this.interfaceSummaryOption.series[1].data[index] = item.failCount;
this.interfaceSummaryOption.series[2].data[index] = item.maxTimeCost;
this.interfaceSummaryOption.series[3].data[index] = item.totalTimeCost;
})
// 绘制图表
const interfaceSummaryChart = echarts.init(this.$refs.interfaceSummaryChart);
interfaceSummaryChart.setOption(this.interfaceSummaryOption);
}).catch(() => {})
}
}
}
</script>

View File

@@ -0,0 +1,241 @@
<template>
<div class="app-container">
<!-- 搜索工作栏 -->
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="公众号" prop="accountId">
<el-select v-model="queryParams.accountId" placeholder="请选择公众号">
<el-option v-for="item in accounts" :key="parseInt(item.id)" :label="item.name" :value="parseInt(item.id)" />
</el-select>
</el-form-item>
<el-form-item label="标签名称" prop="name">
<el-input v-model="queryParams.name" placeholder="请输入标签名称" clearable @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<!-- 操作工具栏 -->
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" plain icon="el-icon-plus" size="mini" @click="handleAdd"
v-hasPermi="['mp:tag:create']">新增
</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="info" plain icon="el-icon-refresh" size="mini" @click="handleSync"
v-hasPermi="['mp:tag:sync']">同步
</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<!-- 列表 -->
<el-table v-loading="loading" :data="list">
<el-table-column label="编号" align="center" prop="id"/>
<el-table-column label="标签名称" align="center" prop="name"/>
<el-table-column label="粉丝数" align="center" prop="count"/>
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template slot-scope="scope">
<el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)"
v-hasPermi="['mp:tag:update']">修改
</el-button>
<el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)"
v-hasPermi="['mp:tag:delete']">删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页组件 -->
<pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNo" :limit.sync="queryParams.pageSize"
@pagination="getList"/>
<!-- 对话框(添加 / 修改) -->
<el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
<el-form ref="form" :model="form" :rules="rules" label-width="80px">
<el-form-item label="标签名称" prop="name">
<el-input v-model="form.name" placeholder="请输入标签名称"/>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import {
createTag,
updateTag,
deleteTag,
getTag,
getTagPage,
syncTag,
} from '@/api/mp/tag'
import { getSimpleAccounts} from '@/api/mp/account'
export default {
name: 'MpTag',
components: {},
data() {
return {
// 遮罩层
loading: true,
// 显示搜索条件
showSearch: true,
// 总条数
total: 0,
// 公众号标签列表
list: [],
// 弹出层标题
title: '',
// 是否显示弹出层
open: false,
// 查询参数
queryParams: {
accountId: null,
name: null,
},
// 表单参数
form: {
accountId: undefined,
name: undefined,
},
// 表单校验
rules: {
name: [{ required: true, message: '请输入标签名称', trigger: 'blur' }]
},
// 公众号账号列表
accounts: []
}
},
created() {
getSimpleAccounts().then(response => {
this.accounts = response.data;
// 默认选中第一个
if (this.accounts.length > 0) {
this.queryParams.accountId = this.accounts[0].id;
}
// 加载数据
this.getList();
})
},
methods: {
/** 查询列表 */
getList() {
// 如果没有选中公众号账号,则进行提示。
if (!this.queryParams.accountId) {
this.$message.error('未选中公众号,无法查询标签')
return false
}
this.loading = false
// 处理查询参数
let params = {...this.queryParams}
// 执行查询
getTagPage(params).then(response => {
this.list = response.data.list
this.total = response.data.total
this.loading = false
})
},
/** 取消按钮 */
cancel() {
this.open = false
this.reset()
},
/** 表单重置 */
reset() {
this.form = {
accountId: undefined,
name: undefined,
}
this.resetForm('form')
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNo = 1
this.getList()
},
/** 重置按钮操作 */
resetQuery() {
this.resetForm('queryForm')
// 默认选中第一个
if (this.accounts.length > 0) {
this.queryParams.accountId = this.accounts[0].id;
}
this.handleQuery()
},
/** 新增按钮操作 */
handleAdd() {
this.reset()
this.open = true
this.title = '添加公众号标签'
},
/** 修改按钮操作 */
handleUpdate(row) {
this.reset()
const id = row.id
getTag(id).then(response => {
this.form = response.data
this.open = true
this.title = '修改公众号标签'
})
},
/** 提交按钮 */
submitForm() {
this.$refs['form'].validate(valid => {
if (!valid) {
return
}
this.form.accountId = this.queryParams.accountId;
// 修改的提交
if (this.form.id != null) {
updateTag(this.form).then(response => {
this.$modal.msgSuccess('修改成功')
this.open = false
this.getList()
})
return
}
// 添加的提交
createTag(this.form).then(response => {
this.$modal.msgSuccess('新增成功')
this.open = false
this.getList()
})
})
},
/** 删除按钮操作 */
handleDelete(row) {
const id = row.id
this.$modal.confirm('是否确认删除公众号标签编号为"' + id + '"的数据项?').then(function () {
return deleteTag(id)
}).then(() => {
this.getList()
this.$modal.msgSuccess('删除成功')
}).catch(() => {
})
},
/** 同步标签 */
handleSync() {
const accountId = this.queryParams.accountId
this.$modal.confirm('是否确认同步标签?').then(function () {
return syncTag(accountId)
}).then(() => {
this.$modal.msgSuccess('同步标签成功')
}).catch(() => {
})
},
}
}
</script>

View File

@@ -0,0 +1,235 @@
<template>
<div class="app-container">
<!-- 搜索工作栏 -->
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="公众号" prop="accountId">
<el-select v-model="queryParams.accountId" placeholder="请选择公众号">
<el-option v-for="item in accounts" :key="parseInt(item.id)" :label="item.name" :value="parseInt(item.id)" />
</el-select>
</el-form-item>
<el-form-item label="用户标识" prop="openid">
<el-input v-model="queryParams.openid" placeholder="请输入用户标识" clearable @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="昵称" prop="nickname">
<el-input v-model="queryParams.nickname" placeholder="请输入昵称" clearable @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<!-- 操作工具栏 -->
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="info" plain icon="el-icon-refresh" size="mini" @click="handleSync"
v-hasPermi="['mp:user:sync']">同步
</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<!-- 列表 -->
<el-table v-loading="loading" :data="list">
<el-table-column label="编号" align="center" prop="id" />
<el-table-column label="用户标识" align="center" prop="openid" width="260" />
<el-table-column label="昵称" align="center" prop="nickname" />
<el-table-column label="备注" align="center" prop="remark" />
<el-table-column label="标签" align="center" prop="tagIds" width="200">
<template slot-scope="scope">
<span v-for="(tagId, index) in scope.row.tagIds" :key="index">
<el-tag>{{ tags.find(tag => tag.tagId === tagId)?.name }} </el-tag>&nbsp;
</span>
</template>
</el-table-column>
<el-table-column label="订阅状态" align="center" prop="subscribeStatus">
<template slot-scope="scope">
<el-tag v-if="scope.row.subscribeStatus === 0" type="success">已订阅</el-tag>
<el-tag v-else type="danger">未订阅</el-tag>
</template>
</el-table-column>
<el-table-column label="订阅时间" align="center" prop="subscribeTime" width="180">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.subscribeTime) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template slot-scope="scope">
<el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)"
v-hasPermi="['mp:user:update']">修改</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页组件 -->
<pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNo" :limit.sync="queryParams.pageSize"
@pagination="getList"/>
<!-- 对话框(添加 / 修改) -->
<el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
<el-form ref="form" :model="form" :rules="rules" label-width="80px">
<el-form-item label="昵称" prop="nickname">
<el-input v-model="form.nickname" placeholder="请输入昵称" />
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" placeholder="请输入备注" />
</el-form-item>
<el-form-item label="标签" prop="tagIds">
<el-select v-model="form.tagIds" multiple clearable placeholder="请选择标签">
<el-option v-for="item in tags" :key="parseInt(item.tagId)" :label="item.name" :value="parseInt(item.tagId)" />
</el-select>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { updateUser, getUser, getUserPage, syncUser } from "@/api/mp/user";
import { getSimpleAccounts } from "@/api/mp/account";
import { getSimpleTags } from "@/api/mp/tag";
export default {
name: "WxAccountFans",
components: {
},
data() {
return {
// 遮罩层
loading: true,
// 显示搜索条件
showSearch: true,
// 总条数
total: 0,
// 微信公众号粉丝列表
list: [],
// 弹出层标题
title: "",
// 是否显示弹出层
open: false,
// 查询参数
queryParams: {
pageNo: 1,
pageSize: 10,
accountId: null,
openid: null,
nickname: null,
},
// 表单参数
form: {},
// 表单校验
rules: {},
// 公众号账号列表
accounts: [],
// 公众号标签列表
tags: [],
};
},
created() {
getSimpleAccounts().then(response => {
this.accounts = response.data;
// 默认选中第一个
if (this.accounts.length > 0) {
this.queryParams.accountId = this.accounts[0].id;
}
// 加载数据
this.getList();
})
// 加载标签
getSimpleTags().then(response => {
this.tags = response.data;
})
},
methods: {
/** 查询列表 */
getList() {
// 如果没有选中公众号账号,则进行提示。
if (!this.queryParams.accountId) {
this.$message.error('未选中公众号,无法查询用户')
return false
}
this.loading = true;
// 处理查询参数
let params = {...this.queryParams};
// 执行查询
getUserPage(params).then(response => {
this.list = response.data.list;
this.total = response.data.total;
this.loading = false;
});
},
/** 取消按钮 */
cancel() {
this.open = false;
this.reset();
},
/** 表单重置 */
reset() {
this.form = {
id: undefined,
nickname: undefined,
remark: undefined,
tagIds: [],
};
this.resetForm("form");
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNo = 1;
this.getList();
},
/** 重置按钮操作 */
resetQuery() {
this.resetForm("queryForm");
// 默认选中第一个
if (this.accounts.length > 0) {
this.queryParams.accountId = this.accounts[0].id;
}
this.handleQuery();
},
/** 修改按钮操作 */
handleUpdate(row) {
this.reset();
const id = row.id;
getUser(id).then(response => {
this.form = response.data;
this.open = true;
this.title = "修改公众号粉丝";
});
},
/** 提交按钮 */
submitForm() {
this.$refs["form"].validate(valid => {
if (!valid) {
return;
}
// 修改的提交
if (this.form.id != null) {
updateUser(this.form).then(response => {
this.$modal.msgSuccess("修改成功");
this.open = false;
this.getList();
});
}
});
},
/** 同步标签 */
handleSync() {
const accountId = this.queryParams.accountId
this.$modal.confirm('是否确认同步粉丝?').then(function () {
return syncUser(accountId)
}).then(() => {
this.$modal.msgSuccess('开始从微信公众号同步粉丝信息,同步需要一段时间,建议稍后再查询')
}).catch(() => {
})
},
}
};
</script>

View File

@@ -47,20 +47,20 @@
<el-table-column label="应用编号" align="center" prop="id"/>
<el-table-column label="应用名" align="center" prop="name"/>
<el-table-column label="开启状态" align="center" prop="status">
<template slot-scope="scope">
<template v-slot="scope">
<el-switch v-model="scope.row.status" :active-value="0" :inactive-value="1"
@change="handleStatusChange(scope.row)"/>
</template>
</el-table-column>
<el-table-column label="商户名称" align="center" prop="payMerchant.name"/>
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
<template slot-scope="scope">
<template v-slot="scope">
<span>{{ parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
<el-table-column label="支付宝配置" align="center">
<el-table-column :label="payChannelEnum.ALIPAY_APP.name" align="center">
<template slot-scope="scope">
<template v-slot="scope">
<el-button type="success" icon="el-icon-check" circle
v-if="judgeChannelExist(scope.row.channelCodes,payChannelEnum.ALIPAY_APP.code)"
@click="handleUpdateChannel(scope.row,payChannelEnum.ALIPAY_APP.code,payType.ALIPAY)">
@@ -72,7 +72,7 @@
</template>
</el-table-column>
<el-table-column :label="payChannelEnum.ALIPAY_PC.name" align="center">
<template slot-scope="scope">
<template v-slot="scope">
<el-button type="success" icon="el-icon-check" circle
v-if="judgeChannelExist(scope.row.channelCodes,payChannelEnum.ALIPAY_PC.code)"
@click="handleUpdateChannel(scope.row,payChannelEnum.ALIPAY_PC.code,payType.ALIPAY)">
@@ -84,7 +84,7 @@
</template>
</el-table-column>
<el-table-column :label="payChannelEnum.ALIPAY_WAP.name" align="center">
<template slot-scope="scope">
<template v-slot="scope">
<el-button type="success" icon="el-icon-check" circle
v-if="judgeChannelExist(scope.row.channelCodes,payChannelEnum.ALIPAY_WAP.code)"
@click="handleUpdateChannel(scope.row,payChannelEnum.ALIPAY_WAP.code,payType.ALIPAY)">
@@ -96,7 +96,7 @@
</template>
</el-table-column>
<el-table-column :label="payChannelEnum.ALIPAY_QR.name" align="center">
<template slot-scope="scope">
<template v-slot="scope">
<el-button type="success" icon="el-icon-check" circle
v-if="judgeChannelExist(scope.row.channelCodes,payChannelEnum.ALIPAY_QR.code)"
@click="handleUpdateChannel(scope.row,payChannelEnum.ALIPAY_QR.code,payType.ALIPAY)">
@@ -110,7 +110,7 @@
</el-table-column>
<el-table-column label="微信配置" align="center">
<el-table-column :label="payChannelEnum.WX_LITE.name" align="center">
<template slot-scope="scope">
<template v-slot="scope">
<el-button type="success" icon="el-icon-check" circle
v-if="judgeChannelExist(scope.row.channelCodes,payChannelEnum.WX_LITE.code)"
@click="handleUpdateChannel(scope.row,payChannelEnum.WX_LITE.code,payType.WECHAT)">
@@ -122,7 +122,7 @@
</template>
</el-table-column>
<el-table-column :label="payChannelEnum.WX_PUB.name" align="center">
<template slot-scope="scope">
<template v-slot="scope">
<el-button type="success" icon="el-icon-check" circle
v-if="judgeChannelExist(scope.row.channelCodes,payChannelEnum.WX_PUB.code)"
@click="handleUpdateChannel(scope.row,payChannelEnum.WX_PUB.code,payType.WECHAT)">
@@ -134,7 +134,7 @@
</template>
</el-table-column>
<el-table-column :label="payChannelEnum.WX_APP.name" align="center">
<template slot-scope="scope">
<template v-slot="scope">
<el-button type="success" icon="el-icon-check" circle
v-if="judgeChannelExist(scope.row.channelCodes,payChannelEnum.WX_APP.code)"
@click="handleUpdateChannel(scope.row,payChannelEnum.WX_APP.code,payType.WECHAT)">
@@ -147,7 +147,7 @@
</el-table-column>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template slot-scope="scope">
<template v-slot="scope">
<el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)"
v-hasPermi="['pay:app:update']">修改
</el-button>

View File

@@ -50,18 +50,18 @@
<el-table-column label="商户全称" align="center" prop="name" />
<el-table-column label="商户简称" align="center" prop="shortName" />
<el-table-column label="开启状态" align="center" prop="status" >
<template slot-scope="scope">
<template v-slot="scope">
<el-switch v-model="scope.row.status" :active-value="0" :inactive-value="1" @change="handleStatusChange(scope.row)" />
</template>
</el-table-column>
<el-table-column label="备注" align="center" prop="remark" />
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
<template slot-scope="scope">
<template v-slot="scope">
<span>{{ parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template slot-scope="scope">
<template v-slot="scope">
<el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)"
v-hasPermi="['pay:merchant:update']">修改</el-button>
<el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)"

View File

@@ -94,48 +94,48 @@
<!-- <el-table-column label="渠道订单号" align="center" prop="channelOrderNo" width="140"/>-->
<el-table-column label="商品标题" align="center" prop="subject" width="180" :show-overflow-tooltip="true"/>
<el-table-column label="支付金额" align="center" prop="amount" width="100">
<template slot-scope="scope">
<template v-slot="scope">
{{ parseFloat(scope.row.amount / 100).toFixed(2) }}
</template>
</el-table-column>
<el-table-column label="手续金额" align="center" prop="channelFeeAmount" width="100">
<template slot-scope="scope">
<template v-slot="scope">
{{ parseFloat(scope.row.channelFeeAmount / 100).toFixed(2) }}
</template>
</el-table-column>
<el-table-column label="退款金额" align="center" prop="refundAmount" width="100">
<template slot-scope="scope">
<template v-slot="scope">
{{ parseFloat(scope.row.refundAmount / 100).toFixed(2) }}
</template>
</el-table-column>
<el-table-column label="支付状态" align="center" prop="status">
<template slot-scope="scope">
<template v-slot="scope">
<dict-tag :type="DICT_TYPE.PAY_ORDER_STATUS" :value="scope.row.status" />
</template>
</el-table-column>
<!-- <el-table-column label="退款状态" align="center" prop="refundStatus">-->
<!-- <template slot-scope="scope">-->
<!-- <template v-slot="scope">-->
<!-- <span>{{ getDictDataLabel(DICT_TYPE.PAY_ORDER_REFUND_STATUS, scope.row.refundStatus) }}</span>-->
<!-- </template>-->
<!-- </el-table-column>-->
<el-table-column label="回调状态" align="center" prop="notifyStatus" width="100">
<template slot-scope="scope">
<template v-slot="scope">
<dict-tag :type="DICT_TYPE.PAY_ORDER_NOTIFY_STATUS" :value="scope.row.notifyStatus" />
</template>
</el-table-column>
<el-table-column label="创建时间" align="center" prop="createTime" width="100">
<template slot-scope="scope">
<template v-slot="scope">
<span>{{ parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
<el-table-column label="支付时间" align="center" prop="successTime" width="100">
<template slot-scope="scope">
<template v-slot="scope">
<span>{{ parseTime(scope.row.successTime) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" fixed="right" class-name="small-padding fixed-width">
<template slot-scope="scope">
<template v-slot="scope">
<el-button size="mini" type="text" icon="el-icon-search" @click="handleQueryDetails(scope.row)"
v-hasPermi="['pay:order:query']">查看详情
</el-button>
@@ -219,7 +219,6 @@ import {getOrder, getOrderPage, exportOrderExcel} from "@/api/pay/order";
import {getMerchantListByName} from "@/api/pay/merchant";
import {getAppListByMerchantId} from "@/api/pay/app";
import {DICT_TYPE, getDictDatas} from "@/utils/dict";
import {PayOrderNotifyStatusEnum, PayOrderRefundStatusEnum, PayOrderStatusEnum} from "@/utils/constants";
import { getNowDateTime} from "@/utils/ruoyi";
const defaultOrderDetail = {

View File

@@ -68,7 +68,7 @@
<!-- <el-table-column label="商户名称" align="center" prop="merchantName" width="120"/>-->
<!-- <el-table-column label="应用名称" align="center" prop="appName" width="120"/>-->
<el-table-column label="支付渠道" align="center" width="130">
<template slot-scope="scope">
<template v-slot="scope">
<el-popover trigger="hover" placement="top">
<p>商户名称: {{ scope.row.merchantName }}</p>
<p>应用名称: {{ scope.row.appName }}</p>
@@ -82,7 +82,7 @@
<!-- <el-table-column label="交易订单号" align="center" prop="tradeNo" width="140"/>-->
<!-- <el-table-column label="商户订单编号" align="center" prop="merchantOrderId" width="140"/>-->
<el-table-column label="商户订单号" align="left" width="230">
<template slot-scope="scope">
<template v-slot="scope">
<p class="order-font">
<el-tag size="mini">退款</el-tag>
{{ scope.row.merchantRefundNo }}
@@ -94,7 +94,7 @@
</template>
</el-table-column>
<el-table-column label="支付订单号" align="center" prop="merchantRefundNo" width="250">
<template slot-scope="scope">
<template v-slot="scope">
<p class="order-font">
<el-tag size="mini">交易</el-tag>
{{ scope.row.tradeNo }}
@@ -106,43 +106,43 @@
</template>
</el-table-column>
<el-table-column label="支付金额(元)" align="center" prop="payAmount" width="100">
<template slot-scope="scope" class="">
<template v-slot="scope" class="">
{{ parseFloat(scope.row.payAmount / 100).toFixed(2) }}
</template>
</el-table-column>
<el-table-column label="退款金额(元)" align="center" prop="refundAmount" width="100">
<template scope="scope">
<template v-slot="scope">
{{ parseFloat(scope.row.refundAmount / 100).toFixed(2) }}
</template>
</el-table-column>
<el-table-column label="退款类型" align="center" prop="type" width="80">
<template slot-scope="scope">
<template v-slot="scope">
<dict-tag :type="DICT_TYPE.PAY_REFUND_ORDER_TYPE" :value="scope.row.type" />
</template>
</el-table-column>
<el-table-column label="退款状态" align="center" prop="status">
<template slot-scope="scope">
<template v-slot="scope">
<dict-tag :type="DICT_TYPE.PAY_REFUND_ORDER_STATUS" :value="scope.row.status" />
</template>
</el-table-column>
<el-table-column label="回调状态" align="center" prop="notifyStatus">
<template slot-scope="scope">
<template v-slot="scope">
<dict-tag :type="DICT_TYPE.PAY_ORDER_NOTIFY_STATUS" :value="scope.row.notifyStatus" />
</template>
</el-table-column>
<el-table-column label="退款原因" align="center" prop="reason" width="140" :show-overflow-tooltip="true"/>
<el-table-column label="创建时间" align="center" prop="createTime" width="100">
<template slot-scope="scope">
<template v-slot="scope">
<span>{{ parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
<el-table-column label="退款成功时间" align="center" prop="successTime" width="100">
<template slot-scope="scope">
<template v-slot="scope">
<span>{{ parseTime(scope.row.successTime) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" fixed="right" class-name="small-padding fixed-width">
<template slot-scope="scope">
<template v-slot="scope">
<el-button size="mini" type="text" icon="el-icon-search" @click="handleQueryDetails(scope.row)"
v-hasPermi="['pay:order:query']">查看详情
</el-button>
@@ -177,7 +177,7 @@
<el-tag class="tag-purple" size="mini">{{ parseFloat(refundDetail.refundAmount / 100).toFixed(2) }}</el-tag>
</el-descriptions-item>
<el-descriptions-item label="退款类型">
<template slot-scope="scope">
<template v-slot="scope">
<dict-tag :type="DICT_TYPE.PAY_REFUND_ORDER_TYPE" :value="refundDetail.type" />
</template>
</el-descriptions-item>

View File

@@ -32,20 +32,11 @@
<svg-icon slot="prefix" icon-class="password" class="el-input__icon input-icon"/>
</el-input>
</el-form-item>
<el-form-item prop="code" v-if="captchaEnable">
<el-input v-model="loginForm.code" auto-complete="off" placeholder="验证码" style="width: 63%"
@keyup.enter.native="handleLogin">
<svg-icon slot="prefix" icon-class="validCode" class="el-input__icon input-icon"/>
</el-input>
<div class="login-code">
<img :src="codeUrl" @click="getCode" class="login-code-img"/>
</div>
</el-form-item>
<el-checkbox v-model="loginForm.rememberMe" style="margin:0 0 25px 0;">记住密码</el-checkbox>
<!-- 下方的登录按钮 -->
<el-form-item style="width:100%;">
<el-button :loading="loading" size="medium" type="primary" style="width:100%;"
@click.native.prevent="handleLogin">
@click.native.prevent="getCode">
<span v-if="!loading"> </span>
<span v-else> 中...</span>
</el-button>
@@ -55,6 +46,11 @@
</div>
</div>
</div>
<!-- 图形验证码 -->
<Verify ref="verify" :captcha-type="'blockPuzzle'" :img-size="{width:'400px',height:'200px'}"
@success="handleLogin" />
<!-- footer -->
<div class="footer">
Copyright © 2020-2022 iocoder.cn All Rights Reserved.
@@ -72,10 +68,15 @@ import {
setRememberMe,
setUsername
} from "@/utils/auth";
import {getCodeImg} from "@/api/login";
import Verify from '@/components/Verifition/Verify';
import {getCaptchaEnable} from "@/utils/ruoyi";
export default {
name: "ThirdLogin",
components: {
Verify
},
data() {
return {
codeUrl: "",
@@ -85,8 +86,7 @@ export default {
username: "admin",
password: "admin123",
rememberMe: false,
code: "",
uuid: "",
captchaVerification: "",
},
loginRules: {
username: [
@@ -104,23 +104,19 @@ export default {
state: undefined,
};
},
// watch: {
// $route: {
// handler: function(route) {
// this.redirect = route.query && route.query.redirect;
// },
// immediate: true
// }
// },
created() {
this.getCookie();
// 验证码开关
this.captchaEnable = getCaptchaEnable();
// 重定向地址
this.redirect = this.$route.query.redirect;
this.getCode();
this.redirect = this.getUrlValue('redirect');
// 社交登录相关
this.type = this.$route.query.type;
this.type = this.getUrlValue('type');
this.code = this.$route.query.code;
this.state = this.$route.query.state;
// 尝试登录一下
this.loading = true;
this.$store.dispatch("SocialLogin", {
code: this.code,
state: this.state,
@@ -133,19 +129,15 @@ export default {
},
methods: {
getCode() {
// 只有开启的状态,才加载验证码。默认开启
// 情况一,未开启:则直接登录
if (!this.captchaEnable) {
this.handleLogin({})
return;
}
// 请求远程,获得验证码
getCodeImg().then(res => {
res = res.data;
this.captchaEnable = res.enable;
if (this.captchaEnable) {
this.codeUrl = "data:image/gif;base64," + res.img;
this.loginForm.uuid = res.uuid;
}
});
// 情况二,已开启:则展示验证码;只有完成验证码的情况,才进行登录
// 弹出验证码
this.$refs.verify.show()
},
getCookie() {
const username = getUsername();
@@ -158,7 +150,7 @@ export default {
loginType: this.loginForm.loginType,
};
},
handleLogin() {
handleLogin(captchaParams) {
this.$refs.loginForm.validate(valid => {
if (valid) {
this.loading = true;
@@ -177,8 +169,7 @@ export default {
// 账号密码登录
username: this.loginForm.username,
password: this.loginForm.password,
code: this.loginForm.code,
uuid: this.loginForm.uuid,
captchaVerification: captchaParams.captchaVerification
}).then(() => {
this.$router.push({ path: this.redirect || "/" }).catch(()=>{});
}).catch(() => {
@@ -187,6 +178,10 @@ export default {
});
}
});
},
getUrlValue(key) {
const url = new URL(decodeURIComponent(location.href));
return url.searchParams.get(key);
}
}
};

View File

@@ -19,12 +19,7 @@
</el-tab-pane>
</el-tabs>
<div>
<el-form ref="loginForm" :model="loginForm" :rules="LoginRules" class="login-form">
<el-form-item prop="tenantName" v-if="tenantEnable">
<el-input v-model="loginForm.tenantName" type="text" auto-complete="off" placeholder='租户'>
<svg-icon slot="prefix" icon-class="tree" class="el-input__icon input-icon"/>
</el-input>
</el-form-item>
<el-form ref="loginForm" :model="loginForm" class="login-form">
<!-- 授权范围的选择 -->
此第三方应用请求获得以下权限
<el-form-item prop="scopes">
@@ -56,18 +51,13 @@
</template>
<script>
import {getTenantIdByName} from "@/api/system/tenant";
import {getTenantEnable} from "@/utils/ruoyi";
import {authorize, getAuthorize} from "@/api/login";
import {getTenantName, setTenantId} from "@/utils/auth";
export default {
name: "Login",
data() {
return {
tenantEnable: true,
loginForm: {
tenantName: "芋道源码",
scopes: [], // 已选中的 scope 数组
},
params: { // URL 上的 client_id、scope 等参数
@@ -81,35 +71,10 @@ export default {
name: '',
logo: '',
},
LoginRules: {
tenantName: [
{required: true, trigger: "blur", message: "租户不能为空"},
{
validator: (rule, value, callback) => {
// debugger
getTenantIdByName(value).then(res => {
const tenantId = res.data;
if (tenantId && tenantId >= 0) {
// 设置租户
setTenantId(tenantId)
callback();
} else {
callback('租户不存在');
}
});
},
trigger: 'blur'
}
]
},
loading: false
};
},
created() {
// 租户开关
this.tenantEnable = getTenantEnable();
this.getCookie();
// 解析参数
// 例如说【自动授权不通过】client_id=default&redirect_uri=https%3A%2F%2Fwww.iocoder.cn&response_type=code&scope=user.read%20user.write
// 例如说【自动授权通过】client_id=default&redirect_uri=https%3A%2F%2Fwww.iocoder.cn&response_type=code&scope=user.read
@@ -162,13 +127,6 @@ export default {
})
},
methods: {
getCookie() {
const tenantName = getTenantName();
this.loginForm = {
...this.loginForm,
tenantName: tenantName ? tenantName : this.loginForm.tenantName,
};
},
handleAuthorize(approved) {
this.$refs.loginForm.validate(valid => {
if (!valid) {
@@ -217,7 +175,7 @@ export default {
@import "~@/assets/styles/login.scss";
.oauth-login {
display: flex;
align-items: cen;
align-items: center;
cursor:pointer;
}
.oauth-login-item {

View File

@@ -0,0 +1,114 @@
<template>
<div class="app-container">
<doc-alert title="地区 & IP" url="https://doc.iocoder.cn/area-and-ip/" />
<!-- 操作工具栏 -->
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" plain icon="el-icon-plus" size="mini" @click="handleAdd">IP 查询
</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<!-- 列表 -->
<el-table v-if="refreshTable" v-loading="loading" :data="list" row-key="id"
:tree-props="{children: 'children', hasChildren: 'hasChildren'}">
<el-table-column label="编号" prop="id"/>
<el-table-column label="名字" prop="name"/>
</el-table>
<!-- 对话框(添加 / 修改) -->
<el-dialog title="IP 查询" :visible.sync="open" width="500px" append-to-body>
<el-form ref="form" :model="form" :rules="rules" label-width="80px">
<el-form-item label="IP" prop="ip">
<el-input v-model="form.ip" placeholder="请输入 IP 地址"/>
</el-form-item>
<el-form-item label="地址" prop="result">
<el-input v-model="form.result" readonly placeholder="展示查询 IP 结果" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import {getAreaByIp, getAreaTree} from "@/api/system/area";
export default {
name: "Area",
components: {
},
data() {
return {
// 遮罩层
loading: true,
// 显示搜索条件
showSearch: true,
// 地区列表
list: [],
// 弹出层标题
title: "",
// 是否显示弹出层
open: false,
// 重新渲染表格状态
refreshTable: true,
// 表单参数
form: {
ip: undefined,
result: undefined,
},
// 表单校验
rules: {
ip: [{required: true, message: "IP 地址不能为控", trigger: "blur"}],
}
};
},
created() {
this.getList();
},
methods: {
/** 查询列表 */
getList() {
this.loading = true;
getAreaTree().then(response => {
this.list = response.data;
this.loading = false;
})
},
/** 取消按钮 */
cancel() {
this.open = false;
this.reset();
},
/** 表单重置 */
reset() {
this.form = {
ip: undefined,
result: undefined,
};
this.resetForm("form");
},
/** 新增按钮操作 */
handleAdd() {
this.reset();
this.open = true;
},
/** 提交按钮 */
submitForm() {
this.$refs["form"].validate(valid => {
if (!valid) {
return;
}
getAreaByIp(this.form.ip).then(response => {
this.$modal.msgSuccess("查询成功");
this.form.result = response.data
});
});
}
}
};
</script>

View File

@@ -32,17 +32,17 @@
<el-table-column prop="leader" label="负责人" :formatter="userNicknameFormat" width="120"/>
<el-table-column prop="sort" label="排序" width="200"></el-table-column>
<el-table-column prop="status" label="状态" width="100">
<template slot-scope="scope">
<template v-slot="scope">
<dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status"/>
</template>
</el-table-column>
<el-table-column label="创建时间" align="center" prop="createTime" width="200">
<template slot-scope="scope">
<template v-slot="scope">
<span>{{ parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template slot-scope="scope">
<template v-slot="scope">
<el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)"
v-hasPermi="['system:dept:update']">修改</el-button>
<el-button size="mini" type="text" icon="el-icon-plus" @click="handleAdd(scope.row)"
@@ -141,8 +141,6 @@ export default {
refreshTable: true,
// 是否展开
expand: false,
// 状态数据字典
statusOptions: [],
// 查询参数
queryParams: {
name: undefined,
@@ -167,7 +165,7 @@ export default {
],
phone: [
{
pattern: /^(?:(?:\+|00)86)?1(?:(?:3[\d])|(?:4[5-79])|(?:5[0-35-9])|(?:6[5-7])|(?:7[0-8])|(?:8[\d])|(?:9[189]))\d{8}$/,
pattern: /^(?:(?:\+|00)86)?1(?:3[\d]|4[5-79]|5[0-35-9]|6[5-7]|7[0-8]|8[\d]|9[189])\d{8}$/,
message: "请输入正确的手机号码",
trigger: "blur"
}

View File

@@ -11,7 +11,7 @@
</el-form-item>
<el-form-item label="状态" prop="status">
<el-select v-model="queryParams.status" placeholder="数据状态" clearable>
<el-option v-for="dict in statusOptions" :key="dict.value" :label="dict.label" :value="dict.value"/>
<el-option v-for="dict in statusDictDatas" :key="dict.value" :label="dict.label" :value="dict.value"/>
</el-select>
</el-form-item>
<el-form-item>
@@ -38,7 +38,7 @@
<el-table-column label="字典键值" align="center" prop="value" />
<el-table-column label="字典排序" align="center" prop="sort" />
<el-table-column label="状态" align="center" prop="status">
<template slot-scope="scope">
<template v-slot="scope">
<dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status"/>
</template>
</el-table-column>
@@ -46,12 +46,12 @@
<el-table-column label="CSS Class" align="center" prop="cssClass" />
<el-table-column label="备注" align="center" prop="remark" :show-overflow-tooltip="true" />
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
<template slot-scope="scope">
<template v-slot="scope">
<span>{{ parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template slot-scope="scope">
<template v-slot="scope">
<el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)"
v-hasPermi="['system:dict:update']">修改</el-button>
<el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)"
@@ -130,8 +130,6 @@ export default {
title: "",
// 是否显示弹出层
open: false,
// 状态数据字典
statusOptions: [],
// 类型数据字典
typeOptions: [],
// 查询参数

View File

@@ -38,25 +38,25 @@
<el-table-column label="字典编号" align="center" prop="id" />
<el-table-column label="字典名称" align="center" prop="name" :show-overflow-tooltip="true" />
<el-table-column label="字典类型" align="center" :show-overflow-tooltip="true">
<template slot-scope="scope">
<template v-slot="scope">
<router-link :to="'/dict/type/data/' + scope.row.id" class="link-type">
<span>{{ scope.row.type }}</span>
</router-link>
</template>
</el-table-column>
<el-table-column label="状态" align="center" prop="status">
<template slot-scope="scope">
<template v-slot="scope">
<dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status"/>
</template>
</el-table-column>
<el-table-column label="备注" align="center" prop="remark" :show-overflow-tooltip="true" />
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
<template slot-scope="scope">
<template v-slot="scope">
<span>{{ parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template slot-scope="scope">
<template v-slot="scope">
<el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)"
v-hasPermi="['system:dict:update']">修改</el-button>
<el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)"
@@ -118,8 +118,6 @@ export default {
title: "",
// 是否显示弹出层
open: false,
// 状态数据字典
statusOptions: [],
// 查询参数
queryParams: {
pageNo: 1,

View File

@@ -45,7 +45,7 @@
<el-table v-loading="loading" :data="list">
<el-table-column label="编号" align="center" prop="id" />
<el-table-column label="类型" align="center" prop="type" width="80">
<template slot-scope="scope">
<template v-slot="scope">
<dict-tag :type="DICT_TYPE.SYSTEM_ERROR_CODE_TYPE" :value="scope.row.type" />
</template>
</el-table-column>
@@ -54,12 +54,12 @@
<el-table-column label="错误码提示" align="center" prop="message" width="300" />
<el-table-column label="备注" align="center" prop="memo" width="200" />
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
<template slot-scope="scope">
<template v-slot="scope">
<span>{{ parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template slot-scope="scope">
<template v-slot="scope">
<el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)"
v-hasPermi="['system:error-code:update']">修改</el-button>
<el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)"

View File

@@ -38,7 +38,7 @@
<el-table v-loading="loading" :data="list">
<el-table-column label="访问编号" align="center" prop="id" />
<el-table-column label="日志类型" align="center" prop="logType" width="120">
<template slot-scope="scope">
<template v-slot="scope">
<dict-tag :type="DICT_TYPE.SYSTEM_LOGIN_TYPE" :value="scope.row.logType" />
</template>
</el-table-column>
@@ -46,12 +46,12 @@
<el-table-column label="登录地址" align="center" prop="userIp" width="130" :show-overflow-tooltip="true" />
<el-table-column label="userAgent" align="center" prop="userAgent" width="400" :show-overflow-tooltip="true" />
<el-table-column label="结果" align="center" prop="status">
<template slot-scope="scope">
<template v-slot="scope">
<dict-tag :type="DICT_TYPE.SYSTEM_LOGIN_RESULT" :value="scope.row.result" />
</template>
</el-table-column>
<el-table-column label="登录日期" align="center" prop="loginTime" width="180">
<template slot-scope="scope">
<template v-slot="scope">
<span>{{ parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
@@ -79,8 +79,6 @@ export default {
total: 0,
// 表格数据
list: [],
// 状态数据字典
statusOptions: [],
// 查询参数
queryParams: {
pageNo: 1,

View File

@@ -0,0 +1,225 @@
<template>
<div class="app-container">
<doc-alert title="邮件配置" url="https://doc.iocoder.cn/mail" />
<!-- 搜索工作栏 -->
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="邮箱" prop="mail">
<el-input v-model="queryParams.mail" placeholder="请输入邮箱" clearable @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="用户名" prop="username">
<el-input v-model="queryParams.username" placeholder="请输入用户名" clearable @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<!-- 操作工具栏 -->
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" plain icon="el-icon-plus" size="mini" @click="handleAdd"
v-hasPermi="['system:mail-account:create']">新增</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<!-- 列表 -->
<el-table v-loading="loading" :data="list">
<el-table-column label="编号" align="center" prop="id" />
<el-table-column label="邮箱" align="center" prop="mail" />
<el-table-column label="用户名" align="center" prop="username" />
<el-table-column label="SMTP 服务器域名" align="center" prop="host" />
<el-table-column label="SMTP 服务器端口" align="center" prop="port" />
<el-table-column label="是否开启 SSL" align="center" prop="sslEnable">
<template v-slot="scope">
<dict-tag :type="DICT_TYPE.INFRA_BOOLEAN_STRING" :value="scope.row.sslEnable" />
</template>
</el-table-column>
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
<template v-slot="scope">
<span>{{ parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template v-slot="scope">
<el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)"
v-hasPermi="['system:mail-account:update']">修改</el-button>
<el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)"
v-hasPermi="['system:mail-account:delete']">删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页组件 -->
<pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNo" :limit.sync="queryParams.pageSize"
@pagination="getList"/>
<!-- 对话框(添加 / 修改) -->
<el-dialog :title="title" :visible.sync="open" width="500px" v-dialogDrag append-to-body>
<el-form ref="form" :model="form" :rules="rules" label-width="140px">
<el-form-item label="邮箱" prop="mail">
<el-input v-model="form.mail" placeholder="请输入邮箱" />
</el-form-item>
<el-form-item label="用户名" prop="username">
<el-input v-model="form.username" placeholder="请输入用户名,一般和邮箱一致" />
</el-form-item>
<el-form-item label="密码" prop="password">
<el-input v-model="form.password" placeholder="请输入密码" />
</el-form-item>
<el-form-item label="SMTP 服务器域名" prop="host">
<el-input v-model="form.host" placeholder="请输入 SMTP 服务器域名" />
</el-form-item>
<el-form-item label="SMTP 服务器端口" prop="port">
<el-input v-model="form.port" placeholder="请输入 SMTP 服务器端口" />
</el-form-item>
<el-form-item label="是否开启 SSL" prop="sslEnable">
<el-radio-group v-model="form.sslEnable">
<el-radio v-for="dict in this.getDictDatas(DICT_TYPE.INFRA_BOOLEAN_STRING)"
:key="dict.value" :label="dict.value === 'true'">{{dict.label}}</el-radio>
</el-radio-group>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { createMailAccount, updateMailAccount, deleteMailAccount, getMailAccount, getMailAccountPage } from "@/api/system/mail/account";
export default {
name: "MailAccount",
components: {
},
data() {
return {
// 遮罩层
loading: true,
// 显示搜索条件
showSearch: true,
// 总条数
total: 0,
// 邮箱账号列表
list: [],
// 弹出层标题
title: "",
// 是否显示弹出层
open: false,
// 查询参数
queryParams: {
pageNo: 1,
pageSize: 10,
mail: null,
username: null
},
// 表单参数
form: {},
// 表单校验
rules: {
mail: [{ required: true, message: "邮箱不能为空", trigger: "blur" }],
username: [{ required: true, message: "用户名不能为空", trigger: "blur" }],
password: [{ required: true, message: "密码不能为空", trigger: "blur" }],
host: [{ required: true, message: "SMTP 服务器域名不能为空", trigger: "blur" }],
port: [{ required: true, message: "SMTP 服务器端口不能为空", trigger: "blur" }],
sslEnable: [{ required: true, message: "是否开启 SSL不能为空", trigger: "blur" }],
},
};
},
created() {
this.getList();
},
methods: {
/** 查询列表 */
getList() {
this.loading = true;
// 执行查询
getMailAccountPage(this.queryParams).then(response => {
this.list = response.data.list;
this.total = response.data.total;
this.loading = false;
});
},
/** 取消按钮 */
cancel() {
this.open = false;
this.reset();
},
/** 表单重置 */
reset() {
this.form = {
id: undefined,
mail: undefined,
username: undefined,
password: undefined,
host: undefined,
port: undefined,
sslEnable: true,
};
this.resetForm("form");
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNo = 1;
this.getList();
},
/** 重置按钮操作 */
resetQuery() {
this.resetForm("queryForm");
this.handleQuery();
},
/** 新增按钮操作 */
handleAdd() {
this.reset();
this.open = true;
this.title = "添加邮箱账号";
},
/** 修改按钮操作 */
handleUpdate(row) {
this.reset();
const id = row.id;
getMailAccount(id).then(response => {
this.form = response.data;
this.open = true;
this.title = "修改邮箱账号";
});
},
/** 提交按钮 */
submitForm() {
this.$refs["form"].validate(valid => {
if (!valid) {
return;
}
// 修改的提交
if (this.form.id != null) {
updateMailAccount(this.form).then(response => {
this.$modal.msgSuccess("修改成功");
this.open = false;
this.getList();
});
return;
}
// 添加的提交
createMailAccount(this.form).then(response => {
this.$modal.msgSuccess("新增成功");
this.open = false;
this.getList();
});
});
},
/** 删除按钮操作 */
handleDelete(row) {
const id = row.id;
this.$modal.confirm('是否确认删除邮箱账号编号为"' + id + '"的数据项?').then(function() {
return deleteMailAccount(id);
}).then(() => {
this.getList();
this.$modal.msgSuccess("删除成功");
}).catch(() => {});
}
}
};
</script>

View File

@@ -0,0 +1,226 @@
<template>
<div class="app-container">
<doc-alert title="邮件配置" url="https://doc.iocoder.cn/mail" />
<!-- 搜索工作栏 -->
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="接收邮箱" prop="toMail">
<el-input v-model="queryParams.toMail" placeholder="请输入接收邮箱" clearable @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="邮箱账号" prop="accountId">
<el-select v-model="queryParams.accountId" placeholder="请输入邮箱账号" clearable>
<el-option v-for="account in accountOptions" :key="account.id" :value="account.id" :label="account.mail" />
</el-select>
</el-form-item>
<el-form-item label="模板编号" prop="templateId">
<el-input v-model="queryParams.templateId" placeholder="请输入模板编号" clearable @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="发送状态" prop="sendStatus">
<el-select v-model="queryParams.sendStatus" placeholder="请选择发送状态" clearable size="small">
<el-option v-for="dict in this.getDictDatas(DICT_TYPE.SYSTEM_MAIL_SEND_STATUS)"
:key="dict.value" :label="dict.label" :value="dict.value"/>
</el-select>
</el-form-item>
<el-form-item label="发送时间" prop="sendTime">
<el-date-picker v-model="queryParams.sendTime" style="width: 240px" value-format="yyyy-MM-dd HH:mm:ss" type="daterange"
range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期" :default-time="['00:00:00', '23:59:59']" />
</el-form-item>
<el-form-item label="用户编号" prop="userId">
<el-input v-model="queryParams.userId" placeholder="请输入用户编号" clearable @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="用户类型" prop="userType">
<el-select v-model="queryParams.userType" placeholder="请选择用户类型" clearable>
<el-option v-for="dict in this.getDictDatas(DICT_TYPE.USER_TYPE)"
:key="dict.value" :label="dict.label" :value="dict.value"/>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<!-- 操作工具栏 -->
<el-row :gutter="10" class="mb8">
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<!-- 列表 -->
<el-table v-loading="loading" :data="list">
<el-table-column label="编号" align="center" prop="id" />
<el-table-column label="发送时间" align="center" prop="sendTime" width="180">
<template v-slot="scope">
<span>{{ parseTime(scope.row.sendTime) }}</span>
</template>
</el-table-column>
<el-table-column label="接收邮箱" align="center" prop="toMail" width="200">
<template v-slot="scope">
<div>{{ scope.row.toMail }}</div>
<div v-if="scope.row.userType && scope.row.userId">
<dict-tag :type="DICT_TYPE.USER_TYPE" :value="scope.row.userType"/>{{ '(' + scope.row.userId + ')' }}
</div>
</template>
</el-table-column>
<el-table-column label="邮件标题" align="center" prop="templateTitle" />
<el-table-column label="发送状态" align="center" prop="sendStatus">
<template v-slot="scope">
<dict-tag :type="DICT_TYPE.SYSTEM_MAIL_SEND_STATUS" :value="scope.row.sendStatus" />
</template>
</el-table-column>
<el-table-column label="邮箱账号" align="center" prop="fromMail" />
<el-table-column label="模板编号" align="center" prop="templateId" />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template v-slot="scope">
<el-button size="mini" type="text" icon="el-icon-view" @click="handleView(scope.row)"
v-hasPermi="['system:mail-log:query']">详细</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页组件 -->
<pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNo" :limit.sync="queryParams.pageSize"
@pagination="getList"/>
<!-- 邮件日志详细-->
<el-dialog :title="title" :visible.sync="open" width="700px" v-dialogDrag append-to-body>
<el-form ref="form" :model="form" label-width="160px">
<el-row>
<el-col :span="24">
<el-form-item label="日志主键:">{{ form.id }}</el-form-item>
<el-form-item label="用户编号:">{{ form.userId }}</el-form-item>
<el-form-item label="用户类型:">
<dict-tag :type="DICT_TYPE.USER_TYPE" :value="form.userType"/>
</el-form-item>
<el-form-item label="接收邮箱地址:">{{ form.toMail }}</el-form-item>
<el-form-item label="邮箱账号编号:">{{ form.accountId }}</el-form-item>
<el-form-item label="发送邮箱地址:">{{ form.fromMail }}</el-form-item>
<el-form-item label="模板编号:">{{ form.templateId }}</el-form-item>
<el-form-item label="模板编码:">{{ form.templateCode }}</el-form-item>
<el-form-item label="模版发送人名称:">{{ form.templateNickname }}</el-form-item>
<el-form-item label="邮件标题:">{{ form.templateTitle }}</el-form-item>
<el-form-item label="邮件内容:">
<editor v-model="form.templateContent" :min-height="192" read-only />
</el-form-item>
<el-form-item label="邮件参数:">{{ form.templateParams }}</el-form-item>
<el-form-item label="发送状态:">
<dict-tag :type="DICT_TYPE.SYSTEM_MAIL_SEND_STATUS" :value="form.sendStatus" />
</el-form-item>
<el-form-item label="发送时间:">{{ parseTime(form.sendTime) }}</el-form-item>
<el-form-item label="发送返回的消息编号:">{{ form.sendMessageId }}</el-form-item>
<el-form-item label="发送异常:">{{ form.sendException }}</el-form-item>
</el-col>
</el-row>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="open = false"> </el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { getMailLog, getMailLogPage } from "@/api/system/mail/log";
import Editor from '@/components/Editor';
import { getSimpleMailAccountList } from "@/api/system/mail/account";
export default {
name: "MailLog",
components: {
Editor,
},
data() {
return {
// 遮罩层
loading: true,
// 导出遮罩层
exportLoading: false,
// 显示搜索条件
showSearch: true,
// 总条数
total: 0,
// 邮件日志列表
list: [],
// 弹出层标题
title: "",
// 是否显示弹出层
open: false,
// 查询参数
queryParams: {
pageNo: 1,
pageSize: 10,
userId: null,
userType: null,
toMail: null,
accountId: null,
templateId: null,
sendStatus: null,
sendTime: [],
},
// 表单参数
form: {},
// 邮箱账号
accountOptions: []
};
},
created() {
this.getList();
// 获得邮箱账号列表
getSimpleMailAccountList().then(response => {
this.accountOptions = response.data
})
},
methods: {
/** 查询列表 */
getList() {
this.loading = true;
// 执行查询
getMailLogPage(this.queryParams).then(response => {
this.list = response.data.list;
this.total = response.data.total;
this.loading = false;
});
},
/** 取消按钮 */
cancel() {
this.open = false;
this.reset();
},
/** 表单重置 */
reset() {
this.form = {
id: undefined,
userId: undefined,
userType: undefined,
toMail: undefined,
accountId: undefined,
fromMail: undefined,
templateId: undefined,
templateCode: undefined,
templateNickname: undefined,
templateTitle: undefined,
templateContent: undefined,
templateParams: undefined,
sendStatus: undefined,
sendTime: undefined,
sendMessageId: undefined,
sendException: undefined,
};
this.resetForm("form");
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNo = 1;
this.getList();
},
/** 重置按钮操作 */
resetQuery() {
this.resetForm("queryForm");
this.handleQuery();
},
/** 详细按钮操作 */
handleView(row) {
this.open = true;
this.form = row;
}
}
};
</script>

View File

@@ -0,0 +1,349 @@
<template>
<div class="app-container">
<doc-alert title="邮件配置" url="https://doc.iocoder.cn/mail" />
<!-- 搜索工作栏 -->
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="模板名称" prop="name">
<el-input v-model="queryParams.name" placeholder="请输入模板名称" clearable @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="模板编码" prop="code">
<el-input v-model="queryParams.code" placeholder="请输入模板编码" clearable @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="邮箱账号" prop="accountId">
<el-select v-model="queryParams.accountId" placeholder="请输入邮箱账号" clearable>
<el-option v-for="account in accountOptions" :key="account.id" :value="account.id" :label="account.mail" />
</el-select>
</el-form-item>
<el-form-item label="开启状态" prop="status">
<el-select v-model="queryParams.status" placeholder="请选择开启状态" clearable size="small">
<el-option v-for="dict in this.getDictDatas(DICT_TYPE.COMMON_STATUS)"
:key="dict.value" :label="dict.label" :value="dict.value"/>
</el-select>
</el-form-item>
<el-form-item label="创建时间" prop="createTime">
<el-date-picker v-model="queryParams.createTime" style="width: 240px" value-format="yyyy-MM-dd HH:mm:ss" type="daterange"
range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期" :default-time="['00:00:00', '23:59:59']" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<!-- 操作工具栏 -->
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" plain icon="el-icon-plus" size="mini" @click="handleAdd"
v-hasPermi="['system:mail-template:create']">新增</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<!-- 列表 -->
<el-table v-loading="loading" :data="list">
<el-table-column label="模板编码" align="center" prop="code" />
<el-table-column label="模板名称" align="center" prop="name" />
<el-table-column label="模板标题" align="center" prop="title" />
<el-table-column label="模板内容" align="center" prop="content" :show-overflow-tooltip="true" />
<el-table-column label="邮箱账号" align="center" prop="accountId" width="200">
<template v-slot="scope">
{{ accountOptions.find(account => account.id === scope.row.accountId)?.mail }}
</template>
</el-table-column>
<el-table-column label="发送人名称" align="center" prop="nickname" />
<el-table-column label="开启状态" align="center" prop="status">
<template v-slot="scope">
<dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status"/>
</template>
</el-table-column>
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
<template v-slot="scope">
<span>{{ parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="150">
<template v-slot="scope">
<el-button size="mini" type="text" icon="el-icon-share" @click="handleSend(scope.row)"
v-hasPermi="['system:mail-template:send-mail']">测试</el-button>
<el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)"
v-hasPermi="['system:mail-template:update']">修改</el-button>
<el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)"
v-hasPermi="['system:mail-template:delete']">删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页组件 -->
<pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNo" :limit.sync="queryParams.pageSize"
@pagination="getList"/>
<!-- 对话框(添加 / 修改) -->
<el-dialog :title="title" :visible.sync="open" width="600px" v-dialogDrag append-to-body>
<el-form ref="form" :model="form" :rules="rules" label-width="100px">
<el-form-item label="模板名称" prop="name">
<el-input v-model="form.name" placeholder="请输入模板名称" />
</el-form-item>
<el-form-item label="模板编码" prop="code">
<el-input v-model="form.code" placeholder="请输入模板编码" />
</el-form-item>
<el-form-item label="邮箱账号" prop="accountId">
<el-select v-model="form.accountId" placeholder="请输入邮箱账号">
<el-option v-for="account in accountOptions" :key="account.id" :value="account.id" :label="account.mail" />
</el-select>
</el-form-item>
<el-form-item label="发送人名称" prop="nickname">
<el-input v-model="form.nickname" placeholder="请输入发送人名称" />
</el-form-item>
<el-form-item label="模板标题" prop="title">
<el-input v-model="form.title" placeholder="请输入模板标题" />
</el-form-item>
<el-form-item label="模板内容">
<editor v-model="form.content" :min-height="192"/>
</el-form-item>
<el-form-item label="开启状态" prop="status">
<el-radio-group v-model="form.status">
<el-radio v-for="dict in this.getDictDatas(DICT_TYPE.COMMON_STATUS)"
:key="dict.value" :label="parseInt(dict.value)">{{dict.label}}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" placeholder="请输入备注" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</el-dialog>
<!-- 对话框(发送邮件) -->
<el-dialog title="测试发送邮件" :visible.sync="sendOpen" width="500px" append-to-body>
<el-form ref="sendForm" :model="sendForm" :rules="sendRules" label-width="140px">
<el-form-item label="模板内容" prop="content">
<editor v-model="sendForm.content" :min-height="192" readonly />
</el-form-item>
<el-form-item label="收件邮箱" prop="mail">
<el-input v-model="sendForm.mail" placeholder="请输入收件邮箱" />
</el-form-item>
<el-form-item v-for="param in sendForm.params" :key="param" :label="'参数 {' + param + '}'" :prop="'templateParams.' + param">
<el-input v-model="sendForm.templateParams[param]" :placeholder="'请输入 ' + param + ' 参数'" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitSendForm"> </el-button>
<el-button @click="cancelSend"> </el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { createMailTemplate, updateMailTemplate, deleteMailTemplate, getMailTemplate, getMailTemplatePage, sendMail } from "@/api/system/mail/template";
import Editor from '@/components/Editor';
import { CommonStatusEnum } from "@/utils/constants";
import { getSimpleMailAccountList } from "@/api/system/mail/account";
export default {
name: "MailTemplate",
components: {
Editor,
},
data() {
return {
// 遮罩层
loading: true,
// 显示搜索条件
showSearch: true,
// 总条数
total: 0,
// 邮件模版列表
list: [],
// 弹出层标题
title: "",
// 是否显示弹出层
open: false,
// 查询参数
queryParams: {
pageNo: 1,
pageSize: 10,
name: null,
code: null,
accountId: null,
status: null,
createTime: [],
},
// 表单参数
form: {},
// 表单校验
rules: {
name: [{ required: true, message: "模板名称不能为空", trigger: "blur" }],
code: [{ required: true, message: "模板编码不能为空", trigger: "blur" }],
accountId: [{ required: true, message: "邮箱账号不能为空", trigger: "blur" }],
title: [{ required: true, message: "模板标题不能为空", trigger: "blur" }],
content: [{ required: true, message: "模板内容不能为空", trigger: "blur" }],
status: [{ required: true, message: "开启状态不能为空", trigger: "blur" }],
},
// 邮箱账号
accountOptions: [],
// 发送邮箱
sendOpen: false,
sendForm: {
params: [], // 模板的参数列表
},
sendRules: {
mail: [{ required: true, message: "收件邮箱不能为空", trigger: "blur" }],
templateCode: [{ required: true, message: "模版编码不能为空", trigger: "blur" }],
templateParams: { }
}
};
},
created() {
this.getList();
// 获得邮箱账号列表
getSimpleMailAccountList().then(response => {
this.accountOptions = response.data
})
},
methods: {
/** 查询列表 */
getList() {
this.loading = true;
// 执行查询
getMailTemplatePage(this.queryParams).then(response => {
this.list = response.data.list;
this.total = response.data.total;
this.loading = false;
});
},
/** 取消按钮 */
cancel() {
this.open = false;
this.reset();
},
/** 表单重置 */
reset() {
this.form = {
id: undefined,
name: undefined,
code: undefined,
accountId: undefined,
nickname: undefined,
title: undefined,
content: undefined,
status: CommonStatusEnum.ENABLE,
remark: undefined,
};
this.resetForm("form");
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNo = 1;
this.getList();
},
/** 重置按钮操作 */
resetQuery() {
this.resetForm("queryForm");
this.handleQuery();
},
/** 新增按钮操作 */
handleAdd() {
this.reset();
this.open = true;
this.title = "添加邮件模版";
},
/** 修改按钮操作 */
handleUpdate(row) {
this.reset();
const id = row.id;
getMailTemplate(id).then(response => {
this.form = response.data;
this.open = true;
this.title = "修改邮件模版";
});
},
/** 提交按钮 */
submitForm() {
this.$refs["form"].validate(valid => {
if (!valid) {
return;
}
// 修改的提交
if (this.form.id != null) {
updateMailTemplate(this.form).then(response => {
this.$modal.msgSuccess("修改成功");
this.open = false;
this.getList();
});
return;
}
// 添加的提交
createMailTemplate(this.form).then(response => {
this.$modal.msgSuccess("新增成功");
this.open = false;
this.getList();
});
});
},
/** 删除按钮操作 */
handleDelete(row) {
const id = row.id;
this.$modal.confirm('是否确认删除邮件模版编号为"' + id + '"的数据项?').then(function() {
return deleteMailTemplate(id);
}).then(() => {
this.getList();
this.$modal.msgSuccess("删除成功");
}).catch(() => {});
},
/** 发送短息按钮 */
handleSend(row) {
this.resetSend(row);
// 设置参数
this.sendForm.content = row.content;
this.sendForm.params = row.params;
this.sendForm.templateCode = row.code;
this.sendForm.templateParams = row.params.reduce(function(obj, item) {
obj[item] = undefined;
return obj;
}, {});
// 根据 row 重置 rules
this.sendRules.templateParams = row.params.reduce(function(obj, item) {
obj[item] = { required: true, message: '参数 ' + item + " 不能为空", trigger: "change" };
return obj;
}, {});
// 设置打开
this.sendOpen = true;
},
/** 重置发送邮箱的表单 */
resetSend() {
// 根据 row 重置表单
this.sendForm = {
content: undefined,
params: undefined,
mail: undefined,
templateCode: undefined,
templateParams: {}
};
this.resetForm("sendForm");
},
/** 取消发送邮箱 */
cancelSend() {
this.sendOpen = false;
this.resetSend();
},
/** 提交按钮 */
submitSendForm() {
this.$refs["sendForm"].validate(valid => {
if (!valid) {
return;
}
// 添加的提交
sendMail(this.sendForm).then(response => {
this.$modal.msgSuccess("提交发送成功!发送结果,见发送日志编号:" + response.data);
this.sendOpen = false;
});
});
},
}
};
</script>

View File

@@ -33,7 +33,7 @@
:tree-props="{children: 'children', hasChildren: 'hasChildren'}">
<el-table-column prop="name" label="菜单名称" :show-overflow-tooltip="true" width="250"></el-table-column>
<el-table-column prop="icon" label="图标" align="center" width="100">
<template slot-scope="scope">
<template v-slot="scope">
<svg-icon :icon-class="scope.row.icon" />
</template>
</el-table-column>
@@ -41,17 +41,17 @@
<el-table-column prop="permission" label="权限标识" :show-overflow-tooltip="true"></el-table-column>
<el-table-column prop="component" label="组件路径" :show-overflow-tooltip="true"></el-table-column>
<el-table-column prop="status" label="状态" width="80">
<template slot-scope="scope">
<template v-slot="scope">
<dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status"/>
</template>
</el-table-column>
<el-table-column label="创建时间" align="center" prop="createTime">
<template slot-scope="scope">
<template v-slot="scope">
<span>{{ parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template slot-scope="scope">
<template v-slot="scope">
<el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)"
v-hasPermi="['system:menu:update']">修改</el-button>
<el-button size="mini" type="text" icon="el-icon-plus" @click="handleAdd(scope.row)"

View File

@@ -4,12 +4,9 @@
<el-form-item label="公告标题" prop="title">
<el-input v-model="queryParams.title" placeholder="请输入公告标题" clearable @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="操作人员" prop="createBy">
<el-input v-model="queryParams.createBy" placeholder="请输入操作人员" clearable @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="类型" prop="type">
<el-select v-model="queryParams.type" placeholder="公告类型" clearable>
<el-option v-for="dict in noticeTypeDictDatas" :key="parseInt(dict.value)" :label="dict.label" :value="parseInt(dict.value)"/>
<el-form-item label="公告状态" prop="status">
<el-select v-model="queryParams.status" placeholder="公告状态" clearable>
<el-option v-for="dict in statusDictDatas" :key="parseInt(dict.value)" :label="dict.label" :value="parseInt(dict.value)"/>
</el-select>
</el-form-item>
<el-form-item>
@@ -30,23 +27,23 @@
<el-table-column label="序号" align="center" prop="id" width="100" />
<el-table-column label="公告标题" align="center" prop="title" :show-overflow-tooltip="true"/>
<el-table-column label="公告类型" align="center" prop="type" width="100">
<template slot-scope="scope">
<template v-slot="scope">
<dict-tag :type="DICT_TYPE.SYSTEM_NOTICE_TYPE" :value="scope.row.type"/>
</template>
</el-table-column>
<el-table-column label="状态" align="center" prop="status" width="100">
<template slot-scope="scope">
<template v-slot="scope">
<dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status"/>
</template>
</el-table-column>
<el-table-column label="创建者" align="center" prop="createBy" width="100" />
<el-table-column label="创建时间" align="center" prop="createTime" width="100">
<template slot-scope="scope">
<template v-slot="scope">
<span>{{ parseTime(scope.row.createTime, '{y}-{m}-{d}') }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template slot-scope="scope">
<template v-slot="scope">
<el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)"
v-hasPermi="['system:notice:update']">修改</el-button>
<el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)"
@@ -136,7 +133,6 @@ export default {
pageNo: 1,
pageSize: 10,
title: undefined,
createBy: undefined,
status: undefined
},
// 表单参数

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