feat: 广告管理

fix-0609-xwk
向文可 3 years ago
parent 4c7cfca1f6
commit 6ba64586e8

@ -26,3 +26,16 @@ export const update = (data) => {
data,
});
};
export const sort = (data) => {
return request({
url: '/mall/marketing/advertisement/updateSort',
method: 'put',
data,
});
};
export const remove = (id) => {
return request({
url: '/mall/marketing/advertisement/' + id,
method: 'delete',
});
};

@ -1,7 +1,7 @@
<template>
<div class="upload-box">
<div :class="sortable && 'upload-box__sortable'">
<ul v-if="sortable" ref="sortableRef" class="sortable">
<div :class="{ 'upload-box__sortable': props.sortable }">
<ul v-if="props.sortable" ref="sortableRef" class="sortable">
<li v-for="(item, idx) in imgList" :key="item.uid" class="sortable--item">
<img :src="item.url" />
<span class="sortable--item-hover">
@ -14,13 +14,13 @@
v-bind="props"
action="none"
:before-upload="handleBeforeUpload"
:class="{ max: imgList.length === props.limit, 'sortable--submit': sortable }"
:class="{ max: imgList.length === props.limit, 'sortable--submit': props.sortable }"
:file-list="imgList"
:http-request="handleUpload"
:list-type="!sortable ? 'picture-card' : 'picture'"
:list-type="!props.sortable ? 'picture-card' : 'picture'"
:on-exceed="handleExceed"
:on-preview="handlePreview"
:show-file-list="!sortable"
:show-file-list="!props.sortable"
>
<el-icon name="Plus" />
</el-upload>

@ -337,6 +337,7 @@
defineExpose({
search: handleSearch,
selection,
checked,
refTable,
clearSelection,
toggleRowSelection,

@ -1,4 +1,4 @@
import * as api from '@/api/permission/employee.js';
import * as api from '@/api/operation/advertise/index.js';
import * as categoryAPI from '@/api/sales/category.js';
import { ElMessage, ElMessageBox } from '@/plugins/element-plus';
const state = () => ({
@ -32,6 +32,24 @@ const state = () => ({
value: 2,
},
],
jumpType: [
{
label: '商品详情',
value: 1,
},
{
label: '分类商品列表',
value: 2,
},
{
label: '链接',
value: 3,
},
{
label: '不跳转',
value: 4,
},
],
status: [
{
label: '未启用',
@ -55,7 +73,15 @@ const mutations = {
};
const actions = {
search: async ({ state, commit, rootGetters }) => {
let res = await api.search({ ...rootGetters['local/page'](state.code), ...state.condition });
let data = {
...state.condition,
};
if (data.dateRange?.length) {
data.advertisementEndTime = data.dateRange[0];
data.advertisementEndTimeEnd = data.dateRange[1];
}
delete data.dateRange;
let res = await api.search({ ...rootGetters['local/page'](state.code), ...data });
if (res) {
commit('setList', res.records);
commit('setTotal', res.total);
@ -79,20 +105,22 @@ const actions = {
}
return res;
},
save: async ({ dispatch }, data) => {
save: async (context, data) => {
let save = data.id ? api.update : api.create;
data = {
...data,
};
if (data.dateRange.length) {
data.registeredStartTime = data.dateRange[0];
data.registeredEndTime = data.dateRange[1];
}
delete data.dateRange;
if (data.jumpType === 1) {
data.jumpUrl = data.jumpUrl.id;
}
let res = await save(data);
if (res) {
if (!data.id) {
data.id = res;
}
res = await api.savePermission(data.id, { roleIds: data.roleIds });
if (res) {
ElMessage.success('保存成功');
dispatch('search');
} else {
ElMessage.error('保存角色失败');
}
ElMessage.success('保存成功');
} else {
ElMessage.error('保存失败');
}

@ -4,7 +4,7 @@
ref="refsForm"
v-loading="loading"
class="form-content"
label-width="100px"
label-width="120px"
:model="form"
:rules="rules"
>
@ -12,78 +12,83 @@
<el-radio-group v-model="form.platform" :opts="opts.platform" />
</el-form-item>
<el-form-item label="广告位置" prop="location">
<el-select v-model="form.location" :opts="opts.location" />
<el-select
v-model="form.location"
:config="{ disabled: (item, index) => form.platform === 2 && index !== 0 }"
:opts="opts.location"
/>
</el-form-item>
<el-form-item label="商品分类" prop="categoryId">
<el-select v-model="form.categoryId" :opts="opts.category" />
<el-form-item v-if="form.location === 3" label="商品分类" prop="productCategoryId">
<el-cascader
v-model="form.productCategoryId"
:options="opts.category"
:props="{
checkStrictly: true,
expandTrigger: 'hover',
label: 'name',
value: 'id',
children: 'childList',
emitPath: false,
}"
/>
</el-form-item>
<el-form-item label="员工姓名" prop="employeeName">
<el-input v-model="form.employeeName" />
<el-form-item label="广告名称" prop="name">
<el-input v-model="form.name" />
</el-form-item>
<el-form-item label="手机号" prop="phone">
<el-input v-model="form.phone" />
<el-form-item label="起止时间" prop="dateRange">
<el-date-picker
v-model="form.dateRange"
:default-time="[new Date(0, 0, 0, 0, 0, 0), new Date(0, 0, 0, 23, 59, 59)]"
type="datetimerange"
value-format="YYYY-MM-DD HH:mm:ss"
/>
</el-form-item>
<el-form-item label="邮箱" prop="email">
<el-input v-model="form.email" />
<el-form-item label="广告图片" prop="pictureUrl">
<el-upload-image v-model="form.pictureUrl" config-id="advertise/" />
</el-form-item>
<el-form-item label="是否启用" prop="isEnable">
<el-switch v-model="form.isEnable" />
<el-form-item label="跳转类型" prop="jumpType">
<el-radio-group v-model="form.jumpType" :opts="opts.jumpType" />
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" type="textarea" />
<el-form-item v-if="form.jumpType === 1" label="商品详情" prop="jumpUrl">
<el-button @click="handlePick"></el-button>
<span style="margin-left: 10px">
{{ form.jumpUrl?.name || '' }}
</span>
<product-picker ref="refsPicker" single @pick="form.jumpUrl = $event" />
</el-form-item>
<el-form-item label="账号角色" prop="roleIds">
<div class="role-list">
<div v-for="(item, index) in opts.system" :key="index" class="system">
<h3 class="system-header">
{{ item.systemName }}
</h3>
<div class="flex">
<el-tag
v-for="(role, j) in handleSystemRole(item.id)"
:key="index + '' + j"
closable
size="large"
@close="handleDelRole(role.id)"
>
{{ role.roleName }}
</el-tag>
<el-button @click="handleAddRole(item.id)"></el-button>
</div>
</div>
</div>
<el-dialog v-model="roleVisible" title="添加角色">
<el-form v-loading="loading2">
<el-form-item>
<el-select
v-model="checkedRole"
:config="{
label: 'roleName',
value: 'id',
disabled: (item) =>
handleSystemRole(currentSystemId).findIndex((role) => role.id === item.id) !==
-1,
}"
multiple
:opts="systemRoleList"
/>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="roleVisible = false">取消</el-button>
<el-button :loading="loading2" type="primary" @click="handleSaveRole"></el-button>
</template>
</el-dialog>
<el-form-item v-if="form.jumpType === 2" label="分类商品列表" prop="jumpUrl">
<el-cascader
v-model="form.jumpUrl"
:options="opts.category"
:props="{
checkStrictly: true,
expandTrigger: 'hover',
label: 'name',
value: 'id',
children: 'childList',
emitPath: false,
}"
/>
</el-form-item>
<el-form-item v-if="form.jumpType === 3" label="链接" prop="jumpUrl">
<el-input v-model="form.jumpUrl" />
</el-form-item>
</el-form>
<div class="form-footer">
<el-checkbox v-model="next" style="margin-right: 10px">广</el-checkbox>
<el-button @click="handleCancel"></el-button>
<el-button :disabled="loading" :loading="submitting" type="primary" @click="handleSave"></el-button>
<el-button :disabled="loading" :loading="loading" plain type="primary" @click="handleSave()">
保存
</el-button>
<el-button :disabled="loading" :loading="loading" type="primary" @click="handleSave(true)">
保存并发布
</el-button>
</div>
</div>
</template>
<script setup>
import ProductPicker from '@/views/sales/product/picker.vue';
/* 全局 */
const store = useStore();
const route = useRoute();
@ -91,69 +96,108 @@
const { proxy } = getCurrentInstance();
/* 表单 */
const loading = ref(false);
const submitting = ref(false);
const refsForm = ref(null);
const form = reactive({
id: null,
departmentId: null,
email: '',
employeeName: '',
employeeType: 1,
platform: 1,
location: 1,
productCategoryId: null,
name: null,
dateRange: [],
pictureUrl: null,
jumpType: 1,
jumpUrl: null,
isEnable: false,
phone: '',
remark: '',
userName: '',
roleIds: [],
});
const permissionList = reactive([]);
const handleSystemRole = (systemId) => permissionList.filter((item) => item.systemId === systemId);
const rules = reactive({
departmentId: [{ required: unref(computed(() => !form.id)), message: '所在组织不能为空' }],
userName: [{ required: true, message: '用户名不能为空' }],
phone: [{ required: true, message: '手机号码不能为空' }],
email: [{ required: true, message: '邮箱地址不能为空' }],
employeeName: [{ required: true, message: '员工姓名不能为空' }],
roleIds: [{ required: true, message: '账号角色不能为空' }],
platform: [{ required: true, message: '广告平台不能为空' }],
location: [{ required: true, message: '广告位置不能为空' }],
productCategoryId: [{ required: true, message: '商品分类不能为空' }],
name: [{ required: true, message: '广告名称不能为空' }],
pictureUrl: [{ required: true, message: '广告图片不能为空' }],
jumpType: [{ required: true, message: '跳转类型不能为空' }],
jumpUrl: [
{
required: true,
validator(rule, value, cb) {
let type = ['', '商品详情', '分类商品列表', '链接'][form.jumpType];
if (type && !value) {
cb(`${type}不能为空`);
} else {
cb();
}
},
},
],
});
const opts = computed(() => store.state.employee.opts);
const opts = computed(() => store.state.advertise.opts);
if (!unref(opts).init) {
store.dispatch('employee/load');
store.dispatch('advertise/load');
}
watch(
() => form.platform,
(value) => {
if (value === 2) {
form.location = 1;
}
},
{ immediate: true }
);
watch(
() => form.jumpType,
() => {
form.jumpUrl = null;
},
{ immediate: true }
);
/* 数据 */
const handleLoad = async () => {
if (route.params.id) {
loading.value = true;
const id = +route.params.id;
if (form.id !== id) {
let res = await store.dispatch('employee/detail', id);
let res = await store.dispatch('advertise/detail', id);
res.dateRange = [res.advertisementStartTime, res.advertisementEndTime];
Object.assign(form, res);
}
permissionList.splice(0);
permissionList.push(...(await store.dispatch('employee/permission', id)));
form.roleIds = permissionList.map((item) => item.id);
loading.value = false;
}
};
onActivated(handleLoad);
/* 交互 */
const handleSave = async () => {
submitting.value = true;
const refsPicker = ref(null);
const handlePick = () => {
refsPicker.value.show();
};
const next = ref(false);
const handleSave = async (isEnable = false) => {
loading.value = true;
try {
await proxy.$validate(refsForm);
let data = { ...unref(form) };
data.voucherClueUrls = data.voucherClueUrls || [];
data.voucherClueUrl = data.voucherClueUrls.join(',');
let res = await store.dispatch('employee/save', data);
data.isEnable = isEnable;
let res = await store.dispatch('advertise/save', data);
if (res) {
if (!data.id) {
unref(refsForm).resetFields();
if (unref(next)) {
Object.assign(form, {
id: null,
name: null,
dateRange: [],
pictureUrl: null,
jumpType: 1,
jumpUrl: null,
isEnable: false,
});
} else {
store.commit('layout/closeTab', { current: true });
handleClose();
}
handleClose();
}
} catch (e) {
console.info('取消保存', e);
}
submitting.value = false;
loading.value = false;
};
const handleCancel = async () => {
try {
@ -164,32 +208,7 @@
}
};
const handleClose = () => {
router.push({ name: 'EmployeeManagement' });
};
const loading2 = ref(false);
const roleVisible = ref(false);
const currentSystemId = ref(null);
const checkedRole = ref([]);
const systemRoleList = ref([]);
const handleAddRole = async (id) => {
loading2.value = true;
currentSystemId.value = id;
checkedRole.value = [];
roleVisible.value = true;
systemRoleList.value = await store.dispatch('deptRole/role', id);
loading2.value = false;
};
const handleDelRole = (id) => {
form.roleIds.splice(form.roleIds.findIndex(id), 1);
permissionList.splice(
permissionList.findIndex((item) => item.id === id),
1
);
};
const handleSaveRole = async () => {
form.roleIds.push(...unref(checkedRole));
permissionList.push(...unref(systemRoleList).filter((item) => unref(checkedRole).indexOf(item.id) !== -1));
roleVisible.value = false;
router.push({ name: 'AdvertiseManagement' });
};
</script>

@ -101,7 +101,7 @@
};
const handleEnable = async (row) => {
loading.value = true;
await store.dispatch('advertise/enable', { id: row.id, isEnable: !row.isEnable });
await store.dispatch('advertise/enable', row);
loading.value = false;
};
const config = reactive({
@ -166,7 +166,7 @@
{
label: '操作',
fixed: 'right',
width: 180,
width: 120,
slots: {
default: ({ row }) => (
<div>

@ -6,6 +6,7 @@
:code="code"
:config="config"
:data="list"
:highlight-current-row="props.single"
:operation="['search']"
:reset="handleReset"
:total="total"
@ -47,6 +48,10 @@
type: Array,
default: () => [],
},
single: {
type: Boolean,
default: false,
},
});
const emits = defineEmits(['pick']);
const store = useStore();
@ -100,7 +105,7 @@
};
//
const handlePick = async () => {
emits('pick', [...unref(refsTable).selection]);
emits('pick', props.single ? unref(refsTable).checked : [...unref(refsTable).selection]);
handleClose();
};
defineExpose({
@ -111,11 +116,6 @@
/* 列表配置 */
const config = reactive({
columns: [
{
type: 'selection',
width: 60,
selectable: (row) => !props.ids.includes(row.id),
},
{
label: '编号',
prop: 'id',
@ -140,6 +140,13 @@
},
],
});
if (!props.single) {
config.columns.unshift({
type: 'selection',
width: 60,
selectable: (row) => !props.ids.includes(row.id),
});
}
</script>
<style lang="less" scoped>

Loading…
Cancel
Save