VIP(一半)

This commit is contained in:
磷叶 2024-12-10 18:13:19 +08:00
parent cdb5393f2a
commit a04348947b
26 changed files with 1813 additions and 37 deletions

39
src/api/mch/vipLevel.js Normal file
View File

@ -0,0 +1,39 @@
import request from '@/utils/request'
export function mchListVipLevel(params) {
return request({
url: "/mch/vipLevel/list",
method: 'get',
params
})
}
export function mchGetVipLevel(id) {
return request({
url: `/mch/vipLevel/${id}`,
method: 'get'
})
}
export function mchAddVipLevel(data) {
return request({
url: '/mch/vipLevel',
method: 'post',
data
})
}
export function mchUpdateVipLevel(data) {
return request({
url: '/mch/vipLevel',
method: 'put',
data
})
}
export function mchDeleteVipLevel(ids) {
return request({
url: `/mch/vipLevel/${ids}`,
method: 'delete'
})
}

44
src/api/ss/vip.js Normal file
View File

@ -0,0 +1,44 @@
import request from '@/utils/request'
// 查询会员列表
export function listVip(query) {
return request({
url: '/ss/vip/list',
method: 'get',
params: query
})
}
// 查询会员详细
export function getVip(id) {
return request({
url: '/ss/vip/' + id,
method: 'get'
})
}
// 新增会员
export function addVip(data) {
return request({
url: '/ss/vip',
method: 'post',
data: data
})
}
// 修改会员
export function updateVip(data) {
return request({
url: '/ss/vip',
method: 'put',
data: data
})
}
// 删除会员
export function delVip(id) {
return request({
url: '/ss/vip/' + id,
method: 'delete'
})
}

53
src/api/ss/vipLevel.js Normal file
View File

@ -0,0 +1,53 @@
import request from '@/utils/request'
// 查询会员等级列表
export function listVipLevel(query) {
return request({
url: '/ss/vipLevel/list',
method: 'get',
params: query
})
}
// 查询会员等级列表
export function listVipLevelByIds(ids) {
return request({
url: '/ss/vipLevel/listByIds',
method: 'post',
data: ids
})
}
// 查询会员等级详细
export function getVipLevel(id) {
return request({
url: '/ss/vipLevel/' + id,
method: 'get'
})
}
// 新增会员等级
export function addVipLevel(data) {
return request({
url: '/ss/vipLevel',
method: 'post',
data: data
})
}
// 修改会员等级
export function updateVipLevel(data) {
return request({
url: '/ss/vipLevel',
method: 'put',
data: data
})
}
// 删除会员等级
export function delVipLevel(id) {
return request({
url: '/ss/vipLevel/' + id,
method: 'delete'
})
}

View File

@ -0,0 +1 @@
<svg t="1733724833747" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4476" width="64" height="64"><path d="M844.89 85.33H179.11L0 436.6l512 502.07 512-502.07L844.89 85.33zM106.34 419.67l126.77-248.64h557.77l126.77 248.64L512 817.46 106.34 419.67z" p-id="4477"></path><path d="M512 603.21L324.41 419.26l-61.8 60.6L512 724.42l249.39-244.56-61.8-60.6z" p-id="4478"></path></svg>

After

Width:  |  Height:  |  Size: 422 B

View File

@ -1,5 +1,5 @@
<template>
<el-tag :type="value ? trueType : falseType" :size="size">{{value ? trueText : falseText}}</el-tag>
<el-tag v-if="value != null" :type="value ? trueType : falseType" :size="size">{{value ? trueText : falseText}}</el-tag>
</template>
<script>
export default {
@ -8,7 +8,6 @@ export default {
value: {
type: Boolean,
default: null,
required: true
},
size: {
type: String,

View File

@ -4,9 +4,12 @@
<template>
<el-dialog :title="title" :visible="show" width="60%" top="2vh" @open="open" @close="close"
:append-to-body="true">
<el-form size="small" :inline="true" label-width="8em">
<el-form-item label="店铺名称:">
<el-input v-model="searchForm.name" clearable></el-input>
<el-form size="small" :inline="true" label-width="5em" @submit.native.prevent="onSearch">
<el-form-item label="商户名称">
<el-input v-model="searchForm.userName" placeholder="请输入商户名称" clearable @keyup.enter.native="onSearch"/>
</el-form-item>
<el-form-item label="店铺名称">
<el-input v-model="searchForm.name" placeholder="请输入店铺名称" clearable/>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="onSearch()" icon="el-icon-search">搜索</el-button>
@ -24,14 +27,14 @@
highlight-current-row
>
<el-table-column align="center" type="selection" v-if="multiple"></el-table-column>
<el-table-column label="#" type="index" align="center"></el-table-column>
<el-table-column label="店铺图片" align="center" prop="picture" width="100">
<el-table-column label="图片" align="center" prop="picture" width="100">
<template slot-scope="scope">
<image-preview :src="scope.row.picture" :width="50" :height="50"/>
</template>
</el-table-column>
<el-table-column label="店铺名称" align="center" prop="name" width="200"></el-table-column>
<el-table-column label="店铺地址" align="center">
<el-table-column label="商户" align="center" prop="userName"/>
<el-table-column label="名称" align="center" prop="name"/>
<el-table-column label="地址" align="center">
<template slot-scope="d">
{{d.row.province}}{{d.row.city}}{{d.row.county}}{{d.row.address}}
</template>
@ -136,6 +139,8 @@ export default {
this.searchForm = {
pageNum: 1,
pageSize: 10,
orderByColumn: 'createTime',
isAsc: "desc",
...this.query,
}

View File

@ -20,7 +20,12 @@
:size="size"
:disabled="disabled"
readonly
:placeholder="placeholder"/>
:placeholder="placeholder">
<template #suffix>
<div class="input-suffix"></div>
<i class="el-icon-arrow-right"/>
</template>
</el-input>
<store-dialog
:show.sync="dialogShow"

View File

@ -0,0 +1,226 @@
<!--version: 3, 店铺选择弹窗-->
<!--版本更新内容添加prop属性修复多选-->
<template>
<el-dialog :title="title" :visible="show" width="60%" top="2vh" @open="open" @close="close"
:append-to-body="true">
<el-form size="small" :inline="true" label-width="5em" @submit.native.prevent="handleQuery">
<el-form-item label="商户" prop="mchName">
<el-input
v-model="searchForm.mchName"
placeholder="请输入商户名称"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="等级名称" prop="name">
<el-input
v-model="searchForm.name"
placeholder="请输入等级名称"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleQuery()" icon="el-icon-search">搜索</el-button>
</el-form-item>
</el-form>
<el-table
ref="multipleTable"
:data="tableData"
v-loading="loadTable"
@row-click="changeSelection"
@row-dblclick="select"
@select-all="selectionAll"
@select="changeSelection"
highlight-current-row
>
<el-table-column align="center" type="selection" v-if="multiple"></el-table-column>
<el-table-column align="center" label="#" type="index"/>
<el-table-column align="center" label="商户" prop="mchName"/>
<el-table-column align="center" label="等级名称" prop="name"/>
<el-table-column align="center" label="折扣" prop="discount">
<template slot-scope="d">{{d.row.discount}} </template>
</el-table-column>
<el-table-column align="center" label="描述" prop="description" show-overflow-tooltip/>
<el-table-column align="center" label="创建时间" prop="createTime"/>
</el-table>
<pagination
:limit.sync="searchForm.pageSize"
:page.sync="searchForm.pageNum"
:total="total"
@pagination="searchList">
</pagination>
<template #footer>
<el-button type="primary" @click="submit()">确定</el-button>
</template>
</el-dialog>
</template>
<script>
import {clone} from "@/utils";
import { listStore } from '@/api/ss/store'
import { mapGetters } from 'vuex'
import { UserType } from '@/utils/constants'
import { mchListStore } from '@/api/mch/store'
import { listVipLevel } from '@/api/ss/vipLevel'
export default {
name: "VipLevelDialog",
props: {
//
title: {
type: String,
default: '选择VIP等级'
},
value: {
type: [String, Number, Array],
default: null,
},
multiple: {
type: Boolean,
default: false,
},
show: {
type: Boolean,
default: false
},
query: {
type: Object,
default: () => ({})
},
initSelect: {
type: Array,
default: null,
},
prop: {
type: String,
default: 'id'
}
},
data() {
return {
loadTable: false,
tableData: [],
searchForm: {},
total: 0,
row: null,
selected: [],
}
},
computed: {
...mapGetters(['userType'])
},
methods: {
//
searchList() {
this.loadTable = true;
if (this.userType === UserType.APP) {
// mchListStore(this.searchForm).then(response => {
// this.tableData = response.rows;
// this.total = response.total;
// //
// this.$nextTick(()=>{
// this.refreshTableSelection();
// })
// }).finally(() =>{
// this.loadTable = false;
// })
} else {
listVipLevel(this.searchForm).then(response => {
this.tableData = response.rows;
this.total = response.total;
//
this.$nextTick(()=>{
this.refreshTableSelection();
})
}).finally(() =>{
this.loadTable = false;
})
}
},
//
open() {
this.searchForm = {
pageNum: 1,
pageSize: 10,
...this.query,
}
if (this.initSelect) {
this.selected = clone(this.initSelect);
} else {
this.selected = [];
}
this.searchList();
},
//
refreshTableSelection() {
if(this.multiple){
this.tableData.forEach(item => {
if (this.selected.map(j => j[this.prop]).includes(item[this.prop])) {
this.$refs.multipleTable.toggleRowSelection(item, true);
} else {
this.$refs.multipleTable.toggleRowSelection(item, false);
}
});
}
},
//
selectionAll(val){
let flag = val.length > 0;
this.tableData.forEach(item => {
if (flag && !this.selected.map(i => i[this.prop]).includes(item[this.prop])){
this.selected.push(item);
} else if (!flag && this.selected.map(i => i[this.prop]).includes(item[this.prop])){
this.selected = this.selected.filter(i => i[this.prop] !== item[this.prop]);
}
})
},
//
submit() {
if (this.multiple) {
this.$emit('select', this.selected);
} else {
this.select(this.row);
}
},
//
changeSelection(row){
if(this.multiple){
if (this.selected.map(i => i[this.prop]).includes(row[this.prop])){
this.$refs.multipleTable.toggleRowSelection(row, false);
this.selected = this.selected.filter(i => i[this.prop] !== row[this.prop]);
}else {
this.$refs.multipleTable.toggleRowSelection(row, true);
this.selected.push(row);
}
} else {
this.row = row;
}
},
//
handleQuery() {
this.searchForm.pageNum = 1;
this.searchList();
},
//
close() {
this.$emit('update:show', false);
this.searchForm.pageNum = 1;
this.searchForm.pageSize = 10;
},
//
select(row) {
if (!this.multiple) {
if (!row) return this.$message.error('请选择一行');
this.selected = [row];
this.$emit('select', row);
}
},
},
}
</script>

View File

@ -0,0 +1,199 @@
<!--version: 4, 店铺选择器-->
<!--版本更新内容添加prop属性修复多选-->
<template>
<div>
<el-input
v-if="multiple"
rows="1"
type="textarea"
:value="inputBindValue"
@focus="openDialog"
:size="size"
:disabled="disabled"
readonly
:placeholder="placeholder"/>
<el-input
v-else
:value="inputBindValue"
@focus="openDialog"
:size="size"
:disabled="disabled"
readonly
:placeholder="placeholder">
<template #suffix>
<div class="input-suffix"></div>
<i class="el-icon-arrow-right"/>
</template>
</el-input>
<vip-level-dialog
:show.sync="dialogShow"
:query="query"
:multiple="multiple"
:init-select="selected"
@select="onSubmit"
:prop="prop"
:title="title"/>
</div>
</template>
<script>
import {isDeepEqual} from "@/utils";
import { mapGetters } from 'vuex'
import { UserType } from '@/utils/constants'
import VipLevelDialog from '@/components/Business/VipLevel/VipLevelDialog.vue'
import { listVipLevelByIds } from '@/api/ss/vipLevel'
export default {
name: 'VipLevelInput',
components: { VipLevelDialog },
props:{
//
title: {
type: String,
default: "选择VIP等级"
},
placeholder: {
type: String,
default: "点击选择VIP等级",
},
//
showProp: {
type: String,
default: 'name'
},
//
prop: {
type: String,
default: 'id'
},
//
multiple: {
type: Boolean,
default: false,
},
// (id)
value: {
type: [Array, String, Number],
default: null
},
//
query: {
type: Object,
default: () => ({})
},
//
beforeOpen: {
type: Function,
default: () => {
return true;
}
},
//
beforeClose: {
type: Function,
default: () => {
return true;
}
},
//
size: {
type: String,
default: "medium"
},
//
disabled: {
type: Boolean,
default: false,
},
//
objectParser: {
type: Function,
default: (obj) => {
return JSON.stringify(obj);
}
},
},
data() {
return {
selected: [], //
dialogShow: false, //
}
},
computed: {
...mapGetters(['userType']),
//
inputBindValue() {
if (this.selected == null || this.selected.length === 0) {
return null;
}
return this.selected.map(item => item[this.showProp]).join(",");
}
},
watch: {
value(nv, ov) {
this.loadSelected(nv);
}
},
created() {
this.loadSelected(this.value);
},
methods: {
//
loadSelected(ids) {
if (ids == null || ids.length === 0) {
this.selected = [];
return;
}
if (ids instanceof Array) {
this.doLoad(ids);
} else {
this.doLoad([ids]);
}
},
//
doLoad(ids) {
if (this.userType === UserType.APP) {
// mchListStoreByIds(ids).then(res => {
// this.selected = res.data;
// })
} else {
listVipLevelByIds(ids).then(res => {
this.selected = res.data;
})
}
},
//
inputValue(val){
this.$emit('input', val);
},
//
onSubmit(selected){
let value = null;
if (this.multiple) {
value = selected.map(item => item[this.prop]);
} else {
value = selected[this.prop];
}
this.$emit('submit', selected);
if (!isDeepEqual(this.value, value)) {
this.$emit('change', selected);
}
this.inputValue(value);
this.closeDialog();
},
closeDialog() {
if (this.beforeClose()) {
this.dialogShow = false;
}
},
//
openDialog(){
if (this.beforeOpen()) {
this.dialogShow = true;
}
}
}
}
</script>

View File

@ -59,6 +59,40 @@ export const mchRoutes = [
},
]
},
{
path: '/sale',
component: Layout,
hidden: false,
alwaysShow: true,
meta: {
title: '营销活动管理',
icon: 'shopping',
},
children: [
{
path: 'vipLevel',
component: () => import('@/views/mch/vipLevel'),
hidden: false,
name: "MchVipLevel",
meta: {
noCache: false,
title: '会员等级',
icon: 'chart',
}
},
{
path: 'vip',
component: () => import('@/views/mch/vip'),
hidden: false,
name: "MchVip",
meta: {
noCache: false,
title: '会员列表',
icon: 'vip',
}
},
]
},
{
path: '/device',
component: Layout,

View File

@ -283,3 +283,9 @@ export const TransactionBillType = {
RECHARGE: "1", // 充值
WITHDRAW: "2", // 提现
}
// VIP有效期类型
export const VipExpireType = {
FOREVER: "1", // 永久有效
TIME: "2", // 有效期
}

View File

@ -9,6 +9,7 @@ import {
views
} from '@/utils/constants'
import { mapGetters } from 'vuex'
import { addVip, getVip, updateVip } from '@/api/ss/vip'
export const $view = {
props: {
@ -52,10 +53,27 @@ export const $view = {
/**
* 显隐列
**/
export const $showColumns = {
props: {
// 隐藏的列
hideColumns: {
type: Array,
default: () => {
return []
}
},
// 隐藏的查询
hideSearch: {
type: Array,
default: () => {
return []
}
}
},
data() {
return {
columns: []
columns: [],
}
},
computed: {
@ -76,10 +94,19 @@ export const $showColumns = {
}
},
methods: {
/**
* 隐藏某些列
* @param columns
*/
// 是否展示查询
visibleSearch(key) {
return this.visibleColumn(key) && !this.hideSearch.includes(key);
},
// 是否展示列
visibleColumn(key) {
return !this.hideColumns.includes(key);
},
// 初始化列
initColumns() {
this.hideColumn(this.hideColumns);
},
// 隐藏列
hideColumn(columns) {
if (this.columns != null) {
this.columns.filter(item => columns.includes(item.key))
@ -88,10 +115,7 @@ export const $showColumns = {
})
}
},
/**
* 删除某些列
* @param columns
*/
// 删除列
removeColumn(columns) {
if (columns != null) {
columns.forEach(column => {
@ -192,3 +216,67 @@ export const $smRoles = {
},
},
}
/**
* 编辑弹窗
*/
export const $editDialog = {
props: {
show: {
type: Boolean,
default: false
},
id: {
type: String,
default: null,
},
rules: {
type: Object,
default: () => ({})
}
},
data() {
return {
title: null,
form: {}
}
},
computed: {
open: {
set(val) {
this.$emit('update:show', val);
},
get() {
return this.show;
}
},
},
methods: {
// 取消按钮
cancel() {
this.open = false;
},
// open事件处理
onOpen(){
if (this.id == null) {
this.reset();
} else {
this.getDetail();
}
},
// 需要实现 获取明细
getDetail() {
},
// 需要实现 表单重置
reset() {
this.title = "新增";
this.form = {
expireType: '1',
};
this.resetForm("form");
},
/** 需要实现 提交按钮 */
submitForm() {
},
}
}

View File

@ -1,6 +1,6 @@
<template>
<div class="todo-list" v-loading="loading">
<div class="todo-item" @click="$router.push('/mch/mchApply?status=0')">
<div class="todo-item" @click="$router.push('/complaint/mchApply?status=0')">
<div class="label"><svg-icon icon-class="apply"/> 商家加盟</div>
<div class="value">
<count-to :start-val="0" :end-val="data.mchApplyCount" :duration="3000"/>

108
src/views/mch/vip/index.vue Normal file
View File

@ -0,0 +1,108 @@
<template>
<div class="app-container">
<vip-table ref="table" @selection-change="handleSelectionChange" >
<template #table-operator>
<el-col :span="1.5">
<el-button
type="primary"
plain
icon="el-icon-plus"
size="mini"
@click="handleAdd"
>新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="danger"
plain
icon="el-icon-delete"
size="mini"
:disabled="isEmpty(ids)"
@click="handleDelete"
>删除</el-button>
</el-col>
</template>
<template v-slot:row-operator="scope">
<el-button
size="mini"
type="text"
icon="el-icon-edit"
@click="handleUpdate(scope.row)"
v-hasPermi="['ss:vip:edit']"
>修改</el-button>
<el-button
size="mini"
type="text"
icon="el-icon-delete"
@click="handleDelete(scope.row)"
v-hasPermi="['ss:vip:remove']"
>删除</el-button>
</template>
</vip-table>
<!-- 添加或修改会员对话框 -->
<vip-edit-dialog :show.sync="open" :id="row.id" @success="getList"/>
</div>
</template>
<script>
import { delVip } from "@/api/ss/vip";
import UserInput from '@/components/Business/SmUser/UserInput.vue'
import StoreInput from '@/components/Business/Store/StoreInput.vue'
import VipLevelInput from '@/components/Business/VipLevel/VipLevelInput.vue'
import VipEditDialog from '@/views/ss/vip/components/VipEditDialog.vue'
import VipTable from '@/views/ss/vip/components/VipTable.vue'
import { isEmpty } from '@/utils'
export default {
name: "MchVip",
components: { VipTable, VipEditDialog, VipLevelInput, StoreInput, UserInput },
data() {
return {
row: {},
//
open: false,
//
ids: [],
};
},
methods: {
isEmpty,
getList() {
this.$refs.table.getList();
},
//
handleSelectionChange(selection) {
this.ids = selection.map(item => item.id)
},
/** 新增按钮操作 */
handleAdd() {
this.row = {}
this.open = true;
},
/** 修改按钮操作 */
handleUpdate(row) {
this.row = row;
this.open = true;
},
/** 删除按钮操作 */
handleDelete(row) {
const ids = row.id || this.ids;
this.$modal.confirm('是否确认删除会员编号为"' + ids + '"的数据项?').then(function() {
return delVip(ids);
}).then(() => {
this.getList();
this.$modal.msgSuccess("删除成功");
}).catch(() => {});
},
/** 导出按钮操作 */
handleExport() {
this.download('ss/vip/export', {
...this.queryParams
}, `vip_${new Date().getTime()}.xlsx`)
}
}
};
</script>

View File

@ -0,0 +1,129 @@
<template>
<div class="app-container">
<vip-level-table
ref="table"
:hide-columns="['mchName']"
:list-api="mchListVipLevel"
@selection-change="handleSelectionChange"
>
<template #table-operator>
<el-col :span="1.5">
<el-button
type="primary"
plain
icon="el-icon-plus"
size="mini"
@click="handleAdd"
>新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="danger"
plain
icon="el-icon-delete"
size="mini"
:disabled="!isEmpty(ids)"
@click="handleDelete"
>删除</el-button>
</el-col>
</template>
<template v-slot:row-operator="scope">
<el-button
size="mini"
type="text"
icon="el-icon-edit"
@click="handleUpdate(scope.row)"
>修改</el-button>
<el-button
size="mini"
type="text"
icon="el-icon-delete"
@click="handleDelete(scope.row)"
>删除</el-button>
</template>
</vip-level-table>
<!-- 添加或修改会员等级对话框 -->
<vip-level-edit-dialog
:show.sync="open"
:id="row.id"
@success="getList"
:get-api="mchGetVipLevel"
:add-api="mchAddVipLevel"
:update-api="mchUpdateVipLevel"
:hide-columns="['mchId']"
/>
</div>
</template>
<script>
import { listVipLevel, getVipLevel, delVipLevel, addVipLevel, updateVipLevel } from "@/api/ss/vipLevel";
import { $showColumns } from '@/utils/mixins';
import UserLink from '@/components/Business/SmUser/UserLink.vue'
import VipLevelEditDialog from '@/views/ss/vipLevel/components/VipLevelEditDialog.vue'
import VipLevelTable from '@/views/ss/vipLevel/components/VipLevelTable.vue'
import { isEmpty } from '@/utils'
import {
mchAddVipLevel,
mchDeleteVipLevel,
mchGetVipLevel,
mchListVipLevel,
mchUpdateVipLevel
} from '@/api/mch/vipLevel'
export default {
name: "MchVipLevel",
components: { VipLevelTable, VipLevelEditDialog, UserLink },
mixins: [$showColumns],
data() {
return {
row: {},
//
open: false,
//
ids: [],
};
},
methods: {
mchListVipLevel,
mchUpdateVipLevel,
mchAddVipLevel,
mchGetVipLevel,
isEmpty,
getList() {
this.$refs.table.getList();
},
//
handleSelectionChange(selection) {
this.ids = selection.map(item => item.id)
},
/** 新增按钮操作 */
handleAdd() {
this.row = {};
this.open = true;
},
/** 修改按钮操作 */
handleUpdate(row) {
this.row = row;
this.open = true;
},
/** 删除按钮操作 */
handleDelete(row) {
const ids = row.id || this.ids;
this.$modal.confirm('是否确认删除会员等级编号为"' + ids + '"的数据项?').then(function() {
return mchDeleteVipLevel(ids);
}).then(() => {
this.getList();
this.$modal.msgSuccess("删除成功");
}).catch(() => {});
},
/** 导出按钮操作 */
handleExport() {
this.download('ss/vipLevel/export', {
...this.queryParams
}, `vipLevel_${new Date().getTime()}.xlsx`)
}
}
};
</script>

View File

@ -28,6 +28,15 @@
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="请求地址" prop="operUrl">
<el-input
v-model="queryParams.operUrl"
placeholder="请输入请求地址"
clearable
style="width: 240px;"
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="类型" prop="businessType">
<el-select
v-model="queryParams.businessType"

View File

@ -114,7 +114,7 @@
icon="el-icon-wallet"
@click="handlePay(scope.row)"
v-hasPermi="['ss:bonus:pay']"
v-show="BonusStatus.canPay().includes(scope.row.status)"
v-show="BonusStatus.canPay().includes(scope.row.status) && scope.row.waitAmount > 0"
>立即分成</el-button>
</template>
</el-table-column>

View File

@ -1,5 +1,5 @@
<template>
<li class="change-field" :class="newValue !== oldValue ? 'no-same' : ''">
<li class="change-field" :class="newValue != null && newValue !== oldValue ? 'no-same' : ''">
<div class="label">
{{label}}
</div>
@ -7,10 +7,12 @@
<slot name="old" :value="oldValue">
<span class="old">{{ oldValue | defaultValue }}</span>
</slot>
<i class="el-icon-right"/>
<slot name="new" :value="newValue">
<span class="new">{{ newValue | defaultValue }}</span>
</slot>
<template v-if="newValue != null && newValue !== oldValue">
<i class="el-icon-right"/>
<slot name="new" :value="newValue">
<span class="new">{{ newValue | defaultValue }}</span>
</slot>
</template>
</div>
</li>
</template>

View File

@ -45,7 +45,7 @@
</ul>
<place-search-map
v-if="detail.newData"
v-if="detail.newData != null && detail.newData.lng != null && detail.newData.lat != null"
:key="detail.id"
width="100%"
height="300px"
@ -54,6 +54,17 @@
enable-geo
marker-type="store"
/>
<place-search-map
v-else-if="detail.oldData != null && detail.oldData.lng != null && detail.oldData.lat != null"
:key="detail.id"
width="100%"
height="300px"
:init-lat="detail.oldData.lat"
:init-lng="detail.oldData.lng"
enable-geo
marker-type="store"
/>
</el-card>
</el-col>
<el-col :span="24 - span">

View File

@ -0,0 +1,139 @@
<template>
<el-dialog :title="title" :visible.sync="open" width="500px" append-to-body @open="onOpen">
<el-form ref="form" :model="form" :rules="rules" label-width="80px">
<el-form-item label="用户" prop="userId">
<user-input v-model="form.userId"/>
</el-form-item>
<el-form-item label="店铺" prop="storeId">
<store-input v-model="form.storeId" @change="onChangeStore"/>
</el-form-item>
<el-form-item label="会员等级" prop="levelId">
<vip-level-input v-model="form.levelId" :before-open="beforeOpenVipLevel" :query="vipLevelQuery"/>
</el-form-item>
<el-form-item label="时效类型" prop="expireType">
<el-radio-group v-model="form.expireType" style="width: 100%">
<el-radio
v-for="dict in dict.type.vip_expire_type"
:key="dict.value"
:label="dict.value"
>{{dict.label}}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="有效期" prop="expireTime" v-if="form.expireType === VipExpireType.TIME">
<el-date-picker
clearable
v-model="form.expireTime"
type="datetime"
value-format="yyyy-MM-dd HH:mm:ss"
placeholder="请选择有效期"
style="width: 100%"
/>
</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>
</template>
<script>
import UserInput from '@/components/Business/SmUser/UserInput.vue'
import StoreInput from '@/components/Business/Store/StoreInput.vue'
import VipLevelInput from '@/components/Business/VipLevel/VipLevelInput.vue'
import { addVip, getVip, updateVip } from '@/api/ss/vip'
import { $editDialog } from '@/utils/mixins'
import { VipExpireType } from '@/utils/constants'
export default {
name: "VipEditDialog",
dicts: ['vip_expire_type'],
mixins: [$editDialog],
components: { VipLevelInput, StoreInput, UserInput },
props: {
rules: {
type: Object,
default: () => ({
userId: [
{ required: true, message: "用户不能为空", trigger: "change" }
],
levelId: [
{ required: true, message: "会员等级不能为空", trigger: "change" }
],
expireTime: [
{ required: true, message: '有效期不允许为空', trigger: 'change'}
],
expireType: [
{ required: true, message: '有效期类型不允许为空', trigger: 'change'}
]
})
}
},
data() {
return {
VipExpireType
}
},
computed: {
vipLevelQuery() {
return {
mchId: this.form.storeMchId
}
}
},
methods: {
getDetail() {
getVip(this.id).then(response => {
this.form = response.data;
this.open = true;
this.title = "修改会员";
});
},
//
reset() {
this.title = "新增会员";
this.form = {
id: null,
userId: null,
storeId: null,
levelId: null,
expireTime: null,
createTime: null,
storeMchId: null,
expireType: VipExpireType.FOREVER
};
this.resetForm("form");
},
/** 提交按钮 */
submitForm() {
this.$refs["form"].validate(valid => {
if (valid) {
if (this.form.id != null) {
updateVip(this.form).then(response => {
this.$modal.msgSuccess("修改成功");
this.open = false;
this.$emit('success');
});
} else {
addVip(this.form).then(response => {
this.$modal.msgSuccess("新增成功");
this.open = false;
this.$emit('success');
});
}
}
});
},
onChangeStore(store) {
this.form.storeMchId = store.userId;
},
beforeOpenVipLevel() {
if (this.form.storeMchId == null) {
this.$message.warning("请先选择店铺")
return false;
}
return true;
},
}
}
</script>

View File

@ -0,0 +1,163 @@
<template>
<div>
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="用户" prop="userName">
<el-input
v-model="queryParams.userName"
placeholder="请输入用户名称"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="店铺" prop="storeName">
<el-input
v-model="queryParams.storeName"
placeholder="请输入店铺名称"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="会员等级" prop="vipLevelName">
<el-input
v-model="queryParams.vipLevelName"
placeholder="请输入会员等级名称"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item>
<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-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<slot name="table-operator"/>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList" :columns="columns"></right-toolbar>
</el-row>
<el-table v-loading="loading" :data="vipList" v-on="$listeners" :default-sort="defaultSort" @sort-change="onSortChange">
<el-table-column type="selection" width="55" align="center" />
<template v-for="column of showColumns">
<el-table-column
:key="column.key"
:label="column.label"
:prop="column.key"
:align="column.align"
:min-width="column.minWidth"
:sort-orders="orderSorts"
:sortable="column.sortable"
:show-overflow-tooltip="column.overflow"
:width="column.width"
>
<template slot-scope="d">
<template v-if="column.key === 'id'">
{{d.row[column.key]}}
</template>
<template v-else>
{{d.row[column.key]}}
</template>
</template>
</el-table-column>
</template>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template slot-scope="scope">
<slot name="row-operator" :row="scope.row"/>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total>0"
:total="total"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
@pagination="getList"
/>
</div>
</template>
<script>
import { listVip } from '@/api/ss/vip'
import { $showColumns } from '@/utils/mixins'
//
const defaultSort = {
prop: "createTime",
order: "descending"
}
export default {
name: 'VipTable',
mixins: [$showColumns],
data() {
return {
//
columns: [
{key: 'id', visible: true, label: 'ID', minWidth: null, sortable: true, overflow: false, align: 'center', width: "80"},
{key: 'userName', visible: true, label: '用户', minWidth: null, sortable: true, overflow: false, align: 'center', width: null},
{key: 'storeName', visible: true, label: '店铺', minWidth: null, sortable: true, overflow: false, align: 'center', width: null},
{key: 'vipLevelName', visible: true, label: '会员等级', minWidth: null, sortable: true, overflow: false, align: 'center', width: null},
{key: 'expireTime', visible: true, label: '有效期', minWidth: null, sortable: false, overflow: false, align: 'center', width: null},
{key: 'createTime', visible: true, label: '创建时间', minWidth: null, sortable: false, overflow: false, align: 'center', width: null},
],
//
orderSorts: ['ascending', 'descending', null],
//
loading: true,
//
showSearch: true,
//
total: 0,
//
vipList: [],
defaultSort,
//
queryParams: {
pageNum: 1,
pageSize: 20,
orderByColumn: defaultSort.prop,
isAsc: defaultSort.order,
id: null,
userId: null,
storeId: null,
levelId: null,
},
}
},
created() {
this.initColumns();
this.getList();
},
methods: {
/** 当排序按钮被点击时触发 **/
onSortChange(column) {
if (column.order == null) {
this.queryParams.orderByColumn = defaultSort.prop;
this.queryParams.isAsc = defaultSort.order;
} else {
this.queryParams.orderByColumn = column.prop;
this.queryParams.isAsc = column.order;
}
this.getList();
},
/** 查询会员列表 */
getList() {
this.loading = true;
listVip(this.queryParams).then(response => {
this.vipList = response.rows;
this.total = response.total;
this.loading = false;
});
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNum = 1;
this.getList();
},
/** 重置按钮操作 */
resetQuery() {
this.resetForm("queryForm");
this.handleQuery();
},
}
}
</script>

121
src/views/ss/vip/index.vue Normal file
View File

@ -0,0 +1,121 @@
<template>
<div class="app-container">
<vip-table ref="table" @selection-change="handleSelectionChange" >
<template #table-operator>
<el-col :span="1.5">
<el-button
type="primary"
plain
icon="el-icon-plus"
size="mini"
@click="handleAdd"
v-hasPermi="['ss:vip:add']"
>新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="danger"
plain
icon="el-icon-delete"
size="mini"
:disabled="isEmpty(ids)"
@click="handleDelete"
v-hasPermi="['ss:vip:remove']"
>删除</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="warning"
plain
icon="el-icon-download"
size="mini"
@click="handleExport"
v-hasPermi="['ss:vip:export']"
>导出</el-button>
</el-col>
</template>
<template v-slot:row-operator="scope">
<el-button
size="mini"
type="text"
icon="el-icon-edit"
@click="handleUpdate(scope.row)"
v-hasPermi="['ss:vip:edit']"
>修改</el-button>
<el-button
size="mini"
type="text"
icon="el-icon-delete"
@click="handleDelete(scope.row)"
v-hasPermi="['ss:vip:remove']"
>删除</el-button>
</template>
</vip-table>
<!-- 添加或修改会员对话框 -->
<vip-edit-dialog :show.sync="open" :id="row.id" @success="getList"/>
</div>
</template>
<script>
import { delVip } from "@/api/ss/vip";
import { $showColumns } from '@/utils/mixins';
import UserInput from '@/components/Business/SmUser/UserInput.vue'
import StoreInput from '@/components/Business/Store/StoreInput.vue'
import VipLevelInput from '@/components/Business/VipLevel/VipLevelInput.vue'
import VipEditDialog from '@/views/ss/vip/components/VipEditDialog.vue'
import VipTable from '@/views/ss/vip/components/VipTable.vue'
import { isEmpty } from '@/utils'
export default {
name: "Vip",
components: { VipTable, VipEditDialog, VipLevelInput, StoreInput, UserInput },
data() {
return {
row: {},
//
open: false,
//
ids: [],
};
},
methods: {
isEmpty,
getList() {
this.$refs.table.getList();
},
//
handleSelectionChange(selection) {
this.ids = selection.map(item => item.id)
},
/** 新增按钮操作 */
handleAdd() {
this.row = {}
this.open = true;
},
/** 修改按钮操作 */
handleUpdate(row) {
this.row = row;
this.open = true;
},
/** 删除按钮操作 */
handleDelete(row) {
const ids = row.id || this.ids;
this.$modal.confirm('是否确认删除会员编号为"' + ids + '"的数据项?').then(function() {
return delVip(ids);
}).then(() => {
this.getList();
this.$modal.msgSuccess("删除成功");
}).catch(() => {});
},
/** 导出按钮操作 */
handleExport() {
this.download('ss/vip/export', {
...this.queryParams
}, `vip_${new Date().getTime()}.xlsx`)
}
}
};
</script>

View File

@ -0,0 +1,109 @@
<template>
<el-dialog :title="title" :visible.sync="open" width="500px" append-to-body @open="onOpen">
<el-form ref="form" :model="form" :rules="rules" label-width="80px">
<el-form-item label="商户" prop="mchId" v-if="visibleColumn('mchId')">
<user-input v-model="form.mchId" placeholder="请选择商户"/>
</el-form-item>
<el-form-item label="等级名称" prop="name">
<el-input v-model="form.name" placeholder="请输入等级名称" maxlength="30" show-word-limit/>
</el-form-item>
<el-form-item label="折扣" prop="discount">
<el-input-number v-model="form.discount" placeholder="请输入折扣" :min="0" :max="10"/>
</el-form-item>
<el-form-item label="描述文本" prop="description">
<el-input v-model="form.description" type="textarea" placeholder="请输入内容" maxlength="1000" show-word-limit/>
</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>
</template>
<script>
import UserInput from '@/components/Business/SmUser/UserInput.vue'
import StoreInput from '@/components/Business/Store/StoreInput.vue'
import VipLevelInput from '@/components/Business/VipLevel/VipLevelInput.vue'
import { $editDialog, $showColumns } from '@/utils/mixins'
import { addVipLevel, getVipLevel, updateVipLevel } from '@/api/ss/vipLevel'
export default {
name: "VipLevelEditDialog",
mixins: [$editDialog, $showColumns],
components: { VipLevelInput, StoreInput, UserInput },
props: {
rules: {
type: Object,
default: () => ({
mchId: [
{ required: true, message: "商户不能为空", trigger: "change" }
],
name: [
{ required: true, message: "等级名称不能为空", trigger: "blur" }
],
})
},
getApi: {
type: Function,
default: getVipLevel
},
addApi: {
typ: Function,
default: addVipLevel
},
updateApi: {
type: Function,
default: updateVipLevel
}
},
data() {
return {
form: {},
title: null,
}
},
methods: {
getDetail() {
this.getApi(this.id).then(response => {
this.form = response.data;
this.open = true;
this.title = "修改会员等级";
});
},
//
reset() {
this.title = "新增会员等级";
this.form = {
id: null,
mchId: null,
name: null,
discount: null,
createTime: null,
description: null
};
this.resetForm("form");
},
/** 提交按钮 */
submitForm() {
this.$refs["form"].validate(valid => {
if (valid) {
if (this.form.id != null) {
this.updateApi(this.form).then(response => {
this.$modal.msgSuccess("修改成功");
this.open = false;
this.$emit('success')
});
} else {
this.addApi(this.form).then(response => {
this.$modal.msgSuccess("新增成功");
this.open = false;
this.$emit('success')
});
}
}
});
},
}
}
</script>

View File

@ -0,0 +1,170 @@
<template>
<div>
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="商户" prop="mchName" v-show="visibleSearch('mchName')">
<el-input
v-model="queryParams.mchName"
placeholder="请输入商户名称"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="等级名称" prop="name" v-show="visibleSearch('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" size="mini" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<slot name="table-operator"/>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList" :columns="columns"></right-toolbar>
</el-row>
<el-table v-loading="loading" :data="vipLevelList" :default-sort="defaultSort" @sort-change="onSortChange">
<el-table-column type="selection" width="55" align="center" />
<template v-for="column of showColumns">
<el-table-column
:key="column.key"
:label="column.label"
:prop="column.key"
:align="column.align"
:min-width="column.minWidth"
:sort-orders="orderSorts"
:sortable="column.sortable"
:show-overflow-tooltip="column.overflow"
:width="column.width"
>
<template slot-scope="d">
<template v-if="column.key === 'id'">
{{d.row[column.key]}}
</template>
<template v-else-if="column.key === 'mchName'">
<user-link :id="d.row.mchId" :name="d.row.mchName"/>
</template>
<template v-else-if="column.key === 'discount'">
{{d.row.discount | defaultValue}}
</template>
<template v-else>
{{d.row[column.key]}}
</template>
</template>
</el-table-column>
</template>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template slot-scope="scope">
<slot name="row-operator" :row="scope.row"/>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total>0"
:total="total"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
@pagination="getList"
/>
</div>
</template>
<script>
import { $showColumns } from '@/utils/mixins'
import UserLink from '@/components/Business/SmUser/UserLink.vue'
import { listVipLevel } from '@/api/ss/vipLevel'
//
const defaultSort = {
prop: "createTime",
order: "descending"
}
export default {
name: 'VipLevelTable',
components: { UserLink },
mixins: [$showColumns],
props: {
listApi: {
type: Function,
default: listVipLevel
}
},
data() {
return {
//
columns: [
{key: 'id', visible: true, label: 'ID', minWidth: null, sortable: true, overflow: false, align: 'center', width: "80"},
{key: 'mchName', visible: true, label: '商户', minWidth: null, sortable: true, overflow: false, align: 'center', width: null},
{key: 'name', visible: true, label: '等级名称', minWidth: null, sortable: true, overflow: false, align: 'center', width: null},
{key: 'discount', visible: true, label: '折扣', minWidth: null, sortable: true, overflow: false, align: 'center', width: null},
{key: 'description', visible: true, label: '描述文本', minWidth: null, sortable: true, overflow: true, align: 'center', width: null},
{key: 'createTime', visible: true, label: '创建时间', minWidth: null, sortable: true, overflow: false, align: 'center', width: null},
],
//
orderSorts: ['ascending', 'descending', null],
//
loading: true,
//
showSearch: true,
//
total: 0,
//
vipLevelList: [],
defaultSort,
//
queryParams: {
pageNum: 1,
pageSize: 20,
orderByColumn: defaultSort.prop,
isAsc: defaultSort.order,
id: null,
mchId: null,
name: null,
description: null
},
}
},
created() {
this.initColumns();
this.getList();
},
methods: {
/** 当排序按钮被点击时触发 **/
onSortChange(column) {
if (column.order == null) {
this.queryParams.orderByColumn = defaultSort.prop;
this.queryParams.isAsc = defaultSort.order;
} else {
this.queryParams.orderByColumn = column.prop;
this.queryParams.isAsc = column.order;
}
this.getList();
},
/** 查询会员等级列表 */
getList() {
this.loading = true;
this.listApi(this.queryParams).then(response => {
this.vipLevelList = response.rows;
this.total = response.total;
this.loading = false;
});
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNum = 1;
this.getList();
},
/** 重置按钮操作 */
resetQuery() {
this.resetForm("queryForm");
this.handleQuery();
},
}
}
</script>

View File

@ -0,0 +1,119 @@
<template>
<div class="app-container">
<vip-level-table ref="table" @selection-change="handleSelectionChange" >
<template #table-operator>
<el-col :span="1.5">
<el-button
type="primary"
plain
icon="el-icon-plus"
size="mini"
@click="handleAdd"
v-hasPermi="['ss:vipLevel:add']"
>新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="danger"
plain
icon="el-icon-delete"
size="mini"
:disabled="!isEmpty(ids)"
@click="handleDelete"
v-hasPermi="['ss:vipLevel:remove']"
>删除</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="warning"
plain
icon="el-icon-download"
size="mini"
@click="handleExport"
v-hasPermi="['ss:vipLevel:export']"
>导出</el-button>
</el-col>
</template>
<template v-slot:row-operator="scope">
<el-button
size="mini"
type="text"
icon="el-icon-edit"
@click="handleUpdate(scope.row)"
v-hasPermi="['ss:vipLevel:edit']"
>修改</el-button>
<el-button
size="mini"
type="text"
icon="el-icon-delete"
@click="handleDelete(scope.row)"
v-hasPermi="['ss:vipLevel:remove']"
>删除</el-button>
</template>
</vip-level-table>
<!-- 添加或修改会员等级对话框 -->
<vip-level-edit-dialog :show.sync="open" :id="row.id" @success="getList"/>
</div>
</template>
<script>
import { listVipLevel, getVipLevel, delVipLevel, addVipLevel, updateVipLevel } from "@/api/ss/vipLevel";
import { $showColumns } from '@/utils/mixins';
import UserLink from '@/components/Business/SmUser/UserLink.vue'
import VipLevelEditDialog from '@/views/ss/vipLevel/components/VipLevelEditDialog.vue'
import VipLevelTable from '@/views/ss/vipLevel/components/VipLevelTable.vue'
import { isEmpty } from '@/utils'
export default {
name: "VipLevel",
components: { VipLevelTable, VipLevelEditDialog, UserLink },
mixins: [$showColumns],
data() {
return {
row: {},
//
open: false,
//
ids: [],
};
},
methods: {
isEmpty,
getList() {
this.$refs.table.getList();
},
//
handleSelectionChange(selection) {
this.ids = selection.map(item => item.id)
},
/** 新增按钮操作 */
handleAdd() {
this.row = {};
this.open = true;
},
/** 修改按钮操作 */
handleUpdate(row) {
this.row = row;
this.open = true;
},
/** 删除按钮操作 */
handleDelete(row) {
const ids = row.id || this.ids;
this.$modal.confirm('是否确认删除会员等级编号为"' + ids + '"的数据项?').then(function() {
return delVipLevel(ids);
}).then(() => {
this.getList();
this.$modal.msgSuccess("删除成功");
}).catch(() => {});
},
/** 导出按钮操作 */
handleExport() {
this.download('ss/vipLevel/export', {
...this.queryParams
}, `vipLevel_${new Date().getTime()}.xlsx`)
}
}
};
</script>

View File

@ -50,14 +50,11 @@
<el-switch v-model="d.row.enabled" @change="(nv) => {onChangeEnabled(d.row, nv)}"/>
</template>
</el-table-column>
<!-- <el-table-column label="服务费收取类型" align="center" prop="serviceType">-->
<!-- <dict-tag slot-scope="d" :options="dict.type.service_type" :value="d.row.serviceType"/>-->
<!-- </el-table-column>-->
<!-- <el-table-column label="服务费" align="center" prop="serviceRate">-->
<!-- <template slot-scope="d">-->
<!-- {{d.row.serviceRate | money}} {{serviceUnit(d.row.serviceType)}}-->
<!-- </template>-->
<!-- </el-table-column>-->
<el-table-column label="适用平台" align="center" prop="platform">
<template slot-scope="d">
<dict-tag :value="d.row.platform" :options="dict.type.channel_platform"/>
</template>
</el-table-column>
<el-table-column label="渠道成本" align="center" prop="costRate">
<template slot-scope="d">{{d.row.costRate | money}} %</template>
</el-table-column>
@ -136,9 +133,9 @@ import ConfigDialog from '@/components/Business/Config/ConfigDialog.vue'
export default {
name: "Channel",
components: { ConfigDialog },
mixins: [$serviceType, $withdrawServiceType],
dicts: ['withdraw_service_type', 'service_type'],
dicts: ['withdraw_service_type', 'service_type', 'channel_platform'],
components: { ConfigDialog },
data() {
return {
showConfig: false,