初始化项目

This commit is contained in:
2026-09-01 22:15:50 +08:00
commit b5a26a8848
11217 changed files with 1710987 additions and 0 deletions
+15
View File
@@ -0,0 +1,15 @@
<template>
<div>
404 page
</div>
</template>
<script>
export default {
name: '404'
}
</script>
<style scoped>
</style>
+140
View File
@@ -0,0 +1,140 @@
<template>
<a-card :bordered="false">
<div class="card-title">{{ $route.meta.title }}</div>
<div class="table-operator">
<!-- 搜索板块 -->
<a-row class="row-item-search">
<a-form class="search-form" :form="searchForm" layout="inline" @submit="handleSearch">
<a-form-item label="用户名称,电话,身份证号">
<a-input v-decorator="['search']" placeholder="请输入用户名称,电话,身份证号" />
</a-form-item>
<a-form-item label="最后使用时间">
<a-range-picker format="YYYY-MM-DD" v-decorator="['last_called_at']" />
</a-form-item>
<a-form-item class="search-btn">
<a-button type="primary" icon="search" html-type="submit">搜索</a-button>
</a-form-item>
</a-form>
</a-row>
</div>
<s-table
ref="table"
rowKey="account_id"
:loading="isLoading"
:columns="columns"
:data="loadData"
:pageSize="15"
>
<!-- 公司 -->
<span slot="company" slot-scope="text, item">
<p class="twoline-hide" style="width: 100px;">{{ item.company.name }}</p>
</span>
<!-- 最后使用时间 -->
<span slot="last_called_at" slot-scope="text, item">
<p class="twoline-hide" style="width: 150px;">{{ item.last_called_at | dateFormat }}</p>
</span>
</s-table>
</a-card>
</template>
<script>
import * as Api from '@/api/account'
import { STable } from '@/components/Table'
export default {
name: 'Index',
components: {
STable
},
data () {
return {
// 当前表单元素
searchForm: this.$form.createForm(this),
// 查询参数
queryParam: {},
// 正在加载
isLoading: false,
// 表头
columns: [
{
title: 'ID',
dataIndex: 'account_id'
},
{
title: '公司信息',
dataIndex: 'company',
scopedSlots: { customRender: 'company' }
},
{
title: '用户编号',
dataIndex: 'userNo'
},
{
title: '用户姓名',
dataIndex: 'userName'
},
{
title: '手机号',
dataIndex: 'mobile'
},
{
title: '用户角色',
dataIndex: 'userType',
scopedSlots: { customRender: 'userType' }
},
{
title: '用户身份证号',
dataIndex: 'userIdCard'
},
{
title: '最后使用时间',
dataIndex: 'last_called_at',
scopedSlots: { customRender: 'last_called_at' }
},
{
title: '注册时间',
dataIndex: 'create_time'
}
],
// 加载数据方法 必须为 Promise 对象
loadData: param => {
return Api.list({ ...param, ...this.queryParam })
.then(response => {
return response.data.list
})
}
}
},
created () {
},
methods: {
/**
* 刷新列表
* @param Boolean bool 强制刷新到第一页
*/
handleRefresh (bool = false) {
this.$refs.table.refresh(bool)
},
// 确认搜索
handleSearch (e) {
e.preventDefault()
this.searchForm.validateFields((error, values) => {
if (!error) {
this.queryParam = { ...this.queryParam, ...values }
this.handleRefresh(true)
}
})
}
}
}
</script>
<style lang="less" scoped>
.ant-card-body {
padding: 22px 29px 25px;
}
</style>
+255
View File
@@ -0,0 +1,255 @@
<template>
<a-card :bordered="false">
<div class="card-title">{{ $route.meta.title }}</div>
<a-spin :spinning="isLoading">
<a-form :form="form" @submit="handleSubmit" :selfUpdate="true">
<a-tabs :activeKey="tabKey" :tabBarStyle="{marginBottom: '30px'}" @change="handleTabs">
<a-tab-pane :key="0" tab="基本信息"></a-tab-pane>
</a-tabs>
<div class="tabs-content">
<!-- 基本信息 -->
<div class="tab-pane" v-show="tabKey == 0">
<a-form-item label="收货人名称" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-input
placeholder="请输入收货人名称"
v-decorator="['name', {rules: [{required: true, min: 2, message: '请输入至少2个字符'}]}]"
/>
</a-form-item>
<a-form-item label="区域" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-select
style="width: 150px"
v-decorator="['province', {rules: [{required: true, message: '请选择省份'}]}]"
placeholder="请选择省份"
@change="getOptionCity"
>
<a-select-option
v-for="(item, index) in addressData.provinces"
:key="index"
:value="item"
>{{ item }}</a-select-option>
</a-select>
<a-select
style="width: 150px"
v-decorator="['city', {rules: [{required: true, message: '请选择城市'}]}]"
placeholder="请选择城市"
@change="getOptionArea"
>
<a-select-option
v-for="(item, index) in addressData.cities"
:key="index"
:value="item"
>{{ item }}</a-select-option>
</a-select>
<a-select
style="width: 150px"
v-decorator="['area', {rules: [{required: true, message: '请选择区县'}]}]"
placeholder="请选择区县"
@change="getOptionStreet"
>
<a-select-option
v-for="(item, index) in addressData.districts"
:key="index"
:value="item"
>{{ item }}</a-select-option>
</a-select>
<a-select
style="width: 150px"
v-decorator="['street', {rules: [{required: true, message: '请选择街道'}]}]"
placeholder="请选择街道"
>
<a-select-option
v-for="(item, index) in addressData.streets"
:key="index"
:value="item"
>{{ item }}</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="详细地址" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-input
placeholder="请输入详细地址"
v-decorator="['detail', {rules: [{required: true, min: 2, message: '请输入至少2个字符'}]}]"
/>
</a-form-item>
<a-form-item label="状态" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-radio-group
v-decorator="['status', {initialValue: 10, rules: [{ required: true }]}]"
>
<a-radio
v-for="(item, index) in AddressStatusEnum.data"
:key="index"
:value="item.value">{{ item.name }}</a-radio>
</a-radio-group>
</a-form-item>
</div>
</div>
<a-form-item class="mt-20" :wrapper-col="{ span: wrapperCol.span, offset: labelCol.span }">
<a-button type="primary" html-type="submit" :loading="isBtnLoading">提交</a-button>
</a-form-item>
</a-form>
</a-spin>
</a-card>
</template>
<script>
import * as AddressApi from '@/api/address'
import AddressModel from '@/common/model/address/Index'
import {
AddressStatusEnum
} from '@/common/enum/address'
import { assignment } from '@/utils/util'
export default {
components: {
},
data () {
return {
// 默认的标签索引
tabKey: 0,
// 标签布局属性
labelCol: { span: 3 },
// 输入框布局属性
wrapperCol: { span: 10 },
// loading状态
isLoading: false,
isBtnLoading: false,
// 当前表单元素
form: this.$form.createForm(this),
// 表单数据
formData: AddressModel.formData,
addressData: {
provinces: [],
cities: [],
districts: [],
streets: []
},
selectedAddress: {
province: '',
city: '',
area: '',
street: ''
}
}
},
beforeCreate () {
// 批量给当前实例赋值
assignment(this, {
AddressStatusEnum
})
},
// 初始化数据
created () {
this.isLoading = true
// 获取form所需的数据
AddressModel.getFromData()
.then(() => {
this.isLoading = false
})
this.getRegion()
},
methods: {
// 手动强制更新页面
onForceUpdate (bool = false) {
this.$forceUpdate()
// bool为true时再执行一次 $forceUpdate, 特殊情况下需执行两次,原因如下:
// 第一次执行 $forceUpdate时, 新元素绑定v-decorator无法获取到form.getFieldValue
bool && setTimeout(() => {
this.$forceUpdate()
}, 10)
},
// 切换tab选项卡
handleTabs (key) {
this.tabKey = key
},
/**
* 确认按钮
*/
handleSubmit (e) {
e.preventDefault()
// 表单验证
const { form: { validateFields } } = this
validateFields((errors, values) => {
// 定位到错误的tab选项卡
if (errors) {
this.onTargetTabError(errors)
return false
}
// 提交到后端api
this.onFormSubmit(values)
return true
})
},
// 获取区域下拉框
getRegion () {
this.isLoading = true
AddressApi.getRegion({ form: this.selectedAddress })
.then(result => {
switch (result.data.field) {
case 'city':
this.addressData['cities'] = result.data.list
break
case 'area':
this.addressData['districts'] = result.data.list
break
case 'street':
this.addressData['streets'] = result.data.list
break
default:
this.addressData['provinces'] = result.data.list
}
})
.finally(() => {
this.isLoading = false
})
},
// 获取区域下拉框
getOptionCity (val) {
this.selectedAddress['province'] = val
this.form.setFieldsValue({ 'city': '', 'area': '', 'street': '' })
this.getRegion()
},
getOptionArea (val) {
this.selectedAddress['city'] = val
this.form.setFieldsValue({ 'area': '', 'street': '' })
this.getRegion()
},
getOptionStreet (val) {
this.selectedAddress['area'] = val
this.form.setFieldsValue({ 'street': '' })
this.getRegion()
},
/**
* 提交到后端api
*/
onFormSubmit (values) {
this.isLoading = true
this.isBtnLoading = true
AddressApi.add({ form: values })
.then(result => {
// 显示提示信息
this.$message.success(result.message, 1.5)
// 跳转到列表页
setTimeout(() => {
this.$router.push('./index')
}, 1500)
})
.catch(() => {
this.isBtnLoading = false
})
.finally(() => {
this.isLoading = false
})
}
}
}
</script>
<style lang="less" scoped>
@import './style.less';
</style>
+291
View File
@@ -0,0 +1,291 @@
<template>
<a-card :bordered="false">
<div class="card-title">{{ $route.meta.title }}</div>
<div class="table-operator">
<!-- 搜索板块 -->
<a-row class="row-item-search">
<a-form class="search-form" :form="searchForm" layout="inline" @submit="handleSearch">
<a-form-item label="请选择使用状态">
<a-select v-decorator="['status', { initialValue: -1 }]">
<a-select-option :value="-1">全部</a-select-option>
<a-select-option
v-for="(item, index) in AddressStatusEnum.data"
:key="index"
:value="item.value"
>{{ item.name }}</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="请输入姓名">
<a-input v-decorator="['name']" placeholder="请输入姓名" />
</a-form-item>
<a-form-item label="请输入省份">
<a-input v-decorator="['province']" placeholder="请输入省份" />
</a-form-item>
<a-form-item label="请输入城市">
<a-input v-decorator="['city']" placeholder="请输入城市" />
</a-form-item>
<a-form-item label="请输入地区">
<a-input v-decorator="['area']" placeholder="请输入地区" />
</a-form-item>
<a-form-item label="请输入街道">
<a-input v-decorator="['street']" placeholder="请输入街道" />
</a-form-item>
<a-form-item label="请输入详细地址">
<a-input v-decorator="['name']" placeholder="请输入详细地址" />
</a-form-item>
<a-form-item class="search-btn">
<a-button type="primary" icon="search" html-type="submit">搜索</a-button>
</a-form-item>
</a-form>
</a-row>
<!-- 操作板块 -->
<div class="row-item-tab clearfix">
<a-button
v-if="$auth('/address/create')"
class="fl-l"
type="primary"
icon="plus"
@click="handleCreate"
>新增</a-button>
<div v-if="selectedRowKeys.length" class="button-group">
<a-button-group class="ml-10">
<a-button v-action:delete icon="delete" @click="handleDelete(selectedRowKeys)">删除</a-button>
<a-button
icon="arrow-up"
@click="handleUpdateState(selectedRowKeys, true)"
>未使用</a-button>
<a-button
icon="arrow-down"
@click="handleUpdateState(selectedRowKeys, false)"
>已使用</a-button>
</a-button-group>
</div>
</div>
</div>
<s-table
ref="table"
rowKey="address_id"
:loading="isLoading"
:columns="columns"
:data="loadData"
:rowSelection="rowSelection"
:pageSize="15"
:scroll="{ x: 1450 }"
>
<!-- 详细地址 -->
<span slot="address" slot-scope="text">
<p class="twoline-hide" style="width: 270px;">{{ text }}</p>
</span>
<!-- 使用状态 -->
<span slot="status" slot-scope="text, item">
<a-tag
class="cur-p"
:color="text == 10 ? 'green' : 'red'"
@click="handleUpdateState([item.address_id], text != 10)"
>{{ text == 10 ? '未使用' : '已使用' }}</a-tag>
</span>
<!-- 操作项 -->
<div class="actions" slot="action" slot-scope="text, item">
<router-link :to="{ path: '/address/update', query: { addressId: item.address_id } }">编辑</router-link>
<a v-action:delete @click="handleDelete([item.address_id])">删除</a>
</div>
</s-table>
</a-card>
</template>
<script>
import * as AddressApi from '@/api/address'
import { ContentHeader, STable } from '@/components'
import {
AddressStatusEnum
} from '@/common/enum/address'
import { assignment, inArray } from '@/utils/util'
// 表格表头
const columns = [
{
title: '地址ID',
dataIndex: 'address_id'
},
{
title: '收货人名称',
dataIndex: 'name'
},
{
title: '省份',
dataIndex: 'province'
},
{
title: '城市',
dataIndex: 'city'
},
{
title: '地区',
dataIndex: 'area'
},
{
title: '街道',
dataIndex: 'street'
},
{
title: '详细地址',
dataIndex: 'detail',
width: '307px'
},
{
title: '使用状态',
dataIndex: 'status',
width: '80px',
scopedSlots: { customRender: 'status' }
},
{
title: '创建时间',
dataIndex: 'create_time'
},
{
title: '操作',
dataIndex: 'action',
width: '150px',
fixed: 'right',
scopedSlots: { customRender: 'action' }
}
]
export default {
name: 'Index',
components: {
ContentHeader,
STable
},
data () {
return {
// 当前表单元素
searchForm: this.$form.createForm(this),
// 查询参数
queryParam: {},
// 正在加载
isLoading: false,
// 表头
columns,
// 选择的元素
selectedRowKeys: [],
// 加载数据方法 必须为 Promise 对象
loadData: param => {
return AddressApi.list({ ...param, ...this.queryParam })
.then(response => {
return response.data.list
})
}
}
},
beforeCreate () {
// 批量给当前实例赋值
assignment(this, {
inArray,
AddressStatusEnum
})
},
created () {
},
computed: {
rowSelection () {
return {
selectedRowKeys: this.selectedRowKeys,
onChange: this.onSelectChange
}
}
},
methods: {
/**
* 选中项发生变化时的回调
*/
onSelectChange (selectedRowKeys) {
this.selectedRowKeys = selectedRowKeys
},
// 确认搜索
handleSearch (e) {
e.preventDefault()
this.searchForm.validateFields((error, values) => {
if (!error) {
this.queryParam = { ...this.queryParam, ...values }
this.handleRefresh(true)
}
})
},
// 重置搜索表单
handleReset () {
this.searchForm.resetFields()
},
/**
* 修改使用状态
*/
handleUpdateState (addressIds, state = true) {
const app = this
const modal = this.$confirm({
title: '您确定要修改地址状态为' + (state ? '未使用' : '已使用') + '?',
content: '地址状态修改',
onOk () {
return AddressApi.changeState({ addressIds, state })
.then(result => {
app.$message.success(result.message, 1.5)
app.handleRefresh()
})
.finally(result => {
modal.destroy()
})
}
})
},
/**
* 删除记录
*/
handleDelete (addressIds) {
const app = this
const modal = this.$confirm({
title: '您确定要删除该记录吗?',
content: '删除后不可恢复',
onOk () {
return AddressApi.deleted(addressIds)
.then(result => {
app.$message.success(result.message, 1.5)
app.handleRefresh()
})
.finally(result => {
modal.destroy()
})
}
})
},
/**
* 新增记录
*/
handleCreate () {
this.$router.push('/address/create')
},
/**
* 刷新列表
* @param Boolean bool 强制刷新到第一页
*/
handleRefresh (bool = false) {
this.selectedRowKeys = []
this.$refs.table.refresh(bool)
}
}
}
</script>
<style lang="less" scoped>
.ant-card-body {
padding: 22px 29px 25px;
}
// 筛选tab
.tab-list {
margin-right: 20px;
}
</style>
+283
View File
@@ -0,0 +1,283 @@
<template>
<a-card :bordered="false">
<div class="card-title">{{ $route.meta.title }}</div>
<a-spin :spinning="isLoading">
<a-form :form="form" @submit="handleSubmit" :selfUpdate="true">
<a-tabs :activeKey="tabKey" :tabBarStyle="{marginBottom: '30px'}" @change="handleTabs">
<a-tab-pane :key="0" tab="基本信息"></a-tab-pane>
</a-tabs>
<div class="tabs-content">
<!-- 基本信息 -->
<div class="tab-pane" v-show="tabKey == 0">
<a-form-item label="收货人名称" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-input
placeholder="请输入收货人名称"
v-decorator="['name', {rules: [{required: true, min: 2, message: '请输入至少2个字符'}]}]"
/>
</a-form-item>
<a-form-item label="区域" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-select
style="width: 150px"
v-decorator="['province', {rules: [{required: true, message: '请选择省份'}]}]"
placeholder="请选择省份"
@change="getOptionCity"
>
<a-select-option
v-for="(item, index) in addressData.provinces"
:key="index"
:value="item"
>{{ item }}</a-select-option>
</a-select>
<a-select
style="width: 150px"
v-decorator="['city', {rules: [{required: true, message: '请选择城市'}]}]"
placeholder="请选择城市"
@change="getOptionArea"
>
<a-select-option
v-for="(item, index) in addressData.cities"
:key="index"
:value="item"
>{{ item }}</a-select-option>
</a-select>
<a-select
style="width: 150px"
v-decorator="['area', {rules: [{required: true, message: '请选择区县'}]}]"
placeholder="请选择区县"
@change="getOptionStreet"
>
<a-select-option
v-for="(item, index) in addressData.districts"
:key="index"
:value="item"
>{{ item }}</a-select-option>
</a-select>
<a-select
style="width: 150px"
v-decorator="['street', {rules: [{required: true, message: '请选择街道'}]}]"
placeholder="请选择街道"
>
<a-select-option
v-for="(item, index) in addressData.streets"
:key="index"
:value="item"
>{{ item }}</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="详细地址" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-input
placeholder="请输入详细地址"
v-decorator="['detail', {rules: [{required: true, min: 2, message: '请输入至少2个字符'}]}]"
/>
</a-form-item>
<a-form-item label="状态" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-radio-group
v-decorator="['status', { rules: [{ required: true }]}]"
>
<a-radio
v-for="(item, index) in AddressStatusEnum.data"
:key="index"
:value="item.value">{{ item.name }}</a-radio>
</a-radio-group>
</a-form-item>
</div>
</div>
<a-form-item class="mt-20" :wrapper-col="{ span: wrapperCol.span, offset: labelCol.span }">
<a-button type="primary" html-type="submit" :loading="isBtnLoading">提交</a-button>
</a-form-item>
</a-form>
</a-spin>
</a-card>
</template>
<script>
import * as AddressApi from '@/api/address'
import { InputNumberGroup } from '@/components'
import AddressModel from '@/common/model/address/Index'
import { isEmptyObject, assignment } from '@/utils/util'
import {
AddressStatusEnum
} from '@/common/enum/address'
export default {
components: {
InputNumberGroup
},
data () {
return {
// 默认的标签索引
tabKey: 0,
// 标签布局属性
labelCol: { span: 3 },
// 输入框布局属性
wrapperCol: { span: 10 },
// loading状态
isLoading: false,
isBtnLoading: false,
// 当前表单元素
form: this.$form.createForm(this),
// 商户ID
addressId: null,
// 表单数据
formData: AddressModel.formData,
addressData: {
provinces: [],
cities: [],
districts: [],
streets: []
},
selectedAddress: {
province: '',
city: '',
area: '',
street: ''
}
}
},
beforeCreate () {
// 批量给当前实例赋值
assignment(this, {
AddressStatusEnum
})
},
created () {
// 初始化数据
this.initData()
},
beforeDestroy () {
// 销毁商户详情
AddressModel.formData.address = {}
},
methods: {
// 初始化数据
initData () {
// 记录地址ID
this.addressId = this.$route.query.addressId
// 获取form所需的数据
this.isLoading = true
AddressModel.getFromData(this.addressId)
.then(() => {
// 商户表单数据
if (!isEmptyObject(this.form.getFieldsValue())) {
this.form.setFieldsValue(AddressModel.getFieldsValue())
}
this.addressData['provinces'] = this.formData.provinces
this.addressData['cities'] = this.formData.cities
this.addressData['districts'] = this.formData.districts
this.addressData['streets'] = this.formData.streets
this.isLoading = false
})
},
// 手动强制更新页面
onForceUpdate (bool = false) {
this.$forceUpdate()
// bool为true时再执行一次 $forceUpdate, 特殊情况下需执行两次,原因如下:
// 第一次执行 $forceUpdate时, 新元素绑定v-decorator无法获取到form.getFieldValue
bool && setTimeout(() => {
this.$forceUpdate()
}, 10)
},
// 切换tab选项卡
handleTabs (key) {
this.tabKey = key
},
/**
* 确认按钮
*/
handleSubmit (e) {
e.preventDefault()
// 表单验证
const { form: { validateFields } } = this
validateFields((errors, values) => {
// 定位到错误的tab选项卡
if (errors) {
this.onTargetTabError(errors)
return false
}
// 提交到后端api
this.onFormSubmit(values)
return true
})
},
/**
* 定位到错误的tab选项卡
*/
onTargetTabError (errors) {
},
// 获取区域下拉框
getRegion () {
this.isLoading = true
AddressApi.getRegion({ form: this.selectedAddress })
.then(result => {
switch (result.data.field) {
case 'city':
this.addressData['cities'] = result.data.list
break
case 'area':
this.addressData['districts'] = result.data.list
break
case 'street':
this.addressData['streets'] = result.data.list
break
default:
this.addressData['provinces'] = result.data.list
}
})
.finally(() => {
this.isLoading = false
})
},
// 获取区域下拉框
getOptionCity (val) {
this.selectedAddress['province'] = val
this.form.setFieldsValue({ 'city': '', 'area': '', 'street': '' })
this.getRegion()
},
getOptionArea (val) {
this.selectedAddress['city'] = val
this.form.setFieldsValue({ 'area': '', 'street': '' })
this.getRegion()
},
getOptionStreet (val) {
this.selectedAddress['area'] = val
this.form.setFieldsValue({ 'street': '' })
this.getRegion()
},
/**
* 提交到后端api
*/
onFormSubmit (values) {
this.isLoading = true
this.isBtnLoading = true
AddressApi.edit({ addressId: this.addressId, form: values })
.then(result => {
// 显示提示信息
this.$message.success(result.message, 1.5)
// 跳转到列表页
setTimeout(() => {
this.$router.push('./index')
}, 1500)
})
.catch(() => {
this.isBtnLoading = false
})
.finally(() => {
this.isLoading = false
})
}
}
}
</script>
<style lang="less" scoped>
@import './style.less';
</style>
+25
View File
@@ -0,0 +1,25 @@
.tabs-content {
.tab-pane {
animation: fadenum 0.6s ease;
}
}
/deep/.ant-form-item-control {
padding-left: 10px;
.ant-form-item-control {
padding-left: 0;
}
}
.ant-form-item {
margin-bottom: 18px;
.ant-form-item {
margin-bottom: 0;
}
}
.input-number-group {
margin: 3px 0;
}
+366
View File
@@ -0,0 +1,366 @@
<template>
<a-card :bordered="false">
<div class="card-title">{{ $route.meta.title }}</div>
<div class="table-operator">
<!-- 搜索板块 -->
<a-row class="row-item-search">
<a-form class="search-form" :form="searchForm" layout="inline" @submit="handleSearch">
<a-form-item label="卡号">
<a-input v-decorator="['card_number']" placeholder="请输入卡号" />
</a-form-item>
<a-form-item label="兑换用户ID">
<a-input v-decorator="['user_id']" placeholder="请输入兑换用户ID" />
</a-form-item>
<a-form-item label="批号">
<a-input v-decorator="['batch']" placeholder="请输入批号" />
</a-form-item>
<a-form-item label="使用状态">
<a-select v-decorator="['is_used', { initialValue: -1 }]">
<a-select-option :value="-1">全部</a-select-option>
<a-select-option
v-for="(item, index) in CardStatusEnum.data"
:key="index"
:value="item.value"
>{{ item.name }}</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="打印状态">
<a-select v-decorator="['is_print', { initialValue: -1 }]">
<a-select-option :value="-1">全部</a-select-option>
<a-select-option
v-for="(item, index) in PrintStatusEnum.data"
:key="index"
:value="item.value"
>{{ item.name }}</a-select-option>
</a-select>
</a-form-item>
<a-form-item class="search-btn">
<a-button type="primary" icon="search" html-type="submit">搜索</a-button>
</a-form-item>
</a-form>
</a-row>
<!-- 操作板块 -->
<div class="row-item-tab clearfix">
<a-button class="fl-l" type="primary" icon="plus" v-action:addCards @click="handleCards()">创建兑换卡</a-button>
<div v-if="selectedRowKeys.length" class="button-group">
<a-button-group class="ml-10">
<!-- <a-button-->
<!-- v-action:pdf-->
<!-- @click="handlePDF(selectedRowKeys)"-->
<!-- >生成实体卡</a-button>-->
<!-- <a-button v-action:delete icon="delete" @click="handleDelete(selectedRowKeys)">删除</a-button>-->
<a-button
v-action:status
icon="arrow-up"
@click="handleUpdateStatus(selectedRowKeys, true)"
>已使用</a-button>
</a-button-group>
</div>
</div>
</div>
<s-table
ref="table"
rowKey="card_id"
:loading="isLoading"
:columns="columns"
:data="loadData"
:rowSelection="rowSelection"
:pageSize="18"
:scroll="{ x: 1450 }"
>
<!-- 密钥 -->
<span slot="secret_key" slot-scope="text">
<p class="twoline-hide" style="width: 150px;">{{ text }}</p>
</span>
<!-- 密钥一维码 -->
<span slot="barcode" slot-scope="item">
<img v-if="item.file_path" width="260" :src="publicConfig.BASE_DOMAIN+item.file_path" alt="一维码图片" />
</span>
<!-- 兑换卡使用状态 -->
<span slot="is_used" slot-scope="text">
<a-tag
class="cur-p"
:color="text === 20 ? 'green' : 'red'"
>{{ text === 20 ? '已使用' : '未使用' }}</a-tag>
</span>
<!-- 兑换卡打印状态 -->
<span slot="is_print" slot-scope="text">
<a-tag
class="cur-p"
:color="text === 1 ? 'green' : 'red'"
>{{ text === 1 ? '已打印' : '未打印' }}</a-tag>
</span>
<!-- 使用人 -->
<span slot="user" slot-scope="text, item">
<p>{{ item.user ? item.user.mobile : '18281516816' }}</p>
</span>
<!-- 使用时间 -->
<span slot="used_time" slot-scope="text">
<p>{{ text | dateFormat }}</p>
</span>
<!-- 操作项 -->
<!-- <div class="actions" slot="action" slot-scope="text, item">-->
<!-- <a v-action:delete @click="handleDelete([item.card_id])">删除</a>-->
<!-- </div>-->
</s-table>
<AddCardsForm ref="AddCardsForm" :moneyList="moneyList" @handleSubmit="handleRefresh" />
</a-card>
</template>
<script>
import * as CardApi from '@/api/card'
import { ContentHeader, STable } from '@/components'
import { CardStatusEnum, PrintStatusEnum, CardMoneyEnum } from '@/common/enum/card'
import { AddCardsForm } from './modules'
import { assignment, inArray } from '@/utils/util'
import store from '@/store'
// 表格表头
const columns = [
{
title: '兑换卡ID',
width: '70px',
dataIndex: 'card_id'
},
{
title: '卡号',
width: '150px',
dataIndex: 'card_number'
},
{
title: '卡密',
width: '150px',
dataIndex: 'secret_key',
scopedSlots: { customRender: 'secret_key' }
},
{
title: '金额',
width: '100px',
dataIndex: 'amount'
},
{
title: '批次号',
width: '150px',
dataIndex: 'batch'
},
{
title: '使用状态',
width: '90px',
dataIndex: 'is_used',
scopedSlots: { customRender: 'is_used' }
},
// {
// title: '打印状态',
// width: '90px',
// dataIndex: 'is_print',
// scopedSlots: { customRender: 'is_print' }
// },
{
title: '使用人',
width: '100px',
scopedSlots: { customRender: 'user' }
},
{
title: '使用时间',
width: '130px',
dataIndex: 'used_time',
scopedSlots: { customRender: 'used_time' }
},
{
title: '创建时间',
width: '130px',
dataIndex: 'create_time'
},
{
title: '一维码',
width: '300px',
scopedSlots: { customRender: 'barcode' }
}
// {
// title: '操作',
// dataIndex: 'action',
// width: '100px',
// fixed: 'right',
// scopedSlots: { customRender: 'action' }
// }
]
export default {
name: 'Index',
components: {
ContentHeader,
AddCardsForm,
STable
},
data () {
return {
// 当前表单元素
searchForm: this.$form.createForm(this),
// 查询参数
queryParam: {},
// 正在加载
isLoading: false,
// 表头
columns,
publicConfig: store.getters.publicConfig,
// 选择的元素
selectedRowKeys: [],
moneyList: [],
// 加载数据方法 必须为 Promise 对象
loadData: param => {
return CardApi.list({ ...param, ...this.queryParam })
.then(response => {
return response.data.list
})
}
}
},
beforeCreate () {
// 批量给当前实例赋值
assignment(this, {
inArray,
CardStatusEnum,
PrintStatusEnum,
CardMoneyEnum
})
},
created () {
this.moneyList = CardMoneyEnum.data
},
computed: {
rowSelection () {
return {
selectedRowKeys: this.selectedRowKeys,
onChange: this.onSelectChange
}
}
},
methods: {
/**
* 选中项发生变化时的回调
*/
onSelectChange (selectedRowKeys) {
this.selectedRowKeys = selectedRowKeys
},
// 确认搜索
handleSearch (e) {
e.preventDefault()
this.searchForm.validateFields((error, values) => {
if (!error) {
this.queryParam = { ...this.queryParam, ...values }
this.handleRefresh(true)
}
})
},
// 重置搜索表单
handleReset () {
this.searchForm.resetFields()
},
// 修改商品状态(上下架)
handleUpdateStatus (cardIds, state = true) {
if (!this.$auth('/card/changeState')) {
return false
}
if (!state) {
this.$message.error('不能改为未使用状态', 1.5)
return false
}
this.isLoading = true
CardApi.changeState({ cardIds, state })
.then(result => {
// 显示成功
this.$message.success(result.message, 1.5)
this.handleRefresh()
})
.finally(result => { this.isLoading = false })
},
// 生成PDF
handlePDF (cardIds) {
if (!this.$auth('/card/pdf')) {
return false
}
this.isLoading = true
CardApi.pdf({ cardIds })
.then(result => {
this.downLoadFile(result.data.url)
// 显示成功
this.$message.success(result.message, 1.5)
this.handleRefresh()
})
.finally(result => { this.isLoading = false })
},
// 商户对接参数
handleCards () {
this.$refs.AddCardsForm.handle()
},
/**
* 删除记录
*/
handleDelete (cardIds) {
const app = this
const modal = this.$confirm({
title: '您确定要删除该记录吗?',
content: '删除后不可恢复',
onOk () {
return CardApi.deleted(cardIds)
.then(result => {
app.$message.success(result.message, 1.5)
app.handleRefresh()
})
.finally(result => {
modal.destroy()
})
}
})
},
/**
* 刷新列表
* @param Boolean bool 强制刷新到第一页
*/
handleRefresh (bool = false) {
this.selectedRowKeys = []
this.$refs.table.refresh(bool)
},
downLoadFile (url) {
const fileName = url.slice(url.lastIndexOf('/') + 1) // 提取文件名
fetch(url)
.then((res) => {
if (!res.ok) {
throw new Error('Network response was not ok')
}
return res.blob()
})
.then((blob) => {
const link = document.createElement('a')
link.href = URL.createObjectURL(blob)
link.download = fileName // 设置下载文件的文件名
document.body.appendChild(link)
link.click() // 触发下载
window.URL.revokeObjectURL(link.href) // 释放URL对象
document.body.removeChild(link) // 清理DOM
})
.catch((error) => {
console.error('下载失败:', error) // 错误处理
})
}
}
}
</script>
<style lang="less" scoped>
.ant-card-body {
padding: 22px 29px 25px;
}
// 筛选tab
.tab-list {
margin-right: 20px;
}
</style>
+227
View File
@@ -0,0 +1,227 @@
<template>
<a-card :bordered="false">
<div class="card-title">{{ $route.meta.title }}</div>
<div class="table-operator">
<!-- 搜索板块 -->
<a-row class="row-item-search">
<a-form class="search-form" :form="searchForm" layout="inline" @submit="handleSearch">
<a-form-item label="批号">
<a-input v-decorator="['batch']" placeholder="请输入批号" />
</a-form-item>
<a-form-item label="下载状态">
<a-select v-decorator="['is_download', { initialValue: -1 }]">
<a-select-option :value="-1">全部</a-select-option>
<a-select-option
v-for="(item, index) in DownloadStatusEnum.data"
:key="index"
:value="item.value"
>{{ item.name }}</a-select-option>
</a-select>
</a-form-item>
<a-form-item class="search-btn">
<a-button type="primary" icon="search" html-type="submit">搜索</a-button>
</a-form-item>
</a-form>
</a-row>
<!-- 操作板块 -->
<div class="row-item-tab clearfix">
<div v-if="selectedRowKeys.length" class="button-group">
<a-button-group class="ml-10">
<a-button
v-action:status
icon="arrow-down "
@click="handleUpdateStatus(selectedRowKeys, true)"
>已下载</a-button>
</a-button-group>
</div>
</div>
</div>
<s-table
ref="table"
rowKey="print_id"
:loading="isLoading"
:columns="columns"
:data="loadData"
:rowSelection="rowSelection"
:pageSize="18"
:scroll="{ x: 1450 }"
>
<!-- 兑换卡下载状态 -->
<span slot="is_download" slot-scope="text">
<a-tag
class="cur-p"
:color="text === 1 ? 'green' : 'red'"
>{{ text === 1 ? '已下载' : '未下载' }}</a-tag>
</span>
<!-- 操作 -->
<span class="actions" slot="action" slot-scope="text, item">
<a
v-if="item.is_download === 0"
v-action:download
:href="item.download_url"
target="_blank"
>下载</a>
</span>
</s-table>
</a-card>
</template>
<script>
import * as CardPrintApi from '@/api/card/print'
import { ContentHeader, STable } from '@/components'
import { DownloadStatusEnum } from '@/common/enum/card'
import { AddCardsForm } from './modules'
import { assignment, inArray } from '@/utils/util'
// 表格表头
const columns = [
{
title: '打印记录ID',
width: '70px',
dataIndex: 'print_id'
},
{
title: '批次号',
width: '100px',
dataIndex: 'batch'
},
{
title: '金额',
width: '100px',
dataIndex: 'amount'
},
{
title: '打印数量',
width: '100px',
dataIndex: 'num'
},
{
title: '下载状态',
width: '90px',
dataIndex: 'is_download',
scopedSlots: { customRender: 'is_download' }
},
{
title: '下载时间',
width: '130px',
dataIndex: 'download_time'
},
{
title: '创建时间',
width: '130px',
dataIndex: 'create_time'
},
{
title: '操作',
dataIndex: 'action',
width: '100px',
fixed: 'right',
scopedSlots: { customRender: 'action' }
}
]
export default {
name: 'Index',
components: {
ContentHeader,
AddCardsForm,
STable
},
data () {
return {
// 当前表单元素
searchForm: this.$form.createForm(this),
// 查询参数
queryParam: {},
// 正在加载
isLoading: false,
// 表头
columns,
// 选择的元素
selectedRowKeys: [],
// 加载数据方法 必须为 Promise 对象
loadData: param => {
return CardPrintApi.list({ ...param, ...this.queryParam })
.then(response => {
return response.data.list
})
}
}
},
beforeCreate () {
// 批量给当前实例赋值
assignment(this, {
inArray,
DownloadStatusEnum
})
},
created () {
},
computed: {
rowSelection () {
return {
selectedRowKeys: this.selectedRowKeys,
onChange: this.onSelectChange
}
}
},
methods: {
/**
* 选中项发生变化时的回调
*/
onSelectChange (selectedRowKeys) {
this.selectedRowKeys = selectedRowKeys
},
// 确认搜索
handleSearch (e) {
e.preventDefault()
this.searchForm.validateFields((error, values) => {
if (!error) {
this.queryParam = { ...this.queryParam, ...values }
this.handleRefresh(true)
}
})
},
// 重置搜索表单
handleReset () {
this.searchForm.resetFields()
},
// 修改下载状态
handleUpdateStatus (printIds, state = true) {
if (!state) {
this.$message.error('不能改为未下载状态', 1.5)
return false
}
this.isLoading = true
CardPrintApi.changeState({ printIds, state })
.then(result => {
// 显示成功
this.$message.success(result.message, 1.5)
this.handleRefresh()
})
.finally(result => { this.isLoading = false })
},
/**
* 刷新列表
* @param Boolean bool 强制刷新到第一页
*/
handleRefresh (bool = false) {
this.selectedRowKeys = []
this.$refs.table.refresh(bool)
}
}
}
</script>
<style lang="less" scoped>
.ant-card-body {
padding: 22px 29px 25px;
}
// 筛选tab
.tab-list {
margin-right: 20px;
}
</style>
@@ -0,0 +1,127 @@
<template>
<a-modal
class="noborder"
:title="title"
:width="520"
:visible="visible"
:confirmLoading="confirmLoading"
:maskClosable="false"
@ok="handleSubmit"
@cancel="handleCancel"
>
<a-spin :spinning="confirmLoading">
<a-form :form="form">
<template>
<a-form-item label="兑换卡数量" :labelCol="labelCol" :wrapperCol="wrapperCol">
18
</a-form-item>
<a-form-item label="兑换卡金额" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-select v-decorator="['amount', { initialValue: -1 }]">
<a-select-option :value="-1">全部</a-select-option>
<a-select-option
v-for="(item, index) in moneyList"
:key="index"
:value="item.value"
>{{ item.name }}</a-select-option>
</a-select>
</a-form-item>
</template>
</a-form>
</a-spin>
</a-modal>
</template>
<script>
// import pick from 'lodash.pick'
import * as Api from '@/api/card'
export default {
components: {
},
props: {
// 金额列表
moneyList: {
type: Array,
required: true
}
},
data () {
return {
// 对话框标题
title: '创建实体兑换卡',
// 标签布局属性
labelCol: { span: 7 },
// 输入框布局属性
wrapperCol: { span: 13 },
// modal(对话框)是否可见
visible: false,
// modal(对话框)确定按钮 loading
confirmLoading: false,
// 当前表单元素
form: this.$form.createForm(this),
// 当前记录
record: {}
}
},
methods: {
// 显示对话框
handle () {
// 显示窗口
this.visible = true
},
/**
* 确认按钮
*/
handleSubmit (e) {
e.preventDefault()
// 表单验证
const { form: { validateFields } } = this
validateFields((errors, values) => {
// 提交到后端api
!errors && this.onFormSubmit(values)
})
},
/**
* 关闭对话框事件
*/
handleCancel () {
this.visible = false
this.form.resetFields()
},
/**
* 提交到后端api
*/
onFormSubmit (values) {
this.confirmLoading = true
Api.addCards({ form: values })
.then((result) => {
// 显示成功
this.$message.success(result.message, 1.5)
// 关闭对话框事件
this.handleCancel()
// 通知父端组件提交完成了
this.$emit('handleSubmit', values)
})
.finally(() => {
this.confirmLoading = false
})
}
}
}
</script>
<style lang="less" scoped>
.ant-modal-body {
padding-top: 0 !important;
}
.ant-form-item {
margin-bottom: 15px;
}
.ant-tabs-nav .ant-tabs-tab {
padding: 10px 16px;
}
</style>
@@ -0,0 +1,5 @@
import AddCardsForm from './AddCardsForm'
export {
AddCardsForm
}
+29
View File
@@ -0,0 +1,29 @@
.tabs-content {
.tab-pane {
animation: fadenum 0.6s ease;
}
}
/deep/.ant-form-item-control {
padding-left: 10px;
.ant-form-item-control {
padding-left: 0;
}
}
.ant-form-item {
margin-bottom: 18px;
.ant-form-item {
margin-bottom: 0;
}
}
.input-number-group {
margin: 3px 0;
}
.company-textarea{
height: 80px;
}
+153
View File
@@ -0,0 +1,153 @@
<template>
<a-card :bordered="false">
<div class="card-title">{{ $route.meta.title }}</div>
<div class="table-operator">
<!-- 搜索板块 -->
<a-row class="row-item-search">
<a-form class="search-form" :form="searchForm" layout="inline" @submit="handleSearch">
<a-form-item label="下单时间">
<a-range-picker format="YYYY-MM-DD" v-decorator="['betweenTime']" />
</a-form-item>
<a-form-item label="请输入商户名">
<a-input v-decorator="['name']" placeholder="请输入商户名" />
</a-form-item>
<a-form-item class="search-btn">
<a-button type="primary" icon="search" html-type="submit">搜索</a-button>
</a-form-item>
</a-form>
</a-row>
<a-row class="row-item-search">
<a-form class="stat-form">
<a-form-item class="stat-item" label="总金额">{{ totalMoney }}</a-form-item>
</a-form>
</a-row>
</div>
<s-table
ref="table"
rowKey="merchant_id"
:loading="isLoading"
:columns="columns"
:data="loadData"
:pageSize="15"
:scroll="{ x: 1450 }"
>
</s-table>
</a-card>
</template>
<script>
import * as CensusApi from '@/api/census'
import { ContentHeader, STable } from '@/components'
import { assignment, inArray } from '@/utils/util'
// 表格表头
const columns = [
{
title: '商户ID',
dataIndex: 'merchant_id'
},
{
title: '商户名称',
dataIndex: 'name'
},
{
title: '今日分油订单金额',
dataIndex: 'today'
},
{
title: '分油总金额',
dataIndex: 'amount'
}
]
export default {
name: 'Index',
components: {
ContentHeader,
STable
},
data () {
return {
totalMoney: 0,
// 当前表单元素
searchForm: this.$form.createForm(this),
// 查询参数
queryParam: {},
// 正在加载
isLoading: false,
// 表头
columns,
// 加载数据方法 必须为 Promise 对象
loadData: param => {
return CensusApi.list({ ...param, ...this.queryParam })
.then(response => {
return response.data.list
})
}
}
},
beforeCreate () {
// 批量给当前实例赋值
assignment(this, {
inArray
})
},
created () {
this.stat()
},
computed: {
},
methods: {
// 获取统计数据
stat () {
this.isLoading = true
const { queryParam } = this
return CensusApi.stat({ ...queryParam })
.then(response => {
this.totalMoney = response.data.totalMoney
})
.finally(() => {
this.isLoading = false
})
},
// 确认搜索
handleSearch (e) {
e.preventDefault()
this.searchForm.validateFields((error, values) => {
if (!error) {
this.queryParam = { ...this.queryParam, ...values }
this.handleRefresh(true)
this.stat()
}
})
},
// 重置搜索表单
handleReset () {
this.searchForm.resetFields()
},
/**
* 刷新列表
* @param Boolean bool 强制刷新到第一页
*/
handleRefresh (bool = false) {
this.$refs.table.refresh(bool)
}
}
}
</script>
<style lang="less" scoped>
.ant-card-body {
padding: 22px 29px 25px;
}
// 筛选tab
.tab-list {
margin-right: 20px;
}
.stat-form{display: flex;}
.stat-item{display: flex;width: 130px;font-size: 20px;}
</style>
+25
View File
@@ -0,0 +1,25 @@
.tabs-content {
.tab-pane {
animation: fadenum 0.6s ease;
}
}
/deep/.ant-form-item-control {
padding-left: 10px;
.ant-form-item-control {
padding-left: 0;
}
}
.ant-form-item {
margin-bottom: 18px;
.ant-form-item {
margin-bottom: 0;
}
}
.input-number-group {
margin: 3px 0;
}
+210
View File
@@ -0,0 +1,210 @@
<template>
<a-card :bordered="false">
<div class="card-title">{{ $route.meta.title }}</div>
<div class="table-operator">
<!-- 搜索板块 -->
<a-row class="row-item-search">
<a-form class="search-form" :form="searchForm" layout="inline" @submit="handleSearch">
<a-form-item label="关键字搜索">
<a-input v-decorator="['search']" placeholder="结账ID" />
</a-form-item>
<a-form-item class="search-btn">
<a-button type="primary" icon="search" html-type="submit">搜索</a-button>
</a-form-item>
</a-form>
</a-row>
<!-- 操作板块 -->
<div class="row-item-tab clearfix">
<a-button
v-if="$auth('/checkout/checkout')"
class="fl-l"
type="primary"
icon="plus"
@click="handleCheckout"
>结账</a-button>
<div v-if="selectedRowKeys.length" class="button-group">
<a-button-group class="ml-10">
<a-button v-action:delete icon="delete" @click="handleDelete(selectedRowKeys)">删除</a-button>
</a-button-group>
</div>
</div>
</div>
<s-table
ref="table"
rowKey="checkout_id"
:loading="isLoading"
:columns="columns"
:data="loadData"
:rowSelection="rowSelection"
:pageSize="15"
:scroll="{ x: 1450 }"
>
<!-- 操作项 -->
<!-- <div class="actions" slot="action" slot-scope="text, item">
<a v-action:delete @click="handleDelete([item.checkout_id])">删除</a>
</div> -->
</s-table>
<CheckoutForm ref="CheckoutForm" @handleSubmit="handleRefresh" />
</a-card>
</template>
<script>
import * as CheckoutApi from '@/api/checkout'
import { ContentHeader, STable } from '@/components'
import { CheckoutForm } from './modules'
import { assignment, inArray } from '@/utils/util'
// 表格表头
const columns = [
{
title: '结账ID',
width: '70px',
dataIndex: 'checkout_id'
},
{
title: '结账金额',
width: '150px',
dataIndex: 'amount'
},
{
title: '结账订单数',
width: '150px',
dataIndex: 'num'
},
{
title: '结账开始时间',
dataIndex: 'start_time'
},
{
title: '结账结束时间',
dataIndex: 'end_time'
},
{
title: '创建时间',
dataIndex: 'create_time'
},
// {
// title: '操作',
// dataIndex: 'action',
// width: '200px',
// fixed: 'right',
// scopedSlots: { customRender: 'action' }
// }
]
export default {
name: 'Index',
components: {
ContentHeader,
CheckoutForm,
STable
},
data () {
return {
// 当前表单元素
searchForm: this.$form.createForm(this),
// 查询参数
queryParam: {},
// 正在加载
isLoading: false,
// 表头
columns,
// 选择的元素
selectedRowKeys: [],
// 加载数据方法 必须为 Promise 对象
loadData: param => {
return CheckoutApi.list({ ...param, ...this.queryParam })
.then(response => {
return response.data.list
})
}
}
},
beforeCreate () {
// 批量给当前实例赋值
assignment(this, {
inArray
})
},
created () {
},
computed: {
rowSelection () {
return {
selectedRowKeys: this.selectedRowKeys,
onChange: this.onSelectChange
}
}
},
methods: {
/**
* 选中项发生变化时的回调
*/
onSelectChange (selectedRowKeys) {
this.selectedRowKeys = selectedRowKeys
},
// 确认搜索
handleSearch (e) {
e.preventDefault()
this.searchForm.validateFields((error, values) => {
if (!error) {
this.queryParam = { ...this.queryParam, ...values }
this.handleRefresh(true)
}
})
},
// 重置搜索表单
handleReset () {
this.searchForm.resetFields()
},
// 结账
handleCheckout (item) {
this.$refs.CheckoutForm.handle(item)
},
/**
* 删除记录
*/
handleDelete (checkoutIds) {
const app = this
const modal = this.$confirm({
title: '您确定要删除该记录吗?',
content: '删除后不可恢复',
onOk () {
return CheckoutApi.deleted(checkoutIds)
.then(result => {
app.$message.success(result.message, 1.5)
app.handleRefresh()
})
.finally(result => {
modal.destroy()
})
}
})
},
/**
* 刷新列表
* @param Boolean bool 强制刷新到第一页
*/
handleRefresh (bool = false) {
this.selectedRowKeys = []
this.$refs.table.refresh(bool)
}
}
}
</script>
<style lang="less" scoped>
.ant-card-body {
padding: 22px 29px 25px;
}
// 筛选tab
.tab-list {
margin-right: 20px;
}
</style>
@@ -0,0 +1,99 @@
<template>
<a-modal
:title="title"
:width="560"
:visible="visible"
:isLoading="isLoading"
:confirmLoading="isLoading"
:maskClosable="false"
@ok="handleSubmit"
@cancel="handleCancel"
>
<a-spin :spinning="isLoading">
<a-form :form="form">
<a-form-item label="结账时间" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-date-picker show-time format="YYYY-MM-DD HH:mm:ss" v-decorator="['end_time', { rules: [{ required: true }] }]" />
</a-form-item>
</a-form>
</a-spin>
</a-modal>
</template>
<script>
import _ from 'lodash'
import * as Api from '@/api/checkout'
export default {
data () {
return {
// 对话框标题
title: '结账',
// 标签布局属性
labelCol: { span: 7 },
// 输入框布局属性
wrapperCol: { span: 13 },
// modal(对话框)是否可见
visible: false,
// modal(对话框)确定按钮 loading
isLoading: false,
// 当前表单元素
form: this.$form.createForm(this),
// 当前记录
record: {}
}
},
created () {
},
methods: {
/**
* 显示对话框
*/
handle (record) {
// 显示窗口
this.visible = true
// 当前记录
this.record = record
},
// 确认按钮
handleSubmit (e) {
e.preventDefault()
// 表单验证
const { form: { validateFields } } = this
validateFields((errors, values) => {
// 提交到后端api
!errors && this.onFormSubmit(values)
})
},
/**
* 关闭对话框事件
*/
handleCancel () {
this.visible = false
this.form.resetFields()
},
/**
* 提交到后端api
*/
onFormSubmit (values) {
this.isLoading = true
Api.checkout({ form: values })
.then((result) => {
// 显示成功
this.$message.success(result.message, 1.5)
// 关闭对话框事件
this.handleCancel()
// 通知父端组件提交完成了
this.$emit('handleSubmit', values)
})
.finally(() => {
this.isLoading = false
})
}
}
}
</script>
@@ -0,0 +1,5 @@
import CheckoutForm from './CheckoutForm'
export {
CheckoutForm
}
+180
View File
@@ -0,0 +1,180 @@
<template>
<a-card :bordered="false">
<div class="card-title">{{ $route.meta.title }}</div>
<a-spin :spinning="isLoading">
<a-form :form="form" @submit="handleSubmit">
<a-form-item label="默认登录/注册方式" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-radio-group v-decorator="['registerMethod', { rules: [{ required: true }] }]">
<a-radio :value="10">手机号 + 短信验证码</a-radio>
</a-radio-group>
<div class="form-item-help">
<p class="extra">
<small>发送短信服务需要先配置</small>
<router-link target="_blank" :to="{ path: '/setting/sms' }">短信通知设置</router-link>
</p>
<p class="extra">使用手机号作为主账户可以实现多种客户端的账户统一,例如H5和微信小程序</p>
</div>
</a-form-item>
<a-divider orientation="left">微信小程序授权登录</a-divider>
<a-form-item label="一键授权登录/注册" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-radio-group v-decorator="['isOauthMpweixin', { rules: [{ required: true }] }]">
<a-radio :value="1">
<span>开启</span>
<a-tag class="ml-5" color="green">推荐</a-tag>
</a-radio>
<a-radio :value="0">关闭</a-radio>
</a-radio-group>
<div class="form-item-help">
<small>开启后在微信小程序端一键获取用户授权并登录和注册</small>
</div>
</a-form-item>
<a-form-item
v-show="form.getFieldValue('isOauthMpweixin') == 1"
label="注册时绑定手机号"
:labelCol="labelCol"
:wrapperCol="wrapperCol"
>
<a-radio-group v-decorator="['isForceBindMpweixin', { rules: [{ required: true }] }]">
<a-radio :value="1">
<span>强制绑定</span>
<a-tag class="ml-5" color="green">推荐</a-tag>
</a-radio>
<a-radio :value="0">不绑定</a-radio>
</a-radio-group>
<div class="form-item-help">
<p class="extra">开启后在微信小程序端一键授权注册时强制绑定手机号,仅首次注册时弹出</p>
<p
v-show="form.getFieldValue('isForceBindMpweixin') == 0"
class="extra c-red"
>如果不强制绑定手机号,会造成多端情况下同一个用户注册多个账户,强烈推荐绑定手机号</p>
</div>
</a-form-item>
<a-form-item
v-show="form.getFieldValue('isOauthMpweixin') == 1 && form.getFieldValue('isForceBindMpweixin') == 0"
label="手动绑定手机号"
:labelCol="labelCol"
:wrapperCol="wrapperCol"
>
<a-radio-group v-decorator="['isManualBind', { rules: [{ required: true }] }]">
<a-radio :value="1">显示</a-radio>
<a-radio :value="0">不显示</a-radio>
</a-radio-group>
<div class="form-item-help">
<small>用户在个人中心页可以手动操作绑定手机号</small>
</div>
</a-form-item>
<a-form-item :wrapper-col="{ span: wrapperCol.span, offset: labelCol.span }">
<a-button type="primary" html-type="submit">提交</a-button>
</a-form-item>
</a-form>
</a-spin>
</a-card>
</template>
<script>
import pick from 'lodash.pick'
import { isEmpty } from '@/utils/util'
import * as Api from '@/api/setting/store'
import SettingSmsSceneEnum from '@/common/enum/setting/sms/Scene'
export default {
data () {
return {
SettingSmsSceneEnum,
// 当前设置项的key
key: 'register',
// 标签布局属性
labelCol: { span: 4 },
// 输入框布局属性
wrapperCol: { span: 10 },
// loading状态
isLoading: false,
// 当前表单元素
form: this.$form.createForm(this),
// 当前记录详情
record: {}
}
},
// 初始化数据
created () {
// 获取当前详情记录
this.getDetail()
},
methods: {
// 获取当前详情记录
getDetail () {
this.isLoading = true
Api.detail(this.key)
.then(result => {
// 当前记录
this.record = result.data.values
// 设置默认值
this.setFieldsValue()
})
.finally(result => {
this.isLoading = false
})
},
/**
* 设置默认值
*/
setFieldsValue () {
const { record, $nextTick, form } = this
!isEmpty(form.getFieldsValue()) && $nextTick(() => {
form.setFieldsValue(pick(record, ['registerMethod', 'isManualBind', 'isOauthMpweixin', 'isForceBindMpweixin']))
})
},
/**
* 确认按钮
*/
handleSubmit (e) {
e.preventDefault()
// 表单验证
const { form: { validateFields } } = this
validateFields((errors, values) => {
// 提交到后端api
!errors && this.onFormSubmit(values)
})
},
/**
* 提交到后端api
*/
onFormSubmit (values) {
this.isLoading = true
Api.update(this.key, { form: values })
.then((result) => {
// 显示提示信息
this.$message.success(result.message, 1.5)
})
.finally((result) => {
this.isLoading = false
})
}
}
}
</script>
<style lang="less" scoped>
.ant-form-item {
margin-bottom: 15px;
}
/deep/.ant-form-item-control {
padding-left: 10px;
.ant-form-item-control {
padding-left: 0;
}
}
.ant-divider {
margin-top: 50px !important;
}
</style>
+140
View File
@@ -0,0 +1,140 @@
<template>
<a-card :bordered="false">
<div class="card-title">{{ $route.meta.title }}</div>
<a-spin :spinning="isLoading">
<a-form :form="form" @submit="handleSubmit">
<a-form-item class="mt-30" label="是否开启访问" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-radio-group v-decorator="['enabled', { rules: [{ required: true }] }]">
<a-radio :value="1">开启</a-radio>
<a-radio :value="0">关闭</a-radio>
</a-radio-group>
<div class="form-item-help">
<p class="extra">
<span>注:如关闭,用户则无法通过H5端访问商城</span>
</p>
</div>
</a-form-item>
<a-form-item label="H5站点地址" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-input
v-decorator="['baseUrl', { rules: [{ required: true, message: '请输入H5站点地址' }] }]"
/>
<div class="form-item-help">
<p class="extra">
<span>请填写H5端实际的访问地址,以</span>
<a-tag class="mlr-5">https://</a-tag>开头; 斜杠
<a-tag class="mlr-5">/</a-tag>
<span>结尾</span>
</p>
<p class="extra">
<span>例如:https://www.aaa.com/</span>
</p>
</div>
</a-form-item>
<a-form-item :wrapper-col="{ span: wrapperCol.span, offset: labelCol.span }">
<a-button type="primary" html-type="submit">提交</a-button>
</a-form-item>
</a-form>
</a-spin>
</a-card>
</template>
<script>
import pick from 'lodash.pick'
import { isEmpty } from '@/utils/util'
import * as Api from '@/api/client/h5/setting'
export default {
data () {
return {
// 标签布局属性
labelCol: { span: 4 },
// 输入框布局属性
wrapperCol: { span: 10 },
// loading状态
isLoading: false,
// 当前表单元素
form: this.$form.createForm(this),
// 当前设置项key
key: 'basic',
// 当前记录详情
record: {}
}
},
// 初始化数据
created () {
// 获取当前详情记录
this.getDetail()
},
methods: {
// 获取当前详情记录
getDetail () {
this.isLoading = true
Api.detail(this.key)
.then(result => {
// 当前记录
this.record = result.data.detail
// 设置默认值
this.setFieldsValue()
})
.finally(result => {
this.isLoading = false
})
},
/**
* 设置默认值
*/
setFieldsValue () {
const { record, $nextTick, form } = this
!isEmpty(form.getFieldsValue()) && $nextTick(() => {
form.setFieldsValue(pick(record, ['enabled', 'baseUrl']))
})
},
/**
* 确认按钮
*/
handleSubmit (e) {
e.preventDefault()
// 表单验证
const { form: { validateFields } } = this
validateFields((errors, values) => {
// 提交到后端api
!errors && this.onFormSubmit(values)
})
},
/**
* 提交到后端api
*/
onFormSubmit (values) {
this.isLoading = true
Api.update(this.key, { form: values })
.then((result) => {
// 显示提示信息
this.$message.success(result.message, 1.5)
})
.finally((result) => {
this.isLoading = false
})
}
}
}
</script>
<style lang="less" scoped>
.ant-form-item {
margin-bottom: 24px;
}
/deep/.ant-form-item-control {
padding-left: 10px;
.ant-form-item-control {
padding-left: 0;
}
}
.ant-divider {
margin-top: 60px !important;
}
</style>
@@ -0,0 +1,149 @@
<template>
<a-card :bordered="false">
<div class="card-title">{{ $route.meta.title }}</div>
<a-spin :spinning="isLoading">
<a-form :form="form" @submit="handleSubmit">
<a-form-item class="mt-30" label="小程序 AppID" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-input
v-decorator="['app_id', { rules: [{ required: true, message: '请输入小程序AppID' }] }]"
/>
</a-form-item>
<a-form-item label="小程序 AppSecret" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-input
type="password"
v-decorator="['app_secret', { rules: [{ required: true, message: '请输入小程序AppSecret' }] }]"
/>
</a-form-item>
<a-divider orientation="left">微信小程序支付</a-divider>
<a-form-item label="微信商户号 MCHID" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-input
v-decorator="['mchid', { rules: [{ required: true, message: '请输入微信商户号mchid' }] }]"
/>
</a-form-item>
<a-form-item label="微信支付密钥 APIKEY" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-input
type="password"
v-decorator="['apikey', { rules: [{ required: true, message: '请输入微信支付密钥' }] }]"
/>
</a-form-item>
<a-form-item class="mt-40" label="证书文件cert" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-textarea :autoSize="{ minRows: 4, maxRows: 6 }" v-decorator="['cert_pem']" />
<div class="form-item-help">
<small>使用文本编辑器打开apiclient_cert.pem文件,将文件的全部内容复制进来</small>
</div>
</a-form-item>
<a-form-item label="证书文件key" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-textarea :autoSize="{ minRows: 4, maxRows: 6 }" v-decorator="['key_pem']" />
<div class="form-item-help">
<small>使用文本编辑器打开apiclient_key.pem文件,将文件的全部内容复制进来</small>
</div>
</a-form-item>
<a-form-item :wrapper-col="{ span: wrapperCol.span, offset: labelCol.span }">
<a-button type="primary" html-type="submit">提交</a-button>
</a-form-item>
</a-form>
</a-spin>
</a-card>
</template>
<script>
import pick from 'lodash.pick'
import { isEmpty } from '@/utils/util'
import * as Api from '@/api/client/wxapp/setting'
export default {
data () {
return {
// 标签布局属性
labelCol: { span: 4 },
// 输入框布局属性
wrapperCol: { span: 10 },
// loading状态
isLoading: false,
// 当前表单元素
form: this.$form.createForm(this),
// 当前设置项key
key: 'basic',
// 当前记录详情
record: {}
}
},
// 初始化数据
created () {
// 获取当前详情记录
this.getDetail()
},
methods: {
// 获取当前详情记录
getDetail () {
this.isLoading = true
Api.detail(this.key)
.then(result => {
// 当前记录
this.record = result.data.detail
// 设置默认值
this.setFieldsValue()
})
.finally(result => {
this.isLoading = false
})
},
/**
* 设置默认值
*/
setFieldsValue () {
const { record, $nextTick, form } = this
!isEmpty(form.getFieldsValue()) && $nextTick(() => {
form.setFieldsValue(pick(record, ['app_id', 'app_secret', 'mchid', 'apikey', 'cert_pem', 'key_pem']))
})
},
/**
* 确认按钮
*/
handleSubmit (e) {
e.preventDefault()
// 表单验证
const { form: { validateFields } } = this
validateFields((errors, values) => {
// 提交到后端api
!errors && this.onFormSubmit(values)
})
},
/**
* 提交到后端api
*/
onFormSubmit (values) {
this.isLoading = true
Api.update(this.key, { form: values })
.then((result) => {
// 显示提示信息
this.$message.success(result.message, 1.5)
})
.finally((result) => {
this.isLoading = false
})
}
}
}
</script>
<style lang="less" scoped>
.ant-form-item {
margin-bottom: 15px;
}
/deep/.ant-form-item-control {
padding-left: 10px;
.ant-form-item-control {
padding-left: 0;
}
}
.ant-divider {
margin-top: 60px !important;
}
</style>
+225
View File
@@ -0,0 +1,225 @@
<template>
<a-card :bordered="false">
<div class="card-title">{{ $route.meta.title }}</div>
<a-spin :spinning="isLoading">
<a-form :form="form" @submit="handleSubmit" :selfUpdate="true">
<a-tabs :activeKey="tabKey" :tabBarStyle="{marginBottom: '30px'}" @change="handleTabs">
<a-tab-pane :key="0" tab="基本信息"></a-tab-pane>
</a-tabs>
<div class="tabs-content">
<!-- 基本信息 -->
<div class="tab-pane" v-show="tabKey == 0">
<a-form-item label="店铺" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-select
style="width: 300px"
v-decorator="['shop_id', {rules: [{required: true, message: '请选择店铺'}]}]"
placeholder="请选择店铺"
>
<a-select-option
v-for="(item, index) in formData.shopList"
:key="index"
:value="item.shop_id"
>{{ item.shop_name }}{{ item.contact_name?'('+item.contact_name+')':'' }}</a-select-option>
</a-select>
<div class="form-item-help">
<router-link
target="_blank"
:to="{ path: '/shop/create' }"
>新增店铺</router-link>
<a href="javascript:;" @click="onReloadShopList">刷新</a>
</div>
</a-form-item>
<a-form-item label="微信链接" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-input
placeholder="请输入微信链接"
v-decorator="['link']"
@blur="getOrderNo"
/>
</a-form-item>
<a-form-item label="支付宝链接" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-input placeholder="请输入支付宝链接" v-decorator="['alipay']"/>
</a-form-item>
<a-form-item label="产码账号" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-input
placeholder="请输入产码账号"
v-decorator="['code_account', {rules: [{required: true, min: 2, message: ''}]}]"
/>
</a-form-item>
<a-form-item label="订单号" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-input
placeholder="请输入订单号"
v-decorator="['order_no', {rules: [{required: true, min: 2, message: '请输入至少2个字符'}]}]"
/>
</a-form-item>
<a-form-item label="通道编码" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-radio-group
v-decorator="['passageway', {initialValue: 'WX', rules: [{ required: true }]}]"
>
<a-radio
v-for="(item, index) in PassagewayEnum.data"
:key="index"
:value="item.value">{{ item.name }}</a-radio>
</a-radio-group>
</a-form-item>
<a-form-item label="启停状态" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-radio-group
v-decorator="['state', {initialValue: 1, rules: [{ required: true }]}]"
>
<a-radio
v-for="(item, index) in StateEnum.data"
:key="index"
:value="item.value">{{ item.name }}</a-radio>
</a-radio-group>
</a-form-item>
<a-form-item label="金额" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-input-number
:min="0"
v-decorator="['money', {initialValue: 0, rules:[{ required: true }]}]"
/>
</a-form-item>
<a-form-item label="有限期限(秒) " :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-input-number
:min="0"
v-decorator="['expiration_time', {initialValue: formData.expiration_time, rules:[{ required: true }]}]"
/>
</a-form-item>
</div>
</div>
<a-form-item class="mt-20" :wrapper-col="{ span: wrapperCol.span, offset: labelCol.span }">
<a-button type="primary" html-type="submit" :loading="isBtnLoading">提交</a-button>
</a-form-item>
</a-form>
</a-spin>
</a-card>
</template>
<script>
import * as CodeApi from '@/api/code'
import CodeModel from '@/common/model/code/Index'
import {
PassagewayEnum,
StateEnum
} from '@/common/enum/code'
import { assignment } from '@/utils/util'
export default {
components: {
},
data () {
return {
// 默认的标签索引
tabKey: 0,
// 标签布局属性
labelCol: { span: 3 },
// 输入框布局属性
wrapperCol: { span: 10 },
// loading状态
isLoading: false,
isBtnLoading: false,
// 当前表单元素
form: this.$form.createForm(this),
// 表单数据
formData: CodeModel.formData
}
},
beforeCreate () {
// 批量给当前实例赋值
assignment(this, {
PassagewayEnum,
StateEnum
})
},
// 初始化数据
created () {
this.isLoading = true
// 获取form所需的数据
CodeModel.getFromData()
.then(() => {
this.isLoading = false
})
},
methods: {
getOrderNo (event) {
const url = event.target.value
let orderId = url.match(/orderId=([^&]+)/)
let custNo = url.match(/custNo=([^&]+)/)
orderId = orderId !== null ? orderId[1] : ''
custNo = custNo !== null ? custNo[1] : ''
this.form.setFieldsValue({ 'code_account': custNo, 'order_no': orderId })
},
// 手动强制更新页面
onForceUpdate (bool = false) {
this.$forceUpdate()
// bool为true时再执行一次 $forceUpdate, 特殊情况下需执行两次,原因如下:
// 第一次执行 $forceUpdate时, 新元素绑定v-decorator无法获取到form.getFieldValue
bool && setTimeout(() => {
this.$forceUpdate()
}, 10)
},
// 切换tab选项卡
handleTabs (key) {
this.tabKey = key
},
// 刷新店铺列表
onReloadShopList () {
this.isLoading = true
CodeModel.getShopList().then(() => {
this.isLoading = false
})
},
/**
* 确认按钮
*/
handleSubmit (e) {
e.preventDefault()
// 表单验证
const { form: { validateFields } } = this
validateFields((errors, values) => {
// 定位到错误的tab选项卡
if (errors) {
this.onTargetTabError(errors)
return false
}
// 提交到后端api
this.onFormSubmit(values)
return true
})
},
/**
* 提交到后端api
*/
onFormSubmit (values) {
this.isLoading = true
this.isBtnLoading = true
CodeApi.add({ form: values })
.then(result => {
// 显示提示信息
this.$message.success(result.message, 1.5)
// 跳转到列表页
setTimeout(() => {
this.$router.push('./index')
}, 1500)
})
.catch(() => {
this.isBtnLoading = false
})
.finally(() => {
this.isLoading = false
})
}
}
}
</script>
<style lang="less" scoped>
@import './style.less';
</style>
+384
View File
@@ -0,0 +1,384 @@
<template>
<a-card :bordered="false">
<div class="card-title">{{ $route.meta.title }}</div>
<div class="table-operator">
<!-- 搜索板块 -->
<a-row class="row-item-search">
<a-form class="search-form" :form="searchForm" layout="inline" @submit="handleSearch">
<a-form-item label="请选择状态">
<a-select v-decorator="['status', { initialValue: -1 }]">
<a-select-option :value="-1">全部</a-select-option>
<a-select-option
v-for="(item, index) in CodeStatusEnum.data"
:key="index"
:value="item.value"
>{{ item.name }}</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="请选择通道">
<a-select v-decorator="['passageway', { initialValue: -1 }]">
<a-select-option :value="-1">全部</a-select-option>
<a-select-option
v-for="(item, index) in PassagewayEnum.data"
:key="index"
:value="item.value"
>{{ item.name }}</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="请选择启停状态">
<a-select v-decorator="['state', { initialValue: -1 }]">
<a-select-option :value="-1">全部</a-select-option>
<a-select-option
v-for="(item, index) in StateEnum.data"
:key="index"
:value="item.value"
>{{ item.name }}</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="请输入产码账号">
<a-input v-decorator="['code_account']" placeholder="请输入产码账号" />
</a-form-item>
<a-form-item label="请输入订单号">
<a-input v-decorator="['order_no']" placeholder="请输入订单号" />
</a-form-item>
<a-form-item class="search-btn">
<a-button type="primary" icon="search" html-type="submit">搜索</a-button>
</a-form-item>
</a-form>
</a-row>
<a-row class="row-item-search">
<a-form class="stat-form">
<a-form-item v-for="(item) in money" class="stat-item" :label="`产码 ${item.money} `">{{ item.num }}</a-form-item>
</a-form>
</a-row>
<!-- 操作板块 -->
<div class="row-item-tab clearfix">
<a-button
v-if="$auth('/code/create')"
class="fl-l"
type="primary"
icon="plus"
@click="handleCreate"
>产码</a-button>
<div v-if="selectedRowKeys.length" class="button-group">
<a-button-group class="ml-10">
<a-button v-action:delete icon="delete" @click="handleDelete(selectedRowKeys)">删除</a-button>
<a-button
icon="arrow-up"
@click="handleUpdateState(selectedRowKeys, true)"
>启用</a-button>
<a-button
icon="arrow-down"
@click="handleUpdateState(selectedRowKeys, false)"
>停用</a-button>
</a-button-group>
</div>
</div>
</div>
<s-table
ref="table"
rowKey="code_id"
:loading="isLoading"
:columns="columns"
:data="loadData"
:rowSelection="rowSelection"
:pageSize="15"
:scroll="{ x: 1450 }"
>
<!-- 店铺 -->
<span slot="shop" slot-scope="text, item">
<p class="twoline-hide" style="width: 270px;">{{ item.shop.shop_name }}</p>
</span>
<!-- 支付链接 -->
<span slot="link" slot-scope="text, item">
<p class="twoline-hide qrcode" @click="qrcode(item)" style="width: 270px;">{{ item.passageway == 'ZFBH5'?item.alipay:item.link }}</p>
</span>
<!-- 产码状态 -->
<span slot="status" slot-scope="text, item">
<a-tag
class="cur-p"
:color="text == 2 ? 'green' : (text == 3?'red':(text == 4?'orange':'black'))"
>{{ CodeStatusEnum[text].name }}</a-tag>
</span>
<!-- 产码启停状态 -->
<span slot="state" slot-scope="text, item">
<a-tag
class="cur-p"
:color="text == 1 ? 'green' : 'red'"
@click="handleUpdateState([item.code_id], text != 1)"
>{{ text == 1 ? '启用' : '停用' }}</a-tag>
</span>
<!-- 管理员 -->
<span slot="user" slot-scope="text, item">
<p class="twoline-hide" style="width: 270px;">{{ item.user.user_name }}</p>
</span>
<!-- 操作项 -->
<div class="actions" slot="action" slot-scope="text, item">
<router-link :to="{ path: '/code/update', query: { codeId: item.code_id } }">编辑</router-link>
<a v-action:delete @click="handleDelete([item.code_id])">删除</a>
</div>
</s-table>
<QrcodeForm ref="QrcodeForm" @handleSubmit="handleRefresh" />
</a-card>
</template>
<script>
import * as CodeApi from '@/api/code'
import { ContentHeader, STable } from '@/components'
import {
CodeStatusEnum,
PassagewayEnum,
StateEnum
} from '@/common/enum/code'
import { assignment, inArray } from "@/utils/util"
import { QrcodeForm } from './modules'
// 表格表头
const columns = [
{
title: 'ID',
dataIndex: 'code_id',
width: '50px'
},
{
title: '店铺',
dataIndex: 'shop',
width: '100px',
scopedSlots: { customRender: 'shop' }
},
{
title: '产码账号',
dataIndex: 'code_account',
width: '150px'
},
{
title: '单号',
dataIndex: 'order_no',
width: '150px'
},
{
title: '通道',
width: '80px',
dataIndex: 'passageway'
},
{
title: '支付链接',
dataIndex: 'link',
width: '307px',
scopedSlots: { customRender: 'link' }
},
{
title: '金额',
dataIndex: 'money',
width: '80px'
},
{
title: '状态',
dataIndex: 'status',
width: '80px',
scopedSlots: { customRender: 'status' }
},
{
title: '启停状态',
dataIndex: 'state',
width: '80px',
scopedSlots: { customRender: 'state' }
},
{
title: '调码次数',
width: '60px',
align: 'center',
dataIndex: 'num'
},
{
title: '到期时间',
dataIndex: 'expiration_time',
width: '150px'
},
{
title: '操作员',
dataIndex: 'user',
width: '80px',
scopedSlots: { customRender: 'user' }
},
{
title: '上传时间',
dataIndex: 'create_time',
width: '150px'
},
{
title: '操作',
dataIndex: 'action',
width: '150px',
fixed: 'right',
scopedSlots: { customRender: 'action' }
}
]
export default {
name: 'Index',
components: {
ContentHeader,
STable,
QrcodeForm
},
data () {
return {
money: [],
// 当前表单元素
searchForm: this.$form.createForm(this),
// 查询参数
queryParam: {},
// 正在加载
isLoading: false,
// 表头
columns,
// 选择的元素
selectedRowKeys: [],
// 加载数据方法 必须为 Promise 对象
loadData: param => {
return CodeApi.list({ ...param, ...this.queryParam })
.then(response => {
return response.data.list
})
}
}
},
beforeCreate () {
// 批量给当前实例赋值
assignment(this, {
inArray,
CodeStatusEnum,
PassagewayEnum,
StateEnum
})
},
created () {
this.init()
},
computed: {
rowSelection () {
return {
selectedRowKeys: this.selectedRowKeys,
onChange: this.onSelectChange
}
}
},
methods: {
// 获取统计数据
init () {
this.isLoading = true
const { queryParam } = this
return CodeApi.stat({ ...queryParam })
.then(response => {
this.money = response.data.money
})
.finally(() => {
this.isLoading = false
})
},
/**
* 选中项发生变化时的回调
*/
onSelectChange (selectedRowKeys) {
this.selectedRowKeys = selectedRowKeys
},
// 确认搜索
handleSearch (e) {
e.preventDefault()
this.searchForm.validateFields((error, values) => {
if (!error) {
this.queryParam = { ...this.queryParam, ...values }
this.handleRefresh(true)
this.init()
}
})
},
// 重置搜索表单
handleReset () {
this.searchForm.resetFields()
},
/**
* 修改启停状态
*/
handleUpdateState (codeIds, state = true) {
const app = this
const modal = this.$confirm({
title: '您确定要修改启停状态为' + (state ? '启用' : '停用') + '?',
content: '启停状态修改',
onOk () {
return CodeApi.stateSwitch({ codeIds, state })
.then(result => {
app.$message.success(result.message, 1.5)
app.handleRefresh()
})
.finally(result => {
modal.destroy()
})
}
})
},
/**
* 删除记录
*/
handleDelete (codeIds) {
const app = this
const modal = this.$confirm({
title: '您确定要删除该记录吗?',
content: '删除后不可恢复',
onOk () {
return CodeApi.deleted(codeIds)
.then(result => {
app.$message.success(result.message, 1.5)
app.handleRefresh()
app.init()
})
.finally(result => {
modal.destroy()
})
}
})
},
/**
* 新增记录
*/
handleCreate () {
this.$router.push('/code/create')
},
// 弹出支付二维码
qrcode (record) {
this.$refs.QrcodeForm.show(record)
},
/**
* 刷新列表
* @param Boolean bool 强制刷新到第一页
*/
handleRefresh (bool = false) {
this.selectedRowKeys = []
this.$refs.table.refresh(bool)
}
}
}
</script>
<style lang="less" scoped>
.ant-card-body {
padding: 22px 29px 25px;
}
// 筛选tab
.tab-list {
margin-right: 20px;
}
.stat-form{display: flex;}
.stat-item{display: flex;width: 130px;font-size: 20px;}
.qrcode{cursor: pointer;}
</style>
+353
View File
@@ -0,0 +1,353 @@
<template>
<a-card :bordered="false">
<div class="card-title">{{ $route.meta.title }}</div>
<div class="table-operator">
<!-- 搜索板块 -->
<a-row class="row-item-search">
<a-form class="search-form" :form="searchForm" layout="inline" @submit="handleSearch">
<a-form-item label="请选择状态">
<a-select v-decorator="['pay_status', { initialValue: 1 }]">
<a-select-option
v-for="(item, index) in PayStatusEnum.data"
:key="index"
:value="item.value"
>{{ item.name }}</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="请选择通道">
<a-select v-decorator="['passageway', { initialValue: -1 }]">
<a-select-option :value="-1">全部</a-select-option>
<a-select-option
v-for="(item, index) in PassagewayEnum.data"
:key="index"
:value="item.value"
>{{ item.name }}</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="请输入产码账号">
<a-input v-decorator="['code_account']" placeholder="请输入产码账号" />
</a-form-item>
<a-form-item label="请输入订单号">
<a-input v-decorator="['order_no']" placeholder="请输入订单号" />
</a-form-item>
<a-form-item class="search-btn">
<a-button type="primary" icon="search" html-type="submit">搜索</a-button>
</a-form-item>
</a-form>
</a-row>
<!-- 操作板块 -->
<div class="row-item-tab clearfix">
<div v-if="selectedRowKeys.length" class="button-group">
<a-button-group class="ml-10">
<a-button
icon="arrow-down"
@click="handleUpdateStatus(selectedRowKeys, false)"
>已核销</a-button>
</a-button-group>
</div>
</div>
</div>
<a-row class="row-item-search">
<a-form class="stat-form">
<a-form-item class="stat-item" label="产码200">{{ money49 }}</a-form-item>
<a-form-item class="stat-item" label="产码200">{{ money99 }}</a-form-item>
<a-form-item class="stat-item" label="产码200">{{ money200 }}</a-form-item>
<a-form-item class="stat-item" label="产码300">{{ money300 }}</a-form-item>
<a-form-item class="stat-item" label="产码500">{{ money500 }}</a-form-item>
<a-form-item class="stat-item" label="产码800">{{ money800 }}</a-form-item>
<a-form-item class="stat-item" label="产码1000">{{ money1000 }}</a-form-item>
</a-form>
</a-row>
</div>
<s-table
ref="table"
rowKey="code_id"
:loading="isLoading"
:columns="columns"
:data="loadData"
:rowSelection="rowSelection"
:pageSize="15"
:scroll="{ x: 1450 }"
>
<!-- 店铺 -->
<span slot="shop" slot-scope="text, item">
<p class="twoline-hide" style="width: 270px;">{{ item.shop.shop_name }}</p>
</span>
<!-- 支付链接 -->
<span slot="link" slot-scope="text, item">
<p class="twoline-hide" style="width: 270px;">{{ item.passageway == 'ZFBH5'?item.alipay:item.link }}</p>
</span>
<!-- 产码状态 -->
<span slot="pay_status" slot-scope="text, item">
<a-tag v-if=" text == 1 "
class="cur-p"
:color="text == 1 ? 'green' : 'blue'"
@click="handleUpdateStatus([item.code_id], text != 1)"
>{{ text == 1 ? '已支付' : '已核销' }}</a-tag>
<a-tag v-else
class="cur-p"
:color="text == 1 ? 'green' : 'blue'"
>{{ text == 1 ? '已支付' : '已核销' }}</a-tag>
</span>
<!-- 管理员 -->
<span slot="user" slot-scope="text, item">
<p class="twoline-hide" style="width: 270px;">{{ item.user.user_name }}</p>
</span>
</s-table>
</a-card>
</template>
<script>
import * as CodeApi from '@/api/code'
import { ContentHeader, STable } from '@/components'
import {
CodeStatusEnum,
PassagewayEnum,
PayStatusEnum
} from '@/common/enum/code'
import {assignment, inArray} from "@/utils/util"
import * as ShopApi from '@/api/shop'
// 表格表头
const columns = [
{
title: 'ID',
dataIndex: 'code_id',
width: '50px'
},
{
title: '店铺',
dataIndex: 'shop',
width: '100px',
scopedSlots: { customRender: 'shop' }
},
{
title: '产码账号',
dataIndex: 'code_account',
width: '150px',
},
{
title: '单号',
dataIndex: 'order_no',
width: '150px',
},
{
title: '通道',
width: '80px',
dataIndex: 'passageway'
},
{
title: '支付链接',
dataIndex: 'link',
width: '307px',
scopedSlots: { customRender: 'link' }
},
{
title: '金额',
dataIndex: 'money',
width: '80px'
},
{
title: '状态',
dataIndex: 'pay_status',
width: '80px',
scopedSlots: { customRender: 'pay_status' }
},
{
title: '调码次数',
width: '60px',
align: 'center',
dataIndex: 'num'
},
{
title: '到期时间',
dataIndex: 'expiration_time',
width: '150px'
},
{
title: '操作员',
dataIndex: 'user',
width: '80px',
scopedSlots: { customRender: 'user' }
},
{
title: '上传时间',
dataIndex: 'create_time',
width: '150px'
}
]
export default {
name: 'Index',
components: {
ContentHeader,
STable
},
data () {
return {
pay_status: 1,
money49: 0,
money99: 0,
money200: 0,
money300: 0,
money500: 0,
money800: 0,
money1000: 0,
// 当前表单元素
searchForm: this.$form.createForm(this),
// 查询参数
queryParam: {
'pay_status': 1
},
// 正在加载
isLoading: false,
// 表头
columns,
// 选择的元素
selectedRowKeys: [],
// 加载数据方法 必须为 Promise 对象
loadData: param => {
return CodeApi.list({ ...param, ...this.queryParam })
.then(response => {
return response.data.list
})
}
}
},
beforeCreate () {
// 批量给当前实例赋值
assignment(this, {
inArray,
CodeStatusEnum,
PassagewayEnum,
PayStatusEnum
})
},
created () {
this.init()
},
computed: {
rowSelection () {
return {
selectedRowKeys: this.selectedRowKeys,
onChange: this.onSelectChange
}
}
},
methods: {
// 获取统计数据
init () {
this.isLoading = true
const { queryParam } = this
return CodeApi.stat({ ...queryParam })
.then(response => {
this.money49 = response.data.money49
this.money99 = response.data.money99
this.money200 = response.data.money200
this.money300 = response.data.money300
this.money500 = response.data.money500
this.money800 = response.data.money800
this.money1000 = response.data.money1000
})
.finally(() => {
this.isLoading = false
})
},
/**
* 选中项发生变化时的回调
*/
onSelectChange (selectedRowKeys) {
this.selectedRowKeys = selectedRowKeys
},
// 确认搜索
handleSearch (e) {
e.preventDefault()
this.searchForm.validateFields((error, values) => {
if (!error) {
this.queryParam = { ...this.queryParam, ...values }
this.handleRefresh(true)
this.init()
}
})
},
// 重置搜索表单
handleReset () {
this.searchForm.resetFields()
},
/**
* 修改核销状态
*/
handleUpdateStatus (codeIds, state = true) {
const app = this
const modal = this.$confirm({
title: '您确定要修改状态为' + (state ? '已支付' : '已核销') + '?',
content: '修改为已核销后不可更改状态',
onOk () {
return CodeApi.changeState({ codeIds, state })
.then(result => {
app.$message.success(result.message, 1.5)
app.handleRefresh()
})
.finally(result => {
modal.destroy()
})
}
})
},
/**
* 删除记录
*/
handleDelete (codeIds) {
const app = this
const modal = this.$confirm({
title: '您确定要删除该记录吗?',
content: '删除后不可恢复',
onOk () {
return CodeApi.deleted(codeIds)
.then(result => {
app.$message.success(result.message, 1.5)
app.handleRefresh()
app.init()
})
.finally(result => {
modal.destroy()
})
}
})
},
/**
* 新增记录
*/
handleCreate () {
this.$router.push('/code/create')
},
/**
* 刷新列表
* @param Boolean bool 强制刷新到第一页
*/
handleRefresh (bool = false) {
this.selectedRowKeys = []
this.$refs.table.refresh(bool)
}
}
}
</script>
<style lang="less" scoped>
.ant-card-body {
padding: 22px 29px 25px;
}
// 筛选tab
.tab-list {
margin-right: 20px;
}
.stat-form{display: flex;}
.stat-item{display: flex;width: 130px;font-size: 20px;}
</style>
+249
View File
@@ -0,0 +1,249 @@
<template>
<a-card :bordered="false">
<div class="card-title">{{ $route.meta.title }}</div>
<a-spin :spinning="isLoading">
<a-form :form="form" @submit="handleSubmit" :selfUpdate="true">
<a-tabs :activeKey="tabKey" :tabBarStyle="{marginBottom: '30px'}" @change="handleTabs">
<a-tab-pane :key="0" tab="基本信息"></a-tab-pane>
</a-tabs>
<div class="tabs-content">
<!-- 基本信息 -->
<div class="tab-pane" v-show="tabKey == 0">
<a-form-item label="店铺" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-select
style="width: 300px"
v-decorator="['shop_id', {rules: [{required: true, message: '请选择店铺'}]}]"
placeholder="请选择店铺"
>
<a-select-option
v-for="(item, index) in formData.shopList"
:key="index"
:value="item.shop_id"
>{{ item.shop_name }}{{ item.contact_name?'('+item.contact_name+')':'' }}</a-select-option>
</a-select>
<div class="form-item-help">
<router-link
target="_blank"
:to="{ path: '/shop/create' }"
>新增店铺</router-link>
<a href="javascript:;" @click="onReloadShopList">刷新</a>
</div>
</a-form-item>
<a-form-item label="微信链接" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-input
placeholder="请输入微信链接"
v-decorator="['link']"
@blur="getOrderNo"
/>
</a-form-item>
<a-form-item label="支付宝链接" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-input placeholder="请输入支付宝链接" v-decorator="['alipay']"/>
</a-form-item>
<a-form-item label="产码账号" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-input
placeholder="请输入产码账号"
v-decorator="['code_account', {rules: [{required: true, min: 2, message: ''}]}]"
/>
</a-form-item>
<a-form-item label="订单号" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-input
placeholder="请输入订单号"
v-decorator="['order_no', {rules: [{required: true, min: 2, message: '请输入至少2个字符'}]}]"
/>
</a-form-item>
<a-form-item label="通道编码" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-radio-group
v-decorator="['passageway', {rules: [{ required: true }]}]"
>
<a-radio
v-for="(item, index) in PassagewayEnum.data"
:key="index"
:value="item.value">{{ item.name }}</a-radio>
</a-radio-group>
</a-form-item>
<a-form-item label="启停状态" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-radio-group
v-decorator="['state', {rules: [{ required: true }]}]"
>
<a-radio
v-for="(item, index) in StateEnum.data"
:key="index"
:value="item.value">{{ item.name }}</a-radio>
</a-radio-group>
</a-form-item>
<a-form-item label="金额" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-input-number
:min="0"
v-decorator="['money', { rules:[{ required: true }]}]"
/>
</a-form-item>
<a-form-item label="有限期限(秒) " :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-input-number
:min="0"
v-decorator="['expiration_time', { rules:[{ required: true }]}]"
/>
</a-form-item>
</div>
</div>
<a-form-item class="mt-20" :wrapper-col="{ span: wrapperCol.span, offset: labelCol.span }">
<a-button type="primary" html-type="submit" :loading="isBtnLoading">提交</a-button>
</a-form-item>
</a-form>
</a-spin>
</a-card>
</template>
<script>
import * as CodeApi from '@/api/code'
import { InputNumberGroup } from '@/components'
import CodeModel from '@/common/model/code/Index'
import { isEmptyObject, assignment } from '@/utils/util'
import {
PassagewayEnum,
StateEnum
} from '@/common/enum/code'
export default {
components: {
InputNumberGroup
},
data () {
return {
// 默认的标签索引
tabKey: 0,
// 标签布局属性
labelCol: { span: 3 },
// 输入框布局属性
wrapperCol: { span: 10 },
// loading状态
isLoading: false,
isBtnLoading: false,
// 当前表单元素
form: this.$form.createForm(this),
// 产码ID
codeId: null,
// 表单数据
formData: CodeModel.formData
}
},
beforeCreate () {
// 批量给当前实例赋值
assignment(this, {
PassagewayEnum,
StateEnum
})
},
created () {
// 初始化数据
this.initData()
},
beforeDestroy () {
// 销毁产码详情
CodeModel.formData.code = {}
},
methods: {
getOrderNo (event) {
const url = event.target.value
let orderId = url.match(/orderId=([^&]+)/)
let custNo = url.match(/custNo=([^&]+)/)
orderId = orderId !== null ? orderId[1] : ''
custNo = custNo !== null ? custNo[1] : ''
this.form.setFieldsValue({ 'code_account': custNo, 'order_no': orderId })
},
// 初始化数据
initData () {
// 记录产码ID
this.codeId = this.$route.query.codeId
// 获取form所需的数据
this.isLoading = true
CodeModel.getFromData(this.codeId)
.then(() => {
// 产码表单数据
if (!isEmptyObject(this.form.getFieldsValue())) {
this.form.setFieldsValue(CodeModel.getFieldsValue())
}
this.isLoading = false
})
},
// 手动强制更新页面
onForceUpdate (bool = false) {
this.$forceUpdate()
// bool为true时再执行一次 $forceUpdate, 特殊情况下需执行两次,原因如下:
// 第一次执行 $forceUpdate时, 新元素绑定v-decorator无法获取到form.getFieldValue
bool && setTimeout(() => {
this.$forceUpdate()
}, 10)
},
// 切换tab选项卡
handleTabs (key) {
this.tabKey = key
},
// 刷新店铺列表
onReloadShopList () {
this.isLoading = true
CodeModel.getShopList().then(() => {
this.isLoading = false
})
},
/**
* 确认按钮
*/
handleSubmit (e) {
e.preventDefault()
// 表单验证
const { form: { validateFields } } = this
validateFields((errors, values) => {
// 定位到错误的tab选项卡
if (errors) {
this.onTargetTabError(errors)
return false
}
// 提交到后端api
this.onFormSubmit(values)
return true
})
},
/**
* 定位到错误的tab选项卡
*/
onTargetTabError (errors) {
},
/**
* 提交到后端api
*/
onFormSubmit (values) {
this.isLoading = true
this.isBtnLoading = true
CodeApi.edit({ codeId: this.codeId, form: values })
.then(result => {
// 显示提示信息
this.$message.success(result.message, 1.5)
// 跳转到列表页
setTimeout(() => {
this.$router.push('./index')
}, 1500)
})
.catch(() => {
this.isBtnLoading = false
})
.finally(() => {
this.isLoading = false
})
}
}
}
</script>
<style lang="less" scoped>
@import './style.less';
</style>
@@ -0,0 +1,59 @@
<template>
<a-modal
:title="title"
:width="300"
:visible="visible"
:isLoading="isLoading"
:confirmLoading="isLoading"
:maskClosable="false"
@cancel="handleCancel"
>
<a-spin :spinning="isLoading">
<p>单号:{{ record.order_no }}</p>
<img class='qrcode' :src="record.qrcode" />
</a-spin>
</a-modal>
</template>
<script>
export default {
data () {
return {
// 对话框标题
title: '二维码',
// modal(对话框)是否可见
visible: false,
// modal(对话框)确定按钮 loading
isLoading: false,
// 当前记录
record: {}
}
},
created () {
},
methods: {
/**
* 显示对话框
*/
show (record) {
// 显示窗口
this.visible = true
// 当前记录
this.record = record
},
/**
* 关闭对话框事件
*/
handleCancel () {
this.visible = false
}
}
}
</script>
<style lang="less" scoped>
.qrcode{width: 260px;}
</style>
@@ -0,0 +1,3 @@
import QrcodeForm from './QrcodeForm'
export { QrcodeForm }
+25
View File
@@ -0,0 +1,25 @@
.tabs-content {
.tab-pane {
animation: fadenum 0.6s ease;
}
}
/deep/.ant-form-item-control {
padding-left: 10px;
.ant-form-item-control {
padding-left: 0;
}
}
.ant-form-item {
margin-bottom: 18px;
.ant-form-item {
margin-bottom: 0;
}
}
.input-number-group {
margin: 3px 0;
}
+184
View File
@@ -0,0 +1,184 @@
<template>
<a-card :bordered="false">
<div class="card-title">{{ $route.meta.title }}</div>
<a-spin :spinning="isLoading">
<a-form :form="form" @submit="handleSubmit" :selfUpdate="true">
<a-tabs :activeKey="tabKey" :tabBarStyle="{marginBottom: '30px'}" @change="handleTabs">
<a-tab-pane :key="0" tab="基本信息"></a-tab-pane>
</a-tabs>
<div class="tabs-content">
<!-- 基本信息 -->
<div class="tab-pane" v-show="tabKey == 0">
<a-form-item label="公司名称" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-input
placeholder="请输入公司名称"
v-decorator="['name', {rules: [{required: true, min: 2, message: '请输入至少2个字符'}]}]"
/>
</a-form-item>
<a-form-item label="公司地址" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-input
placeholder="请输入公司地址"
v-decorator="['address', {rules: [{required: true, min: 2, message: '请输入至少2个字符'}]}]"
/>
</a-form-item>
<a-form-item label="纳税识别号" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-input
placeholder="请输入纳税识别号"
v-decorator="['tin', {rules: [{required: true, min: 2, message: '请输入至少2个字符'}]}]"
/>
</a-form-item>
<a-form-item label="联系人" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-input
placeholder="请输入联系人"
v-decorator="['contact_name', {rules: [{required: false}]}]"
/>
</a-form-item>
<a-form-item label="联系电话" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-input
placeholder="请输入联系电话"
v-decorator="['contact_tel', {rules: [{required: false}]}]"
/>
</a-form-item>
<a-form-item label="身份证号" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-input
placeholder="请输入身份证号"
v-decorator="['idcard', {rules: [{required: false}]}]"
/>
</a-form-item>
<a-form-item label="请求地址" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-input
placeholder="请输入请求地址"
v-decorator="['url', {rules: [{required: false}]}]"
/>
</a-form-item>
<a-form-item label="公司编号(平台提供)" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-input
placeholder="请输入公司编号"
v-decorator="['orgNo', {rules: [{required: false}]}]"
/>
</a-form-item>
<a-form-item label="appKey(平台提供)" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-input
placeholder="请输入公司appKey"
v-decorator="['appKey', {rules: [{required: false}]}]"
/>
</a-form-item>
<a-form-item label="公司私钥(平台提供)" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-textarea class="company-textarea" v-decorator="['privateKey']" placeholder="请输入公司私钥base64编码" />
</a-form-item>
<a-form-item label="公司公钥(平台提供)" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-textarea class="company-textarea" v-decorator="['publicKey']" placeholder="请输入公司公钥base64编码" />
</a-form-item>
<a-form-item label="平台公钥(平台提供)" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-textarea class="company-textarea" v-decorator="['platformPublicKey']" placeholder="请输入平台公钥base64编码" />
</a-form-item>
</div>
</div>
<a-form-item class="mt-20" :wrapper-col="{ span: wrapperCol.span, offset: labelCol.span }">
<a-button type="primary" html-type="submit" :loading="isBtnLoading">提交</a-button>
</a-form-item>
</a-form>
</a-spin>
</a-card>
</template>
<script>
import * as CompanyApi from '@/api/company'
import CompanyModel from '@/common/model/company/Index'
export default {
components: {
},
data () {
return {
// 默认的标签索引
tabKey: 0,
// 标签布局属性
labelCol: { span: 3 },
// 输入框布局属性
wrapperCol: { span: 10 },
// loading状态
isLoading: false,
isBtnLoading: false,
// 当前表单元素
form: this.$form.createForm(this),
// 表单数据
formData: CompanyModel.formData
}
},
// 初始化数据
created () {
this.isLoading = true
// 获取form所需的数据
CompanyModel.getFromData()
.then(() => {
this.isLoading = false
})
},
methods: {
// 手动强制更新页面
onForceUpdate (bool = false) {
this.$forceUpdate()
// bool为true时再执行一次 $forceUpdate, 特殊情况下需执行两次,原因如下:
// 第一次执行 $forceUpdate时, 新元素绑定v-decorator无法获取到form.getFieldValue
bool && setTimeout(() => {
this.$forceUpdate()
}, 10)
},
// 切换tab选项卡
handleTabs (key) {
this.tabKey = key
},
/**
* 确认按钮
*/
handleSubmit (e) {
e.preventDefault()
// 表单验证
const { form: { validateFields } } = this
validateFields((errors, values) => {
// 定位到错误的tab选项卡
if (errors) {
this.onTargetTabError(errors)
return false
}
// 提交到后端api
this.onFormSubmit(values)
return true
})
},
/**
* 提交到后端api
*/
onFormSubmit (values) {
this.isLoading = true
this.isBtnLoading = true
CompanyApi.add({ form: values })
.then(result => {
// 显示提示信息
this.$message.success(result.message, 1.5)
// 跳转到列表页
setTimeout(() => {
this.$router.push('./index')
}, 1500)
})
.catch(() => {
this.isBtnLoading = false
})
.finally(() => {
this.isLoading = false
})
}
}
}
</script>
<style lang="less" scoped>
@import './style.less';
</style>
+341
View File
@@ -0,0 +1,341 @@
<template>
<a-card :bordered="false">
<div class="card-title">{{ $route.meta.title }}</div>
<div class="table-operator">
<!-- 搜索板块 -->
<a-row class="row-item-search">
<a-form class="search-form" :form="searchForm" layout="inline" @submit="handleSearch">
<a-form-item label="关键字搜索">
<a-input v-decorator="['search']" placeholder="公司编号/公司名称/公司地址/联系人/联系电话/身份证号" />
</a-form-item>
<a-form-item class="search-btn">
<a-button type="primary" icon="search" html-type="submit">搜索</a-button>
</a-form-item>
</a-form>
</a-row>
<!-- 操作板块 -->
<div class="row-item-tab clearfix">
<a-button
v-if="$auth('/company/create')"
class="fl-l"
type="primary"
icon="plus"
@click="handleCreate"
>新增</a-button>
<div v-if="selectedRowKeys.length" class="button-group">
<a-button-group class="ml-10">
<a-button v-action:delete icon="delete" @click="handleDelete(selectedRowKeys)">删除</a-button>
</a-button-group>
<a-button-group class="ml-10">
<a-button
v-action:status
icon="arrow-up"
@click="handleUpdateStatus(selectedRowKeys, true)"
>启用</a-button>
<a-button
v-action:status
icon="arrow-down"
@click="handleUpdateStatus(selectedRowKeys, false)"
>停用</a-button>
<a-button v-action:delete icon="delete" @click="handleDelete(selectedRowKeys)">删除</a-button>
</a-button-group>
</div>
</div>
</div>
<s-table
ref="table"
rowKey="company_id"
:loading="isLoading"
:columns="columns"
:data="loadData"
:rowSelection="rowSelection"
:pageSize="15"
:scroll="{ x: 1450 }"
>
<!-- 公司地址 -->
<span slot="address" slot-scope="text">
<p class="twoline-hide" style="width: 400px;">{{ text }}</p>
</span>
<!-- 公司状态 -->
<span slot="status" slot-scope="text, item">
<a-tag
class="cur-p"
:color="text === 10 ? 'green' : 'red'"
@click="handleUpdateStatus([item.company_id], text !== 10)"
>{{ text === 10 ? '启用' : '停用' }}</a-tag>
</span>
<!-- 操作项 -->
<div class="actions" slot="action" slot-scope="text, item">
<a v-action:stock @click="handleStock([item.company_id])">更新库存</a>
<a title="公司对接参数" v-action:para @click="handleApiPara(item)">对接参数</a>
<a title="公司充值" v-action:recharge @click="handleRecharge(item)">充值</a>
<a title="创建用户" v-action:account @click="handleAccount(item)">创建用户</a>
<router-link v-if="$auth('/company/update')" :to="{ path: '/company/update', query: { companyId: item.company_id } }">编辑</router-link>
<a v-action:delete @click="handleDelete([item.company_id])">删除</a>
</div>
</s-table>
<RechargeForm ref="RechargeForm" @handleSubmit="handleRefresh" />
<ApiParaForm ref="ApiParaForm" @handleSubmit="handleRefresh" />
<AccountForm ref="AccountForm" @handleSubmit="handleRefresh" />
</a-card>
</template>
<script>
import * as CompanyApi from '@/api/company'
import { ContentHeader, STable } from '@/components'
import { CompanyStatusEnum } from '@/common/enum/company'
import { RechargeForm, ApiParaForm, AccountForm } from './modules'
import { assignment, inArray } from '@/utils/util'
// 表格表头
const columns = [
{
title: '公司ID',
width: '70px',
dataIndex: 'company_id'
},
{
title: '公司编号',
width: '150px',
dataIndex: 'orgNo'
},
{
title: '公司名称',
width: '150px',
dataIndex: 'name'
},
{
title: '公司地址',
width: '400px',
dataIndex: 'address'
},
{
title: '纳税识别号',
width: '200px',
dataIndex: 'tin'
},
{
title: '余额',
width: '150px',
dataIndex: 'balance'
},
{
title: '状态',
width: '100px',
dataIndex: 'status',
scopedSlots: { customRender: 'status' }
},
/*{
title: '联系人',
width: '150px',
dataIndex: 'contact_name'
},
{
title: '联系电话',
width: '150px',
dataIndex: 'contact_tel'
},
{
title: '身份证号',
width: '150px',
dataIndex: 'idcard'
},*/
{
title: '可下单数量',
width: '100px',
dataIndex: 'canOrderNum'
},
{
title: '未使用数量',
width: '100px',
dataIndex: 'unUseNum'
},
{
title: '已使用数量',
width: '100px',
dataIndex: 'useNum'
},
{
title: '创建时间',
width: '100px',
dataIndex: 'create_time'
},
{
title: '操作',
dataIndex: 'action',
width: '300px',
fixed: 'right',
scopedSlots: { customRender: 'action' }
}
]
export default {
name: 'Index',
components: {
ContentHeader,
RechargeForm,
ApiParaForm,
AccountForm,
STable
},
data () {
return {
// 当前表单元素
searchForm: this.$form.createForm(this),
// 查询参数
queryParam: {},
// 正在加载
isLoading: false,
// 表头
columns,
// 选择的元素
selectedRowKeys: [],
// 加载数据方法 必须为 Promise 对象
loadData: param => {
return CompanyApi.list({ ...param, ...this.queryParam })
.then(response => {
return response.data.list
})
}
}
},
beforeCreate () {
// 批量给当前实例赋值
assignment(this, {
inArray,
CompanyStatusEnum
})
},
created () {
},
computed: {
rowSelection () {
return {
selectedRowKeys: this.selectedRowKeys,
onChange: this.onSelectChange
}
}
},
methods: {
/**
* 选中项发生变化时的回调
*/
onSelectChange (selectedRowKeys) {
this.selectedRowKeys = selectedRowKeys
},
// 确认搜索
handleSearch (e) {
e.preventDefault()
this.searchForm.validateFields((error, values) => {
if (!error) {
this.queryParam = { ...this.queryParam, ...values }
this.handleRefresh(true)
}
})
},
// 重置搜索表单
handleReset () {
this.searchForm.resetFields()
},
// 修改商品状态(上下架)
handleUpdateStatus (companyIds, state = true) {
if (!this.$auth('/company/changeState')) {
return false
}
this.isLoading = true
CompanyApi.changeState({ companyIds, state })
.then(result => {
// 显示成功
this.$message.success(result.message, 1.5)
this.handleRefresh()
})
.finally(result => { this.isLoading = false })
},
// 商户充值
handleRecharge (item) {
this.$refs.RechargeForm.handle(item)
},
// 商户对接参数
handleApiPara (item) {
this.$refs.ApiParaForm.handle(item)
},
// 商户对接参数
handleAccount (item) {
this.$refs.AccountForm.handle(item)
},
/**
* 删除记录
*/
handleDelete (companyIds) {
const app = this
const modal = this.$confirm({
title: '您确定要删除该记录吗?',
content: '删除后不可恢复',
onOk () {
return CompanyApi.deleted(companyIds)
.then(result => {
app.$message.success(result.message, 1.5)
app.handleRefresh()
})
.finally(result => {
modal.destroy()
})
}
})
},
/**
* 更新库存
*/
handleStock (companyIds) {
const app = this
const modal = this.$confirm({
title: '执行此操作将更新商品可用库存',
content: '',
onOk () {
return CompanyApi.stock(companyIds)
.then(result => {
app.$message.success(result.message, 1.5)
app.handleRefresh()
})
.finally(result => {
modal.destroy()
})
}
})
},
/**
* 新增记录
*/
handleCreate () {
this.$router.push('/company/create')
},
/**
* 刷新列表
* @param Boolean bool 强制刷新到第一页
*/
handleRefresh (bool = false) {
this.selectedRowKeys = []
this.$refs.table.refresh(bool)
}
}
}
</script>
<style lang="less" scoped>
.ant-card-body {
padding: 22px 29px 25px;
}
// 筛选tab
.tab-list {
margin-right: 20px;
}
</style>
+210
View File
@@ -0,0 +1,210 @@
<template>
<a-card :bordered="false">
<div class="card-title">{{ $route.meta.title }}</div>
<a-spin :spinning="isLoading">
<a-form :form="form" @submit="handleSubmit" :selfUpdate="true">
<a-tabs :activeKey="tabKey" :tabBarStyle="{marginBottom: '30px'}" @change="handleTabs">
<a-tab-pane :key="0" tab="基本信息"></a-tab-pane>
</a-tabs>
<div class="tabs-content">
<!-- 基本信息 -->
<div class="tab-pane" v-show="tabKey == 0">
<a-form-item label="公司名称" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-input
placeholder="请输入公司名称"
v-decorator="['name', {rules: [{required: true, min: 2, message: '请输入至少2个字符'}]}]"
/>
</a-form-item>
<a-form-item label="公司地址" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-input
placeholder="请输入公司地址"
v-decorator="['address', {rules: [{required: true, min: 2, message: '请输入至少2个字符'}]}]"
/>
</a-form-item>
<a-form-item label="纳税识别号" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-input
placeholder="请输入纳税识别号"
v-decorator="['tin', {rules: [{required: true, min: 2, message: '请输入至少2个字符'}]}]"
/>
</a-form-item>
<a-form-item label="联系人" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-input
placeholder="请输入联系人"
v-decorator="['contact_name', {rules: [{required: false}]}]"
/>
</a-form-item>
<a-form-item label="联系电话" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-input
placeholder="请输入联系电话"
v-decorator="['contact_tel', {rules: [{required: false}]}]"
/>
</a-form-item>
<a-form-item label="身份证号" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-input
placeholder="请输入身份证号"
v-decorator="['idcard', {rules: [{required: false}]}]"
/>
</a-form-item>
<a-form-item label="请求地址" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-input
placeholder="请输入请求地址"
v-decorator="['url', {rules: [{required: false}]}]"
/>
</a-form-item>
<a-form-item label="公司编号(平台提供)" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-input
placeholder="请输入公司编号"
v-decorator="['orgNo', {rules: [{required: false}]}]"
/>
</a-form-item>
<a-form-item label="appKey(平台提供)" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-input
placeholder="请输入公司appKey"
v-decorator="['appKey', {rules: [{required: false}]}]"
/>
</a-form-item>
<a-form-item label="公司私钥(平台提供)" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-textarea class="company-textarea" v-decorator="['privateKey']" placeholder="请输入公司私钥base64编码" />
</a-form-item>
<a-form-item label="公司公钥(平台提供)" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-textarea class="company-textarea" v-decorator="['publicKey']" placeholder="请输入公司公钥base64编码" />
</a-form-item>
<a-form-item label="平台公钥(平台提供)" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-textarea class="company-textarea" v-decorator="['platformPublicKey']" placeholder="请输入平台公钥base64编码" />
</a-form-item>
</div>
</div>
<a-form-item class="mt-20" :wrapper-col="{ span: wrapperCol.span, offset: labelCol.span }">
<a-button type="primary" html-type="submit" :loading="isBtnLoading">提交</a-button>
</a-form-item>
</a-form>
</a-spin>
</a-card>
</template>
<script>
import * as CompanyApi from '@/api/company'
import { InputNumberGroup } from '@/components'
import CompanyModel from '@/common/model/company/Index'
import { isEmptyObject } from '@/utils/util'
export default {
components: {
InputNumberGroup
},
data () {
return {
// 默认的标签索引
tabKey: 0,
// 标签布局属性
labelCol: { span: 3 },
// 输入框布局属性
wrapperCol: { span: 10 },
// loading状态
isLoading: false,
isBtnLoading: false,
// 当前表单元素
form: this.$form.createForm(this),
// 公司ID
companyId: null,
// 表单数据
formData: CompanyModel.formData
}
},
created () {
// 初始化数据
this.initData()
},
beforeDestroy () {
// 销毁商户详情
CompanyModel.formData.company = {}
},
methods: {
// 初始化数据
initData () {
// 记录商户ID
this.companyId = this.$route.query.companyId
// 获取form所需的数据
this.isLoading = true
CompanyModel.getFromData(this.companyId)
.then(() => {
// 商户表单数据
if (!isEmptyObject(this.form.getFieldsValue())) {
this.form.setFieldsValue(CompanyModel.getFieldsValue())
}
this.isLoading = false
})
},
// 手动强制更新页面
onForceUpdate (bool = false) {
this.$forceUpdate()
// bool为true时再执行一次 $forceUpdate, 特殊情况下需执行两次,原因如下:
// 第一次执行 $forceUpdate时, 新元素绑定v-decorator无法获取到form.getFieldValue
bool && setTimeout(() => {
this.$forceUpdate()
}, 10)
},
// 切换tab选项卡
handleTabs (key) {
this.tabKey = key
},
/**
* 确认按钮
*/
handleSubmit (e) {
e.preventDefault()
// 表单验证
const { form: { validateFields } } = this
validateFields((errors, values) => {
// 定位到错误的tab选项卡
if (errors) {
this.onTargetTabError(errors)
return false
}
// 提交到后端api
this.onFormSubmit(values)
return true
})
},
/**
* 定位到错误的tab选项卡
*/
onTargetTabError (errors) {
},
/**
* 提交到后端api
*/
onFormSubmit (values) {
this.isLoading = true
this.isBtnLoading = true
CompanyApi.edit({ companyId: this.companyId, form: values })
.then(result => {
// 显示提示信息
this.$message.success(result.message, 1.5)
// 跳转到列表页
setTimeout(() => {
this.$router.push('./index')
}, 1500)
})
.catch(() => {
this.isBtnLoading = false
})
.finally(() => {
this.isLoading = false
})
}
}
}
</script>
<style lang="less" scoped>
@import './style.less';
</style>
@@ -0,0 +1,146 @@
<template>
<a-card :bordered="false">
<div class="card-title">{{ $route.meta.title }}</div>
<div class="table-operator">
<!-- 搜索板块 -->
<a-row class="row-item-search">
<a-form class="search-form" :form="searchForm" layout="inline" @submit="handleSearch">
<a-form-item label="公司名称">
<a-input v-decorator="['search']" placeholder="请输入公司名称" />
</a-form-item>
<a-form-item label="余额变动场景">
<a-select v-decorator="['scene', { initialValue: 0 }]">
<a-select-option :value="0">全部</a-select-option>
<a-select-option
v-for="(item, index) in SceneEnum.data"
:key="index"
:value="item.value"
>{{ item.name }}</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="变动时间">
<a-range-picker format="YYYY-MM-DD" v-decorator="['betweenTime']" />
</a-form-item>
<a-form-item class="search-btn">
<a-button type="primary" icon="search" html-type="submit">搜索</a-button>
</a-form-item>
</a-form>
</a-row>
</div>
<s-table
ref="table"
rowKey="log_id"
:loading="isLoading"
:columns="columns"
:data="loadData"
:pageSize="15"
>
<!-- 商户 -->
<span slot="company" slot-scope="text, item">
<p class="twoline-hide" style="width: 150px;">{{ item.name }}</p>
</span>
<!-- 余额变动场景 -->
<span slot="scene" slot-scope="text">
<a-tag>{{ SceneEnum[text].name }}</a-tag>
</span>
</s-table>
</a-card>
</template>
<script>
import * as Api from '@/api/company/balance'
import { STable } from '@/components/Table'
import SceneEnum from '@/common/enum/company/balance/log/Scene'
export default {
name: 'Index',
components: {
STable
},
data () {
return {
// 枚举类
SceneEnum,
// 当前表单元素
searchForm: this.$form.createForm(this),
// 查询参数
queryParam: {},
// 正在加载
isLoading: false,
// 表头
columns: [
{
title: 'ID',
dataIndex: 'log_id'
},
{
title: '公司信息',
dataIndex: 'company',
scopedSlots: { customRender: 'company' }
},
{
title: '余额变动场景',
dataIndex: 'scene',
scopedSlots: { customRender: 'scene' }
},
{
title: '变动金额',
dataIndex: 'money'
},
{
title: '描述/说明',
width: '260px',
dataIndex: 'describe'
},
{
title: '管理员备注',
width: '260px',
dataIndex: 'remark'
},
{
title: '变动时间',
dataIndex: 'create_time'
}
],
// 加载数据方法 必须为 Promise 对象
loadData: param => {
return Api.log({ ...param, ...this.queryParam })
.then(response => {
return response.data.list
})
}
}
},
created () {
},
methods: {
/**
* 刷新列表
* @param Boolean bool 强制刷新到第一页
*/
handleRefresh (bool = false) {
this.$refs.table.refresh(bool)
},
// 确认搜索
handleSearch (e) {
e.preventDefault()
this.searchForm.validateFields((error, values) => {
if (!error) {
this.queryParam = { ...this.queryParam, ...values }
this.handleRefresh(true)
}
})
}
}
}
</script>
<style lang="less" scoped>
.ant-card-body {
padding: 22px 29px 25px;
}
</style>
@@ -0,0 +1,123 @@
<template>
<a-modal
class="noborder"
:title="title"
:width="520"
:visible="visible"
:confirmLoading="confirmLoading"
:maskClosable="false"
@ok="handleSubmit"
@cancel="handleCancel"
>
<a-spin :spinning="confirmLoading">
<a-form :form="form">
<template>
<a-form-item class="mb-5" label="公司ID" :labelCol="labelCol" :wrapperCol="wrapperCol">
<span>{{ record.company_id }}</span>
</a-form-item>
<a-form-item class="mb-5" label="公司名称" :labelCol="labelCol" :wrapperCol="wrapperCol">
<span>{{ record.name }}</span>
</a-form-item>
<a-form-item label="创建用户数" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-input-number
:min="1"
v-decorator="[`users`, { initialValue: '', rules: [{ required: true, message: '请输入用户数' }] }]"
/>
</a-form-item>
</template>
</a-form>
</a-spin>
</a-modal>
</template>
<script>
// import pick from 'lodash.pick'
import * as Api from '@/api/company'
export default {
components: {
},
data () {
return {
// 对话框标题
title: '创建公司用户',
// 标签布局属性
labelCol: { span: 7 },
// 输入框布局属性
wrapperCol: { span: 13 },
// modal(对话框)是否可见
visible: false,
// modal(对话框)确定按钮 loading
confirmLoading: false,
// 当前表单元素
form: this.$form.createForm(this),
// 当前记录
record: {}
}
},
methods: {
// 显示对话框
handle (record) {
// 显示窗口
this.visible = true
// 当前记录
this.record = record
},
/**
* 确认按钮
*/
handleSubmit (e) {
e.preventDefault()
// 表单验证
const { form: { validateFields } } = this
validateFields((errors, values) => {
// 提交到后端api
!errors && this.onFormSubmit(values)
})
},
/**
* 关闭对话框事件
*/
handleCancel () {
this.visible = false
this.form.resetFields()
},
/**
* 提交到后端api
*/
onFormSubmit (values) {
const { record } = this
this.confirmLoading = true
values.company_id = record.company_id
Api.account({ companyId: record.company_id, form: values })
.then((result) => {
// 显示成功
this.$message.success(result.message, 1.5)
// 关闭对话框事件
this.handleCancel()
// 通知父端组件提交完成了
this.$emit('handleSubmit', values)
})
.finally(() => {
this.confirmLoading = false
})
}
}
}
</script>
<style lang="less" scoped>
.ant-modal-body {
padding-top: 0 !important;
}
.ant-form-item {
margin-bottom: 15px;
}
.ant-tabs-nav .ant-tabs-tab {
padding: 10px 16px;
}
</style>
@@ -0,0 +1,108 @@
<template>
<a-modal
class="noborder"
:title="title"
:width="900"
:visible="visible"
:confirmLoading="confirmLoading"
:maskClosable="false"
@cancel="handleCancel"
>
<a-spin :spinning="confirmLoading">
<a-form :form="form">
<a-tabs :activeKey="activeKey" @change="onChangeTabs">
<a-tab-pane :key="RECHARGE_TYPE_BALANCE" tab="公司对接参数">
<template v-if="activeKey === RECHARGE_TYPE_BALANCE">
<a-form-item class="mb-5" label="请求地址" :labelCol="labelCol" :wrapperCol="wrapperCol">
<span>{{ record.url }}</span>
</a-form-item>
<a-form-item class="mb-5" label="公司编号" :labelCol="labelCol" :wrapperCol="wrapperCol">
<span>{{ record.orgNo }}</span>
</a-form-item>
<a-form-item class="mb-5" label="公司appKey" :labelCol="labelCol" :wrapperCol="wrapperCol">
<span>{{ record.appKey }}</span>
</a-form-item>
<a-form-item class="mb-5" label="公司私钥" :labelCol="labelCol" :wrapperCol="wrapperCol">
<span>{{ record.privateKey }}</span>
</a-form-item>
<a-form-item class="mb-5" label="公司公钥" :labelCol="labelCol" :wrapperCol="wrapperCol">
<span>{{ record.publicKey }}</span>
</a-form-item>
<a-form-item class="mb-5" label="平台公司公钥" :labelCol="labelCol" :wrapperCol="wrapperCol">
<span>{{ record.platformPublicKey }}</span>
</a-form-item>
</template>
</a-tab-pane>
</a-tabs>
</a-form>
</a-spin>
</a-modal>
</template>
<script>
// import pick from 'lodash.pick'
// 充值类型枚举: 余额
const RECHARGE_TYPE_BALANCE = 'balance'
export default {
components: {
},
data () {
return {
// 对话框标题
title: '公司对接参数',
// 标签布局属性
labelCol: { span: 7 },
// 输入框布局属性
wrapperCol: { span: 13 },
// modal(对话框)是否可见
visible: false,
// modal(对话框)确定按钮 loading
confirmLoading: false,
// 当前表单元素
form: this.$form.createForm(this),
// 当前tab索引
activeKey: RECHARGE_TYPE_BALANCE,
// 充值类型
RECHARGE_TYPE_BALANCE,
// 当前记录
record: {}
}
},
methods: {
// 显示对话框
handle (record) {
// 显示窗口
this.visible = true
// 当前记录
this.record = record
},
// 切换tabs事件
onChangeTabs (activeKey) {
this.activeKey = activeKey
},
/**
* 关闭对话框事件
*/
handleCancel () {
this.visible = false
this.form.resetFields()
}
}
}
</script>
<style lang="less" scoped>
.ant-modal-body {
padding-top: 0 !important;
}
.ant-form-item {
margin-bottom: 15px;
}
.ant-tabs-nav .ant-tabs-tab {
padding: 10px 16px;
}
</style>
@@ -0,0 +1,190 @@
<template>
<a-modal
class="noborder"
:title="title"
:width="520"
:visible="visible"
:confirmLoading="confirmLoading"
:maskClosable="false"
@ok="handleSubmit"
@cancel="handleCancel"
>
<a-spin :spinning="confirmLoading">
<a-form :form="form">
<a-tabs :activeKey="activeKey" @change="onChangeTabs">
<a-tab-pane :key="RECHARGE_TYPE_BALANCE" tab="充值余额">
<template v-if="activeKey === RECHARGE_TYPE_BALANCE">
<a-form-item class="mb-5" label="公司ID" :labelCol="labelCol" :wrapperCol="wrapperCol">
<span>{{ record.company_id }}</span>
</a-form-item>
<a-form-item class="mb-5" label="当前余额" :labelCol="labelCol" :wrapperCol="wrapperCol">
<span class="c-p">{{ record.balance }}</span>
</a-form-item>
<a-form-item label="充值方式" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-radio-group
v-decorator="[`${RECHARGE_TYPE_BALANCE}.mode`, { initialValue: 'inc', rules: [{ required: true }] }]"
>
<a-radio value="inc">增加</a-radio>
<a-radio value="dec">减少</a-radio>
<a-radio value="final">最终金额</a-radio>
</a-radio-group>
</a-form-item>
<a-form-item label="变更金额" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-input-number
:min="0.01"
v-decorator="[`${RECHARGE_TYPE_BALANCE}.money`, { initialValue: '', rules: [{ required: true, message: '请输入变更的金额' }] }]"
/>
</a-form-item>
<a-form-item label="管理员备注" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-textarea
placeholder="请输入管理员备注"
:rows="3"
v-decorator="[`${RECHARGE_TYPE_BALANCE}.remark`, { rules: [{ required: true, message: '请输入管理员备注' }] }]"
/>
</a-form-item>
</template>
</a-tab-pane>
<!--<a-tab-pane :key="RECHARGE_TYPE_POINTS" tab="充值积分">
<template v-if="activeKey === RECHARGE_TYPE_POINTS">
<a-form-item class="mb-5" label="商户ID" :labelCol="labelCol" :wrapperCol="wrapperCol">
<span>{{ record.company_id }}</span>
</a-form-item>
<a-form-item class="mb-5" label="当前积分" :labelCol="labelCol" :wrapperCol="wrapperCol">
<span class="c-p">{{ record.points }}</span>
</a-form-item>
<a-form-item label="充值方式" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-radio-group
v-decorator="[`${RECHARGE_TYPE_POINTS}.mode`, { initialValue: 'inc', rules: [{ required: true }] }]"
>
<a-radio value="inc">增加</a-radio>
<a-radio value="dec">减少</a-radio>
<a-radio value="final">最终积分</a-radio>
</a-radio-group>
</a-form-item>
<a-form-item label="变更数量" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-input-number
:min="0.01"
v-decorator="[`${RECHARGE_TYPE_POINTS}.value`, { initialValue: '', rules: [{ required: true, message: '请输入变更的金数量' }] }]"
/>
</a-form-item>
<a-form-item label="管理员备注" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-textarea
placeholder="请输入管理员备注"
:rows="3"
v-decorator="[`${RECHARGE_TYPE_POINTS}.remark`, { rules: [{ required: true, message: '请输入管理员备注' }] }]"
/>
</a-form-item>
</template>
</a-tab-pane>-->
</a-tabs>
</a-form>
</a-spin>
</a-modal>
</template>
<script>
// import pick from 'lodash.pick'
import * as Api from '@/api/company'
// 充值类型枚举: 余额
const RECHARGE_TYPE_BALANCE = 'balance'
// 充值类型枚举: 积分
const RECHARGE_TYPE_POINTS = 'points'
export default {
components: {
},
data () {
return {
// 对话框标题
title: '公司充值',
// 标签布局属性
labelCol: { span: 7 },
// 输入框布局属性
wrapperCol: { span: 13 },
// modal(对话框)是否可见
visible: false,
// modal(对话框)确定按钮 loading
confirmLoading: false,
// 当前表单元素
form: this.$form.createForm(this),
// 当前tab索引
activeKey: RECHARGE_TYPE_BALANCE,
// 充值类型
RECHARGE_TYPE_BALANCE,
RECHARGE_TYPE_POINTS,
// 当前记录
record: {}
}
},
methods: {
// 显示对话框
handle (record) {
// 显示窗口
this.visible = true
// 当前记录
this.record = record
},
// 切换tabs事件
onChangeTabs (activeKey) {
this.activeKey = activeKey
},
/**
* 确认按钮
*/
handleSubmit (e) {
e.preventDefault()
// 表单验证
const { form: { validateFields } } = this
validateFields((errors, values) => {
// 提交到后端api
!errors && this.onFormSubmit(values)
})
},
/**
* 关闭对话框事件
*/
handleCancel () {
this.visible = false
this.form.resetFields()
},
/**
* 提交到后端api
*/
onFormSubmit (values) {
const { record, activeKey } = this
this.confirmLoading = true
Api.recharge({ companyId: record.company_id, target: activeKey, form: values })
.then((result) => {
// 显示成功
this.$message.success(result.message, 1.5)
// 关闭对话框事件
this.handleCancel()
// 通知父端组件提交完成了
this.$emit('handleSubmit', values)
})
.finally(() => {
this.confirmLoading = false
})
}
}
}
</script>
<style lang="less" scoped>
.ant-modal-body {
padding-top: 0 !important;
}
.ant-form-item {
margin-bottom: 15px;
}
.ant-tabs-nav .ant-tabs-tab {
padding: 10px 16px;
}
</style>
@@ -0,0 +1,9 @@
import RechargeForm from './RechargeForm'
import ApiParaForm from './ApiParaForm'
import AccountForm from './AccountForm'
export {
RechargeForm,
ApiParaForm,
AccountForm
}
+29
View File
@@ -0,0 +1,29 @@
.tabs-content {
.tab-pane {
animation: fadenum 0.6s ease;
}
}
/deep/.ant-form-item-control {
padding-left: 10px;
.ant-form-item-control {
padding-left: 0;
}
}
.ant-form-item {
margin-bottom: 18px;
.ant-form-item {
margin-bottom: 0;
}
}
.input-number-group {
margin: 3px 0;
}
.company-textarea{
height: 80px;
}
@@ -0,0 +1,246 @@
<template>
<a-card :bordered="false">
<div class="card-title">{{ $route.meta.title }}</div>
<div class="table-operator">
<!-- 搜索板块 -->
<a-row class="row-item-search">
<a-form class="search-form" :form="searchForm" layout="inline" @submit="handleSearch">
<a-form-item label="文章标题">
<a-input v-decorator="['title']" placeholder="请输入文章标题" />
</a-form-item>
<a-form-item label="文章分类">
<a-select v-decorator="['categoryId', { initialValue: -1 }]">
<a-select-option :value="-1">全部</a-select-option>
<a-select-option
v-for="(item, index) in categoryList"
:key="index"
:value="item.category_id"
>{{ item.name }}</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="状态">
<a-select v-decorator="['status', { initialValue: -1 }]">
<a-select-option :value="-1">全部</a-select-option>
<a-select-option :value="1">显示</a-select-option>
<a-select-option :value="0">隐藏</a-select-option>
</a-select>
</a-form-item>
<a-form-item class="search-btn">
<a-button type="primary" icon="search" html-type="submit">搜索</a-button>
</a-form-item>
</a-form>
</a-row>
<!-- 操作板块 -->
<div class="row-item-tab clearfix">
<a-button v-action:add type="primary" icon="plus" @click="handleAdd">新增</a-button>
</div>
</div>
<s-table
ref="table"
rowKey="article_id"
:loading="isLoading"
:columns="columns"
:data="loadData"
:pageSize="15"
:scroll="{ x: 1450 }"
>
<!-- 文章封面图 -->
<span slot="image_url" slot-scope="text">
<a title="点击查看原图" :href="text" target="_blank">
<img height="50" :src="text" alt="封面图" />
</a>
</span>
<!-- 文章标题 -->
<span slot="stitle" slot-scope="text">
<p class="twoline-hide" style="width: 270px;">{{ text }}</p>
</span>
<!-- 所属分类 -->
<span slot="category" slot-scope="text">{{ text.name }}</span>
<!-- 状态 -->
<span slot="status" slot-scope="text">
<a-tag :color="text ? 'green' : ''">{{ text ? '显示' : '隐藏' }}</a-tag>
</span>
<!-- 操作项 -->
<span slot="action" slot-scope="text, item">
<a v-action:edit style="margin-right: 8px;" @click="handleEdit(item)">编辑</a>
<a v-action:delete @click="handleDelete(item)">删除</a>
</span>
</s-table>
<AddForm ref="AddForm" :categoryList="categoryList" @handleSubmit="handleRefresh" />
<EditForm ref="EditForm" :categoryList="categoryList" @handleSubmit="handleRefresh" />
</a-card>
</template>
<script>
import * as ArticleApi from '@/api/content/article'
import * as CategoryApi from '@/api/content/article/category'
import { ContentHeader, STable } from '@/components'
import EditForm from './modules/EditForm'
import AddForm from './modules/AddForm'
// 表格表头
const columns = [
{
title: 'ID',
dataIndex: 'article_id'
},
{
title: '封面图',
dataIndex: 'image_url',
scopedSlots: { customRender: 'image_url' }
},
{
title: '文章标题',
dataIndex: 'title',
width: '320px',
scopedSlots: { customRender: 'stitle' }
},
{
title: '所属分类',
dataIndex: 'category',
scopedSlots: { customRender: 'category' }
},
{
title: '状态',
dataIndex: 'status',
scopedSlots: { customRender: 'status' }
},
{
title: '排序',
dataIndex: 'sort'
},
{
title: '创建时间',
width: '180px',
dataIndex: 'create_time'
},
{
title: '更新时间',
width: '180px',
dataIndex: 'update_time'
},
{
title: '操作',
dataIndex: 'action',
width: '150px',
fixed: 'right',
scopedSlots: { customRender: 'action' }
}
]
export default {
name: 'Index',
components: {
ContentHeader,
STable,
AddForm,
EditForm
},
data () {
return {
expand: false,
// 当前表单元素
searchForm: this.$form.createForm(this),
// 分类列表
categoryList: [],
// 查询参数
queryParam: {},
// 正在加载
isLoading: false,
// 表头
columns,
// 加载数据方法 必须为 Promise 对象
loadData: param => {
return ArticleApi.list({ ...param, ...this.queryParam })
.then(response => {
return response.data.list
})
}
}
},
created () {
// 获取分类列表
this.getCategoryList()
},
methods: {
/**
* 获取分类列表
*/
getCategoryList () {
this.isLoading = true
CategoryApi.list()
.then(result => {
this.categoryList = result.data.list
})
.finally(result => {
this.isLoading = false
})
},
// 确认搜索
handleSearch (e) {
e.preventDefault()
this.searchForm.validateFields((error, values) => {
if (!error) {
this.queryParam = { ...this.queryParam, ...values }
this.handleRefresh(true)
}
})
},
/**
* 删除记录
*/
handleDelete (item) {
const app = this
const modal = this.$confirm({
title: '您确定要删除该记录吗?',
content: '删除后不可恢复',
onOk () {
return ArticleApi.deleted({ articleId: item.article_id })
.then(result => {
app.$message.success(result.message, 1.5)
app.handleRefresh()
})
.finally(result => {
modal.destroy()
})
}
})
},
/**
* 新增记录
*/
handleAdd () {
this.$refs.AddForm.add()
},
/**
* 编辑记录
*/
handleEdit (item) {
this.$refs.EditForm.edit(item.article_id)
},
/**
* 刷新列表
* @param Boolean bool 强制刷新到第一页
*/
handleRefresh (bool = false) {
this.$refs.table.refresh(bool)
}
}
}
</script>
<style lang="less" scoped>
.ant-card-body {
padding: 22px 29px 25px;
}
// 筛选tab
.tab-list {
margin-right: 20px;
}
</style>
@@ -0,0 +1,150 @@
<template>
<a-card :bordered="false">
<div class="card-title">{{ $route.meta.title }}</div>
<div class="table-operator">
<a-button v-action:add type="primary" icon="plus" @click="handleAdd">新增</a-button>
</div>
<a-table
v-if="!isLoading"
rowKey="category_id"
:columns="columns"
:dataSource="categoryList"
:defaultExpandAllRows="true"
:expandIconColumnIndex="1"
:pagination="false"
:loading="isLoading"
>
<!-- 状态 -->
<span slot="status" slot-scope="text">
<a-tag :color="text ? 'green' : ''">{{ text ? '显示' : '隐藏' }}</a-tag>
</span>
<span slot="action" slot-scope="text, item">
<a v-action:edit style="margin-right: 8px;" @click="handleEdit(item)">编辑</a>
<a v-action:delete @click="handleDelete(item)">删除</a>
</span>
</a-table>
<AddForm ref="AddForm" @handleSubmit="handleRefresh" />
<EditForm ref="EditForm" @handleSubmit="handleRefresh" />
</a-card>
</template>
<script>
import * as Api from '@/api/content/article/category'
import { STable } from '@/components'
import AddForm from './modules/AddForm'
import EditForm from './modules/EditForm'
export default {
name: 'Index',
components: {
STable,
AddForm,
EditForm
},
data () {
return {
// 分类列表
categoryList: [],
// 查询参数
queryParam: {},
// 正在加载
isLoading: false,
// 表头
columns: [
{
title: '分类ID',
dataIndex: 'category_id'
},
{
title: '分类名称',
dataIndex: 'name'
},
{
title: '状态',
dataIndex: 'status',
scopedSlots: { customRender: 'status' }
},
{
title: '排序',
dataIndex: 'sort'
},
{
title: '添加时间',
dataIndex: 'create_time'
},
{
title: '操作',
dataIndex: 'action',
width: '180px',
scopedSlots: { customRender: 'action' }
}
]
}
},
created () {
// 获取分类列表
this.getCategoryList(true)
},
methods: {
/**
* 获取分类列表
*/
getCategoryList (bool) {
bool && (this.isLoading = true)
Api.list()
.then(result => {
this.categoryList = result.data.list
})
.finally(result => {
this.isLoading = false
})
},
/**
* 新增记录
*/
handleAdd () {
this.$refs.AddForm.add()
},
/**
* 编辑记录
*/
handleEdit (item) {
this.$refs.EditForm.edit(item)
},
/**
* 删除记录
*/
handleDelete (item) {
const self = this
const modal = this.$confirm({
title: '您确定要删除该记录吗?',
content: '删除后不可恢复',
onOk () {
return Api.deleted({ categoryId: item['category_id'] })
.then((result) => {
self.$message.success(result.message, 1.5)
self.handleRefresh()
})
.finally(result => {
modal.destroy()
})
}
})
},
/**
* 刷新列表
* @param Boolean bool 强制刷新到第一页
*/
handleRefresh () {
// 获取分类列表
this.getCategoryList()
}
}
}
</script>
@@ -0,0 +1,113 @@
<template>
<a-modal
title="编辑商品分类"
:width="720"
:visible="visible"
:confirmLoading="confirmLoading"
:maskClosable="false"
@ok="handleSubmit"
@cancel="handleCancel"
>
<a-spin :spinning="confirmLoading">
<a-form :form="form">
<a-form-item label="分类名称" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-input
v-decorator="['name', {rules: [{required: true, min: 2, message: '请输入至少2个字符'}]}]"
/>
</a-form-item>
<a-form-item label="状态" :labelCol="labelCol" :wrapperCol="wrapperCol" extra="用户端是否展示">
<a-radio-group v-decorator="['status', {initialValue: 1, rules: [{required: true}]}]">
<a-radio :value="1">显示</a-radio>
<a-radio :value="0">隐藏</a-radio>
</a-radio-group>
</a-form-item>
<a-form-item label="排序" :labelCol="labelCol" :wrapperCol="wrapperCol" extra="数字越小越靠前">
<a-input-number
:min="0"
v-decorator="['sort', {initialValue: 100, rules: [{required: true, message: '请输入至少1个数字'}]}]"
/>
</a-form-item>
</a-form>
</a-spin>
</a-modal>
</template>
<script>
import * as Api from '@/api/content/article/category'
export default {
data () {
return {
// 对话框标题
title: '',
// 标签布局属性
labelCol: {
span: 7
},
// 输入框布局属性
wrapperCol: {
span: 13
},
// modal(对话框)是否可见
visible: false,
// modal(对话框)确定按钮 loading
confirmLoading: false,
// 当前表单元素
form: this.$form.createForm(this),
}
},
methods: {
/**
* 显示对话框
*/
add () {
// 显示窗口
this.visible = true
},
/**
* 确认按钮
*/
handleSubmit (e) {
e.preventDefault()
// 表单验证
const { form: { validateFields } } = this
validateFields((errors, values) => {
// 提交到后端api
if (!errors) {
this.onFormSubmit(values)
}
})
},
/**
* 关闭对话框事件
*/
handleCancel () {
this.visible = false
this.form.resetFields()
},
/**
* 提交到后端api
*/
onFormSubmit (values) {
this.confirmLoading = true
Api.add({ form: values })
.then((result) => {
// 显示成功
this.$message.success(result.message, 1.5)
// 关闭对话框事件
this.handleCancel()
// 通知父端组件提交完成了
this.$emit('handleSubmit', values)
})
.finally((result) => {
this.confirmLoading = false
})
}
}
}
</script>
@@ -0,0 +1,130 @@
<template>
<a-modal
title="编辑商品分类"
:width="720"
:visible="visible"
:confirmLoading="confirmLoading"
:maskClosable="false"
:destroyOnClose="true"
@ok="handleSubmit"
@cancel="handleCancel"
>
<a-spin :spinning="confirmLoading">
<a-form :form="form">
<a-form-item label="分类名称" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-input
v-decorator="['name', {rules: [{required: true, min: 2, message: '请输入至少2个字符'}]}]"
/>
</a-form-item>
<a-form-item label="状态" :labelCol="labelCol" :wrapperCol="wrapperCol" extra="用户端是否展示">
<a-radio-group v-decorator="['status', {initialValue: 1, rules: [{required: true}]}]">
<a-radio :value="1">显示</a-radio>
<a-radio :value="0">隐藏</a-radio>
</a-radio-group>
</a-form-item>
<a-form-item label="排序" :labelCol="labelCol" :wrapperCol="wrapperCol" extra="数字越小越靠前">
<a-input-number
:min="0"
v-decorator="['sort', {initialValue: 100, rules: [{required: true, message: '请输入至少1个数字'}]}]"
/>
</a-form-item>
</a-form>
</a-spin>
</a-modal>
</template>
<script>
import pick from 'lodash.pick'
import * as Api from '@/api/content/article/category'
export default {
data () {
return {
// 对话框标题
title: '',
// 标签布局属性
labelCol: {
span: 7
},
// 输入框布局属性
wrapperCol: {
span: 13
},
// modal(对话框)是否可见
visible: false,
// modal(对话框)确定按钮 loading
confirmLoading: false,
// 当前表单元素
form: this.$form.createForm(this),
// 当前记录
record: {}
}
},
methods: {
/**
* 显示对话框
*/
edit (record) {
// 显示窗口
this.visible = true
// 当前分类记录
this.record = record
// 设置默认值
this.setFieldsValue()
},
/**
* 设置默认值
*/
setFieldsValue () {
const { $nextTick, form: { setFieldsValue } } = this
$nextTick(() => {
setFieldsValue(pick(this.record, ['name', 'status', 'sort']))
})
},
/**
* 确认按钮
*/
handleSubmit (e) {
e.preventDefault()
// 表单验证
const { form: { validateFields } } = this
validateFields((errors, values) => {
// 提交到后端api
if (!errors) {
this.onFormSubmit(values)
}
})
},
/**
* 关闭对话框事件
*/
handleCancel () {
this.visible = false
this.form.resetFields()
},
/**
* 提交到后端api
*/
onFormSubmit (values) {
this.confirmLoading = true
Api.edit({ categoryId: this.record['category_id'], form: values })
.then((result) => {
// 显示成功
this.$message.success(result.message, 1.5)
// 关闭对话框事件
this.handleCancel()
// 通知父端组件提交完成了
this.$emit('handleSubmit', values)
})
.finally((result) => {
this.confirmLoading = false
})
}
}
}
</script>
@@ -0,0 +1,162 @@
<template>
<a-modal
:title="title"
:width="780"
:visible="visible"
:confirmLoading="confirmLoading"
:maskClosable="false"
:destroyOnClose="true"
@ok="handleSubmit"
@cancel="handleCancel"
>
<a-spin :spinning="confirmLoading">
<a-form :form="form">
<a-form-item label="文章标题" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-input
v-decorator="['title', {rules: [{required: true, min: 2, message: '请输入至少2个字符'}]}]"
/>
</a-form-item>
<a-form-item label="文章分类" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-select
v-decorator="['category_id', {rules: [{required: true, message: '请选择1个文章分类'}]}]"
>
<a-select-option
v-for="(item, index) in categoryList"
:key="index"
:value="item.category_id"
>{{ item.name }}</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="列表显示方式" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-radio-group v-decorator="['show_type', {initialValue: 10, rules: [{required: true}]}]">
<a-radio :value="10">小图模式</a-radio>
<a-radio :value="20">大图模式</a-radio>
</a-radio-group>
<div class="form-item-help">
<p class="extra">小图模式建议封面图尺寸:300 * 188</p>
<p class="extra">大图模式建议封面图尺寸:750 * 455</p>
</div>
</a-form-item>
<a-form-item label="封面图" :labelCol="labelCol" :wrapperCol="wrapperCol">
<SelectImage
v-decorator="['image_id', {rules: [{required: true, message: '请选择1个封面图'}]}]"
/>
</a-form-item>
<a-form-item label="文章内容" :labelCol="labelCol" :wrapperCol="wrapperCol">
<Ueditor v-decorator="['content', {rules: [{required: true, message: '文章内容不能为空'}]}]" />
</a-form-item>
<a-form-item
label="虚拟阅读量"
:labelCol="labelCol"
:wrapperCol="wrapperCol"
extra="用户看到的阅读量 = 实际阅读量 + 虚拟阅读量"
>
<a-input-number :min="0" v-decorator="['virtual_views', {initialValue: 100}]" />
</a-form-item>
<a-form-item label="状态" :labelCol="labelCol" :wrapperCol="wrapperCol" extra="用户端是否展示">
<a-radio-group v-decorator="['status', {initialValue: 1, rules: [{required: true}]}]">
<a-radio :value="1">显示</a-radio>
<a-radio :value="0">隐藏</a-radio>
</a-radio-group>
</a-form-item>
<a-form-item label="排序" :labelCol="labelCol" :wrapperCol="wrapperCol" extra="数字越小越靠前">
<a-input-number
:min="0"
v-decorator="['sort', {initialValue: 100, rules: [{required: true, message: '请输入至少1个数字'}]}]"
/>
</a-form-item>
</a-form>
</a-spin>
</a-modal>
</template>
<script>
import pick from 'lodash.pick'
import * as Api from '@/api/content/article'
import { SelectImage, Ueditor } from '@/components'
export default {
components: {
SelectImage,
Ueditor
},
props: {
// 分类列表
categoryList: {
type: Array,
required: true
}
},
data () {
return {
// 对话框标题
title: '新增文章',
// 标签布局属性
labelCol: {
span: 7
},
// 输入框布局属性
wrapperCol: {
span: 13
},
// modal(对话框)是否可见
visible: false,
// modal(对话框)确定按钮 loading
confirmLoading: false,
// 当前表单元素
form: this.$form.createForm(this)
}
},
methods: {
/**
* 显示对话框
*/
add () {
// 显示窗口
this.visible = true
},
/**
* 确认按钮
*/
handleSubmit (e) {
e.preventDefault()
// 表单验证
const { form: { validateFields } } = this
validateFields((errors, values) => {
// 提交到后端api
!errors && this.onFormSubmit(values)
})
},
/**
* 关闭对话框事件
*/
handleCancel () {
this.visible = false
this.form.resetFields()
},
/**
* 提交到后端api
*/
onFormSubmit (values) {
this.confirmLoading = true
Api.add({ form: values })
.then((result) => {
// 显示成功
this.$message.success(result.message, 1.5)
// 关闭对话框事件
this.handleCancel()
// 通知父端组件提交完成了
this.$emit('handleSubmit', values)
})
.finally((result) => {
this.confirmLoading = false
})
}
}
}
</script>
@@ -0,0 +1,200 @@
<template>
<a-modal
:title="title"
:width="780"
:visible="visible"
:confirmLoading="confirmLoading"
:maskClosable="false"
:destroyOnClose="true"
@ok="handleSubmit"
@cancel="handleCancel"
>
<a-spin :spinning="confirmLoading">
<a-form :form="form">
<a-form-item label="文章标题" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-input
v-decorator="['title', {rules: [{required: true, min: 2, message: '请输入至少2个字符'}]}]"
/>
</a-form-item>
<a-form-item label="文章分类" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-select
v-decorator="['category_id', {rules: [{required: true, message: '请选择1个文章分类'}]}]"
>
<a-select-option
v-for="(item, index) in categoryList"
:key="index"
:value="item.category_id"
>{{ item.name }}</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="列表显示方式" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-radio-group v-decorator="['show_type', {initialValue: 10, rules: [{required: true}]}]">
<a-radio :value="10">小图模式</a-radio>
<a-radio :value="20">大图模式</a-radio>
</a-radio-group>
<div class="form-item-help">
<p class="extra">小图模式建议封面图尺寸:300 * 188</p>
<p class="extra">大图模式建议封面图尺寸:750 * 455</p>
</div>
</a-form-item>
<a-form-item label="封面图" :labelCol="labelCol" :wrapperCol="wrapperCol">
<SelectImage
:defaultList="record.image ? [record.image] : []"
v-decorator="['image_id', { rules: [{ required: true, message: '请选择1个封面图' }] }]"
/>
</a-form-item>
<a-form-item label="文章内容" :labelCol="labelCol" :wrapperCol="wrapperCol">
<Ueditor v-decorator="['content', { rules: [{ required: true, message: '文章内容不能为空' }] }]" />
</a-form-item>
<a-form-item
label="虚拟阅读量"
:labelCol="labelCol"
:wrapperCol="wrapperCol"
extra="用户看到的阅读量 = 实际阅读量 + 虚拟阅读量"
>
<a-input-number :min="0" v-decorator="['virtual_views', {initialValue: 100}]" />
</a-form-item>
<a-form-item label="状态" :labelCol="labelCol" :wrapperCol="wrapperCol" extra="用户端是否展示">
<a-radio-group v-decorator="['status', {initialValue: 1, rules: [{required: true}]}]">
<a-radio :value="1">显示</a-radio>
<a-radio :value="0">隐藏</a-radio>
</a-radio-group>
</a-form-item>
<a-form-item label="排序" :labelCol="labelCol" :wrapperCol="wrapperCol" extra="数字越小越靠前">
<a-input-number
:min="0"
v-decorator="['sort', {initialValue: 100, rules: [{required: true, message: '请输入至少1个数字'}]}]"
/>
</a-form-item>
</a-form>
</a-spin>
</a-modal>
</template>
<script>
import pick from 'lodash.pick'
import * as Api from '@/api/content/article'
import { SelectImage, Ueditor } from '@/components'
export default {
components: {
SelectImage,
Ueditor
},
props: {
// 分类列表
categoryList: {
type: Array,
required: true
}
},
data () {
return {
// 对话框标题
title: '编辑文章',
// 标签布局属性
labelCol: {
span: 7
},
// 输入框布局属性
wrapperCol: {
span: 13
},
// modal(对话框)是否可见
visible: false,
// modal(对话框)确定按钮 loading
confirmLoading: false,
// 当前表单元素
form: this.$form.createForm(this),
// 当前记录ID
articleId: null,
// 当前记录详情
record: {}
}
},
methods: {
/**
* 显示对话框
*/
edit (articleId) {
// 显示窗口
this.visible = true
// 当前记录ID
this.articleId = articleId
// 获取当前详情记录
this.getDetail()
},
// 获取当前详情记录
getDetail () {
this.confirmLoading = true
Api.detail({ articleId: this.articleId })
.then(result => {
// 当前记录
this.record = result.data.detail
// 设置默认值
this.setFieldsValue()
})
.finally((result) => {
this.confirmLoading = false
})
},
/**
* 设置默认值
*/
setFieldsValue () {
const { form: { setFieldsValue } } = this
this.$nextTick(() => {
setFieldsValue(pick(this.record, [
'title', 'show_type', 'category_id', 'image_id', 'content', 'sort', 'status', 'virtual_views'
]))
})
},
/**
* 确认按钮
*/
handleSubmit (e) {
e.preventDefault()
// 表单验证
const { form: { validateFields } } = this
validateFields((errors, values) => {
// 提交到后端api
!errors && this.onFormSubmit(values)
})
},
/**
* 关闭对话框事件
*/
handleCancel () {
this.visible = false
this.record = {}
this.form.resetFields()
},
/**
* 提交到后端api
*/
onFormSubmit (values) {
this.confirmLoading = true
Api.edit({ articleId: this.articleId, form: values })
.then((result) => {
// 显示成功
this.$message.success(result.message, 1.5)
// 关闭对话框事件
this.handleCancel()
// 通知父端组件提交完成了
this.$emit('handleSubmit', values)
})
.finally((result) => {
this.confirmLoading = false
})
}
}
}
</script>
+366
View File
@@ -0,0 +1,366 @@
<template>
<a-card :bordered="false">
<div class="card-title">{{ $route.meta.title }}</div>
<div class="table-operator">
<!-- 搜索板块 -->
<a-row class="row-item-search">
<a-form class="search-form" :form="searchForm" layout="inline" @submit="handleSearch">
<a-form-item label="文件名称">
<a-input v-decorator="['fileName']" placeholder="请输入文件名称" />
</a-form-item>
<a-form-item label="文件分组">
<a-tree-select
treeDefaultExpandAll
allowClear
:treeData="groupListTreeSelect"
:dropdownStyle="{ maxHeight: '500px', overflow: 'auto' }"
v-decorator="['groupId', { initialValue: -1 }]"
></a-tree-select>
</a-form-item>
<a-form-item label="存储方式">
<a-select v-decorator="['storage', { initialValue: '' }]">
<a-select-option :value="''">全部</a-select-option>
<a-select-option
v-for="(item, index) in StorageEnum.data"
:key="index"
:value="item.value"
>{{ item.name }}</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="上传来源">
<a-select v-decorator="['channel', { initialValue: -1 }]">
<a-select-option :value="-1">全部</a-select-option>
<a-select-option
v-for="(item, index) in ChannelEnum.data"
:key="index"
:value="item.value"
>{{ item.name }}</a-select-option>
</a-select>
</a-form-item>
<a-form-item class="search-btn">
<a-button type="primary" icon="search" html-type="submit">搜索</a-button>
</a-form-item>
<a-form-item class="search-btn">
<a-button @click="handleReset">重置</a-button>
</a-form-item>
</a-form>
</a-row>
<!-- 操作板块 -->
<div class="row-item-tab clearfix">
<div class="tab-list fl-l">
<a-radio-group :defaultValue="queryParam.fileType" @change="handleTabs">
<a-radio-button :value="-1">全部</a-radio-button>
<a-radio-button
v-for="(item, index) in FileTypeEnum.data"
:key="index"
:value="item.value"
>{{ item.name }}</a-radio-button>
</a-radio-group>
</div>
<div v-if="selectedRowKeys.length" class="button-group">
<a-button-group>
<a-button icon="swap" @click="handleBatchMove">移动</a-button>
<a-button icon="delete" @click="handleBatchDelete">删除</a-button>
</a-button-group>
</div>
</div>
</div>
<!-- 列表内容 -->
<s-table
ref="table"
rowKey="file_id"
:loading="isLoading"
:columns="columns"
:data="loadData"
:rowSelection="rowSelection"
:pageSize="15"
:scroll="{ x: 1450 }"
>
<!-- 文件预览 -->
<span slot="preview_url" slot-scope="text, item">
<div class="preview-box">
<a :href="item.external_url" target="_blank">
<img :src="text" />
</a>
</div>
</span>
<!-- 存储方式 -->
<span slot="storage" slot-scope="text">
<a-tag>{{ StorageEnum[text].name }}</a-tag>
</span>
<!-- 文件类型 -->
<span slot="file_type" slot-scope="text">
<a-tag>{{ FileTypeEnum[text].name }}</a-tag>
</span>
<!-- 操作 -->
<span slot="action" slot-scope="text, item">
<a v-action:edit style="margin-right: 8px;" @click="handleEdit(item)">编辑</a>
<a v-action:delete @click="handleDelete(item)">删除</a>
</span>
</s-table>
<!-- 移动分组 -->
<MoveGroupForm ref="MoveGroupForm" :groupList="groupListTree" @handleSubmit="handleRefresh" />
<!-- 编辑记录 -->
<EditForm ref="EditForm" :groupList="groupListTree" @handleSubmit="handleRefresh" />
</a-card>
</template>
<script>
import * as Api from '@/api/files'
import * as GroupApi from '@/api/files/group'
import { STable } from '@/components'
import MoveGroupForm from './modules/MoveGroupForm'
import EditForm from './modules/EditForm'
import FileTypeEnum from '@/common/enum/file/FileType'
import StorageEnum from '@/common/enum/file/Storage'
import ChannelEnum from '@/common/enum/file/Channel'
export default {
name: 'Index',
components: {
STable,
MoveGroupForm,
EditForm
},
data () {
return {
// 枚举数据
FileTypeEnum,
StorageEnum,
ChannelEnum,
// 当前表单元素
searchForm: this.$form.createForm(this),
// 查询参数
queryParam: { fileType: -1 },
// 正在加载
isLoading: false,
// 表头
columns: [
{
title: '文件ID',
dataIndex: 'file_id'
},
{
title: '文件预览',
dataIndex: 'preview_url',
scopedSlots: { customRender: 'preview_url' }
},
{
title: '文件名称',
width: '302px',
dataIndex: 'file_name'
},
{
title: '存储方式',
dataIndex: 'storage',
scopedSlots: { customRender: 'storage' }
},
{
title: '文件类型',
dataIndex: 'file_type',
scopedSlots: { customRender: 'file_type' }
},
{
title: '文件大小(字节)',
dataIndex: 'file_size'
},
{
title: '文件后缀',
dataIndex: 'file_ext'
},
{
title: '上传时间',
width: '180px',
dataIndex: 'create_time'
},
{
title: '操作',
dataIndex: 'action',
width: '150px',
fixed: 'right',
scopedSlots: { customRender: 'action' }
}
],
selectedRowKeys: [],
// 加载数据方法 必须为 Promise 对象
loadData: param => {
return Api.list({ ...param, ...this.queryParam })
.then(response => {
return response.data.list
})
},
// 文件分组列表(树状结构)
groupListTree: [],
// 文件分组列表(用于筛选组件)
groupListTreeSelect: []
}
},
created () {
// 获取分组列表
this.getGroupList()
},
computed: {
rowSelection () {
return {
selectedRowKeys: this.selectedRowKeys,
onChange: this.onSelectChange
}
}
},
methods: {
/**
* 选中项发生变化时的回调
*/
onSelectChange (selectedRowKeys) {
this.selectedRowKeys = selectedRowKeys
},
// 切换tab
handleTabs (e) {
this.queryParam.fileType = e.target.value
this.handleRefresh()
},
// 确认搜索
handleSearch (e) {
e.preventDefault()
this.searchForm.validateFields((error, values) => {
if (!error) {
this.queryParam = { ...this.queryParam, ...values }
this.handleRefresh()
}
})
},
// 重置搜索表单
handleReset () {
this.selectedRowKeys = []
this.searchForm.resetFields()
},
/**
* 获取分组列表
*/
getGroupList () {
this.isLoading = true
GroupApi.list().then(result => {
const { list } = result.data
// 格式化分组列表
const groupListTree = this.formatTreeData(list)
// 记录到data
this.groupListTree = groupListTree
this.groupListTreeSelect = [{
title: '全部',
key: -1,
value: -1
}, {
title: '未分组',
key: 0,
value: 0
}].concat(groupListTree)
})
.finally(result => {
this.isLoading = false
})
},
/**
* 格式化分组列表
*/
formatTreeData (list) {
const data = []
list.forEach(item => {
// 新的元素
const netItem = {
title: item.name,
key: item.group_id,
value: item.group_id
}
// 递归整理子集
if (item.children && item.children.length) {
netItem['children'] = this.formatTreeData(item['children'])
}
data.push(netItem)
})
return data
},
/**
* 编辑记录
*/
handleEdit (item) {
this.$refs.EditForm.edit(item)
},
/**
* 删除记录
*/
handleDelete (item) {
this.onDelete([item['file_id']])
},
// 批量删除
handleBatchDelete () {
this.onDelete(this.selectedRowKeys)
},
/**
* 执行删除
*/
onDelete (fileIds) {
const that = this
const modal = this.$confirm({
title: '您确定要删除该文件吗?',
content: '删除后不可恢复,请谨慎操作',
onOk () {
return Api.deleted({ fileIds })
.then(result => {
that.$message.success(result.message, 1.5)
that.handleRefresh()
})
.catch(() => true)
.finally(result => modal.destroy())
}
})
},
/**
* 批量移动文件
*/
handleBatchMove () {
this.$refs.MoveGroupForm.show(this.selectedRowKeys)
},
/**
* 刷新列表
* @param Boolean bool 强制刷新到第一页
*/
handleRefresh (bool = true) {
this.selectedRowKeys = []
this.$refs.table.refresh(bool)
}
}
}
</script>
<style lang="less" scoped>
.ant-card-body {
padding: 22px 29px 25px;
}
// 筛选tab
.tab-list {
margin-right: 20px;
}
// 预览图
.preview-box {
width: 120px;
height: 80px;
line-height: 80px;
img {
display: block;
max-width: 100%;
max-height: 100%;
}
}
</style>
@@ -0,0 +1,141 @@
<template>
<a-card :bordered="false">
<div class="card-title">{{ $route.meta.title }}</div>
<div class="table-operator">
<a-button v-action:add type="primary" icon="plus" @click="handleAdd">新增</a-button>
</div>
<a-table
v-if="!isLoading"
rowKey="group_id"
:columns="columns"
:dataSource="groupList"
:defaultExpandAllRows="true"
:expandIconColumnIndex="1"
:pagination="false"
:loading="isLoading"
>
<span slot="action" slot-scope="text, item">
<a v-action:edit style="margin-right: 8px;" @click="handleEdit(item)">编辑</a>
<a v-action:delete @click="handleDelete(item)">删除</a>
</span>
</a-table>
<AddForm ref="AddForm" :groupList="groupList" @handleSubmit="handleRefresh" />
<EditForm ref="EditForm" :groupList="groupList" @handleSubmit="handleRefresh" />
</a-card>
</template>
<script>
import * as Api from '@/api/files/group'
import { STable } from '@/components'
import AddForm from './modules/AddForm'
import EditForm from './modules/EditForm'
export default {
name: 'Index',
components: {
STable,
AddForm,
EditForm
},
data () {
return {
// 分组列表
groupList: [],
// 查询参数
queryParam: {},
// 正在加载
isLoading: false,
// 表头
columns: [
{
title: '分组ID',
dataIndex: 'group_id'
},
{
title: '分组名称',
dataIndex: 'name'
},
{
title: '排序',
dataIndex: 'sort'
},
{
title: '添加时间',
dataIndex: 'create_time'
},
{
title: '操作',
dataIndex: 'action',
width: '180px',
scopedSlots: { customRender: 'action' }
}
]
}
},
created () {
// 获取分组列表
this.getGroupList()
},
methods: {
/**
* 获取分组列表
*/
getGroupList () {
this.isLoading = true
Api.list()
.then(result => {
this.groupList = result.data.list
})
.finally(result => {
this.isLoading = false
})
},
/**
* 新增记录
*/
handleAdd () {
this.$refs.AddForm.add()
},
/**
* 编辑记录
*/
handleEdit (item) {
this.$refs.EditForm.edit(item)
},
/**
* 删除记录
*/
handleDelete (item) {
const self = this
const modal = this.$confirm({
title: '您确定要删除该记录吗?',
content: '删除后不可恢复',
onOk () {
return Api.deleted({ groupId: item['group_id'] })
.then((result) => {
self.$message.success(result.message, 1.5)
self.handleRefresh()
})
.finally(result => {
modal.destroy()
})
}
})
},
/**
* 刷新列表
* @param Boolean bool 强制刷新到第一页
*/
handleRefresh () {
// 获取分组列表
this.getGroupList()
}
}
}
</script>
@@ -0,0 +1,161 @@
<template>
<a-modal
title="新增文件分组"
:width="720"
:visible="visible"
:confirmLoading="confirmLoading"
:maskClosable="false"
@ok="handleSubmit"
@cancel="handleCancel"
>
<a-spin :spinning="confirmLoading">
<a-form :form="form">
<a-form-item label="分组名称" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-input
v-decorator="['name', {rules: [{required: true, min: 2, message: '请输入至少2个字符'}]}]"
/>
</a-form-item>
<a-form-item label="上级分组" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-tree-select
:treeData="groupListTree"
:dropdownStyle="{ maxHeight: '400px', overflow: 'auto' }"
allowClear
v-decorator="['parent_id', {initialValue: 0}]"
></a-tree-select>
</a-form-item>
<a-form-item label="排序" :labelCol="labelCol" :wrapperCol="wrapperCol" extra="数字越小越靠前">
<a-input-number
:min="0"
v-decorator="['sort', {initialValue: 100, rules: [{required: true, message: '请输入至少1个数字'}]}]"
/>
</a-form-item>
</a-form>
</a-spin>
</a-modal>
</template>
<script>
import * as Api from '@/api/files/group'
export default {
props: {
// 分组列表
groupList: {
type: Array,
required: true
}
},
data () {
return {
// 对话框标题
title: '',
// 标签布局属性
labelCol: {
span: 7
},
// 输入框布局属性
wrapperCol: {
span: 13
},
// modal(对话框)是否可见
visible: false,
// modal(对话框)确定按钮 loading
confirmLoading: false,
// 当前表单元素
form: this.$form.createForm(this),
// 上级分组列表
groupListTree: []
}
},
methods: {
/**
* 显示对话框
*/
add () {
// 显示窗口
this.visible = true
// 获取分组列表
this.getGroupList()
},
/**
* 获取分组列表
*/
getGroupList () {
const { groupList } = this
// 格式化分组列表
this.groupListTree = [{
title: '顶级分组',
key: 0,
value: 0
}].concat(this.formatTreeData(groupList))
},
/**
* 格式化分组列表
*/
formatTreeData (list, disabled = false) {
const data = []
list.forEach(item => {
// 新的元素
const netItem = {
title: item.name,
key: item.group_id,
value: item.group_id
}
// 递归整理子集
if (item.children && item.children.length) {
netItem['children'] = this.formatTreeData(item['children'], netItem.disabled)
}
data.push(netItem)
})
return data
},
/**
* 确认按钮
*/
handleSubmit (e) {
e.preventDefault()
// 表单验证
const { form: { validateFields } } = this
validateFields((errors, values) => {
// 提交到后端api
if (!errors) {
this.onFormSubmit(values)
}
})
},
/**
* 关闭对话框事件
*/
handleCancel () {
this.visible = false
this.form.resetFields()
},
/**
* 提交到后端api
*/
onFormSubmit (values) {
this.confirmLoading = true
Api.add({ form: values })
.then((result) => {
// 显示成功
this.$message.success(result.message, 1.5)
// 关闭对话框事件
this.handleCancel()
// 通知父端组件提交完成了
this.$emit('handleSubmit', values)
})
.finally((result) => {
this.confirmLoading = false
})
}
}
}
</script>
@@ -0,0 +1,188 @@
<template>
<a-modal
title="编辑文件分组"
:width="720"
:visible="visible"
:confirmLoading="confirmLoading"
:maskClosable="false"
@ok="handleSubmit"
@cancel="handleCancel"
>
<a-spin :spinning="confirmLoading">
<a-form :form="form">
<a-form-item label="分组名称" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-input
v-decorator="['name', {rules: [{required: true, min: 2, message: '请输入至少2个字符'}]}]"
/>
</a-form-item>
<a-form-item label="上级分组" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-tree-select
:treeData="groupListTree"
:dropdownStyle="{ maxHeight: '400px', overflow: 'auto' }"
allowClear
v-decorator="['parent_id']"
></a-tree-select>
</a-form-item>
<a-form-item label="排序" :labelCol="labelCol" :wrapperCol="wrapperCol" extra="数字越小越靠前">
<a-input-number
:min="0"
v-decorator="['sort', {initialValue: 100, rules: [{required: true, message: '请输入至少1个数字'}]}]"
/>
</a-form-item>
</a-form>
</a-spin>
</a-modal>
</template>
<script>
import * as Api from '@/api/files/group'
import _ from 'lodash'
export default {
props: {
// 分组列表
groupList: {
type: Array,
required: true
}
},
data () {
return {
// 对话框标题
title: '',
// 标签布局属性
labelCol: {
span: 7
},
// 输入框布局属性
wrapperCol: {
span: 13
},
// modal(对话框)是否可见
visible: false,
// modal(对话框)确定按钮 loading
confirmLoading: false,
// 当前表单元素
form: this.$form.createForm(this),
// 当前记录
record: {},
// 上级分组列表
groupListTree: []
}
},
methods: {
/**
* 显示对话框
*/
edit (record) {
// 显示窗口
this.visible = true
// 当前分组记录
this.record = record
// 获取分组列表
this.getGroupList()
// 设置默认值
this.setFieldsValue()
},
/**
* 设置默认值
*/
setFieldsValue () {
const { $nextTick, form: { setFieldsValue } } = this
$nextTick(() => {
setFieldsValue(_.pick(this.record, ['name', 'parent_id', 'sort']))
})
},
/**
* 获取分组列表
*/
getGroupList () {
const { groupList } = this
// 格式化分组列表
const selectList = this.formatTreeData(groupList)
// 顶级分组
selectList.unshift({
title: '顶级分组',
key: 0,
value: 0
})
this.groupListTree = selectList
},
/**
* 格式化分组列表
*/
formatTreeData (list, disabled = false) {
const data = []
list.forEach(item => {
// 新的元素
const netItem = {
title: item.name,
key: item.group_id,
value: item.group_id
}
// 禁用的分组
if (
[item.group_id, item.parent_id].includes(this.record.group_id) ||
disabled === true
) {
netItem.disabled = true
}
// 递归整理子集
if (item.children && item.children.length) {
netItem['children'] = this.formatTreeData(item['children'], netItem.disabled)
}
data.push(netItem)
})
return data
},
/**
* 确认按钮
*/
handleSubmit (e) {
e.preventDefault()
// 表单验证
const { form: { validateFields } } = this
validateFields((errors, values) => {
// 提交到后端api
if (!errors) {
this.onFormSubmit(values)
}
})
},
/**
* 关闭对话框事件
*/
handleCancel () {
this.visible = false
this.form.resetFields()
},
/**
* 提交到后端api
*/
onFormSubmit (values) {
this.confirmLoading = true
Api.edit({ groupId: this.record['group_id'], form: values })
.then((result) => {
// 显示成功
this.$message.success(result.message, 1.5)
// 关闭对话框事件
this.handleCancel()
// 通知父端组件提交完成了
this.$emit('handleSubmit', values)
})
.finally((result) => {
this.confirmLoading = false
})
}
}
}
</script>
@@ -0,0 +1,152 @@
<template>
<a-modal
title="编辑文件"
:width="720"
:visible="visible"
:confirmLoading="confirmLoading"
:maskClosable="false"
@ok="handleSubmit"
@cancel="handleCancel"
>
<a-spin :spinning="confirmLoading">
<a-form :form="form">
<a-form-item label="文件名称" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-input
v-decorator="['file_name', {rules: [{required: true, min: 2, message: '请输入至少2个字符'}]}]"
/>
</a-form-item>
<a-form-item label="上级分组" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-tree-select
:treeData="groupTreeData"
:dropdownStyle="{ maxHeight: '400px', overflow: 'auto' }"
allowClear
v-decorator="['group_id']"
></a-tree-select>
</a-form-item>
</a-form>
</a-spin>
</a-modal>
</template>
<script>
import * as Api from '@/api/files'
import _ from 'lodash'
export default {
props: {
// 分组列表
groupList: {
type: Array,
required: true
}
},
data () {
return {
// 对话框标题
title: '',
// 标签布局属性
labelCol: {
span: 7
},
// 输入框布局属性
wrapperCol: {
span: 13
},
// modal(对话框)是否可见
visible: false,
// modal(对话框)确定按钮 loading
confirmLoading: false,
// 当前表单元素
form: this.$form.createForm(this),
// 当前记录
record: {},
// 分组列表 树状结构
groupTreeData: []
}
},
methods: {
/**
* 显示对话框
*/
edit (record) {
// 显示窗口
this.visible = true
// 当前分组记录
this.record = record
// 获取分组列表
this.getGroupList()
// 设置默认值
this.setFieldsValue()
},
/**
* 设置默认值
*/
setFieldsValue () {
const { $nextTick, form: { setFieldsValue } } = this
$nextTick(() => {
setFieldsValue(_.pick(this.record, ['file_name', 'group_id']))
})
},
/**
* 获取分组列表
*/
getGroupList () {
if (this.groupTreeData.length <= 0) {
this.groupTreeData = [{
title: '未分组',
key: 0,
value: 0
}].concat(this.groupList)
}
},
/**
* 确认按钮
*/
handleSubmit (e) {
e.preventDefault()
// 表单验证
const { form: { validateFields } } = this
validateFields((errors, values) => {
// 提交到后端api
if (!errors) {
values.status = values.status ? 1 : 0
this.onFormSubmit(values)
}
})
},
/**
* 关闭对话框事件
*/
handleCancel () {
this.visible = false
this.form.resetFields()
},
/**
* 提交到后端api
*/
onFormSubmit (values) {
this.confirmLoading = true
Api.edit({ fileId: this.record['file_id'], form: values })
.then((result) => {
// 显示成功
this.$message.success(result.message, 1.5)
// 关闭对话框事件
this.handleCancel()
// 通知父端组件提交完成了
this.$emit('handleSubmit', values)
})
.finally((result) => {
this.confirmLoading = false
})
}
}
}
</script>
@@ -0,0 +1,136 @@
<template>
<a-modal
:title="title"
:width="420"
:visible="visible"
:confirmLoading="confirmLoading"
:maskClosable="false"
@ok="handleSubmit"
@cancel="handleCancel"
>
<a-spin :spinning="confirmLoading">
<a-tree
v-if="groupTreeData.length"
:selectable="true"
:blockNode="true"
:treeData="groupTreeData"
:autoExpandParent="true"
@select="onSelect"
/>
</a-spin>
</a-modal>
</template>
<script>
import * as Api from '@/api/files'
export default {
props: {
// 分组列表
groupList: {
type: Array,
required: true
}
},
data () {
return {
// 对话框标题
title: '移动到分组',
// 标签布局属性
labelCol: {
xs: { span: 24 },
sm: { span: 7 }
},
// 输入框布局属性
wrapperCol: {
xs: { span: 24 },
sm: { span: 13 }
},
// modal(对话框)是否可见
visible: false,
// modal(对话框)确定按钮 loading
confirmLoading: false,
// 当前表单
form: this.$form.createForm(this),
// 文件ID集
filesIds: {},
// 分组列表 树状结构
groupTreeData: [],
// 选中的分组
selectedKeys: []
}
},
methods: {
/**
* 新增操作权限
*/
show (filesIds) {
// 显示窗口
this.visible = true
this.filesIds = filesIds
// 获取分组列表
this.getList()
},
// 获取分组列表
getList () {
if (this.groupTreeData.length <= 0) {
this.groupTreeData = [{
title: '未分组',
key: 0,
value: 0
}].concat(this.groupList)
}
},
// 记录选中项
onSelect (selectedKeys) {
this.selectedKeys = selectedKeys
},
// 确认按钮
handleSubmit (e) {
e.preventDefault()
if (this.selectedKeys.length) {
// 提交到后端api
this.onFormSubmit()
} else {
// 关闭对话框
this.handleCancel()
}
},
/**
* 取消按钮
*/
handleCancel () {
this.visible = false
this.form.resetFields()
},
/**
* 提交到后端api
*/
onFormSubmit () {
this.confirmLoading = true
// 数据提交
Api.moveGroup({ groupId: this.selectedKeys[0], fileIds: this.filesIds })
.then(result => {
// 显示成功
this.$message.success(result.message)
// 关闭对话框
this.handleCancel()
// 通知父端组件提交完成了
this.$emit('handleSubmit')
})
.finally(() => {
this.confirmLoading = false
})
}
}
}
</script>
+144
View File
@@ -0,0 +1,144 @@
<template>
<a-card :bordered="false">
<div class="card-title">{{ $route.meta.title }}</div>
<div class="table-operator">
<a-button v-action:add type="primary" icon="plus" @click="handleAdd">新增</a-button>
</div>
<s-table
ref="table"
rowKey="help_id"
:loading="isLoading"
:columns="columns"
:data="loadData"
:pageSize="15"
>
<!-- 内容 -->
<span slot="content" slot-scope="text">
<p class="twoline-hide" style="width: 420px;">{{ text }}</p>
</span>
<!-- 操作 -->
<span slot="action" slot-scope="text, item">
<a v-action:edit style="margin-right: 8px;" @click="handleEdit(item)">编辑</a>
<a v-action:delete @click="handleDelete(item)">删除</a>
</span>
</s-table>
<AddForm ref="AddForm" @handleSubmit="handleRefresh" />
<EditForm ref="EditForm" @handleSubmit="handleRefresh" />
</a-card>
</template>
<script>
import * as Api from '@/api/content/help'
import { STable } from '@/components'
import { AddForm, EditForm } from './modules'
export default {
name: 'Index',
components: {
STable,
AddForm,
EditForm
},
data () {
return {
// 查询参数
queryParam: {},
// 正在加载
isLoading: false,
// 表头
columns: [
{
title: '帮助ID',
dataIndex: 'help_id'
},
{
title: '标题',
dataIndex: 'title'
},
{
title: '内容',
dataIndex: 'content',
scopedSlots: { customRender: 'content' }
},
{
title: '排序',
dataIndex: 'sort'
},
{
title: '更新时间',
dataIndex: 'update_time'
},
{
title: '操作',
dataIndex: 'action',
width: '180px',
scopedSlots: { customRender: 'action' }
}
],
// 加载数据方法 必须为 Promise 对象
loadData: param => {
return Api.list({ ...param, ...this.queryParam })
.then(response => {
return response.data.list
})
}
}
},
created () {
},
methods: {
/**
* 新增记录
*/
handleAdd () {
this.$refs.AddForm.add()
},
/**
* 编辑记录
*/
handleEdit (item) {
this.$refs.EditForm.edit(item)
},
/**
* 删除记录
*/
handleDelete (item) {
const app = this
const modal = this.$confirm({
title: '您确定要删除该记录吗?',
content: '删除后不可恢复',
onOk () {
return Api.deleted({ helpId: item.help_id })
.then((result) => {
app.$message.success(result.message, 1.5)
app.handleRefresh()
})
.finally(result => {
modal.destroy()
})
}
})
},
/**
* 刷新列表
* @param Boolean bool 强制刷新到第一页
*/
handleRefresh (bool = false) {
this.$refs.table.refresh(bool)
},
/**
* 检索查询
*/
onSearch () {
this.handleRefresh(true)
}
}
}
</script>
@@ -0,0 +1,110 @@
<template>
<a-modal
:title="title"
:width="720"
:visible="visible"
:confirmLoading="confirmLoading"
:maskClosable="false"
@ok="handleSubmit"
@cancel="handleCancel"
>
<a-spin :spinning="confirmLoading">
<a-form :form="form">
<a-form-item label="帮助标题" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-input v-decorator="['title', { rules: [{ required: true, message: '请输入联系人姓名' }] }]" />
</a-form-item>
<a-form-item label="帮助内容" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-textarea
:autoSize="{ minRows: 4 }"
v-decorator="['content', { rules: [{ required: true, message: '请输入帮助内容' }] }]"
/>
</a-form-item>
<a-form-item label="排序" :labelCol="labelCol" :wrapperCol="wrapperCol" extra="数字越小越靠前">
<a-input-number
:min="0"
v-decorator="['sort', { initialValue: 100, rules: [{ required: true, message: '请输入排序值' }] }]"
/>
</a-form-item>
</a-form>
</a-spin>
</a-modal>
</template>
<script>
import pick from 'lodash.pick'
import * as Api from '@/api/content/help'
import { SelectRegion } from '@/components'
export default {
components: {
SelectRegion
},
data () {
return {
// 对话框标题
title: '编辑帮助',
// 标签布局属性
labelCol: { span: 7 },
// 输入框布局属性
wrapperCol: { span: 13 },
// modal(对话框)是否可见
visible: false,
// modal(对话框)确定按钮 loading
confirmLoading: false,
// 当前表单元素
form: this.$form.createForm(this)
}
},
methods: {
/**
* 显示对话框
*/
add () {
// 显示窗口
this.visible = true
},
/**
* 确认按钮
*/
handleSubmit (e) {
e.preventDefault()
// 表单验证
const { form: { validateFields } } = this
validateFields((errors, values) => {
// 提交到后端api
!errors && this.onFormSubmit(values)
})
},
/**
* 关闭对话框事件
*/
handleCancel () {
this.visible = false
this.form.resetFields()
},
/**
* 提交到后端api
*/
onFormSubmit (values) {
this.confirmLoading = true
Api.add({ form: values })
.then((result) => {
// 显示成功
this.$message.success(result.message, 1.5)
// 关闭对话框事件
this.handleCancel()
// 通知父端组件提交完成了
this.$emit('handleSubmit', values)
})
.finally(() => {
this.confirmLoading = false
})
}
}
}
</script>
@@ -0,0 +1,126 @@
<template>
<a-modal
:title="title"
:width="720"
:visible="visible"
:confirmLoading="confirmLoading"
:maskClosable="false"
@ok="handleSubmit"
@cancel="handleCancel"
>
<a-spin :spinning="confirmLoading">
<a-form :form="form">
<a-form-item label="帮助标题" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-input v-decorator="['title', { rules: [{ required: true, message: '请输入联系人姓名' }] }]" />
</a-form-item>
<a-form-item label="帮助内容" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-textarea
:autoSize="{ minRows: 4 }"
v-decorator="['content', { rules: [{ required: true, message: '请输入帮助内容' }] }]"
/>
</a-form-item>
<a-form-item label="排序" :labelCol="labelCol" :wrapperCol="wrapperCol" extra="数字越小越靠前">
<a-input-number
:min="0"
v-decorator="['sort', { initialValue: 100, rules: [{ required: true, message: '请输入排序值' }] }]"
/>
</a-form-item>
</a-form>
</a-spin>
</a-modal>
</template>
<script>
import pick from 'lodash.pick'
import * as Api from '@/api/content/help'
import { SelectRegion } from '@/components'
export default {
components: {
SelectRegion
},
data () {
return {
// 对话框标题
title: '编辑帮助',
// 标签布局属性
labelCol: { span: 7 },
// 输入框布局属性
wrapperCol: { span: 13 },
// modal(对话框)是否可见
visible: false,
// modal(对话框)确定按钮 loading
confirmLoading: false,
// 当前表单元素
form: this.$form.createForm(this),
// 当前记录
record: {}
}
},
methods: {
/**
* 显示对话框
*/
edit (record) {
// 显示窗口
this.visible = true
// 当前记录
this.record = record
// 设置默认值
this.setFieldsValue()
},
/**
* 设置默认值
*/
setFieldsValue () {
const { record, form: { setFieldsValue } } = this
this.$nextTick(() => {
setFieldsValue(pick(record, ['title', 'content', 'sort']))
})
},
/**
* 确认按钮
*/
handleSubmit (e) {
e.preventDefault()
// 表单验证
const { form: { validateFields } } = this
validateFields((errors, values) => {
// 提交到后端api
!errors && this.onFormSubmit(values)
})
},
/**
* 关闭对话框事件
*/
handleCancel () {
this.visible = false
this.form.resetFields()
},
/**
* 提交到后端api
*/
onFormSubmit (values) {
this.confirmLoading = true
Api.edit({ helpId: this.record.help_id, form: values })
.then((result) => {
// 显示成功
this.$message.success(result.message, 1.5)
// 关闭对话框事件
this.handleCancel()
// 通知父端组件提交完成了
this.$emit('handleSubmit', values)
})
.finally(() => {
this.confirmLoading = false
})
}
}
}
</script>
@@ -0,0 +1,4 @@
import AddForm from './AddForm'
import EditForm from './EditForm'
export { AddForm, EditForm }
+18
View File
@@ -0,0 +1,18 @@
<template>
<a-result status="403" title="403" sub-title="Sorry, you don't have access to this page.">
<template #extra>
<a-button type="primary" @click="toHome">返回首页</a-button>
</template>
</a-result>
</template>
<script>
export default {
name: 'Exception403',
methods: {
toHome () {
this.$router.push({ path: '/' })
}
}
}
</script>
+18
View File
@@ -0,0 +1,18 @@
<template>
<a-result status="404" title="404" sub-title="Sorry, the page you visited does not exist.">
<template #extra>
<a-button type="primary" @click="toHome">返回首页</a-button>
</template>
</a-result>
</template>
<script>
export default {
name: 'Exception404',
methods: {
toHome () {
this.$router.push({ path: '/' })
}
}
}
</script>
+18
View File
@@ -0,0 +1,18 @@
<template>
<a-result status="500" title="500" sub-title="Sorry, the server is reporting an error.">
<template #extra>
<a-button type="primary" @click="toHome">返回首页</a-button>
</template>
</a-result>
</template>
<script>
export default {
name: 'Exception500',
methods: {
toHome () {
this.$router.push({ path: '/' })
}
}
}
</script>
+513
View File
@@ -0,0 +1,513 @@
<template>
<a-card :bordered="false">
<div class="card-title">{{ $route.meta.title }}</div>
<a-spin :spinning="isLoading">
<a-form :form="form" @submit="handleSubmit" :selfUpdate="true">
<a-tabs :activeKey="tabKey" :tabBarStyle="{marginBottom: '30px'}" @change="handleTabs">
<a-tab-pane :key="0" tab="基本信息"></a-tab-pane>
<a-tab-pane :key="1" tab="规格/库存"></a-tab-pane>
<a-tab-pane :key="2" tab="商品详情"></a-tab-pane>
<a-tab-pane :key="3" tab="更多设置"></a-tab-pane>
</a-tabs>
<div class="tabs-content">
<!-- 基本信息 -->
<div class="tab-pane" v-show="tabKey == 0">
<a-form-item label="商品名称" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-input
placeholder="请输入商品名称"
v-decorator="['goods_name', {rules: [{required: true, min: 2, message: '请输入至少2个字符'}]}]"
/>
</a-form-item>
<a-form-item label="商品分类" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-tree-select
placeholder="请选择商品分类"
:dropdownStyle="{ maxHeight: '500px', overflow: 'auto' }"
:treeData="formData.categoryList"
treeCheckable
treeCheckStrictly
allowClear
v-decorator="['categorys', {rules: [{required: true, message: '请至少选择1个商品分类'}]}]"
></a-tree-select>
<div class="form-item-help">
<router-link target="_blank" :to="{ path: '/goods/category/index' }">去新增</router-link>
<a href="javascript:;" @click="onReloadCategoryList">刷新</a>
</div>
</a-form-item>
<a-form-item
label="商品图片"
:labelCol="labelCol"
:wrapperCol="wrapperCol"
extra="建议尺寸:750*750像素, 最多上传10张, 可拖拽图片调整顺序, 第1张将作为商品首图"
>
<SelectImage
multiple
:maxNum="10"
v-decorator="['imagesIds', {rules: [{required: true, message: '请至少上传1张商品图片'}]}]"
/>
</a-form-item>
<a-form-item class="mb-5" label="卡券类型" :labelCol="labelCol" :wrapperCol="wrapperCol" extra="选择中石油油券时所属省份和商品编码必填,选择优惠券时优惠券必选">
<a-select style="width: 300px" v-decorator="[`oilCardType`, {rules: [{required: true, message: '请选择卡券类型'}], initialValue: 'zsy_ticket' }]">
<a-select-option
v-for="(item, index) in OilCardTypeEnum.data"
:key="index"
:value="item.value"
>{{ item.name }}</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="所属省份" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-select
style="width: 300px"
v-decorator="['province', {rules: [{message: '请选择所属省份'}]}]"
placeholder="请选择所属省份"
>
<a-select-option
v-for="(item, index) in formData.province"
:key="index"
:value="item"
>{{ item }}</a-select-option>
</a-select>
<!--<a-select
style="width: 300px"
v-decorator="['delivery_id', {rules: [{required: true, message: '请选择运费模板'}]}]"
placeholder="请选择运费模板"
>
<a-select-option
v-for="(item, index) in formData.deliveryList"
:key="index"
:value="item.delivery_id"
>{{ item.name }}</a-select-option>
</a-select>
<div class="form-item-help">
<router-link
target="_blank"
:to="{ path: '/setting/delivery/template/create' }"
>新增模板</router-link>
<a href="javascript:;" @click="onReloadDeliveryList">刷新</a>
</div>-->
</a-form-item>
<a-form-item label="商品编码" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-input style="width: 300px" placeholder="请输入商品编码" v-decorator="['goods_no', {rules: [{min: 2, message: '请输入至少2个字符'}]}]" />
</a-form-item>
<MultiCoupon ref="MultiCoupon" :couponList="formData.coupon" @change="handleCouponChange" />
<a-form-item label="商品状态" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-radio-group
v-decorator="['status', {initialValue: 10, rules: [{ required: true }]}]"
>
<a-radio :value="10">上架</a-radio>
<a-radio :value="20">下架</a-radio>
</a-radio-group>
</a-form-item>
<a-form-item label="商品排序" :labelCol="labelCol" :wrapperCol="wrapperCol" extra="数字越小越靠前">
<a-input-number
:min="0"
v-decorator="['sort', {initialValue: 100, rules:[{ required: true }]}]"
/>
</a-form-item>
</div>
<!-- 规格/库存 -->
<div class="tab-pane" v-show="tabKey == 1">
<a-form-item label="规格类型" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-radio-group
v-decorator="['spec_type', { initialValue: 10, rules: [{ required: true }] }]"
@change="onForceUpdate()"
>
<a-radio :value="10">单规格</a-radio>
<a-radio :value="20">多规格</a-radio>
</a-radio-group>
</a-form-item>
<!-- 多规格的表单内容 -->
<div v-show="form.getFieldValue('spec_type') == 20">
<MultiSpec ref="MultiSpec" />
</div>
<!-- 单规格的表单内容 -->
<div v-show="form.getFieldValue('spec_type') == 10">
<a-form-item
label="商品价格"
:labelCol="labelCol"
:wrapperCol="wrapperCol"
extra="商品的实际购买金额,最低0.01"
>
<a-input-number
:min="0.01"
:precision="2"
v-decorator="['goods_price', { initialValue: '', rules: [{ required: true, message: '请输入商品价格' }] }]"
/>
<span class="ml-10">元</span>
</a-form-item>
<a-form-item
label="划线价"
:labelCol="labelCol"
:wrapperCol="wrapperCol"
extra="划线价仅用于商品页展示"
>
<a-input-number :min="0" :precision="2" v-decorator="['line_price']" />
<span class="ml-10">元</span>
</a-form-item>
<a-form-item
label="当前库存数量"
:labelCol="labelCol"
:wrapperCol="wrapperCol"
extra="商品的实际库存数量,为0时用户无法下单"
>
<a-input-number
:min="0"
:precision="0"
v-decorator="['stock_num', { initialValue: 100, rules:[{ required: true, message: '请输入库存数量' }] }]"
/>
<span class="ml-10">件</span>
</a-form-item>
<a-form-item
label="商品重量"
:labelCol="labelCol"
:wrapperCol="wrapperCol"
extra="商品的实际重量,用于计算运费"
>
<a-input-number
:min="0"
v-decorator="['goods_weight', { initialValue: 0, rules:[{ required: true, message: '请输入库存数量' }] }]"
/>
<span class="ml-10">千克 (Kg)</span>
</a-form-item>
</div>
<a-form-item label="库存计算方式" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-radio-group
v-decorator="['deduct_stock_type', {initialValue: 10, rules: [{ required: true }]}]"
>
<a-radio :value="10">下单减库存</a-radio>
<a-radio :value="20">付款减库存</a-radio>
</a-radio-group>
</a-form-item>
</div>
<!-- 商品详情 -->
<div class="tab-pane" v-show="tabKey == 2">
<a-form-item label="商品详情" :labelCol="labelCol" :wrapperCol="{span: 16}">
<Ueditor
v-decorator="['content', { rules: [{ required: true, message: '商品详情不能为空' }] }]"
/>
</a-form-item>
</div>
<!-- 更多设置 -->
<div class="tab-pane" v-show="tabKey == 3">
<a-form-item
label="主图视频"
:labelCol="labelCol"
:wrapperCol="wrapperCol"
extra="建议视频宽高比19:9,建议时长8-45秒"
>
<SelectVideo :multiple="false" v-decorator="['video_id']" />
</a-form-item>
<a-form-item
label="视频封面"
:labelCol="labelCol"
:wrapperCol="wrapperCol"
extra="建议尺寸:750像素*750像素"
>
<SelectImage :multiple="false" v-decorator="['video_cover_id']" />
</a-form-item>
<a-form-item
label="商品卖点"
:labelCol="labelCol"
:wrapperCol="wrapperCol"
extra="一句话简述,例如:此款商品美观大方 性价比较高 不容错过"
>
<a-input placeholder="请输入商品卖点" v-decorator="['selling_point']" />
</a-form-item>
<a-form-item label="服务与承诺" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-select
v-if="formData.serviceList"
mode="multiple"
v-decorator="['serviceIds', { initialValue: formData.defaultServiceIds }]"
placeholder="请选择服务与承诺"
>
<a-select-option
v-for="(item, index) in formData.serviceList"
:key="index"
:value="item.service_id"
>{{ item.name }}</a-select-option>
</a-select>
<div class="form-item-help">
<router-link target="_blank" :to="{ path: '/goods/service/index' }">去新增</router-link>
<a href="javascript:;" @click="onReloadServiceList">刷新</a>
</div>
</a-form-item>
<a-form-item
label="初始销量"
:labelCol="labelCol"
:wrapperCol="wrapperCol"
extra="用户端展示的销量 = 初始销量 + 实际销量"
>
<a-input-number v-decorator="['sales_initial', {initialValue: 0}]" />
</a-form-item>
<a-divider orientation="left">积分设置</a-divider>
<a-form-item
label="积分赠送"
:labelCol="labelCol"
:wrapperCol="wrapperCol"
extra="开启后用户购买此商品将获得积分"
>
<a-radio-group
v-decorator="['is_points_gift', {initialValue: 1, rules: [{ required: true }]}]"
>
<a-radio :value="1">开启</a-radio>
<a-radio :value="0">关闭</a-radio>
</a-radio-group>
</a-form-item>
<a-form-item
label="积分抵扣"
:labelCol="labelCol"
:wrapperCol="wrapperCol"
extra="开启后用户购买此商品可以使用积分进行抵扣"
>
<a-radio-group
v-decorator="['is_points_discount', {initialValue: 1, rules: [{ required: true }]}]"
>
<a-radio :value="1">开启</a-radio>
<a-radio :value="0">关闭</a-radio>
</a-radio-group>
</a-form-item>
<a-divider orientation="left">会员折扣设置</a-divider>
<a-form-item
label="会员折扣"
:labelCol="labelCol"
:wrapperCol="wrapperCol"
extra="开启后会员折扣,会员购买此商品可以享受会员等级折扣价"
>
<a-radio-group
v-decorator="['is_enable_grade', {initialValue: 1, rules: [{ required: true }]}]"
@change="onForceUpdate(true)"
>
<a-radio :value="1">开启</a-radio>
<a-radio :value="0">关闭</a-radio>
</a-radio-group>
</a-form-item>
<a-form-item
v-if="form.getFieldValue('is_enable_grade')"
label="会员折扣设置"
:labelCol="labelCol"
:wrapperCol="wrapperCol"
>
<a-radio-group
v-decorator="['is_alone_grade', { initialValue: 0, rules: [{ required: true }] }]"
@change="onForceUpdate(true)"
>
<a-radio :value="0">默认等级折扣</a-radio>
<a-radio :value="1">单独设置折扣</a-radio>
</a-radio-group>
<!-- 会员等级列表 -->
<div v-if="form.getFieldValue('is_alone_grade')">
<a-form-item v-for="item in formData.userGradeList" :key="item.grade_id">
<InputNumberGroup
:addonBefore="item.name"
addonAfter="折"
:inputProps="{ min: 0, max: 9.9 }"
v-decorator="[`alone_grade_equity[grade_id:${item.grade_id}]`, {
initialValue: formData.defaultUserGradeValue[item.grade_id], rules: [{required: true, message: '折扣率不能为空'}]
}]"
/>
</a-form-item>
</div>
<div class="form-item-help">
<p
class="extra"
v-if="form.getFieldValue('is_alone_grade')"
>单独折扣:折扣率范围0.0-9.9,例如: 9.8代表98折,0代表不折扣</p>
<p class="extra" v-else>默认折扣:默认为用户所属会员等级的折扣率</p>
</div>
</a-form-item>
</div>
</div>
<a-form-item class="mt-20" :wrapper-col="{ span: wrapperCol.span, offset: labelCol.span }">
<a-button type="primary" html-type="submit" :loading="isBtnLoading">提交</a-button>
</a-form-item>
</a-form>
</a-spin>
</a-card>
</template>
<script>
import { assignment } from '@/utils/util'
import * as GoodsApi from '@/api/goods'
import { SelectImage, SelectVideo, Ueditor, InputNumberGroup } from '@/components'
import GoodsModel from '@/common/model/goods/Index'
import MultiSpec from './modules/MultiSpec'
import MultiCoupon from './modules/MultiCoupon'
import { OilCardTypeEnum } from '@/common/enum/order'
export default {
components: {
SelectImage,
SelectVideo,
Ueditor,
InputNumberGroup,
MultiSpec,
MultiCoupon
},
data () {
return {
// 默认的标签索引
tabKey: 0,
// 标签布局属性
labelCol: { span: 3 },
// 输入框布局属性
wrapperCol: { span: 10 },
// loading状态
isLoading: false,
isBtnLoading: false,
// 当前表单元素
form: this.$form.createForm(this),
// 表单数据
formData: GoodsModel.formData,
couponList: []
}
},
beforeCreate () {
// 批量给当前实例赋值
assignment(this, {
OilCardTypeEnum
})
},
// 初始化数据
created () {
this.isLoading = true
// 获取form所需的数据
GoodsModel.getFromData()
.then(() => {
this.isLoading = false
})
},
methods: {
// 手动强制更新页面
onForceUpdate (bool = false) {
this.$forceUpdate()
// bool为true时再执行一次 $forceUpdate, 特殊情况下需执行两次,原因如下:
// 第一次执行 $forceUpdate时, 新元素绑定v-decorator无法获取到form.getFieldValue
bool && setTimeout(() => {
this.$forceUpdate()
}, 10)
},
// 切换tab选项卡
handleTabs (key) {
this.tabKey = key
},
// 刷新分类列表
onReloadCategoryList () {
this.isLoading = true
GoodsModel.getCategoryList().then(() => {
this.isLoading = false
})
},
// 刷新服务与承诺列表
onReloadServiceList () {
this.isLoading = true
GoodsModel.getServiceList().then(() => {
this.isLoading = false
})
},
// 刷新配送模板列表
onReloadDeliveryList () {
this.isLoading = true
GoodsModel.getDeliveryList().then(() => {
this.isLoading = false
})
},
/**
* 确认按钮
*/
handleSubmit (e) {
e.preventDefault()
// 表单验证
const { form: { validateFields } } = this
validateFields((errors, values) => {
// 定位到错误的tab选项卡
if (errors) {
this.onTargetTabError(errors)
return false
}
// 验证多规格
if (values.spec_type === 20) {
const MultiSpec = this.$refs.MultiSpec
if (!MultiSpec.verifyForm()) {
this.tabKey = 1
return false
}
// 记录多规格数据
values.specData = MultiSpec.getFromSpecData()
}
// 整理商品分类ID集
values.categoryIds = values.categorys.map(item => item.value)
delete values.categorys
// 整理优惠券数据
values.couponList = this.couponList
// 提交到后端api
this.onFormSubmit(values)
return true
})
},
/**
* 定位到错误的tab选项卡
*/
onTargetTabError (errors) {
// 表单字段与tabKey对应关系
// 只需要必填字段就可
const tabsFieldsMap = [
['goods_name', 'categorys', 'imagesIds', 'province'],
['spec_type', 'goods_price'],
['content'],
['alone_grade_equity', 'first_money', 'second_money', 'third_money']
]
const field = Object.keys(errors).shift()
for (const key in tabsFieldsMap) {
if (tabsFieldsMap[key].indexOf(field) > -1) {
this.tabKey = parseInt(key)
break
}
}
},
/**
* 提交到后端api
*/
onFormSubmit (values) {
this.isLoading = true
this.isBtnLoading = true
GoodsApi.add({ form: values })
.then(result => {
// 显示提示信息
this.$message.success(result.message, 1.5)
// 跳转到列表页
setTimeout(() => {
this.$router.push('./index')
}, 1500)
})
.catch(() => {
this.isBtnLoading = false
})
.finally(() => {
this.isLoading = false
})
},
handleCouponChange (val) {
// console.log('最新优惠券列表:', val)
this.couponList = val
}
}
}
</script>
<style lang="less" scoped>
@import './style.less';
</style>
+335
View File
@@ -0,0 +1,335 @@
<template>
<a-card :bordered="false">
<div class="card-title">{{ $route.meta.title }}</div>
<div class="table-operator">
<!-- 搜索板块 -->
<a-row class="row-item-search">
<a-form class="search-form" :form="searchForm" layout="inline" @submit="handleSearch">
<a-form-item label="商品名称">
<a-input v-decorator="['goodsName']" placeholder="请输入商品名称" />
</a-form-item>
<a-form-item label="商品编码">
<a-input v-decorator="['goodsNo']" placeholder="请输入商品编码" />
</a-form-item>
<a-form-item label="商品分类">
<a-tree-select
:treeData="categoryListTree"
:dropdownStyle="{ maxHeight: '500px', overflow: 'auto' }"
allowClear
v-decorator="['categoryId', { initialValue: 0 }]"
></a-tree-select>
</a-form-item>
<a-form-item class="search-btn">
<a-button type="primary" icon="search" html-type="submit">搜索</a-button>
</a-form-item>
</a-form>
</a-row>
<!-- 操作板块 -->
<div class="row-item-tab clearfix">
<div class="tab-list fl-l">
<a-radio-group :defaultValue="queryParam.listType" @change="handleTabs">
<a-radio-button value="all">全部</a-radio-button>
<a-radio-button value="on_sale">出售中</a-radio-button>
<a-radio-button value="off_sale">已下架</a-radio-button>
<a-radio-button value="sold_out">已售罄</a-radio-button>
</a-radio-group>
</div>
<a-button
v-if="$auth('/goods/create')"
class="fl-l"
type="primary"
icon="plus"
@click="handleCreate"
>创建商品</a-button>
<div v-if="selectedRowKeys.length" class="button-group">
<a-button-group class="ml-10">
<a-button
v-action:status
icon="arrow-up"
@click="handleUpdateStatus(selectedRowKeys, true)"
>上架</a-button>
<a-button
v-action:status
icon="arrow-down"
@click="handleUpdateStatus(selectedRowKeys, false)"
>下架</a-button>
<a-button v-action:delete icon="delete" @click="handleDelete(selectedRowKeys)">删除</a-button>
</a-button-group>
</div>
</div>
</div>
<s-table
ref="table"
rowKey="goods_id"
:loading="isLoading"
:columns="columns"
:data="loadData"
:rowSelection="rowSelection"
:pageSize="15"
:scroll="{ x: 1450 }"
>
<!-- 商品图片 -->
<span slot="goods_image" slot-scope="text">
<a title="点击查看原图" :href="text" target="_blank">
<img width="50" height="50" :src="text" alt="商品图片" />
</a>
</span>
<!-- 商品名称 -->
<span slot="goods_name" slot-scope="text">
<p class="twoline-hide" style="width: 270px;">{{ text }}</p>
</span>
<!-- 商品名称 -->
<span slot="oilCardType" slot-scope="text">
<p>{{ OilCardTypeEnum[text].name }}</p>
</span>
<!-- 商品状态 -->
<span slot="status" slot-scope="text, item">
<a-tag
class="cur-p"
:color="text == 10 ? 'green' : 'red'"
@click="handleUpdateStatus([item.goods_id], text != 10)"
>{{ text == 10 ? '上架' : '下架' }}</a-tag>
</span>
<!-- 操作项 -->
<div class="actions" slot="action" slot-scope="text, item">
<router-link :to="{ path: '/goods/update', query: { goodsId: item.goods_id } }">编辑</router-link>
<a v-action:delete @click="handleDelete([item.goods_id])">删除</a>
</div>
</s-table>
</a-card>
</template>
<script>
import { assignment } from '@/utils/util'
import * as GoodsApi from '@/api/goods'
import { ContentHeader, STable } from '@/components'
import CategoryModel from '@/common/model/Category'
import { OilCardTypeEnum } from '@/common/enum/order'
// 表格表头
const columns = [
{
title: '商品ID',
dataIndex: 'goods_id'
},
{
title: '省份',
dataIndex: 'province'
},
{
title: '卡券类型',
dataIndex: 'oilCardType',
scopedSlots: { customRender: 'oilCardType' }
},
{
title: '商品图片',
dataIndex: 'goods_image',
scopedSlots: { customRender: 'goods_image' }
},
{
title: '商品名称',
dataIndex: 'goods_name',
width: '200px',
scopedSlots: { customRender: 'goods_name' }
},
{
title: '商品编码',
dataIndex: 'goods_no',
width: '100px',
scopedSlots: { customRender: 'goods_no' }
},
{
title: '商品价格',
dataIndex: 'goods_price_min',
scopedSlots: { customRender: 'goods_price_min' }
},
{
title: '总销量',
dataIndex: 'sales_actual'
},
{
title: '库存总量',
dataIndex: 'stock_total'
},
{
title: '状态',
dataIndex: 'status',
scopedSlots: { customRender: 'status' }
},
{
title: '排序',
dataIndex: 'sort'
},
{
title: '添加时间',
width: '180px',
dataIndex: 'create_time'
},
{
title: '操作',
dataIndex: 'action',
width: '150px',
fixed: 'right',
scopedSlots: { customRender: 'action' }
}
]
export default {
name: 'Index',
components: {
ContentHeader,
STable
},
data () {
return {
// 当前表单元素
searchForm: this.$form.createForm(this),
// 商品分类列表
categoryListTree: [],
// 查询参数
queryParam: {
listType: 'all'
},
// 正在加载
isLoading: false,
// 表头
columns,
// 选择的元素
selectedRowKeys: [],
// 加载数据方法 必须为 Promise 对象
loadData: param => {
return GoodsApi.list({ ...param, ...this.queryParam })
.then(response => {
return response.data.list
})
}
}
},
beforeCreate () {
// 批量给当前实例赋值
assignment(this, {
OilCardTypeEnum
})
},
created () {
// 默认的查询参数
if (this.$route.query.listType) {
this.queryParam.listType = this.$route.query.listType
}
// 获取商品分类列表
this.getCategoryList()
},
computed: {
rowSelection () {
return {
selectedRowKeys: this.selectedRowKeys,
onChange: this.onSelectChange
}
}
},
methods: {
/**
* 选中项发生变化时的回调
*/
onSelectChange (selectedRowKeys) {
this.selectedRowKeys = selectedRowKeys
},
// 切换tab
handleTabs (e) {
this.queryParam.listType = e.target.value
this.handleRefresh(true)
},
// 确认搜索
handleSearch (e) {
e.preventDefault()
this.searchForm.validateFields((error, values) => {
if (!error) {
this.queryParam = { ...this.queryParam, ...values }
this.handleRefresh(true)
}
})
},
// 重置搜索表单
handleReset () {
this.searchForm.resetFields()
},
// 获取分类列表
getCategoryList () {
this.isLoading = true
CategoryModel.getListFromScreen()
.then(selectList => {
this.categoryListTree = selectList
})
.finally(result => {
this.isLoading = false
})
},
// 修改商品状态(上下架)
handleUpdateStatus (goodsIds, state = true) {
if (!this.$auth('/goods/index.status')) {
return false
}
this.isLoading = true
GoodsApi.state({ goodsIds, state })
.then(result => {
// 显示成功
this.$message.success(result.message, 1.5)
this.handleRefresh()
})
.finally(result => { this.isLoading = false })
},
/**
* 删除记录
*/
handleDelete (goodsIds) {
const app = this
const modal = this.$confirm({
title: '您确定要删除该记录吗?',
content: '删除后不可恢复',
onOk () {
return GoodsApi.deleted({ goodsIds })
.then(result => {
app.$message.success(result.message, 1.5)
app.handleRefresh()
})
.finally(result => {
modal.destroy()
})
}
})
},
/**
* 新增记录
*/
handleCreate () {
this.$router.push('/goods/create')
},
/**
* 刷新列表
* @param Boolean bool 强制刷新到第一页
*/
handleRefresh (bool = false) {
this.selectedRowKeys = []
this.$refs.table.refresh(bool)
}
}
}
</script>
<style lang="less" scoped>
.ant-card-body {
padding: 22px 29px 25px;
}
// 筛选tab
.tab-list {
margin-right: 20px;
}
</style>
+558
View File
@@ -0,0 +1,558 @@
<template>
<a-card :bordered="false">
<div class="card-title">{{ $route.meta.title }}</div>
<a-spin :spinning="isLoading">
<a-form :form="form" @submit="handleSubmit" :selfUpdate="true">
<a-tabs :activeKey="tabKey" :tabBarStyle="{marginBottom: '30px'}" @change="handleTabs">
<a-tab-pane :key="0" tab="基本信息"></a-tab-pane>
<a-tab-pane :key="1" tab="规格/库存"></a-tab-pane>
<a-tab-pane :key="2" tab="商品详情"></a-tab-pane>
<a-tab-pane :key="3" tab="更多设置"></a-tab-pane>
</a-tabs>
<div class="tabs-content">
<!-- 基本信息 -->
<div class="tab-pane" v-show="tabKey == 0">
<a-form-item label="商品名称" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-input
placeholder="请输入商品名称"
v-decorator="['goods_name', { rules: [{ required: true, min: 2, message: '请输入至少2个字符' }] }]"
/>
</a-form-item>
<a-form-item label="商品分类" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-tree-select
placeholder="请选择商品分类"
:dropdownStyle="{ maxHeight: '500px', overflow: 'auto' }"
:treeData="formData.categoryList"
treeCheckable
treeCheckStrictly
allowClear
v-decorator="['categorys', { rules: [{required: true, message: '请至少选择1个商品分类'}]}]"
></a-tree-select>
<div class="form-item-help">
<router-link target="_blank" :to="{ path: '/goods/category/index' }">去新增</router-link>
<a href="javascript:;" @click="onReloadCategoryList">刷新</a>
</div>
</a-form-item>
<a-form-item
label="商品图片"
:labelCol="labelCol"
:wrapperCol="wrapperCol"
extra="建议尺寸:750*750像素, 最多上传10张, 可拖拽图片调整顺序, 第1张将作为商品首图"
>
<SelectImage
multiple
:maxNum="10"
:defaultList="formData.goods.goods_images"
v-decorator="['imagesIds', { rules: [{ required: true, message: '请至少上传1张商品图片' }] }]"
/>
</a-form-item>
<a-form-item class="mb-5" label="卡券类型" :labelCol="labelCol" :wrapperCol="wrapperCol" extra="选择中石油油券时所属省份和商品编码必填,选择优惠券时优惠券必选">
<a-select style="width: 300px" v-decorator="[`oilCardType`, { rules: [{required: true, message: '请选择卡券类型'}], initialValue: 'zsy_ticket' }]">
<a-select-option
v-for="(item, index) in OilCardTypeEnum.data"
:key="index"
:value="item.value"
>{{ item.name }}</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="所属省份" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-select
style="width: 300px"
v-decorator="['province', {rules: [{message: '请选择所属省份'}]}]"
placeholder="请选择所属省份"
>
<a-select-option key="" value="">无</a-select-option>
<a-select-option
v-for="(item, index) in formData.province"
:key="index"
:value="item"
>{{ item }}</a-select-option>
</a-select>
<!--<a-select
style="width: 300px"
v-decorator="['delivery_id', {rules: [{required: true, message: '请选择运费模板'}]}]"
placeholder="请选择运费模板"
>
<a-select-option
v-for="(item, index) in formData.deliveryList"
:key="index"
:value="item.delivery_id"
>{{ item.name }}</a-select-option>
</a-select>
<div class="form-item-help">
<router-link
target="_blank"
:to="{ path: '/setting/delivery/template/create' }"
>新增模板</router-link>
<a href="javascript:;" @click="onReloadDeliveryList">刷新</a>
</div>-->
</a-form-item>
<a-form-item label="商品编码" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-input style="width: 300px" placeholder="请输入商品编码" v-decorator="['goods_no', {rules: [{message: '请输入至少2个字符'}]}]" />
</a-form-item>
<MultiCoupon ref="MultiCoupon" :couponList="formData.coupon" :defaultCouponList="formData.goods.couponList" @change="handleCouponChange" />
<a-form-item label="商品状态" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-radio-group
v-decorator="['status', {initialValue: 10, rules: [{ required: true }]}]"
>
<a-radio :value="10">上架</a-radio>
<a-radio :value="20">下架</a-radio>
</a-radio-group>
</a-form-item>
<a-form-item label="商品排序" :labelCol="labelCol" :wrapperCol="wrapperCol" extra="数字越小越靠前">
<a-input-number
:min="0"
v-decorator="['sort', {initialValue: 100, rules:[{ required: true }]}]"
/>
</a-form-item>
</div>
<!-- 规格/库存 -->
<div class="tab-pane" v-show="tabKey == 1">
<a-form-item label="规格类型" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-radio-group
v-decorator="['spec_type', {initialValue: 10, rules: [{ required: true }]}]"
@change="onForceUpdate()"
>
<a-radio :value="10">单规格</a-radio>
<a-radio :value="20">多规格</a-radio>
</a-radio-group>
</a-form-item>
<!-- 多规格的表单内容 -->
<div v-show="form.getFieldValue('spec_type') == 20">
<MultiSpec
ref="MultiSpec"
:defaultSpecList="formData.goods.specList"
:defaultSkuList="formData.goods.skuList"
/>
</div>
<!-- 单规格的表单内容 -->
<div v-show="form.getFieldValue('spec_type') == 10">
<a-form-item
label="商品价格"
:labelCol="labelCol"
:wrapperCol="wrapperCol"
extra="商品的实际购买金额,最低0.01"
>
<a-input-number
:min="0.01"
:precision="2"
v-decorator="['goods_price', { rules:[{ required: true, message: '请输入商品价格' }] }]"
/>
<span class="ml-10">元</span>
</a-form-item>
<a-form-item
label="划线价"
:labelCol="labelCol"
:wrapperCol="wrapperCol"
extra="划线价仅用于商品页展示"
>
<a-input-number :min="0" :precision="2" v-decorator="['line_price']" />
<span class="ml-10">元</span>
</a-form-item>
<a-form-item
label="当前库存数量"
:labelCol="labelCol"
:wrapperCol="wrapperCol"
extra="商品的实际库存数量,为0时用户无法下单"
>
<a-input-number
:min="0"
:precision="0"
v-decorator="['stock_num', { initialValue: 100, rules:[{ required: true, message: '请输入库存数量' }] }]"
/>
<span class="ml-10">件</span>
</a-form-item>
<a-form-item
label="商品重量"
:labelCol="labelCol"
:wrapperCol="wrapperCol"
extra="商品的实际重量,用于计算运费"
>
<a-input-number
:min="0"
v-decorator="['goods_weight', { initialValue: 0, rules:[{ required: true, message: '请输入库存数量' }] }]"
/>
<span class="ml-10">千克 (Kg)</span>
</a-form-item>
</div>
<a-form-item label="库存计算方式" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-radio-group
v-decorator="['deduct_stock_type', {initialValue: 10, rules: [{ required: true }]}]"
>
<a-radio :value="10">下单减库存</a-radio>
<a-radio :value="20">付款减库存</a-radio>
</a-radio-group>
</a-form-item>
</div>
<!-- 商品详情 -->
<div class="tab-pane" v-show="tabKey == 2">
<a-form-item label="商品详情" :labelCol="labelCol" :wrapperCol="{span: 16}">
<Ueditor v-decorator="['content', {rules: [{required: true, message: '商品详情不能为空'}]}]" />
</a-form-item>
</div>
<!-- 更多设置 -->
<div class="tab-pane" v-show="tabKey == 3">
<a-form-item
label="主图视频"
:labelCol="labelCol"
:wrapperCol="wrapperCol"
extra="建议视频宽高比19:9,建议时长8-45秒"
>
<SelectVideo
:multiple="false"
:defaultList="formData.goods.video ? [formData.goods.video] : []"
v-decorator="['video_id']"
/>
</a-form-item>
<a-form-item
label="视频封面"
:labelCol="labelCol"
:wrapperCol="wrapperCol"
extra="建议尺寸:750像素*750像素"
>
<SelectImage
:multiple="false"
:defaultList="formData.goods.videoCover ? [formData.goods.videoCover] : []"
v-decorator="['video_cover_id']"
/>
</a-form-item>
<a-form-item
label="商品卖点"
:labelCol="labelCol"
:wrapperCol="wrapperCol"
extra="一句话简述,例如:此款商品美观大方 性价比较高 不容错过"
>
<a-input placeholder="请输入商品卖点" v-decorator="['selling_point']" />
</a-form-item>
<a-form-item label="服务与承诺" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-select
v-if="formData.serviceList"
mode="multiple"
v-decorator="['serviceIds']"
placeholder="请选择服务与承诺"
>
<a-select-option
v-for="(item, index) in formData.serviceList"
:key="index"
:value="item.service_id"
>{{ item.name }}</a-select-option>
</a-select>
<div class="form-item-help">
<router-link target="_blank" :to="{ path: '/goods/service/index' }">去新增</router-link>
<a href="javascript:;" @click="onReloadServiceList">刷新</a>
</div>
</a-form-item>
<a-form-item
label="初始销量"
:labelCol="labelCol"
:wrapperCol="wrapperCol"
extra="用户端展示的销量 = 初始销量 + 实际销量"
>
<a-input-number v-decorator="['sales_initial', {initialValue: 0}]" />
</a-form-item>
<a-divider orientation="left">积分设置</a-divider>
<a-form-item
label="积分赠送"
:labelCol="labelCol"
:wrapperCol="wrapperCol"
extra="开启后用户购买此商品将获得积分"
>
<a-radio-group
v-decorator="['is_points_gift', {initialValue: 1, rules: [{ required: true }]}]"
>
<a-radio :value="1">开启</a-radio>
<a-radio :value="0">关闭</a-radio>
</a-radio-group>
</a-form-item>
<a-form-item
label="积分抵扣"
:labelCol="labelCol"
:wrapperCol="wrapperCol"
extra="开启后用户购买此商品可以使用积分进行抵扣"
>
<a-radio-group
v-decorator="['is_points_discount', {initialValue: 1, rules: [{ required: true }]}]"
>
<a-radio :value="1">开启</a-radio>
<a-radio :value="0">关闭</a-radio>
</a-radio-group>
</a-form-item>
<a-divider orientation="left">会员折扣设置</a-divider>
<a-form-item
label="会员折扣"
:labelCol="labelCol"
:wrapperCol="wrapperCol"
extra="开启后会员折扣,会员购买此商品可以享受会员等级折扣价"
>
<a-radio-group
v-decorator="['is_enable_grade', {initialValue: 1, rules: [{ required: true }]}]"
@change="onForceUpdate(true)"
>
<a-radio :value="1">开启</a-radio>
<a-radio :value="0">关闭</a-radio>
</a-radio-group>
</a-form-item>
<a-form-item
v-show="form.getFieldValue('is_enable_grade')"
label="会员折扣设置"
:labelCol="labelCol"
:wrapperCol="wrapperCol"
>
<a-radio-group
v-decorator="['is_alone_grade', {initialValue: 0, rules: [{ required: true }]}]"
@change="onForceUpdate(true)"
>
<a-radio :value="0">默认等级折扣</a-radio>
<a-radio :value="1">单独设置折扣</a-radio>
</a-radio-group>
<!-- 会员等级列表 -->
<div v-show="form.getFieldValue('is_alone_grade')">
<a-form-item v-for="item in formData.userGradeList" :key="item.grade_id">
<InputNumberGroup
:addonBefore="item.name"
addonAfter="折"
:inputProps="{ min: 0, max: 9.9 }"
v-decorator="[`alone_grade_equity[grade_id:${item.grade_id}]`, {
initialValue: formData.defaultUserGradeValue[item.grade_id], rules: [{required: true, message: '折扣率不能为空'}]
}]"
/>
</a-form-item>
</div>
<div class="form-item-help">
<p
class="extra"
v-if="form.getFieldValue('is_alone_grade')"
>单独折扣:折扣率范围0.0-9.9,例如: 9.8代表98折,0代表不折扣</p>
<p class="extra" v-else>默认折扣:默认为用户所属会员等级的折扣率</p>
</div>
</a-form-item>
</div>
</div>
<a-form-item class="mt-20" :wrapper-col="{ span: wrapperCol.span, offset: labelCol.span }">
<a-button type="primary" html-type="submit" :loading="isBtnLoading">提交</a-button>
</a-form-item>
</a-form>
</a-spin>
</a-card>
</template>
<script>
import * as GoodsApi from '@/api/goods'
import { SelectImage, SelectVideo, Ueditor, InputNumberGroup } from '@/components'
import GoodsModel from '@/common/model/goods/Index'
import MultiSpec from './modules/MultiSpec'
import MultiCoupon from './modules/MultiCoupon'
import { isEmptyObject, assignment } from '@/utils/util'
import { OilCardTypeEnum } from '@/common/enum/order'
export default {
components: {
SelectImage,
SelectVideo,
Ueditor,
InputNumberGroup,
MultiSpec,
MultiCoupon
},
data () {
return {
// 默认的标签索引
tabKey: 0,
// 标签布局属性
labelCol: { span: 3 },
// 输入框布局属性
wrapperCol: { span: 10 },
// loading状态
isLoading: false,
isBtnLoading: false,
// 当前表单元素
form: this.$form.createForm(this),
// 商品ID
goodsId: null,
// 表单数据
formData: GoodsModel.formData,
couponList: []
}
},
beforeCreate () {
// 批量给当前实例赋值
assignment(this, {
OilCardTypeEnum
})
},
created () {
// 初始化数据
this.initData()
},
beforeDestroy () {
// 销毁商品详情
GoodsModel.formData.goods = {}
},
methods: {
// 初始化数据
initData () {
// 记录商品ID
this.goodsId = this.$route.query.goodsId
// 获取form所需的数据
this.isLoading = true
GoodsModel.getFromData(this.goodsId)
.then(() => {
// 商品表单数据
if (!isEmptyObject(this.form.getFieldsValue())) {
this.form.setFieldsValue(GoodsModel.getFieldsValue())
}
this.isLoading = false
})
},
// 手动强制更新页面
onForceUpdate (bool = false) {
this.$forceUpdate()
// bool为true时再执行一次 $forceUpdate, 特殊情况下需执行两次,原因如下:
// 第一次执行 $forceUpdate时, 新元素绑定v-decorator无法获取到form.getFieldValue
bool && setTimeout(() => {
this.$forceUpdate()
}, 10)
},
// 切换tab选项卡
handleTabs (key) {
this.tabKey = key
},
// 刷新分类列表
onReloadCategoryList () {
this.isLoading = true
GoodsModel.getCategoryList().then(() => {
this.isLoading = false
})
},
// 刷新服务与承诺列表
onReloadServiceList () {
this.isLoading = true
GoodsModel.getServiceList().then(() => {
this.isLoading = false
})
},
// 获取最新库存
onReloadStock () {
this.isLoading = true
GoodsApi.onReloadStock({ goodsId: this.goodsId })
.then(result => {
// 显示提示信息
this.$message.success(result.message, 1.5)
this.form.setFieldsValue({ stock_num: result.data.canOrderNum })
})
.catch(() => {
this.isBtnLoading = false
})
.finally(() => {
this.isLoading = false
})
},
// 刷新配送模板列表
onReloadDeliveryList () {
this.isLoading = true
GoodsModel.getDeliveryList().then(() => {
this.isLoading = false
})
},
/**
* 确认按钮
*/
handleSubmit (e) {
e.preventDefault()
// 表单验证
const { form: { validateFields } } = this
validateFields((errors, values) => {
// 定位到错误的tab选项卡
if (errors) {
this.onTargetTabError(errors)
return false
}
// 验证多规格
if (values.spec_type === 20) {
const MultiSpec = this.$refs.MultiSpec
if (!MultiSpec.verifyForm()) {
this.tabKey = 1
return false
}
// 记录多规格数据
values.specData = MultiSpec.getFromSpecData()
}
// 整理商品分类ID集
values.categoryIds = values.categorys.map(item => item.value)
delete values.categorys
// 整理优惠券数据
values.couponList = this.couponList
// 提交到后端api
this.onFormSubmit(values)
return true
})
},
/**
* 定位到错误的tab选项卡
*/
onTargetTabError (errors) {
// 表单字段与tabKey对应关系
// 只需要必填字段就可
const tabsFieldsMap = [
['goods_name', 'categorys', 'imagesIds', 'province'],
['spec_type', 'goods_price'],
['content'],
['alone_grade_equity', 'first_money', 'second_money', 'third_money']
]
const field = Object.keys(errors).shift()
for (const key in tabsFieldsMap) {
if (tabsFieldsMap[key].indexOf(field) > -1) {
this.tabKey = parseInt(key)
break
}
}
},
/**
* 提交到后端api
*/
onFormSubmit (values) {
this.isLoading = true
this.isBtnLoading = true
GoodsApi.edit({ goodsId: this.goodsId, form: values })
.then(result => {
// 显示提示信息
this.$message.success(result.message, 1.5)
// 跳转到列表页
setTimeout(() => {
this.$router.push('./index')
}, 1500)
})
.catch(() => {
this.isBtnLoading = false
})
.finally(() => {
this.isLoading = false
})
},
handleCouponChange (val) {
// console.log('最新优惠券列表:', val)
this.couponList = val
}
}
}
</script>
<style lang="less" scoped>
@import './style.less';
</style>
@@ -0,0 +1,169 @@
<template>
<a-card :bordered="false">
<div class="card-title">{{ $route.meta.title }}</div>
<div class="table-operator">
<a-button v-action:add type="primary" icon="plus" @click="handleAdd">新增</a-button>
</div>
<a-table
v-if="!isLoading"
rowKey="category_id"
:columns="columns"
:dataSource="categoryList"
:defaultExpandAllRows="true"
:expandIconColumnIndex="1"
:pagination="false"
:loading="isLoading"
>
<!-- 状态 -->
<span slot="status" slot-scope="text">
<a-tag :color="text ? 'green' : ''">{{ text ? '显示' : '隐藏' }}</a-tag>
</span>
<span slot="action" slot-scope="text, item">
<a v-action:edit style="margin-right: 8px;" @click="handleEdit(item)">编辑</a>
<a v-action:delete @click="handleDelete(item)">删除</a>
</span>
</a-table>
<AddForm ref="AddForm" :categoryList="categoryList" :provinceList="provinceList" @handleSubmit="handleRefresh" />
<EditForm ref="EditForm" :categoryList="categoryList" :provinceList="provinceList" @handleSubmit="handleRefresh" />
</a-card>
</template>
<script>
import * as Api from '@/api/category'
import { STable } from '@/components'
import AddForm from './modules/AddForm'
import EditForm from './modules/EditForm'
import * as GoodsApi from '@/api/goods'
export default {
name: 'Index',
components: {
STable,
AddForm,
EditForm
},
data () {
return {
// 分类列表
categoryList: [],
provinceList: [],
// 查询参数
queryParam: {},
// 正在加载
isLoading: false,
// 表头
columns: [
{
title: '分类ID',
dataIndex: 'category_id'
},
{
title: '分类名称',
dataIndex: 'name'
},
{
title: '状态',
dataIndex: 'status',
scopedSlots: { customRender: 'status' }
},
{
title: '省份',
dataIndex: 'province'
},
{
title: '排序',
dataIndex: 'sort'
},
{
title: '添加时间',
dataIndex: 'create_time'
},
{
title: '操作',
dataIndex: 'action',
width: '180px',
scopedSlots: { customRender: 'action' }
}
]
}
},
created () {
// 获取分类列表
this.getCategoryList(true)
// 获取省份
this.getProvinceList()
},
methods: {
/**
* 获取分类列表
*/
getCategoryList (bool) {
bool && (this.isLoading = true)
Api.list()
.then(result => {
this.categoryList = result.data.list
})
.finally(result => {
this.isLoading = false
})
},
// 获取省份
getProvinceList () {
return new Promise((resolve, reject) => {
GoodsApi.province()
.then(result => {
this.provinceList = result.data.list
resolve()
})
})
},
/**
* 新增记录
*/
handleAdd () {
this.$refs.AddForm.add()
},
/**
* 编辑记录
*/
handleEdit (item) {
this.$refs.EditForm.edit(item)
},
/**
* 删除记录
*/
handleDelete (item) {
const self = this
const modal = this.$confirm({
title: '您确定要删除该记录吗?',
content: '删除后不可恢复',
onOk () {
return Api.deleted({ categoryId: item['category_id'] })
.then((result) => {
self.$message.success(result.message, 1.5)
self.handleRefresh()
})
.finally(result => {
modal.destroy()
})
}
})
},
/**
* 刷新列表
* @param Boolean bool 强制刷新到第一页
*/
handleRefresh () {
// 获取分类列表
this.getCategoryList()
}
}
}
</script>
@@ -0,0 +1,179 @@
<template>
<a-modal
title="新增商品分类"
:width="720"
:visible="visible"
:confirmLoading="confirmLoading"
:maskClosable="false"
:destroyOnClose="true"
@ok="handleSubmit"
@cancel="handleCancel"
>
<a-spin :spinning="confirmLoading">
<a-form :form="form">
<a-form-item label="分类名称" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-input
v-decorator="['name', {rules: [{required: true, min: 2, message: '请输入至少2个字符'}]}]"
/>
</a-form-item>
<a-form-item label="上级分类" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-tree-select
:treeData="categoryListTree"
:dropdownStyle="{ maxHeight: '400px', overflow: 'auto' }"
allowClear
v-decorator="['parent_id', {initialValue: 0}]"
></a-tree-select>
</a-form-item>
<a-form-item label="所属省份" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-select
style="width: 300px"
v-decorator="['province', {rules: [{message: '请选择所属省份'}]}]"
placeholder="请选择所属省份"
>
<a-select-option
key=""
value=""
>不限</a-select-option>
<a-select-option
v-for="(item, index) in provinceList"
:key="index"
:value="item"
>{{ item }}</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="分类图片" :labelCol="labelCol" :wrapperCol="wrapperCol">
<SelectImage v-decorator="['image_id']" />
</a-form-item>
<a-form-item label="状态" :labelCol="labelCol" :wrapperCol="wrapperCol" extra="用户端是否展示">
<a-radio-group v-decorator="['status', {initialValue: 1, rules: [{required: true}]}]">
<a-radio :value="1">显示</a-radio>
<a-radio :value="0">隐藏</a-radio>
</a-radio-group>
</a-form-item>
<a-form-item label="排序" :labelCol="labelCol" :wrapperCol="wrapperCol" extra="数字越小越靠前">
<a-input-number
:min="0"
v-decorator="['sort', {initialValue: 100, rules: [{required: true, message: '请输入至少1个数字'}]}]"
/>
</a-form-item>
</a-form>
</a-spin>
</a-modal>
</template>
<script>
import * as Api from '@/api/category'
import { SelectImage } from '@/components'
import CategoryModel from '@/common/model/Category'
export default {
components: {
SelectImage
},
props: {
// 分类列表
categoryList: {
type: Array,
required: true
},
// 省份列表
provinceList: {
type: Array,
required: true
}
},
data () {
return {
// 对话框标题
title: '',
// 标签布局属性
labelCol: {
span: 7
},
// 输入框布局属性
wrapperCol: {
span: 13
},
// modal(对话框)是否可见
visible: false,
// modal(对话框)确定按钮 loading
confirmLoading: false,
// 当前表单元素
form: this.$form.createForm(this),
// 上级分类列表
categoryListTree: []
}
},
methods: {
/**
* 显示对话框
*/
add () {
// 显示窗口
this.visible = true
// 获取分类列表
this.getCategoryList()
},
/**
* 获取分类列表
*/
getCategoryList () {
const { categoryList } = this
// 格式化分类列表
const selectList = CategoryModel.formatTreeData(categoryList)
// 顶级分类
this.categoryListTree = [{
title: '顶级分类',
key: 0,
value: 0
}].concat(selectList)
},
/**
* 确认按钮
*/
handleSubmit (e) {
e.preventDefault()
// 表单验证
const { form: { validateFields } } = this
validateFields((errors, values) => {
// 提交到后端api
if (!errors) {
this.onFormSubmit(values)
}
})
},
/**
* 关闭对话框事件
*/
handleCancel () {
this.visible = false
this.form.resetFields()
},
/**
* 提交到后端api
*/
onFormSubmit (values) {
this.confirmLoading = true
Api.add({ form: values })
.then((result) => {
// 显示成功
this.$message.success(result.message, 1.5)
// 关闭对话框事件
this.handleCancel()
// 通知父端组件提交完成了
this.$emit('handleSubmit', values)
})
.finally((result) => {
this.confirmLoading = false
})
}
}
}
</script>
@@ -0,0 +1,196 @@
<template>
<a-modal
title="编辑商品分类"
:width="720"
:visible="visible"
:confirmLoading="confirmLoading"
:maskClosable="false"
:destroyOnClose="true"
@ok="handleSubmit"
@cancel="handleCancel"
>
<a-spin :spinning="confirmLoading">
<a-form :form="form">
<a-form-item label="分类名称" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-input
v-decorator="['name', {rules: [{required: true, min: 2, message: '请输入至少2个字符'}]}]"
/>
</a-form-item>
<a-form-item label="上级分类" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-tree-select
:treeData="categoryListTree"
:dropdownStyle="{ maxHeight: '400px', overflow: 'auto' }"
allowClear
v-decorator="['parent_id']"
/>
</a-form-item>
<a-form-item label="分类图片" :labelCol="labelCol" :wrapperCol="wrapperCol">
<SelectImage
:defaultList="record.image ? [record.image] : []"
v-decorator="['image_id']"
/>
</a-form-item>
<a-form-item label="所属省份" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-select
style="width: 300px"
v-decorator="['province', {rules: [{message: '请选择所属省份'}]}]"
placeholder="请选择所属省份"
>
<a-select-option
key=""
value=""
>不限</a-select-option>
<a-select-option
v-for="(item, index) in provinceList"
:key="index"
:value="item"
>{{ item }}</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="状态" :labelCol="labelCol" :wrapperCol="wrapperCol" extra="用户端是否展示">
<a-radio-group v-decorator="['status', { initialValue: 1, rules: [{ required: true }] }]">
<a-radio :value="1">显示</a-radio>
<a-radio :value="0">隐藏</a-radio>
</a-radio-group>
</a-form-item>
<a-form-item label="排序" :labelCol="labelCol" :wrapperCol="wrapperCol" extra="数字越小越靠前">
<a-input-number
:min="0"
v-decorator="['sort', {initialValue: 100, rules: [{required: true, message: '请输入至少1个数字'}]}]"
/>
</a-form-item>
</a-form>
</a-spin>
</a-modal>
</template>
<script>
import _ from 'lodash'
import * as Api from '@/api/category'
import { SelectImage } from '@/components'
import CategoryModel from '@/common/model/Category'
export default {
components: {
SelectImage
},
props: {
// 分类列表
categoryList: {
type: Array,
required: true
},
// 省份列表
provinceList: {
type: Array,
required: true
}
},
data () {
return {
// 对话框标题
title: '',
// 标签布局属性
labelCol: { span: 7 },
// 输入框布局属性
wrapperCol: {
span: 13
},
// modal(对话框)是否可见
visible: false,
// modal(对话框)确定按钮 loading
confirmLoading: false,
// 当前表单元素
form: this.$form.createForm(this),
// 上级分类列表
categoryListTree: [],
// 当前记录
record: {}
}
},
methods: {
/**
* 显示对话框
*/
edit (record) {
// 显示窗口
this.visible = true
// 当前分类记录
this.record = record
// 获取分类列表
this.getCategoryList()
// 设置默认值
this.setFieldsValue()
},
/**
* 设置默认值
*/
setFieldsValue () {
const { record, form: { setFieldsValue } } = this
// 设置表单内容
this.$nextTick(() => {
setFieldsValue(_.pick(record, ['name', 'parent_id', 'image_id', 'status', 'province', 'sort']))
})
},
/**
* 获取分类列表
*/
getCategoryList () {
const { categoryList } = this
// 格式化分类列表
const selectList = CategoryModel.formatTreeData(categoryList)
// 顶级分类
this.categoryListTree = [{
title: '顶级分类',
key: 0,
value: 0
}].concat(selectList)
},
/**
* 确认按钮
*/
handleSubmit (e) {
e.preventDefault()
// 表单验证
const { form: { validateFields } } = this
validateFields((errors, values) => {
// 提交到后端api
if (!errors) {
this.onFormSubmit(values)
}
})
},
/**
* 关闭对话框事件
*/
handleCancel () {
this.visible = false
this.form.resetFields()
},
/**
* 提交到后端api
*/
onFormSubmit (values) {
this.confirmLoading = true
Api.edit({ categoryId: this.record['category_id'], form: values })
.then((result) => {
// 显示成功
this.$message.success(result.message, 1.5)
// 关闭对话框事件
this.handleCancel()
// 通知父端组件提交完成了
this.$emit('handleSubmit', values)
})
.finally((result) => {
this.confirmLoading = false
})
}
}
}
</script>
+256
View File
@@ -0,0 +1,256 @@
<template>
<a-card :bordered="false">
<div class="card-title">{{ $route.meta.title }}</div>
<div class="table-operator">
<!-- 搜索板块 -->
<a-row class="row-item-search">
<a-form class="search-form" :form="searchForm" layout="inline" @submit="handleSearch">
<a-form-item label="商品名称">
<a-input v-decorator="['goodsName']" placeholder="请输入商品名称/编码" />
</a-form-item>
<a-form-item label="订单号">
<a-input v-decorator="['orderNo']" placeholder="请输入订单号" />
</a-form-item>
<a-form-item label="会员ID">
<a-input v-decorator="['userId']" placeholder="请输入会员ID" />
</a-form-item>
<a-form-item label="状态">
<a-select v-decorator="['status', { initialValue: -1 }]">
<a-select-option :value="-1">全部</a-select-option>
<a-select-option :value="1">显示</a-select-option>
<a-select-option :value="0">隐藏</a-select-option>
</a-select>
</a-form-item>
<a-form-item class="search-btn">
<a-button type="primary" icon="search" html-type="submit">搜索</a-button>
</a-form-item>
</a-form>
</a-row>
<!-- 操作板块 -->
<div class="row-item-tab clearfix">
<div class="tab-list fl-l">
<a-radio-group :defaultValue="queryParam.score" @change="handleTabs">
<a-radio-button :value="0">全部</a-radio-button>
<a-radio-button :value="10">好评</a-radio-button>
<a-radio-button :value="20">中评</a-radio-button>
<a-radio-button :value="30">差评</a-radio-button>
</a-radio-group>
</div>
</div>
</div>
<s-table
ref="table"
rowKey="comment_id"
:loading="isLoading"
:columns="columns"
:data="loadData"
:pageSize="15"
:scroll="{ x: 1450 }"
>
<!-- 商品信息 -->
<span slot="orderGoods" slot-scope="goods">
<GoodsItem
:data="{
image: goods.goods_image,
imageAlt: '商品图片',
title: goods.goods_name,
goodsProps: goods.goods_props
}"
/>
</span>
<!-- 会员信息 -->
<span slot="user" slot-scope="text">
<UserItem :user="text" />
</span>
<!-- 评分 -->
<span slot="score" slot-scope="text">
<a-tag
:color="{ 10: 'green', 20: '', 30: 'red' }[text]"
>{{ {10: '好评', 20: '中评', 30: '差评'}[text] }}</a-tag>
</span>
<!-- 评价内容 -->
<span slot="content" slot-scope="text">
<p class="twoline-hide" style="width: 270px;">{{ text }}</p>
</span>
<!-- 图片评价 -->
<span slot="is_picture" slot-scope="text">
<a-tag :color="text ? 'green' : ''">{{ text ? '是' : '否' }}</a-tag>
</span>
<!-- 状态 -->
<span slot="status" slot-scope="text">
<a-tag :color="text ? 'green' : ''">{{ text ? '显示' : '隐藏' }}</a-tag>
</span>
<!-- 操作项 -->
<span slot="action" slot-scope="text, item">
<a v-action:edit style="margin-right: 8px;" @click="handleEdit(item)">编辑</a>
<a v-action:delete @click="handleDelete(item)">删除</a>
</span>
</s-table>
<EditForm ref="EditForm" @handleSubmit="handleRefresh" />
</a-card>
</template>
<script>
import * as CommentApi from '@/api/goods/comment'
// import { ContentHeader } from '@/components'
import { STable, GoodsItem, UserItem } from '@/components/Table'
import EditForm from './modules/EditForm'
// 表格表头
const columns = [
{
title: 'ID',
dataIndex: 'comment_id'
},
{
title: '商品信息',
dataIndex: 'orderGoods',
width: '320px',
scopedSlots: { customRender: 'orderGoods' }
},
{
title: '买家',
dataIndex: 'user',
scopedSlots: { customRender: 'user' }
},
{
title: '评分',
dataIndex: 'score',
scopedSlots: { customRender: 'score' }
},
{
title: '评价内容',
dataIndex: 'content',
scopedSlots: { customRender: 'content' }
},
{
title: '图片评价',
dataIndex: 'is_picture',
scopedSlots: { customRender: 'is_picture' }
},
{
title: '状态',
dataIndex: 'status',
scopedSlots: { customRender: 'status' }
},
{
title: '排序',
dataIndex: 'sort'
},
{
title: '评价时间',
width: '180px',
dataIndex: 'create_time'
},
{
title: '操作',
dataIndex: 'action',
width: '150px',
fixed: 'right',
scopedSlots: { customRender: 'action' }
}
]
export default {
name: 'Index',
components: {
// ContentHeader,
STable,
GoodsItem,
UserItem,
EditForm
},
data () {
return {
expand: false,
// 当前表单元素
searchForm: this.$form.createForm(this),
// 评价分类列表
categoryListTree: [],
// 查询参数
queryParam: {
score: 0
},
// 正在加载
isLoading: false,
// 表头
columns,
// 加载数据方法 必须为 Promise 对象
loadData: param => {
return CommentApi.list({ ...param, ...this.queryParam })
.then(response => {
return response.data.list
})
}
}
},
created () {
},
methods: {
// 切换tab
handleTabs (e) {
this.queryParam.score = e.target.value
this.handleRefresh(true)
},
// 确认搜索
handleSearch (e) {
e.preventDefault()
this.searchForm.validateFields((error, values) => {
if (!error) {
this.queryParam = { ...this.queryParam, ...values }
this.handleRefresh(true)
}
})
},
/**
* 删除记录
*/
handleDelete (item) {
const app = this
const modal = this.$confirm({
title: '您确定要删除该记录吗?',
content: '删除后不可恢复',
onOk () {
return CommentApi.deleted({ commentId: item.comment_id })
.then(result => {
app.$message.success(result.message, 1.5)
app.handleRefresh()
})
.finally(result => {
modal.destroy()
})
}
})
},
/**
* 编辑记录
*/
handleEdit (item) {
this.$refs.EditForm.edit(item.comment_id)
},
/**
* 刷新列表
* @param Boolean bool 强制刷新到第一页
*/
handleRefresh (bool = false) {
this.$refs.table.refresh(bool)
}
}
}
</script>
<style lang="less" scoped>
.ant-card-body {
padding: 22px 29px 25px;
}
// 筛选tab
.tab-list {
margin-right: 20px;
}
</style>
@@ -0,0 +1,174 @@
<template>
<a-modal
:title="title"
:width="720"
:visible="visible"
:confirmLoading="confirmLoading"
:maskClosable="false"
:destroyOnClose="true"
@ok="handleSubmit"
@cancel="handleCancel"
>
<a-spin :spinning="confirmLoading">
<a-form :form="form">
<a-form-item label="评分" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-radio-group v-decorator="['score', {initialValue: 10, rules: [{required: true}]}]">
<a-radio :value="10">好评</a-radio>
<a-radio :value="20">中评</a-radio>
<a-radio :value="30">差评</a-radio>
</a-radio-group>
</a-form-item>
<a-form-item label="评价内容" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-textarea
placeholder="请输入评价内容 (300个字符以内)"
:autoSize="{ minRows: 4 }"
v-decorator="['content']"
/>
</a-form-item>
<a-form-item
label="评价图片"
:labelCol="labelCol"
:wrapperCol="wrapperCol"
extra="最多允许6张,可拖拽调整显示顺序"
>
<SelectImage
multiple
:maxNum="6"
:defaultList="record.imageList"
v-decorator="['imageIds']"
/>
</a-form-item>
<a-form-item label="状态" :labelCol="labelCol" :wrapperCol="wrapperCol" extra="用户端是否展示">
<a-radio-group v-decorator="['status', {initialValue: 1, rules: [{required: true}]}]">
<a-radio :value="1">显示</a-radio>
<a-radio :value="0">隐藏</a-radio>
</a-radio-group>
</a-form-item>
<a-form-item label="排序" :labelCol="labelCol" :wrapperCol="wrapperCol" extra="数字越小越靠前">
<a-input-number
:min="0"
v-decorator="['sort', {initialValue: 100, rules: [{required: true, message: '请输入至少1个数字'}]}]"
/>
</a-form-item>
</a-form>
</a-spin>
</a-modal>
</template>
<script>
import pick from 'lodash.pick'
import * as Api from '@/api/goods/comment'
import { SelectImage } from '@/components'
export default {
components: {
SelectImage
},
data () {
return {
// 对话框标题
title: '编辑评价',
// 标签布局属性
labelCol: {
span: 7
},
// 输入框布局属性
wrapperCol: {
span: 13
},
// modal(对话框)是否可见
visible: false,
// modal(对话框)确定按钮 loading
confirmLoading: false,
// 当前表单元素
form: this.$form.createForm(this),
// 当前记录ID
commentId: null,
// 当前记录详情
record: {}
}
},
methods: {
/**
* 显示对话框
*/
edit (commentId) {
// 显示窗口
this.visible = true
// 当前记录ID
this.commentId = commentId
// 获取当前详情记录
this.getRecordData()
},
// 获取当前详情记录
getRecordData () {
this.confirmLoading = true
Api.detail({ commentId: this.commentId })
.then(result => {
// 当前记录
this.record = result.data.detail
// 设置默认值
this.setFieldsValue()
})
.finally((result) => {
this.confirmLoading = false
})
},
/**
* 设置默认值
*/
setFieldsValue () {
const { form: { setFieldsValue } } = this
this.$nextTick(() => {
setFieldsValue(pick(this.record, ['score', 'content', 'status', 'sort', 'imageIds']))
})
},
/**
* 确认按钮
*/
handleSubmit (e) {
e.preventDefault()
// 表单验证
const { form: { validateFields } } = this
validateFields((errors, values) => {
// 提交到后端api
!errors && this.onFormSubmit(values)
})
},
/**
* 关闭对话框事件
*/
handleCancel () {
this.visible = false
this.record = {}
this.form.resetFields()
},
/**
* 提交到后端api
*/
onFormSubmit (values) {
this.confirmLoading = true
Api.edit({ commentId: this.commentId, form: values })
.then((result) => {
// 显示成功
this.$message.success(result.message, 1.5)
// 关闭对话框事件
this.handleCancel()
// 通知父端组件提交完成了
this.$emit('handleSubmit', values)
})
.finally((result) => {
this.confirmLoading = false
})
}
}
}
</script>
@@ -0,0 +1,119 @@
<template>
<div class="goods-type">
<div
class="type-item"
v-for="(item, index) in list"
:key="index"
:class="{ checked: value == item.value }"
v-show="!onlyShowChecked || value == item.value"
@click="handleItem(item.value)"
>
<div class="type-title">{{ item.title }}</div>
<div class="type-help">{{ item.help }}</div>
<a-icon v-if="value == item.value" class="icon-checked" type="check" />
</div>
</div>
</template>
<script>
import PropTypes from 'ant-design-vue/es/_util/vue-types'
import { GoodsTypeEnum } from '@/common/enum/goods'
// 数字输入框组
export default {
name: 'GoodsType',
model: {
prop: 'value',
event: 'change'
},
props: {
// 当前选中项的value
value: PropTypes.any,
// 仅显示选中的项
onlyShowChecked: PropTypes.bool.def(false)
},
data () {
return {
defaultValue: GoodsTypeEnum.PHYSICAL.value,
list: [
{
title: GoodsTypeEnum.PHYSICAL.name,
help: '物流发货',
value: GoodsTypeEnum.PHYSICAL.value
}
]
}
},
methods: {
// 切换选中事件
handleItem (value) {
this.$emit('change', value)
}
}
}
</script>
<style lang="less" scoped>
// 商品类型
.goods-type {
display: flex;
margin-bottom: 15px;
.type-item {
position: relative;
border-radius: 2px;
padding: 8px 26px;
margin-right: 20px;
line-height: 20px;
text-align: center;
background-color: #ffffff;
color: #262626;
border: 1px solid #d9d9d9;
cursor: pointer;
user-select: none;
transition: all .2s ease .1s;
&.checked {
border-color: @primary-color;
.type-title {
color: @primary-color;
transition: all .2s ease .1s;
}
&:after {
position: absolute;
right: 0;
bottom: 0;
content: '';
width: 0;
height: 0;
border: 20px solid transparent;
border-bottom-color: @primary-color;
transform: translate(20px, 20px) rotate(135deg);
}
}
.type-title {
color: #262626;
font-size: 14px;
}
.type-help {
color: #8a9099;
font-size: 12px;
}
.icon-checked {
position: absolute;
right: 2px;
bottom: 2px;
z-index: 1;
font-size: 12px;
color: #fff;
}
}
}
</style>
@@ -0,0 +1,264 @@
<template>
<div>
<a-form-item
label="优惠券"
:labelCol="labelCol"
:wrapperCol="wrapperCol"
>
<!-- 添加按钮 -->
<div class="sku-batch">
<a-button @click="handleAddCoupon">添加优惠券</a-button>
</div>
<!-- 优惠券列表 -->
<a-table
class="sku-list"
:columns="columns"
:dataSource="multiCouponData"
:scroll="{ x: true }"
:pagination="false"
bordered
rowKey="row_id"
>
<!-- 选择优惠券 -->
<template slot="coupon_id" slot-scope="text, item">
<a-select
v-model="item.coupon_id"
placeholder="请选择优惠券"
style="width: 100%"
@change="emitChange"
>
<a-select-option
v-for="(sub_item, index) in couponList"
:key="index"
:value="sub_item.coupon_id"
>
{{ sub_item.name }}
</a-select-option>
</a-select>
</template>
<!-- 发送数量 -->
<template slot="send_num" slot-scope="text, item">
<a-input-number
v-model="item.send_num"
size="small"
:min="0"
:precision="0"
@change="emitChange"
/>
</template>
<!-- 删除操作 -->
<template slot="action" slot-scope="text, item">
<a @click="handleDelete(item)">删除</a>
</template>
</a-table>
</a-form-item>
</div>
</template>
<script>
import PropTypes from 'ant-design-vue/es/_util/vue-types'
// 表格表头
const columns = [
{
title: '优惠券',
dataIndex: 'coupon_id',
width: 120,
scopedSlots: { customRender: 'coupon_id' }
},
{
title: '数量',
dataIndex: 'send_num',
width: 120,
scopedSlots: { customRender: 'send_num' }
},
{
title: '操作',
dataIndex: 'action',
width: '150px',
scopedSlots: { customRender: 'action' }
}
]
export default {
props: {
couponList: PropTypes.any,
defaultCouponList: PropTypes.array.def([]),
isCouponLocked: PropTypes.bool.def(false)
},
data () {
return {
columns,
labelCol: { span: 3 },
wrapperCol: { span: 21 },
multiCouponData: []
}
},
watch: {
defaultCouponList (val) {
if (val.length) {
this.getData()
}
}
},
created () {
this.getData()
},
methods: {
getData () {
this.multiCouponData = this.defaultCouponList.map((item, index) => ({
...item,
row_id: item.row_id || Date.now() + index
}))
this.emitChange()
},
handleAddCoupon () {
this.multiCouponData.push({
coupon_id: null,
send_num: 1,
row_id: Date.now()
})
this.emitChange()
},
handleDelete (record) {
this.multiCouponData = this.multiCouponData.filter(item => item !== record)
this.emitChange()
},
emitChange () {
this.$emit('change', this.multiCouponData)
}
}
}
</script>
<style lang="less" scoped>
// 商品多规格
.spec-group {
width: 895px;
margin-bottom: 15px;
line-height: normal;
.ant-input {
height: 28px;
padding: 4px 11px;
font-size: @font-size-base;
line-height: 1.5;
&::placeholder {
font-size: @font-size-base;
}
}
.form-item-help {
line-height: 36px;
}
.spec-group-item {
background: #f4f5f9;
padding: 7px 12px;
line-height: 28px;
.group-item-input {
float: left;
width: 180px;
}
.group-item-delete {
font-size: 12px;
float: right;
}
}
.spec-value {
background: #fbfbfb;
padding: 8px 35px;
.spec-value-add,
.spec-value-item {
float: left;
margin-bottom: 6px;
margin-top: 6px;
}
.spec-value-add {
height: 28px;
line-height: 28px;
a {
font-size: 12px;
}
}
.spec-value-item {
position: relative;
margin-right: 20px;
.value-item-input {
width: 186px;
}
&:hover {
.icon-close {
display: block;
}
}
.icon-close {
display: none;
position: absolute;
top: -8px;
right: -8px;
cursor: pointer;
font-size: 16px;
color: #7d7d7d;
}
}
}
}
// 添加规格按钮
.spec-group-add-btn {
font-size: @font-size-base;
}
// 批量设置sku
.sku-batch {
line-height: 40px;
margin-bottom: 12px;
.title {
line-height: 28px;
margin-right: 15px;
font-size: @font-size-base;
}
/deep/.ant-input,
/deep/.ant-input-number {
width: 120px;
margin-right: 15px;
}
/deep/.ant-input {
width: 140px;
}
}
// sku列表
.sku-list {
width: 895px;
/deep/.ant-table-thead > tr > th,
/deep/.ant-table-tbody > tr > td {
white-space: nowrap;
}
/deep/.ant-table-tbody > tr > td {
padding: 12px 18px;
}
/deep/.ant-input-sm,
/deep/.ant-input-number-sm {
height: 28px;
}
/deep/.ant-input-number-sm input {
height: 26px;
}
}
</style>
@@ -0,0 +1,407 @@
<template>
<div>
<a-form-item label="商品规格" :labelCol="labelCol" :wrapperCol="wrapperCol">
<div v-if="true" class="form-item-help" style="line-height: 36px">
<small>最多添加3个商品规格组,生成的SKU数量不能超出50个</small>
</div>
<!-- 规格组 -->
<div class="spec-group" v-for="(item, index) in multiSpecData.specList" :key="index">
<div class="spec-group-item clearfix">
<a-input
class="group-item-input"
v-model="item.spec_name"
:readOnly="isSpecLocked"
placeholder="请输入规格名称"
@change="onChangeSpecGroupIpt"
/>
<a
v-if="!isSpecLocked"
class="group-item-delete"
href="javascript:;"
@click="handleDeleteSpecGroup(index)"
>删除规格组</a>
</div>
<div class="spec-value clearfix">
<div class="spec-value-item" v-for="(itm, idx) in item.valueList" :key="idx">
<a-input
class="value-item-input"
v-model="itm.spec_value"
:readOnly="isSpecLocked"
placeholder="请输入规格值"
@change="onChangeSpecValueIpt"
/>
<a-icon
v-if="!isSpecLocked"
class="icon-close"
theme="filled"
type="close-circle"
@click="handleDeleteSpecValue(index, idx)"
/>
</div>
<div v-if="!isSpecLocked" class="spec-value-add">
<a
class="group-item-delete"
href="javascript:;"
@click="handleAddSpecValue(index)"
>新增规格值</a>
</div>
</div>
</div>
<!-- 添加规格按钮 -->
<a-button
v-if="!isSpecLocked && multiSpecData.specList.length < 3"
class="spec-group-add-btn"
icon="plus"
@click="handleAddSpecGroup"
>添加规格组</a-button>
</a-form-item>
<a-form-item
v-show="multiSpecData.skuList.length"
label="SKU列表"
:labelCol="labelCol"
:wrapperCol="wrapperCol"
>
<!-- 批量设置 -->
<div v-if="multiSpecData.skuList.length > 1" class="sku-batch">
<span class="title">批量设置:</span>
<a-input-number
v-model="multiSpecData.skuBatchForm.goods_price"
placeholder="商品价格"
:min="0.01"
:precision="2"
/>
<a-input-number
v-model="multiSpecData.skuBatchForm.line_price"
placeholder="划线价格"
:min="0"
:precision="2"
/>
<a-input-number
v-model="multiSpecData.skuBatchForm.stock_num"
placeholder="库存数量"
:min="0"
:precision="0"
/>
<a-input-number
v-model="multiSpecData.skuBatchForm.goods_weight"
placeholder="商品重量"
:min="0"
:precision="2"
/>
<a-input v-model="multiSpecData.skuBatchForm.goods_sku_no" placeholder="sku编码" />
<a-button @click="handleSkuBatch">立即设置</a-button>
</div>
<!-- sku列表table -->
<a-table
class="sku-list"
:columns="multiSpecData.skuColumns"
:dataSource="multiSpecData.skuList"
:scroll="{ x: true }"
:pagination="false"
bordered
>
<!-- 预览图 -->
<template slot="image" slot-scope="text, item">
<SelectImage
v-model="item.image_id"
:defaultList="(item.image_id > 0 && item.image) ? [item.image] : []"
:width="50"
/>
</template>
<!-- 商品价格 -->
<template slot="goods_price" slot-scope="text, item">
<a-input-number v-model="item.goods_price" size="small" :min="0.01" :precision="2" />
</template>
<!-- 划线价格 -->
<template slot="line_price" slot-scope="text, item">
<a-input-number v-model="item.line_price" size="small" :min="0" :precision="2" />
</template>
<!-- 库存数量 -->
<template slot="stock_num" slot-scope="text, item">
<a-input-number v-model="item.stock_num" size="small" :min="0" :precision="0" />
</template>
<!-- 商品重量 -->
<template slot="goods_weight" slot-scope="text, item">
<a-input-number v-model="item.goods_weight" size="small" :min="0" :precision="2" />
</template>
<!-- sku编码 -->
<template slot="goods_sku_no" slot-scope="text, item">
<a-input v-model="item.goods_sku_no" size="small" />
</template>
</a-table>
</a-form-item>
</div>
</template>
<script>
import PropTypes from 'ant-design-vue/es/_util/vue-types'
import MultiSpecModel from '@/common/model/goods/MultiSpec'
import { SelectImage } from '@/components'
export default {
components: {
SelectImage
},
props: {
// 默认的规格列表
defaultSpecList: PropTypes.array.def([]),
// 默认的SKU列表
defaultSkuList: PropTypes.array.def([]),
// 商品规格是否锁定(锁定状态下不允许编辑规格)
isSpecLocked: PropTypes.bool.def(false)
},
data () {
return {
// 标签布局属性
labelCol: { span: 3 },
// 输入框布局属性
wrapperCol: { span: 21 },
// 商品多规格模型
MultiSpecModel: new MultiSpecModel(),
// MultiSpecModel: Object,
multiSpecData: {
// 规格列表
specList: [],
// SKU列表
skuList: []
}
}
},
watch: {
defaultSpecList (val) {
if (val.length && this.MultiSpecModel.isEmpty()) {
this.getData()
}
}
},
// 初始化数据
created () {
// 获取规格及SKU信息
this.getData()
},
methods: {
// 获取规格及SKU信息(展示)
getData () {
const { defaultSpecList, defaultSkuList } = this
this.multiSpecData = this.MultiSpecModel.getData(defaultSpecList, defaultSkuList)
},
// 获取规格及SKU信息(表单提交)
getFromSpecData () {
return this.MultiSpecModel.getFromSpecData()
},
// 添加规格组
handleAddSpecGroup () {
if (this.checkSkuMaxNum()) {
this.MultiSpecModel.handleAddSpecGroup()
}
},
// 删除规格组
handleDeleteSpecGroup (groupIndex) {
const app = this
const modal = this.$confirm({
title: '您确定要删除该规格组吗?',
content: '删除后不可恢复',
onOk () {
// 删除元素
app.MultiSpecModel.handleDeleteSpecGroup(groupIndex)
// 关闭对话框
modal.destroy()
}
})
},
// 新增规格值
handleAddSpecValue (groupIndex) {
if (this.checkSkuMaxNum()) {
this.MultiSpecModel.handleAddSpecValue(groupIndex)
}
},
// 删除规格值
handleDeleteSpecValue (groupIndex, valueIndex) {
const app = this
const modal = this.$confirm({
title: '您确定要删除该规格值吗?',
content: '删除后不可恢复',
onOk () {
// 删除元素
app.MultiSpecModel.handleDeleteSpecValue(groupIndex, valueIndex)
// 关闭对话框
modal.destroy()
}
})
},
// 规格组输入框change事件
onChangeSpecGroupIpt () {
// 更新skuList
this.MultiSpecModel.onUpdate(true)
},
// 规格值输入框change事件
onChangeSpecValueIpt (event, itm) {
// 更新skuList
this.MultiSpecModel.onUpdate(true)
},
// 验证最大sku数量
checkSkuMaxNum () {
const skuList = this.multiSpecData.skuList
if (skuList.length >= 50) {
this.$message.error(`生成的sku列表数量不能大于50个,当前数量:${skuList.length}个`, 2.5)
return false
}
return true
},
// 批量设置sku事件
handleSkuBatch () {
this.MultiSpecModel.handleSkuBatch()
},
// 验证多规格表单
verifyForm () {
if (!this.MultiSpecModel.verifyForm()) {
this.$message.error(this.MultiSpecModel.getError(), 2)
return false
}
return true
}
}
}
</script>
<style lang="less" scoped>
// 商品多规格
.spec-group {
width: 895px;
margin-bottom: 15px;
line-height: normal;
.ant-input {
height: 28px;
padding: 4px 11px;
font-size: @font-size-base;
line-height: 1.5;
&::placeholder {
font-size: @font-size-base;
}
}
.form-item-help {
line-height: 36px;
}
.spec-group-item {
background: #f4f5f9;
padding: 7px 12px;
line-height: 28px;
.group-item-input {
float: left;
width: 180px;
}
.group-item-delete {
font-size: 12px;
float: right;
}
}
.spec-value {
background: #fbfbfb;
padding: 8px 35px;
.spec-value-add,
.spec-value-item {
float: left;
margin-bottom: 6px;
margin-top: 6px;
}
.spec-value-add {
height: 28px;
line-height: 28px;
a {
font-size: 12px;
}
}
.spec-value-item {
position: relative;
margin-right: 20px;
.value-item-input {
width: 186px;
}
&:hover {
.icon-close {
display: block;
}
}
.icon-close {
display: none;
position: absolute;
top: -8px;
right: -8px;
cursor: pointer;
font-size: 16px;
color: #7d7d7d;
}
}
}
}
// 添加规格按钮
.spec-group-add-btn {
font-size: @font-size-base;
}
// 批量设置sku
.sku-batch {
line-height: 40px;
margin-bottom: 12px;
.title {
line-height: 28px;
margin-right: 15px;
font-size: @font-size-base;
}
/deep/.ant-input,
/deep/.ant-input-number {
width: 120px;
margin-right: 15px;
}
/deep/.ant-input {
width: 140px;
}
}
// sku列表
.sku-list {
width: 895px;
/deep/.ant-table-thead > tr > th,
/deep/.ant-table-tbody > tr > td {
white-space: nowrap;
}
/deep/.ant-table-tbody > tr > td {
padding: 12px 18px;
}
/deep/.ant-input-sm,
/deep/.ant-input-number-sm {
height: 28px;
}
/deep/.ant-input-number-sm input {
height: 26px;
}
}
</style>
@@ -0,0 +1,5 @@
import GoodsType from './GoodsType'
import MultiSpec from './MultiSpec'
import MultiCoupon from './MultiCoupon'
export { GoodsType, MultiSpec, MultiCoupon }
+181
View File
@@ -0,0 +1,181 @@
<template>
<a-card :bordered="false">
<div class="card-title">{{ $route.meta.title }}</div>
<div class="table-operator">
<a-row>
<a-col :span="6">
<a-button v-action:add type="primary" icon="plus" @click="handleAdd">新增</a-button>
</a-col>
<a-col :span="8" :offset="10">
<a-input-search
style="max-width: 300px; min-width: 150px;float: right;"
v-model="queryParam.search"
placeholder="请输入服务名称"
@search="onSearch"
/>
</a-col>
</a-row>
</div>
<s-table
ref="table"
rowKey="service_id"
:loading="isLoading"
:columns="columns"
:data="loadData"
:pageSize="15"
>
<!-- 概述 -->
<span slot="summary" slot-scope="text">
<p class="summary oneline-hide">{{ text }}</p>
</span>
<!-- 是否默认 -->
<span slot="is_default" slot-scope="text">
<a-tag :color="text ? 'green' : ''">{{ text ? '是' : '否' }}</a-tag>
</span>
<!-- 状态 -->
<span slot="status" slot-scope="text">
<a-tag :color="text ? 'green' : ''">{{ text ? '显示' : '隐藏' }}</a-tag>
</span>
<!-- 操作 -->
<span slot="action" slot-scope="text, item">
<a v-action:edit style="margin-right: 8px;" @click="handleEdit(item)">编辑</a>
<a v-action:delete @click="handleDelete(item)">删除</a>
</span>
</s-table>
<AddForm ref="AddForm" @handleSubmit="handleRefresh" />
<EditForm ref="EditForm" @handleSubmit="handleRefresh" />
</a-card>
</template>
<script>
import * as Api from '@/api/goods/service'
import { STable } from '@/components'
import AddForm from './modules/AddForm'
import EditForm from './modules/EditForm'
export default {
name: 'Index',
components: {
STable,
AddForm,
EditForm
},
data () {
return {
// 查询参数
queryParam: {},
// 正在加载
isLoading: false,
// 表头
columns: [
{
title: '服务ID',
dataIndex: 'service_id'
},
{
title: '服务名称',
dataIndex: 'name',
width: '300px'
},
{
title: '概述',
dataIndex: 'summary',
scopedSlots: { customRender: 'summary' }
},
{
title: '是否默认',
dataIndex: 'is_default',
scopedSlots: { customRender: 'is_default' }
},
{
title: '状态',
dataIndex: 'status',
scopedSlots: { customRender: 'status' }
},
{
title: '排序',
dataIndex: 'sort'
},
{
title: '更新时间',
dataIndex: 'update_time'
},
{
title: '操作',
dataIndex: 'action',
width: '180px',
scopedSlots: { customRender: 'action' }
}
],
// 加载数据方法 必须为 Promise 对象
loadData: param => {
return Api.list({ ...param, ...this.queryParam })
.then(response => {
return response.data.list
})
}
}
},
created () {
},
methods: {
/**
* 新增记录
*/
handleAdd () {
this.$refs.AddForm.add()
},
/**
* 编辑记录
*/
handleEdit (item) {
this.$refs.EditForm.edit(item)
},
/**
* 删除记录
*/
handleDelete (item) {
const self = this
const modal = this.$confirm({
title: '您确定要删除该记录吗?',
content: '删除后不可恢复',
onOk () {
return Api.deleted({ serviceId: item['service_id'] })
.then((result) => {
self.$message.success(result.message, 1.5)
self.handleRefresh()
})
.finally(result => {
modal.destroy()
})
}
})
},
/**
* 刷新列表
* @param Boolean bool 强制刷新到第一页
*/
handleRefresh (bool = false) {
this.$refs.table.refresh(bool)
},
/**
* 检索查询
*/
onSearch () {
this.handleRefresh(true)
}
}
}
</script>
<style lang="less" scoped>
.summary {
max-width: 400px;
}
</style>
@@ -0,0 +1,126 @@
<template>
<a-modal
:title="title"
:width="720"
:visible="visible"
:confirmLoading="confirmLoading"
:maskClosable="false"
@ok="handleSubmit"
@cancel="handleCancel"
>
<a-spin :spinning="confirmLoading">
<a-form :form="form">
<a-form-item label="服务名称" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-input
placeholder="请输入服务与承诺的名称"
v-decorator="['name', {rules: [{required: true, min: 2, message: '请输入至少2个字符'}]}]"
/>
</a-form-item>
<a-form-item label="概述" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-textarea
placeholder="请输入概述内容 (300个字符以内)"
:autoSize="{ minRows: 4 }"
v-decorator="['summary']"
/>
</a-form-item>
<a-form-item label="是否默认" :labelCol="labelCol" :wrapperCol="wrapperCol" extra="新增商品时是否默认勾选">
<a-radio-group v-decorator="['is_default', {initialValue: 1, rules: [{required: true}]}]">
<a-radio :value="1">是</a-radio>
<a-radio :value="0">否</a-radio>
</a-radio-group>
</a-form-item>
<a-form-item label="状态" :labelCol="labelCol" :wrapperCol="wrapperCol" extra="用户端是否展示">
<a-radio-group v-decorator="['status', {initialValue: 1, rules: [{required: true}]}]">
<a-radio :value="1">显示</a-radio>
<a-radio :value="0">隐藏</a-radio>
</a-radio-group>
</a-form-item>
<a-form-item label="排序" :labelCol="labelCol" :wrapperCol="wrapperCol" extra="数字越小越靠前">
<a-input-number
:min="0"
v-decorator="['sort', {initialValue: 100, rules: [{required: true, message: '请输入至少1个数字'}]}]"
/>
</a-form-item>
</a-form>
</a-spin>
</a-modal>
</template>
<script>
import * as Api from '@/api/goods/service'
export default {
data () {
return {
// 对话框标题
title: '',
// 标签布局属性
labelCol: {
span: 7
},
// 输入框布局属性
wrapperCol: {
span: 13
},
// modal(对话框)是否可见
visible: false,
// modal(对话框)确定按钮 loading
confirmLoading: false,
// 当前表单元素
form: this.$form.createForm(this),
}
},
methods: {
/**
* 显示对话框
*/
add () {
// 显示窗口
this.title = '新增记录'
this.visible = true
},
/**
* 确认按钮
*/
handleSubmit (e) {
e.preventDefault()
// 表单验证
const { form: { validateFields } } = this
validateFields((errors, values) => {
// 提交到后端api
!errors && this.onFormSubmit(values)
})
},
/**
* 关闭对话框事件
*/
handleCancel () {
this.visible = false
this.form.resetFields()
},
/**
* 提交到后端api
*/
onFormSubmit (values) {
this.confirmLoading = true
Api.add({ form: values })
.then((result) => {
// 显示成功
this.$message.success(result.message, 1.5)
// 关闭对话框事件
this.handleCancel()
// 通知父端组件提交完成了
this.$emit('handleSubmit', values)
})
.finally((result) => {
this.confirmLoading = false
})
}
}
}
</script>
@@ -0,0 +1,144 @@
<template>
<a-modal
:title="title"
:width="720"
:visible="visible"
:confirmLoading="confirmLoading"
:maskClosable="false"
@ok="handleSubmit"
@cancel="handleCancel"
>
<a-spin :spinning="confirmLoading">
<a-form :form="form">
<a-form-item label="服务名称" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-input
placeholder="请输入服务与承诺的名称"
v-decorator="['name', {rules: [{required: true, min: 2, message: '请输入至少2个字符'}]}]"
/>
</a-form-item>
<a-form-item label="概述" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-textarea
placeholder="请输入概述内容 (300个字符以内)"
:autoSize="{ minRows: 4 }"
v-decorator="['summary']"
/>
</a-form-item>
<a-form-item label="是否默认" :labelCol="labelCol" :wrapperCol="wrapperCol" extra="新增商品时是否默认勾选">
<a-radio-group v-decorator="['is_default', {initialValue: 1, rules: [{required: true}]}]">
<a-radio :value="1">是</a-radio>
<a-radio :value="0">否</a-radio>
</a-radio-group>
</a-form-item>
<a-form-item label="状态" :labelCol="labelCol" :wrapperCol="wrapperCol" extra="用户端是否展示">
<a-radio-group v-decorator="['status', {initialValue: 1, rules: [{required: true}]}]">
<a-radio :value="1">显示</a-radio>
<a-radio :value="0">隐藏</a-radio>
</a-radio-group>
</a-form-item>
<a-form-item label="排序" :labelCol="labelCol" :wrapperCol="wrapperCol" extra="数字越小越靠前">
<a-input-number
:min="0"
v-decorator="['sort', {initialValue: 100, rules: [{required: true, message: '请输入至少1个数字'}]}]"
/>
</a-form-item>
</a-form>
</a-spin>
</a-modal>
</template>
<script>
import pick from 'lodash.pick'
import * as Api from '@/api/goods/service'
export default {
data () {
return {
// 对话框标题
title: '',
// 标签布局属性
labelCol: {
span: 7
},
// 输入框布局属性
wrapperCol: {
span: 13
},
// modal(对话框)是否可见
visible: false,
// modal(对话框)确定按钮 loading
confirmLoading: false,
// 当前表单元素
form: this.$form.createForm(this),
// 当前记录
record: {}
}
},
methods: {
/**
* 显示对话框
*/
edit (record) {
// 显示窗口
this.title = '编辑记录'
this.visible = true
// 当前管理员记录
this.record = record
// 设置默认值
this.setFieldsValue()
},
/**
* 设置默认值
*/
setFieldsValue () {
const { form: { setFieldsValue } } = this
this.$nextTick(() => {
setFieldsValue(pick(this.record, ['name', 'summary', 'is_default', 'status', 'sort']))
})
},
/**
* 确认按钮
*/
handleSubmit (e) {
e.preventDefault()
// 表单验证
const { form: { validateFields } } = this
validateFields((errors, values) => {
// 提交到后端api
!errors && this.onFormSubmit(values)
})
},
/**
* 关闭对话框事件
*/
handleCancel () {
this.visible = false
this.form.resetFields()
},
/**
* 提交到后端api
*/
onFormSubmit (values) {
this.confirmLoading = true
Api.edit({ serviceId: this.record['service_id'], form: values })
.then((result) => {
// 显示成功
this.$message.success(result.message, 1.5)
// 关闭对话框事件
this.handleCancel()
// 通知父端组件提交完成了
this.$emit('handleSubmit', values)
})
.finally((result) => {
this.confirmLoading = false
})
}
}
}
</script>
+25
View File
@@ -0,0 +1,25 @@
.tabs-content {
.tab-pane {
animation: fadenum 0.6s ease;
}
}
/deep/.ant-form-item-control {
padding-left: 10px;
.ant-form-item-control {
padding-left: 0;
}
}
.ant-form-item {
margin-bottom: 18px;
.ant-form-item {
margin-bottom: 0;
}
}
.input-number-group {
margin: 3px 0;
}
+526
View File
@@ -0,0 +1,526 @@
<template>
<a-spin :spinning="isLoading">
<div class="container">
<!-- 实时概况 -->
<a-card class="overview" :bordered="false">
<div class="card-title">
<span>实时概况(今日)</span>
</div>
<a-row :gutter="32">
<a-col class :span="6">
<div class="item flex">
<div class="col-left">
<div class="icon-body flex flex-x-center flex-y-center">
<a-icon :component="Icons.overview.sale" />
</div>
</div>
<div class="col-right">
<p class="name">销售额 (元)</p>
<p class="value">{{ data.overview.orderTotalPrice.tday }}</p>
<p class="yesterday">昨日:{{ data.overview.orderTotalPrice.yday }}</p>
</div>
</div>
</a-col>
<a-col class :span="6">
<div class="item flex">
<div class="col-right">
<p class="name">支付订单数</p>
<p class="value">{{ data.overview.orderTotal.tday }}</p>
<p class="yesterday">昨日:{{ data.overview.orderTotal.yday }}</p>
</div>
</div>
</a-col>
<!--<a-col class :span="6">
<div class="item flex">
<div class="col-left">
<div class="icon-body flex flex-x-center flex-y-center">
<a-icon :component="Icons.overview.increase" />
</div>
</div>
<div class="col-right">
<p class="name">新增会员数</p>
<p class="value">{{ data.overview.newUserTotal.tday }}</p>
<p class="yesterday">昨日:{{ data.overview.newUserTotal.yday }}</p>
</div>
</div>
</a-col>
<a-col class :span="6">
<div class="item flex">
<div class="col-right">
<p class="name">付款会员数</p>
<p class="value">{{ data.overview.consumeUserTotal.tday }}</p>
<p class="yesterday">昨日:{{ data.overview.consumeUserTotal.yday }}</p>
</div>
</div>
</a-col>-->
</a-row>
</a-card>
<!-- 统计 -->
<a-row class="statistics mt-20" :gutter="32" v-if="data.power.super">
<a-col class="item" :span="6">
<a-card :bordered="false">
<div class="flex flex-y-center">
<div class="col-left">
<div class="icon-body flex flex-x-center flex-y-center">
<a-icon class="icon" :component="Icons.statistics.goods" />
</div>
</div>
<div class="col-right">
<p class="info">商品总数量</p>
<p class="keynote">{{ data.statistics.goodsTotal }}</p>
</div>
</div>
</a-card>
</a-col>
<a-col class="item" :span="6">
<a-card :bordered="false">
<div class="flex flex-y-center">
<div class="col-left">
<div class="icon-body flex flex-x-center flex-y-center">
<a-icon class="icon" :component="Icons.statistics.user" />
</div>
</div>
<div class="col-right">
<p class="info">会员总人数</p>
<p class="keynote">{{ data.statistics.userTotal }}</p>
</div>
</div>
</a-card>
</a-col>
<a-col class="item" :span="6">
<a-card :bordered="false">
<div class="flex flex-y-center">
<div class="col-left">
<div class="icon-body flex flex-x-center flex-y-center">
<a-icon class="icon" :component="Icons.statistics.order" />
</div>
</div>
<div class="col-right">
<p class="info">付款订单总量</p>
<p class="keynote">{{ data.statistics.orderTotal }}</p>
</div>
</div>
</a-card>
</a-col>
<a-col class="item" :span="6">
<a-card :bordered="false">
<div class="flex flex-y-center">
<div class="col-left">
<div class="icon-body flex flex-x-center flex-y-center">
<a-icon class="icon" :component="Icons.statistics.consume" />
</div>
</div>
<div class="col-right">
<p class="info">消费总金额</p>
<p class="keynote">{{ data.statistics.orderMoneyTotal }}</p>
</div>
</div>
</a-card>
</a-col>
</a-row>
<!-- 待办事项 -->
<!--<a-card class="pending mt-20" :bordered="false">
<a-row :gutter="32">
<a-col :span="6">
<div class="item">
<router-link to="/order/list/delivery">
<p class="name">待发货订单(笔)</p>
<p class="value">{{ data.pending.deliverOrderTotal }}</p>
</router-link>
</div>
</a-col>
<a-col :span="6">
<div class="item">
<router-link to="/order/refund/index">
<p class="name">售后单(笔)</p>
<p class="value">{{ data.pending.refundTotal }}</p>
</router-link>
</div>
</a-col>
<a-col :span="6">
<div class="item">
<router-link to="/order/list/pay">
<p class="name">待付款订单(笔)</p>
<p class="value">{{ data.pending.paidOrderTotal }}</p>
</router-link>
</div>
</a-col>
<a-col :span="6">
<div class="item">
<router-link :to="{ path: '/goods/index', query: { listType: 'sold_out' } }">
<p class="name">已售罄商品</p>
<p class="value">{{ data.pending.soldoutGoodsTotal }}</p>
</router-link>
</div>
</a-col>
</a-row>
</a-card>--》
<!-- 常用功能 -->
<!--<a-card class="functions mt-20" :bordered="false">
<div class="card-title">
<span>常用功能</span>
</div>
<a-row :gutter="32">
<a-col class :span="6" v-for="(item, index) in functions" :key="index">
<div class="item" @click="handleLink(item.link)">
<div class="flex flex-y-center">
<div
class="icon flex flex-x-center flex-y-center"
:style="{ backgroundColor: item.color }"
>
<a-icon :component="item.icon" />
</div>
<span class="name">{{ item.name }}</span>
</div>
</div>
</a-col>
</a-row>
</a-card>-->
<!-- 近七日交易走势 -->
<a-card class="trade-trend mt-20" :bordered="false">
<div class="card-title">
<span>近七日交易走势</span>
</div>
<div class="echarts-body">
<div id="main" style="width: 100%; height: 400px;"></div>
</div>
</a-card>
</div>
</a-spin>
</template>
<script>
import echarts from 'echarts'
import 'echarts/theme/fresh-cut'
import * as Api from '@/api/home'
import * as Icons from './modules/icon'
// 常用功能
const functions = [
{
name: '发布商品',
icon: Icons.functions.goods,
color: '#00BF57',
link: '/goods/create'
},
{
name: '店铺装修',
icon: Icons.functions.decorate,
color: '#f15858',
link: '/page/index'
},
{
name: '会员管理',
icon: Icons.functions.user,
color: '#139dff',
link: '/user/index'
},
{
name: '订单管理',
icon: Icons.functions.order,
color: '#139dff',
link: '/order/list/all'
},
{
name: '售后管理',
icon: Icons.functions.refund,
color: '#139dff',
link: '/order/refund/index'
},
{
name: '优惠券',
icon: Icons.functions.coupon,
color: '#f15858',
link: '/market/coupon/index'
},
{
name: '数据统计',
icon: Icons.functions.statistics,
color: '#139dff',
link: '/statistics'
}
]
const data = {
overview: {
orderTotalPrice: {
tday: '0.00',
ytd: '0.00'
},
orderTotal: {
tday: '0',
ytd: '0'
},
newUserTotal: {
tday: '0',
ytd: '0'
},
consumeUserTotal: {
tday: '0',
ytd: '0'
}
},
statistics: {
goodsTotal: '0',
userTotal: '0',
orderTotal: '0',
orderMoneyTotal: '0',
consumeUserTotal: '0'
},
pending: {
deliverOrderTotal: 0,
refundTotal: 0,
paidOrderTotal: 0,
soldoutGoodsTotal: 0
},
tradeTrend: {
date: [],
orderTotal: [],
orderTotalPrice: []
},
power: {
super: 0
}
}
export default {
name: 'Index',
data () {
return {
Icons,
functions,
// 正在提交
isLoading: false,
// 首页数据
data
}
},
created () {
// 获取首页数据
this.getData()
},
methods: {
// 获取首页数据
getData () {
this.isLoading = true
Api.data()
.then(result => {
this.data = result.data.data
// 渲染走势图
this.$nextTick(() => {
this.myEcharts()
})
})
.finally(() => {
this.isLoading = false
})
},
// 跳转到指定link
handleLink (link) {
const app = this
if (!app.$auth(link)) {
app.$message.info('很抱歉,没有该页面的访问权限')
return false
}
app.$router.push(link)
},
// 渲染走势图
myEcharts () {
const { data } = this
// 基于准备好的dom,初始化echarts实例
const myChart = echarts.init(document.getElementById('main'), 'fresh-cut')
// 指定图表的配置项和数据
const option = {
tooltip: {
trigger: 'axis'
},
legend: {
data: ['成交量', '成交额']
},
toolbox: {
show: true,
showTitle: false,
feature: {
mark: { show: true },
magicType: { show: true, type: ['line', 'bar'] }
}
},
calculable: true,
xAxis: {
type: 'category',
boundaryGap: false,
data: data.tradeTrend.date
},
yAxis: {
type: 'value'
},
series: [
{
name: '成交额',
type: 'line',
data: data.tradeTrend.orderTotalPrice
},
{
name: '成交量',
type: 'line',
data: data.tradeTrend.orderTotal
}
]
}
// 使用刚指定的配置项和数据显示图表。
myChart.setOption(option)
}
}
}
</script>
<style lang="less" scoped>
.card-title {
font-size: 14px;
font-weight: bold;
margin-bottom: 15px;
}
// 统计
.statistics {
line-height: 1;
.ant-card {
.ant-card-body {
height: 100%;
padding: 30px 40px;
}
}
.col-left {
margin-right: 25px;
font-size: 24px;
color: #4799ff;
.icon-body {
width: 58px;
height: 58px;
border-radius: 50%;
background-color: #eff6ff;
}
}
.col-right {
margin-top: -5px;
.keynote {
font-size: 26px;
}
}
}
// 待办事项
.pending {
text-align: center;
padding: 15px 0;
.item {
display: inline-block;
padding: 0 20px;
a {
color: unset;
&:hover {
color: #1890ff;
}
}
}
.name {
font-weight: bold;
margin-bottom: 10px;
font-size: 13.6px;
}
.value {
font-weight: bold;
color: red;
font-size: 16px;
}
}
// 常用功能
.functions {
.item {
display: block;
padding: 13px 20px;
cursor: pointer;
&:hover {
color: #1890ff;
}
}
.name {
font-size: @font-size-base;
margin-left: 10px;
}
.icon {
width: 28px;
height: 28px;
// background : #139dff !important;
color: #fff;
font-size: @font-size-base;
border-radius: 5px;
}
}
// 实时概况
.overview {
a {
color: unset;
&:hover {
color: #1890ff;
}
}
.item {
padding: 13px 20px;
}
.col-left {
color: #4799ff;
font-size: 28px;
margin-right: 35px;
.icon-body {
width: 58px;
height: 58px;
border-radius: 50%;
background-color: #eff6ff;
}
}
.col-right {
.name {
font-size: @font-size-base;
color: #666;
}
.value {
color: #000;
font-size: 24px;
}
.yesterday {
display: none;
font-size: 12px;
color: #999;
}
}
}
</style>
+37
View File
@@ -0,0 +1,37 @@
/**
* 首页图标库
*/
import fCoupon from '@/assets/icons/home/functions/coupon.svg?inline'
import fGoods from '@/assets/icons/home/functions/goods.svg?inline'
import fOrder from '@/assets/icons/home/functions/order.svg?inline'
import fRefund from '@/assets/icons/home/functions/refund.svg?inline'
import fStatistics from '@/assets/icons/home/functions/statistics.svg?inline'
import fUser from '@/assets/icons/home/functions/user.svg?inline'
import fDecorate from '@/assets/icons/home/functions/decorate.svg?inline'
import oSale from '@/assets/icons/home/overview/sale.svg?inline'
import oIncrease from '@/assets/icons/home/overview/increase.svg?inline'
import sGoods from '@/assets/icons/home/statistics/goods.svg?inline'
import sOrder from '@/assets/icons/home/statistics/order.svg?inline'
import sUser from '@/assets/icons/home/statistics/user.svg?inline'
import sConsume from '@/assets/icons/home/statistics/consume.svg?inline'
// 常用功能
const functions = {
coupon: fCoupon,
goods: fGoods,
order: fOrder,
refund: fRefund,
statistics: fStatistics,
user: fUser,
decorate: fDecorate
}
// 实时概况
const overview = { sale: oSale, increase: oIncrease }
// 统计
const statistics = { goods: sGoods, order: sOrder, user: sUser, consume: sConsume }
export { functions, overview, statistics }
+139
View File
@@ -0,0 +1,139 @@
<template>
<a-card :bordered="false">
<content-header title="管理员设置"></content-header>
<a-spin :spinning="isLoading">
<a-form :form="form">
<a-form-item label="姓名" :labelCol="labelCol" :wrapperCol="wrapperCol" extra="管理员姓名">
<a-input
v-decorator="['real_name', {rules: [{required: true, min: 2, message: '请输入至少2个字符'}]}]"
/>
</a-form-item>
<a-form-item label="用户名" :labelCol="labelCol" :wrapperCol="wrapperCol" extra="后台登录用户名">
<a-input
v-decorator="['user_name', {rules: [{required: true, min: 4, message: '请输入至少4个字符'}]}]"
/>
</a-form-item>
<a-form-item label="用户密码" :labelCol="labelCol" :wrapperCol="wrapperCol" extra="后台登录密码">
<a-input
type="password"
v-decorator="['password', {rules: [{min: 6, message: '请输入至少6个字符'}]}]"
/>
</a-form-item>
<a-form-item label="确认密码" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-input
type="password"
v-decorator="['password_confirm', {rules: [
{message: '请输入确认密码'},
{validator: compareToFirstPassword}
]}]"
/>
</a-form-item>
<a-form-item :wrapperCol="{span: 13, offset: 6}">
<a-button
type="primary"
:loading="isLoading"
:disabled="isLoading"
@click="handleSubmit"
>提交</a-button>
</a-form-item>
</a-form>
</a-spin>
</a-card>
</template>
<script>
import * as Api from '@/api/store/user'
import { ContentHeader, STable } from '@/components'
import _ from 'lodash'
export default {
name: 'TableList',
components: {
ContentHeader,
STable
},
data () {
return {
// 标签布局属性
labelCol: {
span: 6
},
// 输入框布局属性
wrapperCol: {
span: 13
},
// 正在提交
isLoading: false,
// 当前表单元素
form: this.$form.createForm(this)
}
},
mounted () {
// 获取管理员设置
this.getInfo()
},
methods: {
/**
* 获取管理员设置
*/
getInfo () {
this.isLoading = true
Api.getInfo()
.then((result) => {
const { data: { userInfo } } = result
this.form.setFieldsValue(_.pick(userInfo, 'user_name', 'real_name'))
})
.finally(() => {
this.isLoading = false
})
},
/**
* 验证确认密码是否一致
*/
compareToFirstPassword (rule, value, callback) {
const { form } = this
const passwordVal = form.getFieldValue('password')
if (passwordVal && (value !== passwordVal)) {
return new Error('您输入的确认密码不一致')
}
return true
},
/**
* 确认按钮
*/
handleSubmit (e) {
e.preventDefault()
const { form: { validateFields } } = this
// 表单的验证
validateFields((errors, values) => {
if (!errors) {
this.isLoading = true
// 提交到后端api
this.onFormSubmit(values)
.finally(() => {
this.isLoading = false
})
}
})
},
/**
* 提交到后端api
*/
onFormSubmit (values) {
return Api.renew({ form: values })
.then(result => {
// 显示成功
this.$message.success(result.message)
setTimeout(() => {
window.location.reload()
}, 800)
})
}
}
}
</script>
+185
View File
@@ -0,0 +1,185 @@
<template>
<a-card :bordered="false">
<div class="card-title">{{ $route.meta.title }}</div>
<div class="table-operator">
<a-button type="primary" icon="plus" @click="handleAdd">新增</a-button>
</div>
<a-table
v-if="!isLoading"
rowKey="role_id"
:columns="columns"
:dataSource="roleList"
:defaultExpandAllRows="true"
:expandIconColumnIndex="1"
:pagination="false"
:loading="isLoading"
>
<!-- 创建人 -->
<span slot="storeUser" slot-scope="text, item">
<p class="twoline-hide" >{{ item.storeUser?item.storeUser.real_name:'无' }}</p>
</span>
<span slot="action" slot-scope="text, item">
<template>
<a v-action:edit style="margin-right: 8px;" @click="handleEdit(item)">编辑</a>
<a v-action:delete @click="handleDelete(item)">删除</a>
</template>
</span>
</a-table>
<AddForm
ref="AddForm"
:roleList="roleList"
:menuList="menuList"
@handleSubmit="handleRefresh"
/>
<EditForm
ref="EditForm"
:roleList="roleList"
:menuList="menuList"
@handleSubmit="handleRefresh"
/>
</a-card>
</template>
<script>
import * as Api from '@/api/store/role'
import * as MenuApi from '@/api/store/menu'
import { STable } from '@/components'
import AddForm from './modules/AddForm'
import EditForm from './modules/EditForm'
export default {
name: 'Index',
components: {
STable,
AddForm,
EditForm
},
data () {
return {
// 角色列表
roleList: [],
// 查询参数
queryParam: {},
// 正在加载
isLoading: true,
// 表头
columns: [
{
title: '角色ID',
dataIndex: 'role_id'
},
{
title: '角色名称',
dataIndex: 'role_name'
},
{
title: '创建人',
dataIndex: 'storeUser',
width: '100px',
scopedSlots: { customRender: 'storeUser' }
},
{
title: '排序',
dataIndex: 'sort'
},
{
title: '添加时间',
dataIndex: 'create_time'
},
{
title: '操作',
dataIndex: 'action',
width: '180px',
scopedSlots: { customRender: 'action' }
}
],
// 权限列表
menuList: []
}
},
created () {
// 获取角色列表
this.getRoleList()
},
methods: {
/**
* 获取角色列表
*/
getRoleList () {
this.isLoading = true
Api.list()
.then(result => {
this.roleList = result.data.list
})
.finally(result => {
this.isLoading = false
})
},
/**
* 获取菜单列表数据
*/
async getMenuList () {
if (!this.menuList.length) {
this.isLoading = true
await MenuApi.list().then(response => {
this.menuList = response.data.list
}).finally(() => {
this.isLoading = false
})
}
},
/**
* 新增记录
*/
async handleAdd () {
// 获取菜单列表数据
await this.getMenuList()
// 显示对话框
this.$refs.AddForm.add()
},
/**
* 编辑记录
*/
async handleEdit (record) {
// 获取菜单列表数据
await this.getMenuList()
// 显示对话框
this.$refs.EditForm.edit(record)
},
/**
* 删除记录
*/
handleDelete (record) {
const self = this
const modal = this.$confirm({
title: '您确定要删除该记录吗?',
content: '删除后不可恢复',
onOk () {
return Api.deleted({ roleId: record['role_id'] })
.then(result => {
self.$message.success(result.message, 1.5)
self.handleRefresh()
})
.finally(result => {
modal.destroy()
})
}
})
},
/**
* 刷新列表
*/
handleRefresh () {
// 获取角色列表
this.getRoleList()
}
}
}
</script>
@@ -0,0 +1,309 @@
<template>
<a-modal
title="新增角色"
:width="720"
:visible="visible"
:confirmLoading="confirmLoading"
:maskClosable="false"
@ok="handleSubmit"
@cancel="handleCancel"
>
<a-spin :spinning="confirmLoading">
<a-form :form="form">
<a-form-item label="角色名称" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-input
v-decorator="['role_name', {rules: [{required: true, min: 2, message: '请输入至少2个字符'}]}]"
/>
</a-form-item>
<a-form-item label="上级角色" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-tree-select
:treeData="roleListTree"
:dropdownStyle="{ maxHeight: '400px', overflow: 'auto' }"
allowClear
v-decorator="['parent_id']"
></a-tree-select>
</a-form-item>
<a-form-item
label="菜单权限"
:labelCol="labelCol"
:wrapperCol="wrapperCol"
extra="设置该角色有权操作的功能"
>
<a-tree
ref="MenuTree"
v-model="checkedKeys"
checkable
checkStrictly
:treeData="menuListTreeData"
:autoExpandParent="false"
@check="onCheckedMenu"
/>
</a-form-item>
<a-form-item label="排序" :labelCol="labelCol" :wrapperCol="wrapperCol" extra="数字越小越靠前">
<a-input-number
:min="0"
v-decorator="['sort', {initialValue: 100, rules: [{required: true, message: '请输入至少1个数字'}]}]"
/>
</a-form-item>
</a-form>
</a-spin>
</a-modal>
</template>
<script>
import * as Api from '@/api/store/role'
import _ from 'lodash'
export default {
props: {
// 角色列表
roleList: {
type: Array,
required: true
},
// 菜单列表
menuList: {
type: Array,
required: true
}
},
data () {
return {
// 对话框标题
title: '',
// 标签布局属性
labelCol: {
span: 7
},
// 输入框布局属性
wrapperCol: {
span: 13
},
// modal(对话框)是否可见
visible: false,
// modal(对话框)确定按钮 loading
confirmLoading: false,
// 当前表单元素
form: this.$form.createForm(this),
// 上级角色列表
roleListTree: [],
// 菜单列表 树状结构
menuListTreeData: [],
// 当前选中的keys
checkedKeys: {
checked: [],
halfChecked: []
}
}
},
methods: {
/**
* 显示对话框
*/
add () {
// 显示窗口
this.visible = true
// 获取角色列表
this.getRoleList()
// 获取API权限列表
this.getMenuList()
},
/**
* 获取菜单列表
*/
getMenuList () {
const { menuList } = this
this.menuListTreeData = this.formatTreeDataForMenuList(menuList)
},
// 节点选中事件
onCheckedMenu (checkedKeys, { checked, node }) {
// 寻找当前选择的节点
const { menuListTreeData } = this
const nodeData = this.findNode(node.eventKey, menuListTreeData)
// 选择节点时联动所有子节点
this.onCheckChilds(checked, nodeData)
// 选择节点时联动所有父节点
this.onCheckParents(checked, nodeData)
},
// 寻找指定的的节点
findNode (key, list) {
for (let index = 0; index < list.length; index++) {
const item = list[index]
if (item.key === key) {
return item
} else if (item.children) {
const result = this.findNode(key, item.children)
if (result) {
return result
}
}
}
return false
},
// 选择节点时联动所有父节点
onCheckParents (checked, node) {
// 查找所有父节点(方法)
const { menuListTreeData } = this
const findParentKeys = (parentKey) => {
let keys = []
const parent = this.findNode(parentKey, menuListTreeData)
if (!parent) {
return keys
}
keys.push(parent.key)
if (parent.children) {
const arr = findParentKeys(parent.parentKey)
arr.length && (keys = keys.concat(arr))
}
return keys
}
// 查找所有父节点
const parentKeys = findParentKeys(node.parentKey)
// 设置为选中
if (checked && parentKeys.length) {
this.checkedKeys.checked = _.union(this.checkedKeys.checked, parentKeys)
}
},
// 选择节点时联动所有子节点
onCheckChilds (checked, node) {
// 获取当前节点的所有子级ID
const childKeys = node.children ? this.getAllMenuKeys(node.children) : []
if (childKeys.length) {
this.checkedKeys.checked = checked ? _.union(this.checkedKeys.checked, childKeys) : _.difference(this.checkedKeys.checked, childKeys)
}
},
// 获取菜单列表中所有子级的id
getAllMenuKeys (list) {
let keysArr = []
list.forEach(item => {
keysArr.push(item.key)
if (item.children && item.children.length) {
const childrenArr = this.getAllMenuKeys(item.children)
childrenArr.length && (keysArr = keysArr.concat(childrenArr))
}
})
return keysArr
},
/**
* 获取角色列表
*/
getRoleList () {
const { roleList } = this
// 格式化角色列表
const selectList = this.formatTreeForRoleList(roleList)
// 顶级角色
selectList.unshift({
title: '顶级角色',
key: 0,
value: 0
})
this.roleListTree = selectList
},
/**
* 格式化角色列表
*/
formatTreeForRoleList (list) {
const data = []
list.forEach(item => {
// 新的元素
const netItem = {
title: item.role_name,
key: item.role_id,
value: item.role_id
}
// 递归整理子集
if (item.children && item.children.length) {
netItem['children'] = this.formatTreeForRoleList(item['children'])
}
data.push(netItem)
})
return data
},
// 格式化菜单列表
formatTreeDataForMenuList (list) {
const data = []
list.forEach(item => {
// 新的元素
const netItem = {
title: item.name,
key: item.menu_id,
parentKey: item.parent_id
}
// 递归整理子集
if (item.children && item.children.length) {
netItem['children'] = this.formatTreeDataForMenuList(item['children'])
}
data.push(netItem)
})
return data
},
/**
* 确认按钮
*/
handleSubmit (e) {
e.preventDefault()
// 表单验证
const { form: { validateFields } } = this
validateFields((errors, values) => {
// 提交到后端api
if (!errors) {
this.onFormSubmit({ ...values, menus: this.getCheckedKeys() })
}
})
},
/**
* 获取所有选中的节点(菜单)
*/
getCheckedKeys () {
const { $refs: { MenuTree } } = this
return [
...MenuTree.getCheckedKeys(),
...MenuTree.getHalfCheckedKeys()
]
},
/**
* 关闭对话框事件
*/
handleCancel () {
this.visible = false
this.form.resetFields()
const { $refs: { MenuTree } } = this
MenuTree.clearExpandedKeys()
this.checkedKeys.checked = []
},
/**
* 提交到后端api
*/
onFormSubmit (values) {
this.confirmLoading = true
Api.add({ form: values })
.then((result) => {
// 显示成功
this.$message.success(result.message, 1.5)
// 关闭对话框事件
this.handleCancel()
// 通知父端组件提交完成了
this.$emit('handleSubmit', values)
})
.finally((result) => {
this.confirmLoading = false
})
}
}
}
</script>
@@ -0,0 +1,346 @@
<template>
<a-modal
title="编辑角色"
:width="720"
:visible="visible"
:confirmLoading="confirmLoading"
:maskClosable="false"
@ok="handleSubmit"
@cancel="handleCancel"
>
<a-spin :spinning="confirmLoading">
<a-form :form="form">
<a-form-item label="角色名称" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-input
v-decorator="['role_name', {rules: [{required: true, min: 2, message: '请输入至少2个字符'}]}]"
/>
</a-form-item>
<a-form-item label="上级角色" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-tree-select
:treeData="roleListTree"
:dropdownStyle="{ maxHeight: '400px', overflow: 'auto' }"
allowClear
v-decorator="['parent_id']"
></a-tree-select>
</a-form-item>
<a-form-item
label="菜单权限"
:labelCol="labelCol"
:wrapperCol="wrapperCol"
extra="设置该角色有权操作的功能"
>
<a-tree
ref="MenuTree"
v-model="checkedKeys"
checkable
checkStrictly
:treeData="menuListTreeData"
:autoExpandParent="false"
@check="onCheckedMenu"
/>
</a-form-item>
<a-form-item label="排序" :labelCol="labelCol" :wrapperCol="wrapperCol" extra="数字越小越靠前">
<a-input-number
:min="0"
v-decorator="['sort', {initialValue: 100, rules: [{required: true, message: '请输入至少1个数字'}]}]"
/>
</a-form-item>
</a-form>
</a-spin>
</a-modal>
</template>
<script>
import * as Api from '@/api/store/role'
import _ from 'lodash'
export default {
props: {
// 角色列表
roleList: {
type: Array,
required: true
},
// 菜单列表
menuList: {
type: Array,
required: true
}
},
data () {
return {
// 对话框标题
title: '',
// 标签布局属性
labelCol: {
span: 7
},
// 输入框布局属性
wrapperCol: {
span: 13
},
// modal(对话框)是否可见
visible: false,
// modal(对话框)确定按钮 loading
confirmLoading: false,
// 当前表单元素
form: this.$form.createForm(this),
// 当前记录
record: {},
// 上级角色列表
roleListTree: [],
// 菜单列表 树状结构
menuListTreeData: [],
// 当前选中的keys
checkedKeys: {
checked: [],
halfChecked: []
}
}
},
methods: {
/**
* 显示对话框
*/
edit (record) {
// 显示窗口
this.visible = true
// 当前角色记录
this.record = record
// 获取角色列表
this.getRoleList()
// 获取API权限列表
this.getMenuList()
// 默认选中的API权限
this.setMenuChecked()
// 设置默认值
this.setFieldsValue()
},
/**
* 设置默认值
*/
setFieldsValue () {
const { $nextTick, form: { setFieldsValue } } = this
$nextTick(() => {
setFieldsValue(_.pick(this.record, ['role_name', 'parent_id', 'sort']))
})
},
/**
* 获取菜单列表
*/
getMenuList () {
const { menuList } = this
this.menuListTreeData = this.formatTreeDataForMenuList(menuList)
},
// 节点选中事件
onCheckedMenu (checkedKeys, { checked, node }) {
// 寻找当前选择的节点
const { menuListTreeData } = this
const nodeData = this.findNode(node.eventKey, menuListTreeData)
// 选择节点时联动所有子节点
this.onCheckChilds(checked, nodeData)
// 选择节点时联动所有父节点
this.onCheckParents(checked, nodeData)
},
// 寻找指定的的节点
findNode (key, list) {
for (let index = 0; index < list.length; index++) {
const item = list[index]
if (item.key === key) {
return item
} else if (item.children) {
const result = this.findNode(key, item.children)
if (result) {
return result
}
}
}
return false
},
// 选择节点时联动所有父节点
onCheckParents (checked, node) {
// 查找所有父节点(方法)
const { menuListTreeData } = this
const findParentKeys = (parentKey) => {
let keys = []
const parent = this.findNode(parentKey, menuListTreeData)
if (!parent) {
return keys
}
keys.push(parent.key)
if (parent.children) {
const arr = findParentKeys(parent.parentKey)
arr.length && (keys = keys.concat(arr))
}
return keys
}
// 查找所有父节点
const parentKeys = findParentKeys(node.parentKey)
// 设置为选中
if (checked && parentKeys.length) {
this.checkedKeys.checked = _.union(this.checkedKeys.checked, parentKeys)
}
},
// 选择节点时联动所有子节点
onCheckChilds (checked, node) {
// 获取当前节点的所有子级ID
const childKeys = node.children ? this.getAllMenuKeys(node.children) : []
if (childKeys.length) {
this.checkedKeys.checked = checked ? _.union(this.checkedKeys.checked, childKeys) : _.difference(this.checkedKeys.checked, childKeys)
}
},
// 获取菜单列表中所有子级的id
getAllMenuKeys (list) {
let keysArr = []
list.forEach(item => {
keysArr.push(item.key)
if (item.children && item.children.length) {
const childrenArr = this.getAllMenuKeys(item.children)
childrenArr.length && (keysArr = keysArr.concat(childrenArr))
}
})
return keysArr
},
/**
* 设置默认选中的菜单
*/
setMenuChecked () {
// 菜单列表所有子集
const { menuListTreeData, record } = this
const allMenuKeys = this.getAllMenuKeys(menuListTreeData)
// 默认选中的菜单
this.checkedKeys.checked = _.intersection(record.menuIds, allMenuKeys)
},
/**
* 获取角色列表
*/
getRoleList () {
const { roleList } = this
// 格式化角色列表
const selectList = this.formatTreeForRoleList(roleList)
// 顶级角色
selectList.unshift({
title: '顶级角色',
key: 0,
value: 0
})
this.roleListTree = selectList
},
/**
* 格式化角色列表
*/
formatTreeForRoleList (list, disabled = false) {
const data = []
list.forEach(item => {
// 新的元素
const netItem = {
title: item.role_name,
key: item.role_id,
value: item.role_id
}
// 禁用的角色
if (
[item.role_id, item.parent_id].includes(this.record.role_id) ||
disabled === true
) {
netItem.disabled = true
}
// 递归整理子集
if (item.children && item.children.length) {
netItem['children'] = this.formatTreeForRoleList(item['children'], netItem.disabled)
}
data.push(netItem)
})
return data
},
// 格式化菜单列表
formatTreeDataForMenuList (list) {
const data = []
list.forEach(item => {
// 新的元素
const netItem = {
title: item.name,
key: item.menu_id,
parentKey: item.parent_id
}
// 递归整理子集
if (item.children && item.children.length) {
netItem['children'] = this.formatTreeDataForMenuList(item['children'])
}
data.push(netItem)
})
return data
},
/**
* 确认按钮
*/
handleSubmit (e) {
e.preventDefault()
// 表单验证
const { form: { validateFields } } = this
validateFields((errors, values) => {
// 提交到后端api
if (!errors) {
this.onFormSubmit({ ...values, menus: this.getCheckedKeys() })
}
})
},
/**
* 获取所有选中的节点(菜单)
*/
getCheckedKeys () {
const { $refs: { MenuTree } } = this
return [
...MenuTree.getCheckedKeys(),
...MenuTree.getHalfCheckedKeys()
]
},
/**
* 关闭对话框事件
*/
handleCancel () {
this.visible = false
this.form.resetFields()
const { $refs: { MenuTree } } = this
MenuTree.clearExpandedKeys()
this.checkedKeys.checked = []
},
/**
* 提交到后端api
*/
onFormSubmit (values) {
this.confirmLoading = true
Api.edit({ roleId: this.record['role_id'], form: values })
.then((result) => {
// 显示成功
this.$message.success(result.message, 1.5)
// 关闭对话框事件
this.handleCancel()
// 通知父端组件提交完成了
this.$emit('handleSubmit', values)
})
.finally((result) => {
this.confirmLoading = false
})
}
}
}
</script>
+178
View File
@@ -0,0 +1,178 @@
<template>
<a-card :bordered="false">
<div class="card-title">{{ $route.meta.title }}</div>
<div class="table-operator">
<a-row>
<a-col :span="6">
<a-button v-action:add type="primary" icon="plus" @click="handleAdd">新增</a-button>
</a-col>
<a-col :span="8" :offset="10">
<a-input-search
style="max-width: 300px; min-width: 150px;float: right;"
v-model="queryParam.search"
placeholder="请输入用户名/姓名"
@search="onSearch"
/>
</a-col>
</a-row>
</div>
<s-table
ref="table"
rowKey="store_user_id"
:loading="isLoading"
:columns="columns"
:data="loadData"
:pageSize="15"
>
<div slot="user_name" slot-scope="text, item">
<span style="margin-right: 6px">{{ text }}</span>
<a-tag v-if="item.is_super" color="green">超级管理员</a-tag>
</div>
<div class="actions" slot="action" slot-scope="text, item">
<a v-action:edit @click="handleEdit(item)">编辑</a>
<template v-if="!item.is_super && item.store_user_id !== user.store_user_id">
<a v-action:delete @click="handleDelete(item)">删除</a>
</template>
</div>
</s-table>
<AddForm ref="AddForm" :roleList="roleList" @handleSubmit="handleRefresh" />
<EditForm ref="EditForm" :roleList="roleList" @handleSubmit="handleRefresh" />
</a-card>
</template>
<script>
import * as Api from '@/api/store/user'
import * as RoleApi from '@/api/store/role'
import { STable } from '@/components'
import AddForm from './modules/AddForm'
import EditForm from './modules/EditForm'
export default {
name: 'Index',
components: {
STable,
AddForm,
EditForm
},
data () {
return {
// 角色列表
roleList: [],
user: [],
// 查询参数
queryParam: {},
// 正在加载
isLoading: false,
// 表头
columns: [
{
title: '管理员ID',
dataIndex: 'store_user_id'
},
{
title: '用户名',
dataIndex: 'user_name',
scopedSlots: { customRender: 'user_name' }
},
{
title: '姓名',
dataIndex: 'real_name'
},
{
title: '排序',
dataIndex: 'sort'
},
{
title: '添加时间',
dataIndex: 'create_time'
},
{
title: '操作',
dataIndex: 'action',
width: '180px',
scopedSlots: { customRender: 'action' }
}
],
// 加载数据方法 必须为 Promise 对象
loadData: param => {
return Api.list({ ...param, ...this.queryParam })
.then(response => {
return response.data.list
})
}
}
},
created () {
// 获取角色列表
this.getRoleList()
},
methods: {
/**
* 新增记录
*/
handleAdd () {
this.$refs.AddForm.add()
},
/**
* 编辑记录
*/
handleEdit (item) {
this.$refs.EditForm.edit(item)
},
/**
* 获取角色列表
*/
getRoleList () {
this.isLoading = true
RoleApi.list()
.then(result => {
this.roleList = result.data.list
this.user = result.data.user
})
.finally(() => {
this.isLoading = false
})
},
/**
* 删除记录
*/
handleDelete (item) {
const app = this
const modal = this.$confirm({
title: '您确定要删除该记录吗?',
content: '删除后不可恢复',
onOk () {
return Api.deleted({ userId: item['store_user_id'] })
.then((result) => {
app.$message.success(result.message, 1.5)
app.handleRefresh()
})
.finally(result => {
modal.destroy()
})
}
})
},
/**
* 刷新列表
* @param Boolean bool 强制刷新到第一页
*/
handleRefresh (bool = false) {
this.$refs.table.refresh(bool)
},
/**
* 检索查询
*/
onSearch () {
this.handleRefresh(true)
}
}
}
</script>
@@ -0,0 +1,252 @@
<template>
<a-modal
:title="title"
:width="720"
:visible="visible"
:confirmLoading="confirmLoading"
:maskClosable="false"
@ok="handleSubmit"
@cancel="handleCancel"
>
<a-spin :spinning="confirmLoading">
<a-form :form="form">
<a-form-item label="管理员姓名" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-input
v-decorator="['real_name', {rules: [{required: true, min: 2, message: '请输入至少2个字符'}]}]"
/>
</a-form-item>
<a-form-item label="用户名" :labelCol="labelCol" :wrapperCol="wrapperCol" extra="后台登录用户名">
<a-input
v-decorator="['user_name', {rules: [{required: true, min: 4, message: '请输入至少4个字符'}]}]"
/>
</a-form-item>
<a-form-item label="所属角色" :labelCol="labelCol" :wrapperCol="wrapperCol" extra="后台管理员角色">
<a-tree-select
treeCheckable
treeCheckStrictly
treeDefaultExpandAll
allowClear
:treeData="roleListTreeData"
:dropdownStyle="{ maxHeight: '500px', overflow: 'auto' }"
v-decorator="['roles', {rules: [{required: true, message: '请至少选择一个角色'}]}]"
></a-tree-select>
</a-form-item>
<a-form-item label="关联代理商" :labelCol="labelCol" :wrapperCol="wrapperCol" extra="关联代理商">
<!--mode="multiple"-->
<a-select
style="width: 300px"
v-decorator="['merchant_id', {rules: [{required: false, message: '请选择代理商'}]}]"
placeholder="请选择代理商"
>
<a-select-option :value="0">无</a-select-option>
<a-select-option
v-for="(item, index) in merchantListData"
:key="index"
:value="item.merchant_id"
>{{ item.name }}</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="用户密码" :labelCol="labelCol" :wrapperCol="wrapperCol" extra="后台登录密码">
<a-input
type="password"
v-decorator="['password', {rules: [
{required: true, min: 6, message: '请输入至少6个字符'}
]}]"
/>
</a-form-item>
<a-form-item label="确认密码" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-input
type="password"
v-decorator="['password_confirm', {rules: [
{required: true, message: '请输入确认密码'},
{validator: compareToFirstPassword}
]}]"
/>
</a-form-item>
<a-form-item label="排序" :labelCol="labelCol" :wrapperCol="wrapperCol" extra="数字越小越靠前">
<a-input-number
:min="0"
v-decorator="['sort', {initialValue: 100, rules: [{required: true, message: '请输入至少1个数字'}]}]"
/>
</a-form-item>
</a-form>
</a-spin>
</a-modal>
</template>
<script>
import * as Api from '@/api/store/user'
import * as MerchantApi from '@/api/merchant'
export default {
props: {
// 角色列表
roleList: {
type: Array,
required: true
}
},
data () {
return {
// 对话框标题
title: '',
// 标签布局属性
labelCol: {
span: 7
},
// 输入框布局属性
wrapperCol: {
span: 13
},
// modal(对话框)是否可见
visible: false,
// modal(对话框)确定按钮 loading
confirmLoading: false,
// 当前表单元素
form: this.$form.createForm(this),
// 角色列表 树状结构
roleListTreeData: [],
//商户数据
merchantListData: []
}
},
methods: {
/**
* 显示对话框
*/
add () {
this.title = '新增管理员'
this.visible = true
// 获取角色列表
this.getRoleList()
this.getMerchantList()
},
/**
* 获取角色列表
*/
getRoleList () {
const { roleList } = this
// 格式化角色列表
const selectList = this.formatTreeData(roleList)
this.roleListTreeData = selectList
},
/**
* 获取商户列表
*/
getMerchantList () {
this.isLoading = true
MerchantApi.all()
.then(result => {
this.merchantListData = result.data.list
})
.finally(() => {
this.isLoading = false
})
},
/**
* 设置默认选中的角色
*/
getCheckedRoleKeys () {
const { roleList, record } = this
const getCheckedKeys = (list) => {
let keysArr = []
list.forEach(item => {
if (record['roleIds'].includes(item['role_id'])) {
keysArr.push({
label: item['role_name'],
value: item['role_id']
})
}
if (item.children && item.children.length) {
const childrenArr = getCheckedKeys(item.children)
childrenArr.length && (keysArr = keysArr.concat(childrenArr))
}
})
return keysArr
}
return getCheckedKeys(roleList)
},
/**
* 格式化角色列表
*/
formatTreeData (list) {
const data = []
list.forEach(item => {
const netItem = {
title: item.role_name,
key: item.role_id,
value: item.role_id
}
if (item.children && item.children.length) {
netItem['children'] = this.formatTreeData(item['children'])
}
data.push(netItem)
})
return data
},
/**
* 确认按钮
*/
handleSubmit (e) {
e.preventDefault()
const { form: { validateFields } } = this
// 表单验证
validateFields((errors, values) => {
// 整理角色id
if (values.roles) {
values.roles = values.roles.map(item => item.value)
}
// 提交到后端api
!errors && this.onFormSubmit(values)
})
},
/**
* 关闭对话框事件
*/
handleCancel () {
this.visible = false
this.form.resetFields()
},
/**
* 验证确认密码是否一致
*/
compareToFirstPassword (rule, value, callback) {
const { form } = this
if (value && value !== form.getFieldValue('password')) {
return new Error('您输入的确认密码不一致')
}
return true
},
/**
* 提交到后端api
*/
onFormSubmit (values) {
this.confirmLoading = true
Api.add({ form: values })
.then((result) => {
// 显示成功
this.$message.success(result.message, 1.5)
// 关闭对话框
this.handleCancel()
// 通知父端组件提交完成了
this.$emit('handleSubmit', values)
})
.finally((result) => {
this.confirmLoading = false
})
}
}
}
</script>
@@ -0,0 +1,278 @@
<template>
<a-modal
:title="title"
:width="720"
:visible="visible"
:confirmLoading="confirmLoading"
:maskClosable="false"
@ok="handleSubmit"
@cancel="handleCancel"
>
<a-spin :spinning="confirmLoading">
<a-form :form="form">
<a-form-item label="管理员姓名" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-input
v-decorator="['real_name', {rules: [{required: true, min: 2, message: '请输入至少2个字符'}]}]"
/>
</a-form-item>
<a-form-item label="用户名" :labelCol="labelCol" :wrapperCol="wrapperCol" extra="后台登录用户名">
<a-input
v-decorator="['user_name', {rules: [{required: true, min: 4, message: '请输入至少4个字符'}]}]"
/>
</a-form-item>
<a-form-item
v-if="!record.is_super && record.store_user_id !== user.store_user_id"
label="所属角色"
:labelCol="labelCol"
:wrapperCol="wrapperCol"
extra="后台管理员角色"
>
<a-tree-select
treeCheckable
treeCheckStrictly
treeDefaultExpandAll
allowClear
:treeData="roleListTreeData"
:dropdownStyle="{ maxHeight: '500px', overflow: 'auto' }"
v-decorator="['roles', {rules: [{required: true, message: '请至少选择一个角色'}]}]"
></a-tree-select>
</a-form-item>
<a-form-item label="关联代理商" :labelCol="labelCol" :wrapperCol="wrapperCol" extra="关联代理商">
<!--mode="multiple"-->
<a-select
style="width: 300px"
v-decorator="['merchant_id', {rules: [{required: false, message: '请选择代理商'}]}]"
placeholder="请选择代理商"
>
<a-select-option :value="0">无</a-select-option>
<a-select-option
v-for="(item, index) in merchantListData"
:key="index"
:value="item.merchant_id"
>{{ item.name }}</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="用户密码" :labelCol="labelCol" :wrapperCol="wrapperCol" extra="后台登录密码">
<a-input
type="password"
v-decorator="['password', {rules: [{min: 6, message: '请输入至少6个字符'}]}]"
/>
</a-form-item>
<a-form-item label="确认密码" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-input
type="password"
v-decorator="['password_confirm', {rules: [
{ message: '请输入确认密码'},
{validator: compareToFirstPassword}
]}]"
/>
</a-form-item>
<a-form-item label="排序" :labelCol="labelCol" :wrapperCol="wrapperCol" extra="数字越小越靠前">
<a-input-number
:min="0"
v-decorator="['sort', {initialValue: 100, rules: [{required: true, message: '请输入至少1个数字'}]}]"
/>
</a-form-item>
</a-form>
</a-spin>
</a-modal>
</template>
<script>
import _ from 'lodash'
import * as UserApi from '@/api/store/user'
import * as MerchantApi from '@/api/merchant'
export default {
props: {
// 角色列表
roleList: {
type: Array,
required: true
}
},
data () {
return {
// 对话框标题
title: '',
// 标签布局属性
labelCol: {
span: 7
},
// 输入框布局属性
wrapperCol: {
span: 13
},
// modal(对话框)是否可见
visible: false,
// modal(对话框)确定按钮 loading
confirmLoading: false,
// 当前表单元素
form: this.$form.createForm(this),
// 角色列表 树状结构
roleListTreeData: [],
// 当前记录
record: {},
//商户数据
merchantListData: [],
user: []
}
},
methods: {
/**
* 显示对话框
*/
edit (record) {
// 显示窗口
this.title = '编辑管理员'
this.visible = true
// 当前管理员记录
this.record = record
// 获取角色列表
!record['is_super'] && this.getRoleList()
// 设置默认值
this.setFieldsValue()
this.getMerchantList()
},
/**
* 设置默认值
*/
setFieldsValue () {
const { form: { setFieldsValue }, getCheckedRoleKeys } = this
this.$nextTick(() => {
const data = _.pick(this.record, ['user_name', 'real_name', 'sort'])
data.roles = getCheckedRoleKeys()
data.merchant_id = eval('(' + this.record.merchant_id + ')')
setFieldsValue(data)
})
},
/**
* 获取角色列表
*/
getRoleList () {
const { roleList } = this
// 格式化角色列表
const selectList = this.formatTreeData(roleList)
this.roleListTreeData = selectList
},
/**
* 获取商户列表
*/
getMerchantList () {
this.isLoading = true
MerchantApi.all()
.then(result => {
this.merchantListData = result.data.list
this.user = result.data.user
})
.finally(() => {
this.isLoading = false
})
},
/**
* 设置默认选中的角色
*/
getCheckedRoleKeys () {
const { roleList, record } = this
const getCheckedKeys = (list) => {
let keysArr = []
list.forEach(item => {
if (record['roleIds'].includes(item['role_id'])) {
keysArr.push({
label: item['role_name'],
value: item['role_id']
})
}
if (item.children && item.children.length) {
const childrenArr = getCheckedKeys(item.children)
childrenArr.length && (keysArr = keysArr.concat(childrenArr))
}
})
return keysArr
}
return getCheckedKeys(roleList)
},
/**
* 格式化角色列表
*/
formatTreeData (list) {
const data = []
list.forEach(item => {
const netItem = {
title: item.role_name,
key: item.role_id,
value: item.role_id
}
if (item.children && item.children.length) {
netItem['children'] = this.formatTreeData(item['children'])
}
data.push(netItem)
})
return data
},
/**
* 确认按钮
*/
handleSubmit (e) {
e.preventDefault()
// 表单验证
const { form: { validateFields } } = this
validateFields((errors, values) => {
// 整理角色id
if (values.roles) {
values.roles = values.roles.map(item => item.value)
}
// 提交到后端api
!errors && this.onFormSubmit(values)
})
},
/**
* 关闭对话框事件
*/
handleCancel () {
this.visible = false
this.form.resetFields()
},
/**
* 验证确认密码是否一致
*/
compareToFirstPassword (rule, value, callback) {
const { form } = this
if (value && value !== form.getFieldValue('password')) {
return new Error('您输入的确认密码不一致')
}
return true
},
/**
* 提交到后端api
*/
onFormSubmit (values) {
this.confirmLoading = true
UserApi.edit({ userId: this.record['store_user_id'], form: values })
.then((result) => {
// 显示成功
this.$message.success(result.message, 1.5)
// 关闭对话框事件
this.handleCancel()
// 通知父端组件提交完成了
this.$emit('handleSubmit', values)
})
.finally((result) => {
this.confirmLoading = false
})
}
}
}
</script>
+210
View File
@@ -0,0 +1,210 @@
<template>
<a-card :bordered="false">
<div class="card-title">{{ $route.meta.title }}</div>
<a-spin :spinning="isLoading">
<a-form :form="form" @submit="handleSubmit">
<a-form-item label="是否开启满额包邮" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-radio-group v-decorator="['is_open', { rules: [{ required: true }] }]">
<a-radio :value="1">开启</a-radio>
<a-radio :value="0">关闭</a-radio>
</a-radio-group>
</a-form-item>
<a-form-item label="单笔订单满" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-input-number
:min="0"
:precision="2"
v-decorator="['money', { rules: [{ required: true, message: '请输入包邮的订单额度' }] }]"
/>
<span class="ml-10">元包邮</span>
<div class="form-item-help">
<small>如设置0为全场包邮</small>
</div>
</a-form-item>
<a-form-item label="不参与包邮的商品" :labelCol="labelCol" :wrapperCol="wrapperCol">
<SelectGoods :defaultList="excludedGoodsList" v-decorator="['excludedGoodsIds']" />
</a-form-item>
<a-form-item label="不参与包邮的地区" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-button @click="handleAreasModal">选择地区</a-button>
<p class="content">
<span v-for="(province, pidx) in excludedRegions.selectedText" :key="pidx">
<span>{{ province.name }}</span>
<template v-if="province.citys.length">
<span>[</span>
<span
class="city-name"
v-for="(city, cidx) in province.citys"
:key="cidx"
>{{ city.name }}{{ province.citys.length > cidx + 1 ? '、': '' }}</span>
<span>]</span>
</template>
<span class="mr-5"></span>
</span>
</p>
</a-form-item>
<a-form-item label="满额包邮说明" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-textarea
:autoSize="{ minRows: 4 }"
v-decorator="['describe', { rules: [{ required: true, message: '请输入满额包邮说明' }] }]"
/>
</a-form-item>
<a-form-item :wrapper-col="{ span: wrapperCol.span, offset: labelCol.span }">
<a-button type="primary" html-type="submit">提交</a-button>
</a-form-item>
</a-form>
<AreasModal ref="AreasModal" @handleSubmit="handleAreaSubmit" />
</a-spin>
</a-card>
</template>
<script>
import pick from 'lodash.pick'
import * as Api from '@/api/setting/store'
import * as GoodsApi from '@/api/goods'
import { SelectGoods } from '@/components'
import { AreasModal } from '@/components/Modal'
export default {
components: {
SelectGoods,
AreasModal
},
data () {
return {
// 当前设置项的key
key: 'full_free',
// 标签布局属性
labelCol: { span: 4 },
// 输入框布局属性
wrapperCol: { span: 12 },
// loading状态
isLoading: false,
// 当前表单元素
form: this.$form.createForm(this),
// 当前记录详情
record: {},
// 不参与包邮的地区
excludedRegions: {
cityIds: [],
selectedText: []
},
// 不参与包邮的商品列表
excludedGoodsList: []
}
},
// 初始化数据
async created () {
// 获取当前详情记录
await this.getDetail()
// 获取不参与包邮的商品列表
await this.getExcludedGoodsList()
},
methods: {
// 获取当前详情记录
async getDetail () {
this.isLoading = true
await Api.detail(this.key)
.then(result => {
// 当前记录
this.record = result.data.values
// 设置默认值
this.setFieldsValue()
})
.finally(result => {
this.isLoading = false
})
},
// 获取不参与包邮的商品列表
async getExcludedGoodsList () {
const { record: { excludedGoodsIds } } = this
if (excludedGoodsIds.length > 0) {
this.isLoading = true
await GoodsApi.listByIds(excludedGoodsIds)
.then(result => {
this.excludedGoodsList = result.data.list
})
.finally(result => {
this.isLoading = false
})
}
},
/**
* 设置默认值
*/
setFieldsValue () {
const { record, $nextTick, form: { setFieldsValue } } = this
$nextTick(() => {
this.excludedRegions = record.excludedRegions
setFieldsValue(pick(record, ['is_open', 'money', 'describe']))
})
},
// 显示选择地区对话框
handleAreasModal () {
this.$refs.AreasModal.handle({}, this.excludedRegions.cityIds)
},
// 选择地区后的回调
handleAreaSubmit (result) {
this.excludedRegions = {
cityIds: result.selectedCityIds,
selectedText: result.selectedText
}
},
/**
* 确认按钮
*/
handleSubmit (e) {
e.preventDefault()
// 表单验证
const { form: { validateFields }, excludedRegions } = this
validateFields((errors, values) => {
// 提交到后端api
if (!errors) {
values.excludedRegions = excludedRegions
this.onFormSubmit(values)
}
})
},
// 提交到后端api
onFormSubmit (values) {
this.isLoading = true
Api.update(this.key, { form: values })
.then((result) => {
// 显示提示信息
this.$message.success(result.message, 1.5)
})
.finally((result) => {
this.isLoading = false
})
}
}
}
</script>
<style lang="less" scoped>
.ant-form-item {
.ant-form-item {
margin-bottom: 0;
}
}
/deep/.ant-form-item-control {
padding-left: 10px;
.ant-form-item-control {
padding-left: 0;
}
}
.content {
color: #505050;
line-height: 1.6;
.city-name {
font-size: 12.5px;
color: #7b7b7b;
}
}
</style>
@@ -0,0 +1,210 @@
<template>
<a-card :bordered="false">
<div class="card-title">{{ $route.meta.title }}</div>
<a-spin :spinning="isLoading">
<a-form :form="form" @submit="handleSubmit">
<a-form-item label="优惠券名称" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-input
placeholder="请输入优惠券名称"
v-decorator="['name', { rules: [{ required: true, min: 2, message: '请输入至少2个字符' }] }]"
/>
</a-form-item>
<a-form-item label="优惠券类型" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-radio-group
v-decorator="['coupon_type', { initialValue: 10, rules: [{ required: true }] }]"
>
<a-radio :value="10">满减券</a-radio>
<a-radio :value="20">折扣券</a-radio>
</a-radio-group>
</a-form-item>
<a-form-item
v-if="form.getFieldValue('coupon_type') == 10"
label="减免金额"
:labelCol="labelCol"
:wrapperCol="wrapperCol"
>
<a-input-number
:min="0.01"
:precision="2"
v-decorator="['reduce_price', { rules: [{ required: true, message: '请输入减免金额' }] }]"
/>
<span class="ml-5">元</span>
</a-form-item>
<a-form-item
v-if="form.getFieldValue('coupon_type') == 20"
label="折扣率"
:labelCol="labelCol"
:wrapperCol="wrapperCol"
>
<a-input-number
:min="0"
:max="9.9"
:precision="1"
v-decorator="['discount', { initialValue: 9.9, rules: [{ required: true, message: '请输入折扣率' }] }]"
/>
<span class="ml-5">%</span>
<p class="form-item-help">
<small>折扣率范围 0-9.9,8代表打8折,0代表不折扣</small>
</p>
</a-form-item>
<a-form-item label="最低消费金额" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-input-number
:min="1"
:precision="2"
v-decorator="['min_price', { rules: [{ required: true, message: '请输入最低消费金额' }] }]"
/>
<span class="ml-5">元</span>
</a-form-item>
<a-form-item label="到期类型" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-radio-group
v-decorator="['expire_type', { initialValue: 10, rules: [{ required: true }] }]"
>
<a-radio :value="10">领取后生效</a-radio>
<a-radio :value="20">固定时间</a-radio>
</a-radio-group>
<a-form-item v-show="form.getFieldValue('expire_type') == 10" class="expire_type-10">
<InputNumberGroup
addonBefore="有效期"
addonAfter="天"
:inputProps="{ min: 1, precision: 0 }"
v-decorator="['expire_day', { initialValue: 7, rules: [{ required: true, message: '请输入有效期天数' }] }]"
/>
</a-form-item>
<a-form-item v-show="form.getFieldValue('expire_type') == 20" class="expire_type-20">
<a-range-picker
format="YYYY-MM-DD"
v-decorator="['betweenTime', { initialValue: defaultDate, rules: [{ required: true, message: '请选择有效期范围' }] }]"
/>
</a-form-item>
</a-form-item>
<a-form-item label="券适用范围" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-radio-group
v-decorator="['apply_range', { initialValue: 10, rules: [{ required: true }] }]"
>
<a-radio :value="10">全场通用</a-radio>
<a-radio :value="20">指定商品</a-radio>
</a-radio-group>
<a-form-item v-if="form.getFieldValue('apply_range') == 20">
<SelectGoods
:defaultList="containGoodsList"
v-decorator="['apply_range_config.applyGoodsIds', { initialValue: [], rules: [{ required: true, message: '请选择指定的商品' }] }]"
/>
</a-form-item>
</a-form-item>
<a-form-item label="发放总数量" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-input-number
:min="-1"
:precision="0"
v-decorator="['total_num', { initialValue: -1, rules: [{ required: true, message: '请输入发放总数量' }] }]"
/>
<span class="ml-5">张</span>
<p class="form-item-help">
<small>发放的优惠券总数量,-1为不限制</small>
</p>
</a-form-item>
<a-form-item label="显示状态" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-radio-group v-decorator="['status', { initialValue: 1, rules: [{ required: true }] }]">
<a-radio :value="1">显示</a-radio>
<a-radio :value="0">隐藏</a-radio>
</a-radio-group>
<p class="form-item-help">
<small>如果设为隐藏将不会展示在用户端页面</small>
</p>
</a-form-item>
<a-form-item label="优惠券描述" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-textarea :autoSize="{ minRows: 4 }" v-decorator="['describe']" />
</a-form-item>
<a-form-item label="排序" :labelCol="labelCol" :wrapperCol="wrapperCol" extra="数字越小越靠前">
<a-input-number
:min="0"
v-decorator="['sort', {initialValue: 100, rules:[{required: true, message: '请输入排序值'}]}]"
/>
</a-form-item>
<a-form-item class="mt-20" :wrapper-col="{ span: wrapperCol.span, offset: labelCol.span }">
<a-button type="primary" html-type="submit" :loading="isBtnLoading">提交</a-button>
</a-form-item>
</a-form>
</a-spin>
</a-card>
</template>
<script>
import moment from 'moment'
import * as Api from '@/api/market/coupon'
import { InputNumberGroup, SelectGoods } from '@/components'
import { ApplyRangeEnum, CouponTypeEnum, ExpireTypeEnum } from '@/common/enum/coupon'
export default {
components: {
SelectGoods,
InputNumberGroup
},
data () {
return {
// 枚举类
ApplyRangeEnum,
CouponTypeEnum,
ExpireTypeEnum,
// 正在加载
isLoading: false,
isBtnLoading: false,
// 标签布局属性
labelCol: { span: 3 },
// 输入框布局属性
wrapperCol: { span: 10 },
// 当前表单元素
form: this.$form.createForm(this),
// 默认日期范围
defaultDate: [moment(), moment()],
// 适用范围:指定的商品
containGoodsList: []
}
},
created () {
this.$nextTick(() => {
this.$forceUpdate()
})
},
methods: {
// 确认按钮
handleSubmit (e) {
e.preventDefault()
// 表单验证
const { form: { validateFields }, onFormSubmit } = this
validateFields((errors, values) => {
!errors && onFormSubmit(values)
})
},
// 提交到后端api
onFormSubmit (values) {
this.isLoading = true
this.isBtnLoading = true
Api.add({ form: values })
.then(result => {
// 显示提示信息
this.$message.success(result.message, 1.5)
// 跳转到列表页
setTimeout(() => {
this.$router.push('./index')
}, 1500)
})
.catch(() => {
this.isBtnLoading = false
})
.finally(result => {
this.isLoading = false
})
}
}
}
</script>
<style lang="less" scoped>
.ant-form-item {
.ant-form-item {
margin-bottom: 0;
}
}
</style>
+215
View File
@@ -0,0 +1,215 @@
<template>
<a-card :bordered="false">
<div class="card-title">{{ $route.meta.title }}</div>
<a-alert message="注:优惠券只能抵扣商品金额,最多优惠到0.01元,不能抵扣运费" banner />
<div class="table-operator">
<a-row>
<a-col :span="6">
<a-button
v-if="$auth('/market/coupon/create')"
type="primary"
icon="plus"
@click="handleAdd"
>新增</a-button>
</a-col>
<a-col :span="8" :offset="10">
<a-input-search
style="max-width: 300px; min-width: 150px;float: right;"
v-model="queryParam.search"
placeholder="请输入优惠券名称"
@search="onSearch"
/>
</a-col>
</a-row>
</div>
<s-table
ref="table"
rowKey="coupon_id"
:loading="isLoading"
:columns="columns"
:data="loadData"
:pageSize="15"
>
<!-- 优惠券类型 -->
<template slot="coupon_type" slot-scope="text">
<a-tag>{{ CouponTypeEnum[text].name }}</a-tag>
</template>
<!-- 最低消费金额 -->
<template slot="min_price" slot-scope="text">
<p class="c-p">{{ text }}</p>
</template>
<!-- 优惠方式 -->
<template slot="discount" slot-scope="item">
<template v-if="item.coupon_type == 10">
<span>立减</span>
<span class="c-p mlr-2">{{ item.reduce_price }}</span>
<span>元</span>
</template>
<template v-if="item.coupon_type == 20">
<span>打</span>
<span class="c-p mlr-2">{{ item.discount }}</span>
<span>折</span>
</template>
</template>
<!-- 有效期 -->
<template slot="duetime" slot-scope="item">
<template v-if="item.expire_type == 10">
<span>领取</span>
<span class="c-p mlr-2">{{ item.expire_day }}</span>
<span>天内有效</span>
</template>
<template v-if="item.expire_type == 20">
<span>{{ item.start_time }} ~ {{ item.end_time }}</span>
</template>
</template>
<!-- 状态 -->
<template slot="status" slot-scope="text">
<a-tag :color="text ? 'green' : ''">{{ text ? '显示' : '隐藏' }}</a-tag>
</template>
<!-- 操作 -->
<span class="actions" slot="action" slot-scope="item">
<a v-if="$auth('/market/coupon/update')" @click="handleEdit(item)">编辑</a>
<a v-action:delete @click="handleDelete(item)">删除</a>
</span>
</s-table>
</a-card>
</template>
<script>
import * as Api from '@/api/market/coupon'
import { STable } from '@/components'
import { ApplyRangeEnum, CouponTypeEnum, ExpireTypeEnum } from '@/common/enum/coupon'
export default {
name: 'Index',
components: {
STable
},
data () {
return {
// 查询参数
queryParam: {},
// 枚举类
ApplyRangeEnum,
CouponTypeEnum,
ExpireTypeEnum,
// 正在加载
isLoading: false,
// 表头
columns: [
{
title: '优惠券ID',
dataIndex: 'coupon_id'
},
{
title: '优惠券名称',
dataIndex: 'name'
},
{
title: '优惠券类型',
dataIndex: 'coupon_type',
scopedSlots: { customRender: 'coupon_type' }
},
{
title: '最低消费金额 (元)',
dataIndex: 'min_price',
scopedSlots: { customRender: 'min_price' }
},
{
title: '优惠方式',
scopedSlots: { customRender: 'discount' }
},
// {
// title: '有效期',
// scopedSlots: { customRender: 'duetime' }
// },
// {
// title: '发放总数量',
// dataIndex: 'total_num'
// },
{
title: '已发放/领取数量',
dataIndex: 'receive_num'
},
{
title: '状态',
dataIndex: 'status',
scopedSlots: { customRender: 'status' }
},
{
title: '排序',
dataIndex: 'sort'
},
{
title: '添加时间',
dataIndex: 'create_time'
},
{
title: '操作',
width: '180px',
scopedSlots: { customRender: 'action' }
}
],
// 加载数据方法 必须为 Promise 对象
loadData: param => {
return Api.list({ ...param, ...this.queryParam })
.then(response => {
return response.data.list
})
}
}
},
created () {
},
methods: {
// 新增记录
handleAdd () {
this.$router.push('./create')
},
// 编辑记录
handleEdit (item) {
this.$router.push({ path: './update', query: { couponId: item.coupon_id } })
},
/**
* 删除记录
*/
handleDelete (item) {
const app = this
const modal = this.$confirm({
title: '您确定要删除该记录吗?',
content: '删除后不可恢复',
onOk () {
return Api.deleted({ couponId: item.coupon_id })
.then((result) => {
app.$message.success(result.message, 1.5)
app.handleRefresh()
})
.finally(result => {
modal.destroy()
})
}
})
},
/**
* 刷新列表
* @param Boolean bool 强制刷新到第一页
*/
handleRefresh (bool = false) {
this.$refs.table.refresh(bool)
},
/**
* 检索查询
*/
onSearch () {
this.handleRefresh(true)
}
}
}
</script>
@@ -0,0 +1,165 @@
<template>
<a-card :bordered="false">
<div class="card-title">{{ $route.meta.title }}</div>
<div class="table-operator">
<!-- 搜索板块 -->
<a-row class="row-item-search">
<a-form class="search-form" :form="searchForm" layout="inline" @submit="handleSearch">
<a-form-item label="优惠券名称">
<a-input v-decorator="['couponName']" placeholder="请输入优惠券名称" />
</a-form-item>
<a-form-item label="会员昵称">
<a-input v-decorator="['nickName']" placeholder="请输入会员昵称" />
</a-form-item>
<a-form-item label="领取时间">
<a-range-picker format="YYYY-MM-DD" v-decorator="['betweenTime']" />
</a-form-item>
<a-form-item class="search-btn">
<a-button type="primary" icon="search" html-type="submit">搜索</a-button>
</a-form-item>
</a-form>
</a-row>
</div>
<s-table
ref="table"
rowKey="user_coupon_id"
:loading="isLoading"
:columns="columns"
:data="loadData"
:pageSize="15"
>
<!-- 会员信息 -->
<template slot="user" slot-scope="item">
<UserItem :user="item" />
</template>
<!-- 优惠券类型 -->
<template slot="coupon_type" slot-scope="text">
<a-tag>{{ CouponTypeEnum[text].name }}</a-tag>
</template>
<!-- 最低消费金额 -->
<template slot="min_price" slot-scope="text">
<p class="c-p">{{ text }}</p>
</template>
<!-- 优惠方式 -->
<template slot="discount" slot-scope="item">
<template v-if="item.coupon_type == CouponTypeEnum.FULL_DISCOUNT.value">
<span>立减</span>
<span class="c-p mlr-2">{{ item.reduce_price }}</span>
<span>元</span>
</template>
<template v-if="item.coupon_type == CouponTypeEnum.DISCOUNT.value">
<span>打</span>
<span class="c-p mlr-2">{{ item.discount }}</span>
<span>折</span>
</template>
</template>
<!-- 有效期 -->
<template slot="duetime" slot-scope="item">
<template v-if="item.expire_type == 10">
<span>领取</span>
<span class="c-p mlr-2">{{ item.expire_day }}</span>
<span>天内有效</span>
</template>
<template v-if="item.expire_type == 20">
<span>{{ item.start_time }} ~ {{ item.end_time }}</span>
</template>
</template>
</s-table>
</a-card>
</template>
<script>
import * as Api from '@/api/market/coupon'
import { STable, UserItem } from '@/components/Table'
import { CouponTypeEnum } from '@/common/enum/coupon'
export default {
name: 'Index',
components: {
STable,
UserItem
},
data () {
return {
// 当前表单元素
searchForm: this.$form.createForm(this),
// 查询参数
queryParam: {},
// 枚举类
CouponTypeEnum,
// 正在加载
isLoading: false,
// 表头
columns: [
{
title: 'ID',
dataIndex: 'user_coupon_id'
},
{
title: '会员信息',
dataIndex: 'user',
scopedSlots: { customRender: 'user' }
},
{
title: '优惠券名称',
dataIndex: 'name'
},
{
title: '优惠券类型',
dataIndex: 'coupon_type',
scopedSlots: { customRender: 'coupon_type' }
},
{
title: '最低消费金额 (元)',
dataIndex: 'min_price',
scopedSlots: { customRender: 'min_price' }
},
{
title: '优惠方式',
scopedSlots: { customRender: 'discount' }
},
{
title: '有效期',
scopedSlots: { customRender: 'duetime' }
},
{
title: '领取时间',
dataIndex: 'create_time'
}
],
// 加载数据方法 必须为 Promise 对象
loadData: param => {
return Api.receive({ ...param, ...this.queryParam })
.then(response => {
return response.data.list
})
}
}
},
created () {
},
methods: {
/**
* 刷新列表
* @param Boolean bool 强制刷新到第一页
*/
handleRefresh (bool = false) {
this.$refs.table.refresh(bool)
},
// 确认搜索
handleSearch (e) {
e.preventDefault()
this.searchForm.validateFields((error, values) => {
if (!error) {
this.queryParam = { ...this.queryParam, ...values }
this.handleRefresh(true)
}
})
}
}
}
</script>
@@ -0,0 +1,278 @@
<template>
<a-card :bordered="false">
<div class="card-title">{{ $route.meta.title }}</div>
<a-spin :spinning="isLoading">
<a-form :form="form" @submit="handleSubmit">
<a-form-item label="优惠券名称" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-input
placeholder="请输入优惠券名称"
v-decorator="['name', { rules: [{ required: true, min: 2, message: '请输入至少2个字符' }] }]"
/>
</a-form-item>
<a-form-item label="优惠券类型" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-radio-group
v-decorator="['coupon_type', { initialValue: 10, rules: [{ required: true }] }]"
>
<a-radio :value="10">满减券</a-radio>
<a-radio :value="20">折扣券</a-radio>
</a-radio-group>
</a-form-item>
<a-form-item
v-show="form.getFieldValue('coupon_type') == CouponTypeEnum.FULL_DISCOUNT.value"
label="减免金额"
:labelCol="labelCol"
:wrapperCol="wrapperCol"
>
<a-input-number
:min="0.01"
:precision="2"
v-decorator="['reduce_price', { rules: [{ required: true, message: '请输入减免金额' }] }]"
/>
<span class="ml-5">元</span>
</a-form-item>
<a-form-item
v-show="form.getFieldValue('coupon_type') == CouponTypeEnum.DISCOUNT.value"
label="折扣率"
:labelCol="labelCol"
:wrapperCol="wrapperCol"
>
<a-input-number
:min="0"
:max="9.9"
:precision="1"
v-decorator="['discount', { initialValue: 9.9, rules: [{ required: true, message: '请输入折扣率' }] }]"
/>
<span class="ml-5">%</span>
<p class="form-item-help">
<small>折扣率范围 0-9.9,8代表打8折,0代表不折扣</small>
</p>
</a-form-item>
<a-form-item label="最低消费金额" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-input-number
:min="1"
:precision="2"
v-decorator="['min_price', { rules: [{ required: true, message: '请输入最低消费金额' }] }]"
/>
<span class="ml-5">元</span>
</a-form-item>
<a-form-item label="到期类型" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-radio-group
v-decorator="['expire_type', { initialValue: 10, rules: [{ required: true }] }]"
>
<a-radio :value="10">领取后生效</a-radio>
<a-radio :value="20">固定时间</a-radio>
</a-radio-group>
<a-form-item v-show="form.getFieldValue('expire_type') == 10" class="expire_type-10">
<InputNumberGroup
addonBefore="有效期"
addonAfter="天"
:inputProps="{ min: 1, precision: 0 }"
v-decorator="['expire_day', { initialValue: 7, rules: [{ required: true, message: '请输入有效期天数' }] }]"
/>
</a-form-item>
<a-form-item v-show="form.getFieldValue('expire_type') == 20" class="expire_type-20">
<a-range-picker
format="YYYY-MM-DD"
v-decorator="['betweenTime', { initialValue: defaultDate, rules: [{ required: true, message: '请选择有效期范围' }] }]"
/>
</a-form-item>
</a-form-item>
<a-form-item label="券适用范围" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-radio-group
v-decorator="['apply_range', { initialValue: 10, rules: [{ required: true }] }]"
>
<a-radio :value="10">全场通用</a-radio>
<a-radio :value="20">指定商品</a-radio>
</a-radio-group>
<a-form-item v-if="form.getFieldValue('apply_range') == 20">
<SelectGoods
:defaultList="containGoodsList"
v-decorator="['apply_range_config.applyGoodsIds', { rules: [{ required: true, message: '请选择指定的商品' }] }]"
/>
</a-form-item>
</a-form-item>
<a-form-item label="发放总数量" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-input-number
:min="-1"
:precision="0"
v-decorator="['total_num', { initialValue: -1, rules: [{ required: true, message: '请输入发放总数量' }] }]"
/>
<span class="ml-5">张</span>
<p class="form-item-help">
<small>发放的优惠券总数量,-1为不限制</small>
</p>
</a-form-item>
<a-form-item label="显示状态" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-radio-group v-decorator="['status', { initialValue: 1, rules: [{ required: true }] }]">
<a-radio :value="1">显示</a-radio>
<a-radio :value="0">隐藏</a-radio>
</a-radio-group>
<p class="form-item-help">
<small>如果设为隐藏将不会展示在用户端页面</small>
</p>
</a-form-item>
<a-form-item label="优惠券描述" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-textarea :autoSize="{ minRows: 4 }" v-decorator="['describe']" />
</a-form-item>
<a-form-item label="排序" :labelCol="labelCol" :wrapperCol="wrapperCol" extra="数字越小越靠前">
<a-input-number
:min="0"
v-decorator="['sort', {initialValue: 100, rules:[{required: true, message: '请输入排序值'}]}]"
/>
</a-form-item>
<a-form-item class="mt-20" :wrapper-col="{ span: wrapperCol.span, offset: labelCol.span }">
<a-button type="primary" html-type="submit" :loading="isBtnLoading">提交</a-button>
</a-form-item>
</a-form>
</a-spin>
</a-card>
</template>
<script>
import moment from 'moment'
import { pick, get } from 'lodash'
import * as Api from '@/api/market/coupon'
import * as GoodsApi from '@/api/goods'
import { isEmpty } from '@/utils/util'
import { InputNumberGroup, SelectGoods } from '@/components'
import { ApplyRangeEnum, CouponTypeEnum, ExpireTypeEnum } from '@/common/enum/coupon'
export default {
components: {
SelectGoods,
InputNumberGroup
},
data () {
return {
// 枚举类
ApplyRangeEnum,
CouponTypeEnum,
ExpireTypeEnum,
// 正在加载
isLoading: false,
isBtnLoading: false,
// 标签布局属性
labelCol: { span: 3 },
// 输入框布局属性
wrapperCol: { span: 10 },
// 当前表单元素
form: this.$form.createForm(this),
// 默认日期范围
defaultDate: [moment(), moment()],
// 优惠券ID
couponId: null,
// 当前记录
record: {},
// 适用范围:指定的商品
containGoodsList: []
}
},
async created () {
// 记录优惠券ID
this.couponId = this.$route.query.couponId
// 获取当前记录
await this.getDetail()
// 获取适用范围:指定的商品
await this.getContainGoodsList()
},
methods: {
// 获取当前记录
async getDetail () {
const { couponId } = this
this.isLoading = true
await Api.detail({ couponId })
.then(result => {
// 当前记录
this.record = result.data.detail
// 设置表单默认值
this.setFieldsValue()
})
.finally(() => {
this.isLoading = false
})
},
// 获取指定的商品列表
async getContainGoodsList () {
const { record } = this
const goodsIds = get(record, 'apply_range_config.applyGoodsIds')
if (goodsIds !== undefined && goodsIds.length) {
this.isLoading = true
await GoodsApi.listByIds(goodsIds)
.then(result => {
this.containGoodsList = result.data.list
})
.finally(result => {
this.isLoading = false
})
}
},
// 设置表单默认值
setFieldsValue () {
const { record, form, $nextTick } = this
// 设置表单内容
!isEmpty(form.getFieldsValue()) && $nextTick(() => {
// 表单数据
const data = pick(record, [
'name', 'coupon_type', 'reduce_price', 'discount', 'min_price', 'status',
'expire_type', 'expire_day', 'apply_range', 'total_num', 'describe', 'sort'
])
// 时间范围
data.betweenTime = this.getBetweenTime(record)
form.setFieldsValue(data)
})
},
// 格式化时间范围
getBetweenTime (record) {
if (record.expire_type === ExpireTypeEnum.FIXED_TIME.value) {
return [moment(new Date(record.start_time)), moment(new Date(record.end_time))]
}
return this.defaultDate
},
// 确认按钮
handleSubmit (e) {
e.preventDefault()
// 表单验证
const { form: { validateFields }, onFormSubmit } = this
validateFields((errors, values) => {
!errors && onFormSubmit(values)
})
},
/**
* 提交到后端api
*/
onFormSubmit (values) {
this.isLoading = true
this.isBtnLoading = true
Api.edit({ couponId: this.couponId, form: values })
.then(result => {
// 显示提示信息
this.$message.success(result.message, 1.5)
// 跳转到列表页
setTimeout(() => {
this.$router.push('./index')
}, 1500)
})
.catch(() => {
this.isBtnLoading = false
})
.finally(result => {
this.isLoading = false
})
}
}
}
</script>
<style lang="less" scoped>
.ant-form-item {
.ant-form-item {
margin-bottom: 0;
}
}
</style>
@@ -0,0 +1,128 @@
<template>
<a-card :bordered="false">
<div class="card-title">{{ $route.meta.title }}</div>
<a-spin :spinning="isLoading">
<a-form :form="form" @submit="handleSubmit">
<a-form-item label="是否开启实体卡兑换" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-radio-group v-decorator="['is_entrance', { rules: [{ required: true }] }]">
<a-radio :value="1">开启</a-radio>
<a-radio :value="0">关闭</a-radio>
</a-radio-group>
<div class="form-item-help">
<small>如设置关闭则用户端不显示兑换按钮</small>
</div>
</a-form-item>
<a-form-item label="兑换说明" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-textarea
:autoSize="{ minRows: 4 }"
v-decorator="['describe', { rules: [{ required: true, message: '请输入兑换说明' }] }]"
/>
</a-form-item>
<a-form-item :wrapper-col="{ span: wrapperCol.span, offset: labelCol.span }">
<a-button type="primary" html-type="submit">提交</a-button>
</a-form-item>
</a-form>
</a-spin>
</a-card>
</template>
<script>
import pick from 'lodash.pick'
import * as Api from '@/api/setting/store'
export default {
components: {
},
data () {
return {
// 当前设置项的key
key: 'exchange',
// 标签布局属性
labelCol: { span: 4 },
// 输入框布局属性
wrapperCol: { span: 10 },
// loading状态
isLoading: false,
// 当前表单元素
form: this.$form.createForm(this),
// 当前记录详情
record: {}
}
},
// 初始化数据
created () {
// 获取当前详情记录
this.getDetail()
},
methods: {
// 获取当前详情记录
getDetail () {
this.isLoading = true
Api.detail(this.key)
.then(result => {
// 当前记录
this.record = result.data.values
// 设置默认值
this.setFieldsValue()
})
.finally(result => {
this.isLoading = false
})
},
/**
* 设置默认值
*/
setFieldsValue () {
const { record, $nextTick, form: { setFieldsValue } } = this
$nextTick(() => {
setFieldsValue(pick(record, ['is_entrance', 'describe']))
})
},
/**
* 确认按钮
*/
handleSubmit (e) {
e.preventDefault()
// 表单验证
const { form: { validateFields } } = this
validateFields((errors, values) => {
// 提交到后端api
!errors && this.onFormSubmit(values)
})
},
/**
* 提交到后端api
*/
onFormSubmit (values) {
this.isLoading = true
Api.update(this.key, { form: values })
.then((result) => {
// 显示提示信息
this.$message.success(result.message, 1.5)
})
.finally((result) => {
this.isLoading = false
})
}
}
}
</script>
<style lang="less" scoped>
.ant-form-item {
.ant-form-item {
margin-bottom: 0;
}
}
/deep/.ant-form-item-control {
padding-left: 10px;
.ant-form-item-control {
padding-left: 0;
}
}
</style>
+131
View File
@@ -0,0 +1,131 @@
<template>
<a-card :bordered="false">
<div class="card-title">{{ $route.meta.title }}</div>
<div class="table-operator">
<!-- 搜索板块 -->
<a-row class="row-item-search">
<a-form class="search-form" :form="searchForm" layout="inline" @submit="handleSearch">
<a-form-item label="会员昵称">
<a-input v-decorator="['search']" placeholder="请输入会员昵称" />
</a-form-item>
<a-form-item label="变动时间">
<a-range-picker format="YYYY-MM-DD" v-decorator="['betweenTime']" />
</a-form-item>
<a-form-item class="search-btn">
<a-button type="primary" icon="search" html-type="submit">搜索</a-button>
</a-form-item>
</a-form>
</a-row>
</div>
<s-table
ref="table"
rowKey="log_id"
:loading="isLoading"
:columns="columns"
:data="loadData"
:pageSize="15"
>
<!-- 会员信息 -->
<span slot="user" slot-scope="text">
<UserItem :user="text" />
</span>
<!-- 变动数量 -->
<span slot="value" slot-scope="text">
<p class="c-p">{{ text > 0 ? '+' : '' }}{{ text }}</p>
</span>
</s-table>
</a-card>
</template>
<script>
import * as Api from '@/api/market/points'
import { STable, UserItem } from '@/components/Table'
import SceneEnum from '@/common/enum/user/balance/log/Scene'
export default {
name: 'Index',
components: {
STable,
UserItem
},
data () {
return {
// 枚举类
SceneEnum,
// 当前表单元素
searchForm: this.$form.createForm(this),
// 查询参数
queryParam: {},
// 正在加载
isLoading: false,
// 表头
columns: [
{
title: 'ID',
dataIndex: 'log_id'
},
{
title: '会员信息',
dataIndex: 'user',
scopedSlots: { customRender: 'user' }
},
{
title: '变动数量',
dataIndex: 'value',
scopedSlots: { customRender: 'value' }
},
{
title: '描述/说明',
dataIndex: 'describe'
},
{
title: '管理员备注',
dataIndex: 'remark'
},
{
title: '变动时间',
dataIndex: 'create_time'
}
],
// 加载数据方法 必须为 Promise 对象
loadData: param => {
return Api.log({ ...param, ...this.queryParam })
.then(response => {
return response.data.list
})
}
}
},
created () {
},
methods: {
/**
* 刷新列表
* @param Boolean bool 强制刷新到第一页
*/
handleRefresh (bool = false) {
this.$refs.table.refresh(bool)
},
// 确认搜索
handleSearch (e) {
e.preventDefault()
this.searchForm.validateFields((error, values) => {
if (!error) {
this.queryParam = { ...this.queryParam, ...values }
this.handleRefresh(true)
}
})
}
}
}
</script>
<style lang="less" scoped>
.ant-card-body {
padding: 22px 29px 25px;
}
</style>
@@ -0,0 +1,209 @@
<template>
<a-card :bordered="false">
<div class="card-title">{{ $route.meta.title }}</div>
<a-spin :spinning="isLoading">
<a-form :form="form" @submit="handleSubmit">
<a-form-item label="积分名称" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-input
v-decorator="['points_name', { rules: [{ required: true, message: '请输入积分名称' }] }]"
/>
<div class="form-item-help">
<small>注:商家使用自定义的积分名称来做品牌运营。如京东把积分称为“京豆”,淘宝把积分称为“淘金币”</small>
</div>
</a-form-item>
<a-form-item label="积分说明" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-textarea
:autoSize="{ minRows: 4 }"
v-decorator="['describe', { rules: [{ required: true, message: '请输入积分说明' }] }]"
/>
</a-form-item>
<a-divider orientation="left">积分赠送</a-divider>
<a-form-item label="购物送积分" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-radio-group
v-decorator="['is_shopping_gift', { initialValue: 1, rules: [{ required: true }] }]"
>
<a-radio :value="1">开启</a-radio>
<a-radio :value="0">关闭</a-radio>
</a-radio-group>
<div class="form-item-help">
<small>积分赠送规则:1. 订单确认收货已完成; 2. 已完成订单超出后台设置的申请售后期限</small>
</div>
</a-form-item>
<div v-show="form.getFieldValue('is_shopping_gift') == 1">
<a-form-item label="积分赠送比例" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-input-number
:min="0.1"
:max="100"
:precision="1"
v-decorator="['gift_ratio', { rules: [{ required: true, message: '请输入积分赠送比例' }] }]"
/>
<span class="ml-10">%</span>
<div class="form-item-help">
<p class="extra">注:赠送比例请填写数字0~100;订单的运费不参与积分赠送</p>
<p class="extra">例:订单付款金额(100.00元) * 积分赠送比例(100%) = 实际赠送的积分(100积分)</p>
</div>
</a-form-item>
</div>
<a-divider orientation="left">积分抵扣</a-divider>
<a-form-item label="下单使用积分抵扣" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-radio-group
v-decorator="['is_shopping_discount', { initialValue: 1, rules: [{ required: true }] }]"
>
<a-radio :value="1">开启</a-radio>
<a-radio :value="0">关闭</a-radio>
</a-radio-group>
<div class="form-item-help">
<small>注:如开启则用户下单时可选择使用积分抵扣订单金额</small>
</div>
</a-form-item>
<div v-show="form.getFieldValue('is_shopping_discount') == 1">
<a-form-item label="积分抵扣比例" :labelCol="labelCol" :wrapperCol="wrapperCol">
<InputNumberGroup
addonBefore="1个积分可抵扣"
addonAfter="元"
:inputProps="{ min: 0.01, precision: 2 }"
v-decorator="['discount.discount_ratio', { rules: [{ required: true, message: '积分抵扣比例不能为空' }] }]"
/>
<div class="form-item-help">
<small>例如:设置1个积分可抵扣0.1元,则10积分可抵扣1元,100积分可抵扣10元</small>
</div>
</a-form-item>
<a-form-item label="最高可抵扣金额" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-form-item>
<InputNumberGroup
addonBefore="订单满"
addonAfter="元"
:inputProps="{ min: 0.01, precision: 2 }"
v-decorator="['discount.full_order_price', { rules: [{ required: true, message: '抵扣条件不能为空' }] }]"
/>
</a-form-item>
<a-form-item>
<InputNumberGroup
addonBefore="最高可抵扣金额"
addonAfter="%"
:inputProps="{ min: 0.1, max: 90, precision: 1 }"
v-decorator="['discount.max_money_ratio', { rules: [{ required: true, message: '最高可抵扣金额不能为空' }] }]"
/>
<div class="form-item-help">
<small>例如:设置最高可抵扣10%,订单金额100元,此时用户可抵扣10元</small>
</div>
</a-form-item>
</a-form-item>
</div>
<a-form-item :wrapper-col="{ span: wrapperCol.span, offset: labelCol.span }">
<a-button type="primary" html-type="submit">提交</a-button>
</a-form-item>
</a-form>
</a-spin>
</a-card>
</template>
<script>
import pick from 'lodash.pick'
import * as Api from '@/api/setting/store'
import { InputNumberGroup } from '@/components'
export default {
components: {
InputNumberGroup
},
data () {
return {
// 当前设置项的key
key: 'points',
// 标签布局属性
labelCol: { span: 4 },
// 输入框布局属性
wrapperCol: { span: 10 },
// loading状态
isLoading: false,
// 当前表单元素
form: this.$form.createForm(this),
// 当前记录详情
record: {}
}
},
// 初始化数据
created () {
// 获取当前详情记录
this.getDetail()
},
methods: {
// 获取当前详情记录
getDetail () {
this.isLoading = true
Api.detail(this.key)
.then(result => {
// 当前记录
this.record = result.data.values
// 设置默认值
this.setFieldsValue()
})
.finally(result => {
this.isLoading = false
})
},
/**
* 设置默认值
*/
setFieldsValue () {
const { record, $nextTick, form: { setFieldsValue } } = this
$nextTick(() => {
setFieldsValue(pick(record, [
'points_name', 'describe',
'is_shopping_gift', 'gift_ratio',
'is_shopping_discount', 'discount'
]))
})
},
/**
* 确认按钮
*/
handleSubmit (e) {
e.preventDefault()
// 表单验证
const { form: { validateFields } } = this
validateFields((errors, values) => {
// 提交到后端api
!errors && this.onFormSubmit(values)
})
},
/**
* 提交到后端api
*/
onFormSubmit (values) {
this.isLoading = true
Api.update(this.key, { form: values })
.then((result) => {
// 显示提示信息
this.$message.success(result.message, 1.5)
})
.finally((result) => {
this.isLoading = false
})
}
}
}
</script>
<style lang="less" scoped>
.ant-form-item {
.ant-form-item {
margin-bottom: 0;
}
}
/deep/.ant-form-item-control {
padding-left: 10px;
.ant-form-item-control {
padding-left: 0;
}
}
</style>
@@ -0,0 +1,162 @@
<template>
<a-card :bordered="false">
<div class="card-title">{{ $route.meta.title }}</div>
<a-spin :spinning="isLoading">
<a-form :form="form" @submit="handleSubmit">
<a-form-item label="是否开启会员充值" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-radio-group v-decorator="['is_entrance', { rules: [{ required: true }] }]">
<a-radio :value="1">开启</a-radio>
<a-radio :value="0">关闭</a-radio>
</a-radio-group>
<div class="form-item-help">
<small>如设置关闭则用户端不显示充值按钮</small>
</div>
</a-form-item>
<a-form-item label="充值自定义金额" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-radio-group v-decorator="['is_custom', { rules: [{ required: true }] }]">
<a-radio :value="1">允许</a-radio>
<a-radio :value="0">不允许</a-radio>
</a-radio-group>
<div class="form-item-help">
<small>是否允许用户填写自定义的充值金额</small>
</div>
</a-form-item>
<a-form-item
v-show="form.getFieldValue('is_custom') == 1"
label="最低充值金额"
:labelCol="labelCol"
:wrapperCol="wrapperCol"
>
<a-input-number
:min="0.01"
:precision="2"
v-decorator="['lowest_money', { rules: [{ required: true, message: '请输入最低充值金额' }] }]"
/>
<span class="ml-10">元</span>
<div class="form-item-help">
<small>低于该设定金额时不允许充值</small>
</div>
</a-form-item>
<a-form-item label="自动匹配套餐" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-radio-group v-decorator="['is_match_plan', { rules: [{ required: true }] }]">
<a-radio :value="1">开启</a-radio>
<a-radio :value="0">关闭</a-radio>
</a-radio-group>
<div class="form-item-help">
<small>充值自定义金额时 是否自动匹配充值套餐,如不开启则不参与套餐金额赠送</small>
</div>
</a-form-item>
<a-form-item label="充值说明" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-textarea
:autoSize="{ minRows: 4 }"
v-decorator="['describe', { rules: [{ required: true, message: '请输入充值说明' }] }]"
/>
</a-form-item>
<a-form-item :wrapper-col="{ span: wrapperCol.span, offset: labelCol.span }">
<a-button type="primary" html-type="submit">提交</a-button>
</a-form-item>
</a-form>
</a-spin>
</a-card>
</template>
<script>
import pick from 'lodash.pick'
import * as Api from '@/api/setting/store'
export default {
components: {
},
data () {
return {
// 当前设置项的key
key: 'recharge',
// 标签布局属性
labelCol: { span: 4 },
// 输入框布局属性
wrapperCol: { span: 10 },
// loading状态
isLoading: false,
// 当前表单元素
form: this.$form.createForm(this),
// 当前记录详情
record: {}
}
},
// 初始化数据
created () {
// 获取当前详情记录
this.getDetail()
},
methods: {
// 获取当前详情记录
getDetail () {
this.isLoading = true
Api.detail(this.key)
.then(result => {
// 当前记录
this.record = result.data.values
// 设置默认值
this.setFieldsValue()
})
.finally(result => {
this.isLoading = false
})
},
/**
* 设置默认值
*/
setFieldsValue () {
const { record, $nextTick, form: { setFieldsValue } } = this
$nextTick(() => {
setFieldsValue(pick(record, ['is_entrance', 'is_custom', 'lowest_money', 'is_match_plan', 'describe']))
})
},
/**
* 确认按钮
*/
handleSubmit (e) {
e.preventDefault()
// 表单验证
const { form: { validateFields } } = this
validateFields((errors, values) => {
// 提交到后端api
!errors && this.onFormSubmit(values)
})
},
/**
* 提交到后端api
*/
onFormSubmit (values) {
this.isLoading = true
Api.update(this.key, { form: values })
.then((result) => {
// 显示提示信息
this.$message.success(result.message, 1.5)
})
.finally((result) => {
this.isLoading = false
})
}
}
}
</script>
<style lang="less" scoped>
.ant-form-item {
.ant-form-item {
margin-bottom: 0;
}
}
/deep/.ant-form-item-control {
padding-left: 10px;
.ant-form-item-control {
padding-left: 0;
}
}
</style>
@@ -0,0 +1,165 @@
<template>
<a-card :bordered="false">
<div class="card-title">{{ $route.meta.title }}</div>
<div class="table-operator">
<a-row>
<a-col :span="6">
<a-button v-action:add type="primary" icon="plus" @click="handleAdd">新增</a-button>
</a-col>
<a-col :span="8" :offset="10">
<a-input-search
style="max-width: 300px; min-width: 150px;float: right;"
v-model="queryParam.search"
placeholder="请输入套餐名称"
@search="onSearch"
/>
</a-col>
</a-row>
</div>
<s-table
ref="table"
rowKey="plan_id"
:loading="isLoading"
:columns="columns"
:data="loadData"
:pageSize="15"
>
<!-- 充值金额 -->
<span slot="money" slot-scope="text">
<p class="c-p">{{ text }}</p>
</span>
<!-- 赠送金额 -->
<span slot="gift_money" slot-scope="text">
<p class="c-p">{{ text }}</p>
</span>
<!-- 操作 -->
<span slot="action" slot-scope="text, item">
<a v-action:edit style="margin-right: 8px;" @click="handleEdit(item)">编辑</a>
<a v-action:delete @click="handleDelete(item)">删除</a>
</span>
</s-table>
<AddForm ref="AddForm" @handleSubmit="handleRefresh" />
<EditForm ref="EditForm" @handleSubmit="handleRefresh" />
</a-card>
</template>
<script>
import * as Api from '@/api/market/recharge/plan'
import { STable } from '@/components'
import { AddForm, EditForm } from './modules'
export default {
name: 'Index',
components: {
STable,
AddForm,
EditForm
},
data () {
return {
// 查询参数
queryParam: {},
// 正在加载
isLoading: false,
// 表头
columns: [
{
title: '套餐ID',
dataIndex: 'plan_id'
},
{
title: '套餐名称',
dataIndex: 'plan_name'
},
{
title: '充值金额 (元)',
dataIndex: 'money',
scopedSlots: { customRender: 'money' }
},
{
title: '赠送金额 (元)',
dataIndex: 'gift_money',
scopedSlots: { customRender: 'gift_money' }
},
{
title: '排序',
dataIndex: 'sort'
},
{
title: '添加时间',
dataIndex: 'create_time'
},
{
title: '操作',
dataIndex: 'action',
width: '180px',
scopedSlots: { customRender: 'action' }
}
],
// 加载数据方法 必须为 Promise 对象
loadData: param => {
return Api.list({ ...param, ...this.queryParam })
.then(response => {
return response.data.list
})
}
}
},
created () {
},
methods: {
/**
* 新增记录
*/
handleAdd () {
this.$refs.AddForm.add()
},
/**
* 编辑记录
*/
handleEdit (item) {
this.$refs.EditForm.edit(item)
},
/**
* 删除记录
*/
handleDelete (item) {
const app = this
const modal = this.$confirm({
title: '您确定要删除该记录吗?',
content: '删除后不可恢复',
onOk () {
return Api.deleted({ planId: item.plan_id })
.then((result) => {
app.$message.success(result.message, 1.5)
app.handleRefresh()
})
.finally(result => {
modal.destroy()
})
}
})
},
/**
* 刷新列表
* @param Boolean bool 强制刷新到第一页
*/
handleRefresh (bool = false) {
this.$refs.table.refresh(bool)
},
/**
* 检索查询
*/
onSearch () {
this.handleRefresh(true)
}
}
}
</script>
@@ -0,0 +1,125 @@
<template>
<a-modal
:title="title"
:width="720"
:visible="visible"
:confirmLoading="confirmLoading"
:maskClosable="false"
@ok="handleSubmit"
@cancel="handleCancel"
>
<a-spin :spinning="confirmLoading">
<a-form :form="form">
<a-form-item
label="套餐名称"
:labelCol="labelCol"
:wrapperCol="wrapperCol"
extra="便于后台查找,例如:充100元送10元"
>
<a-input
v-decorator="['plan_name', {rules: [{required: true, min: 2, message: '请输入至少2个字符'}]}]"
/>
</a-form-item>
<a-form-item label="充值金额" :labelCol="labelCol" :wrapperCol="wrapperCol" extra="会员充值并支付的金额">
<a-input-number
:min="0.01"
v-decorator="['money', {rules: [{required: true, message: '请输入充值的金额'}]}]"
/>
<span class="ml-10">元</span>
</a-form-item>
<a-form-item
label="赠送金额"
:labelCol="labelCol"
:wrapperCol="wrapperCol"
extra="充值成功后赠送的金额,不能大于充值金额"
>
<a-input-number
:min="0.01"
v-decorator="['gift_money', {rules: [{required: true, message: '请输入赠送的金额'}]}]"
/>
<span class="ml-10">元</span>
</a-form-item>
<a-form-item label="排序" :labelCol="labelCol" :wrapperCol="wrapperCol" extra="数字越小越靠前">
<a-input-number
:min="0"
v-decorator="['sort', {initialValue: 100, rules: [{required: true, message: '请输入排序值'}]}]"
/>
</a-form-item>
</a-form>
</a-spin>
</a-modal>
</template>
<script>
import * as Api from '@/api/market/recharge/plan'
export default {
data () {
return {
// 对话框标题
title: '新增充值套餐',
// 标签布局属性
labelCol: { span: 7 },
// 输入框布局属性
wrapperCol: { span: 13 },
// modal(对话框)是否可见
visible: false,
// modal(对话框)确定按钮 loading
confirmLoading: false,
// 当前表单元素
form: this.$form.createForm(this)
}
},
methods: {
/**
* 显示对话框
*/
add () {
// 显示窗口
this.visible = true
},
/**
* 确认按钮
*/
handleSubmit (e) {
e.preventDefault()
// 表单验证
const { form: { validateFields } } = this
validateFields((errors, values) => {
// 提交到后端api
!errors && this.onFormSubmit(values)
})
},
/**
* 关闭对话框事件
*/
handleCancel () {
this.visible = false
this.form.resetFields()
},
/**
* 提交到后端api
*/
onFormSubmit (values) {
this.confirmLoading = true
Api.add({ form: values })
.then((result) => {
// 显示成功
this.$message.success(result.message, 1.5)
// 关闭对话框事件
this.handleCancel()
// 通知父端组件提交完成了
this.$emit('handleSubmit', values)
})
.finally(() => {
this.confirmLoading = false
})
}
}
}
</script>
@@ -0,0 +1,142 @@
<template>
<a-modal
:title="title"
:width="720"
:visible="visible"
:confirmLoading="confirmLoading"
:maskClosable="false"
@ok="handleSubmit"
@cancel="handleCancel"
>
<a-spin :spinning="confirmLoading">
<a-form :form="form">
<a-form-item
label="套餐名称"
:labelCol="labelCol"
:wrapperCol="wrapperCol"
extra="便于后台查找,例如:充100元送10元"
>
<a-input
v-decorator="['plan_name', {rules: [{required: true, min: 2, message: '请输入至少2个字符'}]}]"
/>
</a-form-item>
<a-form-item label="充值金额" :labelCol="labelCol" :wrapperCol="wrapperCol" extra="会员充值并支付的金额">
<a-input-number
:min="0.01"
v-decorator="['money', {rules: [{required: true, message: '请输入充值的金额'}]}]"
/>
<span class="ml-10">元</span>
</a-form-item>
<a-form-item
label="赠送金额"
:labelCol="labelCol"
:wrapperCol="wrapperCol"
extra="充值成功后赠送的金额,不能大于充值金额"
>
<a-input-number
:min="0.01"
v-decorator="['gift_money', {rules: [{required: true, message: '请输入赠送的金额'}]}]"
/>
<span class="ml-10">元</span>
</a-form-item>
<a-form-item label="排序" :labelCol="labelCol" :wrapperCol="wrapperCol" extra="数字越小越靠前">
<a-input-number
:min="0"
v-decorator="['sort', {initialValue: 100, rules: [{required: true, message: '请输入排序值'}]}]"
/>
</a-form-item>
</a-form>
</a-spin>
</a-modal>
</template>
<script>
import pick from 'lodash.pick'
import * as Api from '@/api/market/recharge/plan'
export default {
data () {
return {
// 对话框标题
title: '编辑充值套餐',
// 标签布局属性
labelCol: { span: 7 },
// 输入框布局属性
wrapperCol: { span: 13 },
// modal(对话框)是否可见
visible: false,
// modal(对话框)确定按钮 loading
confirmLoading: false,
// 当前表单元素
form: this.$form.createForm(this),
// 当前记录
record: {}
}
},
methods: {
/**
* 显示对话框
*/
edit (record) {
// 显示窗口
this.visible = true
// 当前记录
this.record = record
// 设置默认值
this.setFieldsValue()
},
/**
* 设置默认值
*/
setFieldsValue () {
const { form: { setFieldsValue } } = this
this.$nextTick(() => {
setFieldsValue(pick(this.record, ['plan_name', 'money', 'gift_money', 'sort']))
})
},
/**
* 确认按钮
*/
handleSubmit (e) {
e.preventDefault()
// 表单验证
const { form: { validateFields } } = this
validateFields((errors, values) => {
// 提交到后端api
!errors && this.onFormSubmit(values)
})
},
/**
* 关闭对话框事件
*/
handleCancel () {
this.visible = false
this.form.resetFields()
},
/**
* 提交到后端api
*/
onFormSubmit (values) {
this.confirmLoading = true
Api.edit({ planId: this.record.plan_id, form: values })
.then((result) => {
// 显示成功
this.$message.success(result.message, 1.5)
// 关闭对话框事件
this.handleCancel()
// 通知父端组件提交完成了
this.$emit('handleSubmit', values)
})
.finally(() => {
this.confirmLoading = false
})
}
}
}
</script>
@@ -0,0 +1,4 @@
import AddForm from './AddForm'
import EditForm from './EditForm'
export { AddForm, EditForm }
@@ -0,0 +1,128 @@
<template>
<a-card :bordered="false">
<div class="card-title">{{ $route.meta.title }}</div>
<a-spin :spinning="isLoading">
<a-form :form="form" @submit="handleSubmit">
<a-form-item label="是否开启余额分享" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-radio-group v-decorator="['is_entrance', { rules: [{ required: true }] }]">
<a-radio :value="1">开启</a-radio>
<a-radio :value="0">关闭</a-radio>
</a-radio-group>
<div class="form-item-help">
<small>如设置关闭则用户端不显示分享按钮</small>
</div>
</a-form-item>
<a-form-item label="分享说明" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-textarea
:autoSize="{ minRows: 4 }"
v-decorator="['describe', { rules: [{ required: true, message: '请输入分享说明' }] }]"
/>
</a-form-item>
<a-form-item :wrapper-col="{ span: wrapperCol.span, offset: labelCol.span }">
<a-button type="primary" html-type="submit">提交</a-button>
</a-form-item>
</a-form>
</a-spin>
</a-card>
</template>
<script>
import pick from 'lodash.pick'
import * as Api from '@/api/setting/store'
export default {
components: {
},
data () {
return {
// 当前设置项的key
key: 'share',
// 标签布局属性
labelCol: { span: 4 },
// 输入框布局属性
wrapperCol: { span: 10 },
// loading状态
isLoading: false,
// 当前表单元素
form: this.$form.createForm(this),
// 当前记录详情
record: {}
}
},
// 初始化数据
created () {
// 获取当前详情记录
this.getDetail()
},
methods: {
// 获取当前详情记录
getDetail () {
this.isLoading = true
Api.detail(this.key)
.then(result => {
// 当前记录
this.record = result.data.values
// 设置默认值
this.setFieldsValue()
})
.finally(result => {
this.isLoading = false
})
},
/**
* 设置默认值
*/
setFieldsValue () {
const { record, $nextTick, form: { setFieldsValue } } = this
$nextTick(() => {
setFieldsValue(pick(record, ['is_entrance', 'describe']))
})
},
/**
* 确认按钮
*/
handleSubmit (e) {
e.preventDefault()
// 表单验证
const { form: { validateFields } } = this
validateFields((errors, values) => {
// 提交到后端api
!errors && this.onFormSubmit(values)
})
},
/**
* 提交到后端api
*/
onFormSubmit (values) {
this.isLoading = true
Api.update(this.key, { form: values })
.then((result) => {
// 显示提示信息
this.$message.success(result.message, 1.5)
})
.finally((result) => {
this.isLoading = false
})
}
}
}
</script>
<style lang="less" scoped>
.ant-form-item {
.ant-form-item {
margin-bottom: 0;
}
}
/deep/.ant-form-item-control {
padding-left: 10px;
.ant-form-item-control {
padding-left: 0;
}
}
</style>
+152
View File
@@ -0,0 +1,152 @@
<template>
<a-card :bordered="false">
<div class="card-title">{{ $route.meta.title }}</div>
<a-spin :spinning="isLoading">
<a-form :form="form" @submit="handleSubmit" :selfUpdate="true">
<a-tabs :activeKey="tabKey" :tabBarStyle="{marginBottom: '30px'}" @change="handleTabs">
<a-tab-pane :key="0" tab="基本信息"></a-tab-pane>
</a-tabs>
<div class="tabs-content">
<!-- 基本信息 -->
<div class="tab-pane" v-show="tabKey == 0">
<a-form-item label="代理商名称" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-input
placeholder="请输入代理商名称"
v-decorator="['name', {rules: [{required: true, min: 2, message: '请输入至少2个字符'}]}]"
/>
</a-form-item>
<a-form-item label="代理商地址" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-input
placeholder="请输入代理商地址"
v-decorator="['address', {rules: [{required: true, min: 2, message: '请输入至少2个字符'}]}]"
/>
</a-form-item>
<a-form-item label="联系人" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-input
placeholder="请输入联系人"
v-decorator="['contact_name', {rules: [{required: false}]}]"
/>
</a-form-item>
<a-form-item label="联系电话" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-input
placeholder="请输入联系电话"
v-decorator="['contact_tel', {rules: [{required: false}]}]"
/>
</a-form-item>
<!--<a-form-item label="回调地址" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-input
placeholder="请输入回调地址"
v-decorator="['callback', {rules: [{required: true, min: 2, message: '请输入至少2个字符'}]}]"
/>
</a-form-item>-->
</div>
</div>
<a-form-item class="mt-20" :wrapper-col="{ span: wrapperCol.span, offset: labelCol.span }">
<a-button type="primary" html-type="submit" :loading="isBtnLoading">提交</a-button>
</a-form-item>
</a-form>
</a-spin>
</a-card>
</template>
<script>
import * as MerchantApi from '@/api/merchant'
import MerchantModel from '@/common/model/merchant/Index'
export default {
components: {
},
data () {
return {
// 默认的标签索引
tabKey: 0,
// 标签布局属性
labelCol: { span: 3 },
// 输入框布局属性
wrapperCol: { span: 10 },
// loading状态
isLoading: false,
isBtnLoading: false,
// 当前表单元素
form: this.$form.createForm(this),
// 表单数据
formData: MerchantModel.formData
}
},
// 初始化数据
created () {
this.isLoading = true
// 获取form所需的数据
MerchantModel.getFromData()
.then(() => {
this.isLoading = false
})
},
methods: {
// 手动强制更新页面
onForceUpdate (bool = false) {
this.$forceUpdate()
// bool为true时再执行一次 $forceUpdate, 特殊情况下需执行两次,原因如下:
// 第一次执行 $forceUpdate时, 新元素绑定v-decorator无法获取到form.getFieldValue
bool && setTimeout(() => {
this.$forceUpdate()
}, 10)
},
// 切换tab选项卡
handleTabs (key) {
this.tabKey = key
},
/**
* 确认按钮
*/
handleSubmit (e) {
e.preventDefault()
// 表单验证
const { form: { validateFields } } = this
validateFields((errors, values) => {
// 定位到错误的tab选项卡
if (errors) {
this.onTargetTabError(errors)
return false
}
// 提交到后端api
this.onFormSubmit(values)
return true
})
},
/**
* 提交到后端api
*/
onFormSubmit (values) {
this.isLoading = true
this.isBtnLoading = true
MerchantApi.add({ form: values })
.then(result => {
// 显示提示信息
this.$message.success(result.message, 1.5)
// 跳转到列表页
setTimeout(() => {
this.$router.push('./index')
}, 1500)
})
.catch(() => {
this.isBtnLoading = false
})
.finally(() => {
this.isLoading = false
})
}
}
}
</script>
<style lang="less" scoped>
@import './style.less';
</style>
+294
View File
@@ -0,0 +1,294 @@
<template>
<a-card :bordered="false">
<div class="card-title">{{ $route.meta.title }}</div>
<div class="table-operator">
<!-- 搜索板块 -->
<a-row class="row-item-search">
<a-form class="search-form" :form="searchForm" layout="inline" @submit="handleSearch">
<a-form-item label="关键字搜索">
<a-input v-decorator="['search']" placeholder="代理商编号/代理商名称/代理商地址/联系人/联系电话" />
</a-form-item>
<a-form-item class="search-btn">
<a-button type="primary" icon="search" html-type="submit">搜索</a-button>
</a-form-item>
</a-form>
</a-row>
<!-- 操作板块 -->
<div class="row-item-tab clearfix">
<a-button
v-if="$auth('/merchant/create')"
class="fl-l"
type="primary"
icon="plus"
@click="handleCreate"
>新增</a-button>
<div v-if="selectedRowKeys.length" class="button-group">
<a-button-group class="ml-10">
<a-button v-action:delete icon="delete" @click="handleDelete(selectedRowKeys)">删除</a-button>
</a-button-group>
</div>
</div>
</div>
<s-table
ref="table"
rowKey="merchant_id"
:loading="isLoading"
:columns="columns"
:data="loadData"
:rowSelection="rowSelection"
:pageSize="15"
:scroll="{ x: 1450 }"
>
<!-- 代理商地址 -->
<span slot="address" slot-scope="text">
<p class="twoline-hide" style="width: 270px;">{{ text }}</p>
</span>
<!-- 操作项 -->
<div class="actions" slot="action" slot-scope="text, item">
<!-- <a title="代理商对接参数" v-action:para @click="handleApiPara(item)">对接参数</a>-->
<a title="推广码" v-action:qrCode @click="handleQrCode(item)">推广码</a>
<a title="代理商充值" v-action:recharge @click="handleRecharge(item)">充值</a>
<router-link v-if="$auth('/merchant/update')" :to="{ path: '/merchant/update', query: { merchantId: item.merchant_id } }">编辑</router-link>
<a v-action:delete @click="handleDelete([item.merchant_id])">删除</a>
<a title="抖音发券" v-action:sendCoupon @click="handleSendCoupon(item)">抖音发券</a>
</div>
</s-table>
<RechargeForm ref="RechargeForm" @handleSubmit="handleRefresh" />
<ApiParaForm ref="ApiParaForm" @handleSubmit="handleRefresh" />
<QrCodeForm ref="QrCodeForm" @handleSubmit="handleRefresh" />
<SendCouponForm ref="SendCouponForm" :coupongoods="couponGoodsData" @handleSubmit="handleRefresh" />
</a-card>
</template>
<script>
import * as MerchantApi from '@/api/merchant'
import * as GoodsApi from '@/api/goods'
import { ContentHeader, STable } from '@/components'
import { RechargeForm, ApiParaForm, QrCodeForm, SendCouponForm } from './modules'
import { assignment, inArray } from '@/utils/util'
// 表格表头
const columns = [
{
title: '代理商ID',
width: '70px',
dataIndex: 'merchant_id'
},
{
title: '代理商编号',
width: '150px',
dataIndex: 'orgNo'
},
{
title: '代理商名称',
width: '150px',
dataIndex: 'name'
},
{
title: '代理商地址',
width: '200px',
dataIndex: 'address'
},
{
title: '联系人',
width: '150px',
dataIndex: 'contact_name'
},
{
title: '联系电话',
width: '150px',
dataIndex: 'contact_tel'
},
{
title: '余额',
width: '150px',
dataIndex: 'balance'
},
{
title: '今日发券金额',
width: '150px',
dataIndex: 'day_money'
},
{
title: '发券总金额',
width: '150px',
dataIndex: 'total_money'
},
/*{
title: 'appKey',
dataIndex: 'appKey',
width: '300px'
},
{
title: '回调地址',
dataIndex: 'callback',
width: '300px',
scopedSlots: { customRender: 'callback' }
},*/
{
title: '创建时间',
dataIndex: 'create_time'
},
{
title: '操作',
dataIndex: 'action',
width: '300px',
fixed: 'right',
scopedSlots: { customRender: 'action' }
}
]
export default {
name: 'Index',
components: {
ContentHeader,
RechargeForm,
ApiParaForm,
QrCodeForm,
SendCouponForm,
STable
},
data () {
return {
// 当前表单元素
searchForm: this.$form.createForm(this),
// 查询参数
queryParam: {},
// 正在加载
isLoading: false,
// 表头
columns,
// 选择的元素
selectedRowKeys: [],
// 加载数据方法 必须为 Promise 对象
loadData: param => {
return MerchantApi.list({ ...param, ...this.queryParam })
.then(response => {
return response.data.list
})
},
// 优惠券商品
couponGoodsData: []
}
},
beforeCreate () {
// 批量给当前实例赋值
assignment(this, {
inArray
})
},
created () {
this.getCouponGoods()
},
computed: {
rowSelection () {
return {
selectedRowKeys: this.selectedRowKeys,
onChange: this.onSelectChange
}
}
},
methods: {
/**
* 选中项发生变化时的回调
*/
onSelectChange (selectedRowKeys) {
this.selectedRowKeys = selectedRowKeys
},
// 确认搜索
handleSearch (e) {
e.preventDefault()
this.searchForm.validateFields((error, values) => {
if (!error) {
this.queryParam = { ...this.queryParam, ...values }
this.handleRefresh(true)
}
})
},
// 重置搜索表单
handleReset () {
this.searchForm.resetFields()
},
// 代理商充值
handleRecharge (item) {
this.$refs.RechargeForm.handle(item)
},
// 代理商推广码
handleQrCode (item) {
this.$refs.QrCodeForm.handle(item)
},
// 代理商对接参数
handleApiPara (item) {
this.$refs.ApiParaForm.handle(item)
},
// 发券弹窗
handleSendCoupon (item) {
this.$refs.SendCouponForm.handle(item)
},
/**
* 删除记录
*/
handleDelete (merchantIds) {
const app = this
const modal = this.$confirm({
title: '您确定要删除该记录吗?',
content: '删除后不可恢复',
onOk () {
return MerchantApi.deleted(merchantIds)
.then(result => {
app.$message.success(result.message, 1.5)
app.handleRefresh()
})
.finally(result => {
modal.destroy()
})
}
})
},
/**
* 新增记录
*/
handleCreate () {
this.$router.push('/merchant/create')
},
/**
* 刷新列表
* @param Boolean bool 强制刷新到第一页
*/
handleRefresh (bool = false) {
this.selectedRowKeys = []
this.$refs.table.refresh(bool)
},
/**
* 获取优惠券商品
*/
getCouponGoods () {
this.isLoading = true
GoodsApi.all({ province: '', oilCardType: 'coupon' })
.then(result => {
this.couponGoodsData = result.data.list
})
.finally(() => {
this.isLoading = false
})
}
}
}
</script>
<style lang="less" scoped>
.ant-card-body {
padding: 22px 29px 25px;
}
// 筛选tab
.tab-list {
margin-right: 20px;
}
</style>
+177
View File
@@ -0,0 +1,177 @@
<template>
<a-card :bordered="false">
<div class="card-title">{{ $route.meta.title }}</div>
<a-spin :spinning="isLoading">
<a-form :form="form" @submit="handleSubmit" :selfUpdate="true">
<a-tabs :activeKey="tabKey" :tabBarStyle="{marginBottom: '30px'}" @change="handleTabs">
<a-tab-pane :key="0" tab="基本信息"></a-tab-pane>
</a-tabs>
<div class="tabs-content">
<!-- 基本信息 -->
<div class="tab-pane" v-show="tabKey == 0">
<a-form-item label="代理商名称" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-input
placeholder="请输入代理商名称"
v-decorator="['name', {rules: [{required: true, min: 2, message: '请输入至少2个字符'}]}]"
/>
</a-form-item>
<a-form-item label="代理商地址" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-input
placeholder="请输入代理商地址"
v-decorator="['address', {rules: [{required: true, min: 2, message: '请输入至少2个字符'}]}]"
/>
</a-form-item>
<a-form-item label="联系人" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-input
placeholder="请输入联系人"
v-decorator="['contact_name', {rules: [{required: false}]}]"
/>
</a-form-item>
<a-form-item label="联系电话" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-input
placeholder="请输入联系电话"
v-decorator="['contact_tel', {rules: [{required: false}]}]"
/>
</a-form-item>
<!--<a-form-item label="回调地址" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-input
placeholder="请输入回调地址"
v-decorator="['callback', {rules: [{required: true, min: 2, message: '请输入至少2个字符'}]}]"
/>
</a-form-item>-->
</div>
</div>
<a-form-item class="mt-20" :wrapper-col="{ span: wrapperCol.span, offset: labelCol.span }">
<a-button type="primary" html-type="submit" :loading="isBtnLoading">提交</a-button>
</a-form-item>
</a-form>
</a-spin>
</a-card>
</template>
<script>
import * as MerchantApi from '@/api/merchant'
import { InputNumberGroup } from '@/components'
import MerchantModel from '@/common/model/merchant/Index'
import { isEmptyObject } from '@/utils/util'
export default {
components: {
InputNumberGroup
},
data () {
return {
// 默认的标签索引
tabKey: 0,
// 标签布局属性
labelCol: { span: 3 },
// 输入框布局属性
wrapperCol: { span: 10 },
// loading状态
isLoading: false,
isBtnLoading: false,
// 当前表单元素
form: this.$form.createForm(this),
// 代理商ID
merchantId: null,
// 表单数据
formData: MerchantModel.formData
}
},
created () {
// 初始化数据
this.initData()
},
beforeDestroy () {
// 销毁代理商详情
MerchantModel.formData.merchant = {}
},
methods: {
// 初始化数据
initData () {
// 记录代理商ID
this.merchantId = this.$route.query.merchantId
// 获取form所需的数据
this.isLoading = true
MerchantModel.getFromData(this.merchantId)
.then(() => {
// 代理商表单数据
if (!isEmptyObject(this.form.getFieldsValue())) {
this.form.setFieldsValue(MerchantModel.getFieldsValue())
}
this.isLoading = false
})
},
// 手动强制更新页面
onForceUpdate (bool = false) {
this.$forceUpdate()
// bool为true时再执行一次 $forceUpdate, 特殊情况下需执行两次,原因如下:
// 第一次执行 $forceUpdate时, 新元素绑定v-decorator无法获取到form.getFieldValue
bool && setTimeout(() => {
this.$forceUpdate()
}, 10)
},
// 切换tab选项卡
handleTabs (key) {
this.tabKey = key
},
/**
* 确认按钮
*/
handleSubmit (e) {
e.preventDefault()
// 表单验证
const { form: { validateFields } } = this
validateFields((errors, values) => {
// 定位到错误的tab选项卡
if (errors) {
this.onTargetTabError(errors)
return false
}
// 提交到后端api
this.onFormSubmit(values)
return true
})
},
/**
* 定位到错误的tab选项卡
*/
onTargetTabError (errors) {
},
/**
* 提交到后端api
*/
onFormSubmit (values) {
this.isLoading = true
this.isBtnLoading = true
MerchantApi.edit({ merchantId: this.merchantId, form: values })
.then(result => {
// 显示提示信息
this.$message.success(result.message, 1.5)
// 跳转到列表页
setTimeout(() => {
this.$router.push('./index')
}, 1500)
})
.catch(() => {
this.isBtnLoading = false
})
.finally(() => {
this.isLoading = false
})
}
}
}
</script>
<style lang="less" scoped>
@import './style.less';
</style>
@@ -0,0 +1,146 @@
<template>
<a-card :bordered="false">
<div class="card-title">{{ $route.meta.title }}</div>
<div class="table-operator">
<!-- 搜索板块 -->
<a-row class="row-item-search">
<a-form class="search-form" :form="searchForm" layout="inline" @submit="handleSearch">
<a-form-item label="代理商名称">
<a-input v-decorator="['search']" placeholder="请输入代理商名称" />
</a-form-item>
<a-form-item label="余额变动场景">
<a-select v-decorator="['scene', { initialValue: 0 }]">
<a-select-option :value="0">全部</a-select-option>
<a-select-option
v-for="(item, index) in SceneEnum.data"
:key="index"
:value="item.value"
>{{ item.name }}</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="变动时间">
<a-range-picker format="YYYY-MM-DD" v-decorator="['betweenTime']" />
</a-form-item>
<a-form-item class="search-btn">
<a-button type="primary" icon="search" html-type="submit">搜索</a-button>
</a-form-item>
</a-form>
</a-row>
</div>
<s-table
ref="table"
rowKey="log_id"
:loading="isLoading"
:columns="columns"
:data="loadData"
:pageSize="15"
>
<!-- 代理商 -->
<span slot="merchant" slot-scope="text, item">
<p class="twoline-hide" style="width: 150px;">{{ item.name }}</p>
</span>
<!-- 余额变动场景 -->
<span slot="scene" slot-scope="text">
<a-tag>{{ SceneEnum[text].name }}</a-tag>
</span>
</s-table>
</a-card>
</template>
<script>
import * as Api from '@/api/merchant/balance'
import { STable } from '@/components/Table'
import SceneEnum from '@/common/enum/merchant/balance/log/Scene'
export default {
name: 'Index',
components: {
STable
},
data () {
return {
// 枚举类
SceneEnum,
// 当前表单元素
searchForm: this.$form.createForm(this),
// 查询参数
queryParam: {},
// 正在加载
isLoading: false,
// 表头
columns: [
{
title: 'ID',
dataIndex: 'log_id'
},
{
title: '代理商信息',
dataIndex: 'merchant',
scopedSlots: { customRender: 'merchant' }
},
{
title: '余额变动场景',
dataIndex: 'scene',
scopedSlots: { customRender: 'scene' }
},
{
title: '变动金额',
dataIndex: 'money'
},
{
title: '描述/说明',
width: '260px',
dataIndex: 'describe'
},
{
title: '管理员备注',
width: '260px',
dataIndex: 'remark'
},
{
title: '变动时间',
dataIndex: 'create_time'
}
],
// 加载数据方法 必须为 Promise 对象
loadData: param => {
return Api.log({ ...param, ...this.queryParam })
.then(response => {
return response.data.list
})
}
}
},
created () {
},
methods: {
/**
* 刷新列表
* @param Boolean bool 强制刷新到第一页
*/
handleRefresh (bool = false) {
this.$refs.table.refresh(bool)
},
// 确认搜索
handleSearch (e) {
e.preventDefault()
this.searchForm.validateFields((error, values) => {
if (!error) {
this.queryParam = { ...this.queryParam, ...values }
this.handleRefresh(true)
}
})
}
}
}
</script>
<style lang="less" scoped>
.ant-card-body {
padding: 22px 29px 25px;
}
</style>
@@ -0,0 +1,106 @@
<template>
<a-modal
class="noborder"
:title="title"
:width="900"
:visible="visible"
:confirmLoading="confirmLoading"
:maskClosable="false"
@cancel="handleCancel"
>
<a-spin :spinning="confirmLoading">
<a-form :form="form">
<a-tabs :activeKey="activeKey" @change="onChangeTabs">
<a-tab-pane :key="RECHARGE_TYPE_BALANCE" tab="商户对接参数">
<template v-if="activeKey === RECHARGE_TYPE_BALANCE">
<a-form-item class="mb-5" label="商户编号" :labelCol="labelCol" :wrapperCol="wrapperCol">
<span>{{ record.orgNo }}</span>
</a-form-item>
<a-form-item class="mb-5" label="商户appKey" :labelCol="labelCol" :wrapperCol="wrapperCol">
<span>{{ record.appKey }}</span>
</a-form-item>
<a-form-item class="mb-5" label="商户私钥" :labelCol="labelCol" :wrapperCol="wrapperCol">
<span>{{ record.privateKey }}</span>
</a-form-item>
<a-form-item class="mb-5" label="商户公钥" :labelCol="labelCol" :wrapperCol="wrapperCol">
<span>{{ record.publicKey }}</span>
</a-form-item>
<a-form-item class="mb-5" label="回调地址" :labelCol="labelCol" :wrapperCol="wrapperCol">
<span>{{ record.callback }}</span>
</a-form-item>
</template>
</a-tab-pane>
</a-tabs>
</a-form>
</a-spin>
</a-modal>
</template>
<script>
// import pick from 'lodash.pick'
// 充值类型枚举: 余额
const RECHARGE_TYPE_BALANCE = 'balance'
export default {
components: {
},
data () {
return {
// 对话框标题
title: '商户对接参数',
// 标签布局属性
labelCol: { span: 7 },
// 输入框布局属性
wrapperCol: { span: 13 },
// modal(对话框)是否可见
visible: false,
// modal(对话框)确定按钮 loading
confirmLoading: false,
// 当前表单元素
form: this.$form.createForm(this),
// 当前tab索引
activeKey: RECHARGE_TYPE_BALANCE,
// 充值类型
RECHARGE_TYPE_BALANCE,
// 当前记录
record: {}
}
},
methods: {
// 显示对话框
handle (record) {
// 显示窗口
this.visible = true
// 当前记录
this.record = record
},
// 切换tabs事件
onChangeTabs (activeKey) {
this.activeKey = activeKey
},
/**
* 关闭对话框事件
*/
handleCancel () {
this.visible = false
this.form.resetFields()
},
}
}
</script>
<style lang="less" scoped>
.ant-modal-body {
padding-top: 0 !important;
}
.ant-form-item {
margin-bottom: 15px;
}
.ant-tabs-nav .ant-tabs-tab {
padding: 10px 16px;
}
</style>
@@ -0,0 +1,85 @@
<template>
<a-modal
class="noborder"
:title="title"
:width="500"
:visible="visible"
:confirmLoading="confirmLoading"
:maskClosable="false"
@cancel="handleCancel"
>
<a-spin :spinning="confirmLoading">
<img v-if="qrcode" width="260" :src="qrcode" alt="推广码" />
</a-spin>
</a-modal>
</template>
<script>
// import pick from 'lodash.pick'
import * as MerchantApi from '@/api/merchant'
export default {
components: {
},
data () {
return {
// 对话框标题
title: '推广码',
// 标签布局属性
labelCol: { span: 7 },
// 输入框布局属性
wrapperCol: { span: 13 },
// modal(对话框)是否可见
visible: false,
// modal(对话框)确定按钮 loading
confirmLoading: false,
// 当前记录
record: {},
qrcode: {}
}
},
methods: {
// 显示对话框
handle (record) {
// 显示窗口
this.visible = true
// 当前记录
this.record = record
this.getMerchantQrcode(record.merchant_id)
},
// 获取商户详情
getMerchantQrcode (merchantId = null) {
if (!merchantId) return false
return new Promise((resolve, reject) => {
MerchantApi.qrCode({ merchantId })
.then(result => {
this.qrcode = result.data.data
resolve()
})
})
},
/**
* 关闭对话框事件
*/
handleCancel () {
this.visible = false
this.form.resetFields()
}
}
}
</script>
<style lang="less" scoped>
.ant-modal-body {
padding-top: 0 !important;
}
.ant-form-item {
margin-bottom: 15px;
}
.ant-tabs-nav .ant-tabs-tab {
padding: 10px 16px;
}
</style>
@@ -0,0 +1,190 @@
<template>
<a-modal
class="noborder"
:title="title"
:width="520"
:visible="visible"
:confirmLoading="confirmLoading"
:maskClosable="false"
@ok="handleSubmit"
@cancel="handleCancel"
>
<a-spin :spinning="confirmLoading">
<a-form :form="form">
<a-tabs :activeKey="activeKey" @change="onChangeTabs">
<a-tab-pane :key="RECHARGE_TYPE_BALANCE" tab="充值余额">
<template v-if="activeKey === RECHARGE_TYPE_BALANCE">
<a-form-item class="mb-5" label="商户ID" :labelCol="labelCol" :wrapperCol="wrapperCol">
<span>{{ record.merchant_id }}</span>
</a-form-item>
<a-form-item class="mb-5" label="当前余额" :labelCol="labelCol" :wrapperCol="wrapperCol">
<span class="c-p">{{ record.balance }}</span>
</a-form-item>
<a-form-item label="充值方式" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-radio-group
v-decorator="[`${RECHARGE_TYPE_BALANCE}.mode`, { initialValue: 'inc', rules: [{ required: true }] }]"
>
<a-radio value="inc">增加</a-radio>
<a-radio value="dec">减少</a-radio>
<a-radio value="final">最终金额</a-radio>
</a-radio-group>
</a-form-item>
<a-form-item label="变更金额" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-input-number
:min="0.01"
v-decorator="[`${RECHARGE_TYPE_BALANCE}.money`, { initialValue: '', rules: [{ required: true, message: '请输入变更的金额' }] }]"
/>
</a-form-item>
<a-form-item label="管理员备注" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-textarea
placeholder="请输入管理员备注"
:rows="3"
v-decorator="[`${RECHARGE_TYPE_BALANCE}.remark`, { rules: [{ required: true, message: '请输入管理员备注' }] }]"
/>
</a-form-item>
</template>
</a-tab-pane>
<!--<a-tab-pane :key="RECHARGE_TYPE_POINTS" tab="充值积分">
<template v-if="activeKey === RECHARGE_TYPE_POINTS">
<a-form-item class="mb-5" label="商户ID" :labelCol="labelCol" :wrapperCol="wrapperCol">
<span>{{ record.merchant_id }}</span>
</a-form-item>
<a-form-item class="mb-5" label="当前积分" :labelCol="labelCol" :wrapperCol="wrapperCol">
<span class="c-p">{{ record.points }}</span>
</a-form-item>
<a-form-item label="充值方式" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-radio-group
v-decorator="[`${RECHARGE_TYPE_POINTS}.mode`, { initialValue: 'inc', rules: [{ required: true }] }]"
>
<a-radio value="inc">增加</a-radio>
<a-radio value="dec">减少</a-radio>
<a-radio value="final">最终积分</a-radio>
</a-radio-group>
</a-form-item>
<a-form-item label="变更数量" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-input-number
:min="0.01"
v-decorator="[`${RECHARGE_TYPE_POINTS}.value`, { initialValue: '', rules: [{ required: true, message: '请输入变更的金数量' }] }]"
/>
</a-form-item>
<a-form-item label="管理员备注" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-textarea
placeholder="请输入管理员备注"
:rows="3"
v-decorator="[`${RECHARGE_TYPE_POINTS}.remark`, { rules: [{ required: true, message: '请输入管理员备注' }] }]"
/>
</a-form-item>
</template>
</a-tab-pane>-->
</a-tabs>
</a-form>
</a-spin>
</a-modal>
</template>
<script>
// import pick from 'lodash.pick'
import * as Api from '@/api/merchant'
// 充值类型枚举: 余额
const RECHARGE_TYPE_BALANCE = 'balance'
// 充值类型枚举: 积分
const RECHARGE_TYPE_POINTS = 'points'
export default {
components: {
},
data () {
return {
// 对话框标题
title: '商户充值',
// 标签布局属性
labelCol: { span: 7 },
// 输入框布局属性
wrapperCol: { span: 13 },
// modal(对话框)是否可见
visible: false,
// modal(对话框)确定按钮 loading
confirmLoading: false,
// 当前表单元素
form: this.$form.createForm(this),
// 当前tab索引
activeKey: RECHARGE_TYPE_BALANCE,
// 充值类型
RECHARGE_TYPE_BALANCE,
RECHARGE_TYPE_POINTS,
// 当前记录
record: {}
}
},
methods: {
// 显示对话框
handle (record) {
// 显示窗口
this.visible = true
// 当前记录
this.record = record
},
// 切换tabs事件
onChangeTabs (activeKey) {
this.activeKey = activeKey
},
/**
* 确认按钮
*/
handleSubmit (e) {
e.preventDefault()
// 表单验证
const { form: { validateFields } } = this
validateFields((errors, values) => {
// 提交到后端api
!errors && this.onFormSubmit(values)
})
},
/**
* 关闭对话框事件
*/
handleCancel () {
this.visible = false
this.form.resetFields()
},
/**
* 提交到后端api
*/
onFormSubmit (values) {
const { record, activeKey } = this
this.confirmLoading = true
Api.recharge({ merchantId: record.merchant_id, target: activeKey, form: values })
.then((result) => {
// 显示成功
this.$message.success(result.message, 1.5)
// 关闭对话框事件
this.handleCancel()
// 通知父端组件提交完成了
this.$emit('handleSubmit', values)
})
.finally(() => {
this.confirmLoading = false
})
}
}
}
</script>
<style lang="less" scoped>
.ant-modal-body {
padding-top: 0 !important;
}
.ant-form-item {
margin-bottom: 15px;
}
.ant-tabs-nav .ant-tabs-tab {
padding: 10px 16px;
}
</style>

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