提交 245c93c7 authored 作者: lidongxu's avatar lidongxu

feat(add-edit): 促销计划移动端_新增编辑删除

同上
上级 2f99bade
......@@ -130,7 +130,7 @@ export function batchUpdatePlanAPI(data) {
})
}
// mobile 端:查询计划详情(获取促销员打卡任务列表)
// 查询计划详情(获取促销员打卡任务列表)
export function getPlanDetailAPI(id) {
return request({
baseURL: VITE_APP_PROMOTION,
......
<template>
<div>
<van-popup v-model:show="innerShowPicker"
destroy-on-close
round
position="bottom"
@close="onPopupClose">
<van-picker
:columns="columns"
:cancel-button-text="searchShow ? '' : '取消'"
@confirm="onConfirm">
<template #title>
<!-- 搜索框 -->
<van-search v-if="searchShow"
v-model="searchText"
:placeholder="placeholder"
shape="round"
@update:model-value="onSearch" />
</template>
<template #empty>
<van-empty image="https://fastly.jsdelivr.net/npm/@vant/assets/custom-empty-image.png"
image-size="40"
description="暂无数据" />
</template>
</van-picker>
</van-popup>
</div>
</template>
<script setup>
const props = defineProps({
// 控制弹窗显示隐藏
modelValue: {
type: Boolean,
default: false
},
// 选择器列数据
columns: {
type: Array,
default: () => []
},
// 占位提示文字
placeholder: {
type: String,
default: '请输入搜索内容'
},
// 搜索是否显示
searchShow: {
type: Boolean,
default: true
}
});
// 定义向外触发的事件
const emit = defineEmits(['update:modelValue', 'confirm', 'search']);
// 内部显示状态
const innerShowPicker = ref(props.modelValue);
// 搜索文本
const searchText = ref('');
// 监听 props 变化更新内部状态
watch(() => props.modelValue, (newValue) => {
innerShowPicker.value = newValue;
});
// 弹窗关闭时触发
const onPopupClose = () => {
searchText.value = '';
emit('update:modelValue', false);
};
/**
* 选择器确认事件处理函数
* @param values - 选中的值
* @param indexes - 选中项的索引
*/
const onConfirm = (values) => {
emit('confirm', values);
emit('update:modelValue', false);
};
/**
* 搜索事件处理函数
* @param values - 搜索的值
*/
const onSearch = (values) => {
emit('search', values);
}
</script>
<style scoped>
.van-search {
flex: 1;
}
::v-deep(.van-picker__confirm) {
width: 70px;
}
</style>
\ No newline at end of file
......@@ -55,7 +55,11 @@ import XLToolTip from '@/components/XLToolTip'
import XlSelect from '@/components/XLSelect'
// 开窗查询组件
import OpenDialog from '@/components/OpenDialog'
// 只有在移动端引入 flexible.js
// 移动端组件
import PickerSearch from '@/components/Mobile/PickerSearch'
// 只有在移动端引入
if (isMobile()) {
(function () {
const docEl = document.documentElement;
......@@ -102,6 +106,8 @@ app.component('BackToUp', BackToUp)
app.component('XlToolTip', XLToolTip)
app.component('XlSelect', XlSelect)
app.component('OpenDialog', OpenDialog)
// 移动端组件
app.component('PickerSearch', PickerSearch)
// 全局插件
app.use(plugins)
......
<template>
<div>这里只是针对移动设备开放的页面</div>
</template>
<script setup>
</script>
<style scoped>
</style>
\ No newline at end of file
<template>
<div class="mobile-container">
<van-nav-bar left-text="返回"
left-arrow
@click-left="clickBack()"
fixed
placeholder>
</van-nav-bar>
<div>
<van-form @submit="onSubmit"
ref="myForm">
<van-cell-group inset>
<van-field v-model="form.storeName"
is-link
readonly
:disabled="!!planId"
name="storeName"
label="选择门店"
placeholder="点击选择门店"
required
:rules="[{ required: true, message: '请选择门店' }]"
@click="selStoreName" />
<van-field v-model="form.date"
is-link
readonly
:disabled="!!planId"
name="date"
label="活动日期"
placeholder="点击选择时间"
required
:rules="[{ required: true, message: '请选择日期' }]"
@click="selDate" />
<van-field v-model="form.pattern"
is-link
readonly
name="pattern"
label="活动计划"
placeholder="请选择计划名称"
required
:rules="[{ required: true, message: '请选择计划' }]"
@click="selPattern" />
<van-field v-model="form.employeeName"
is-link
readonly
name="employeeName"
label="归属人"
placeholder="请选择归属人"
required
:rules="[{ required: true, message: '请选择归属人' }]"
@click="selEmployee" />
<van-field v-model="form.inTime"
is-link
readonly
name="inTime"
label="上班打卡时间"
placeholder="请选择时间"
required
:rules="[{ required: true, message: '请选择时间' }]"
@click="selInTime" />
<van-field v-model="form.outTime"
is-link
readonly
:disabled="!form.inTime"
name="outTime"
label="下班打卡时间"
placeholder="请选择时间"
required
:rules="[{ required: true, message: '请选择时间' }]"
@click="selOutTime" />
<van-field v-model="form.salary"
readonly
name="salary"
label="工资"
placeholder="请输入工资"
required
:rules="[{ validator: validatorSalary, message: '请输入工资' }]"
:error-message="errorSalaryMessage">
<template #input>
<van-stepper v-model="form.salary"
integer
:min="0"
:default-value="0" />
</template>
</van-field>
<van-field v-model="form.incidentals"
readonly
name="incidentals"
label="杂费"
placeholder="请输入工资"
required
:rules="[{ validator: validatorIncidentals, message: '请输入工资' }]"
:error-message="erroIncidentalMessage">
<template #input>
<van-stepper v-model="form.incidentals"
integer
:min="0"
:default-value="0" />
</template>
</van-field>
</van-cell-group>
<div style="margin: 16px;">
<van-button round
block
type="primary"
native-type="submit">
{{ planId ? '修改' : '创建' }}计划
</van-button>
<van-button round
block
style="margin-top: 10px;"
@click="reset">
<van-icon name="replay" /> 重置表单
</van-button>
</div>
</van-form>
</div>
<PickerSearch v-model="showPicker"
:columns="columns"
:searchShow="showPickerSearch"
@search="search"
@confirm="confirm"></PickerSearch>
<van-popup v-model:show="showDatePicker"
destroy-on-close
position="bottom">
<van-date-picker v-model="selDateArr"
@confirm="onConfirmDate"
:min-date="minDate"
:max-date="maxDate" />
</van-popup>
<van-popup v-model:show="showInTimePicker"
destroy-on-close
position="bottom">
<van-time-picker v-model="inTime"
:formatter="formatterTime"
@confirm="onConfirmInTime"
title="选择时间" />
</van-popup>
<van-popup v-model:show="showOutTimePicker"
destroy-on-close
position="bottom">
<van-time-picker v-model="outTime"
:min-time="minTime"
:formatter="formatterTime"
@confirm="onConfirmOutTime"
title="选择时间" />
</van-popup>
</div>
</template>
<script setup>
import { getPlanStoreListAPI, getChargeListAPI, addPlanByWebAPI, getPlanDetailAPI, updatePlanByWebAPI } from '@/api'
import userStore from '@/store/modules/user'
import { parseTime } from '@/utils'
const myForm = ref({})
const { proxy } = getCurrentInstance();
const isCityManager = ref(userStore().getPromotionIdentity)
const form = reactive({})
const router = useRouter();
const route = useRoute()
const clickBack = () => {
router.back()
}
const planId = route.params.planId
const init = async () => {
const res = await getPlanDetailAPI(planId)
Object.keys(res.data.planInfo).forEach(o => {
form[o] = res.data.planInfo[o]
})
form.date = parseTime(new Date(form.date), "{y}-{m}-{d}")
form.inTime = parseTime(res.data.planInfo.clockInTime, "{h}:{i}")
form.outTime = parseTime(res.data.planInfo.clockOutTime, "{h}:{i}")
minTime.value = form.inTime + ":00"
}
if (planId) {
init()
}
const errorSalaryMessage = ref('')
const erroIncidentalMessage = ref('')
const validatorSalary = (value, obj) => {
if (value <= 0) {
errorSalaryMessage.value = obj.message
return false
} else {
errorSalaryMessage.value = ''
return true
}
}
const validatorIncidentals = (value, obj) => {
if (value <= 0) {
erroIncidentalMessage.value = obj.message
return false
} else {
erroIncidentalMessage.value = ''
return true
}
}
const onSubmit = async () => {
const data = userStore().getEmployeeInfo
for (const key in data) {
form[key] = data[key]
}
if (planId) {
const res = await updatePlanByWebAPI(form)
proxy.$modal.msgSuccess(res.msg)
} else {
const res = await addPlanByWebAPI(form)
proxy.$modal.msgSuccess(res.msg)
}
clickBack()
}
// 选择门店
const selStoreName = () => {
isType.value = '门店'
showPicker.value = true
showPickerSearch.value = true
searchPlaceholder.value = '搜索门店名称'
getStoreList()
}
// 获取门店列表
const getStoreList = async (storeName) => {
const res = await getPlanStoreListAPI({
storeName
})
columns.value = res.data.map(item => ({
text: item.storeName,
value: item.storeCode
}))
}
// 活动日期
const showDatePicker = ref(false)
const selDateArr = ref([])
const minDate = ref('')
const maxDate = ref('')
const selDate = () => {
showDatePicker.value = true
selDateArr.value = form.date ? form.date.split('-') : [new Date().getFullYear(), new Date().getMonth() + 1, new Date().getDate()]
minDate.value = new Date(new Date().getFullYear(), new Date().getMonth(), new Date().getDate())
// 最大值到下个月最后一天
maxDate.value = new Date(new Date().getFullYear(), new Date().getMonth() + 2, 0)
}
const onConfirmDate = (value) => {
showDatePicker.value = false
form.date = value?.selectedValues.join('-')
}
// 选择计划
const selPattern = () => {
isType.value = '计划'
showPicker.value = true
showPickerSearch.value = false
searchPlaceholder.value = '搜索计划名称'
columns.value = [
{ text: '单点 CP', value: '单点 CP' },
{ text: '常规 MINI 秀', value: '常规 MINI 秀' },
{ text: '校园活动', value: '校园活动' },
]
}
// 归属人
const allBelongList = ref([]) // 所有归属人列表
const selEmployee = () => {
isType.value = '归属人'
showPicker.value = true
showPickerSearch.value = true
columns.value = allBelongList.value
}
const getBelongList = async () => {
const res = await getChargeListAPI()
allBelongList.value = res.data.map(item => ({
text: item.name,
value: item.employeeNo
}))
columns.value = allBelongList.value
// 判断如果是城市经理,则设置默认归属人为自己
if (isCityManager.value) {
form.employeeName = userStore().getEmployeeName
form.employeeNo = userStore().getEmployeeNo
}
}
getBelongList()
// 上班打卡时间
const showInTimePicker = ref(false)
const inTime = ref([]) // 选择器里的时间
const selInTime = () => {
showInTimePicker.value = true
inTime.value = form.inTime ? form.inTime.split(":") : [new Date().getHours(), new Date().getMinutes()]
}
const onConfirmInTime = (value) => {
showInTimePicker.value = false
form.inTime = value.selectedValues.join(':')
minTime.value = value.selectedValues.join(':') + ":00"
form.outTime = ''
}
// 下班打卡时间
const showOutTimePicker = ref(false)
const outTime = ref([]) // 选择器里的时间
const minTime = ref([])
const selOutTime = () => {
showOutTimePicker.value = true
outTime.value = form.outTime ? form.outTime.split(":") : [new Date().getHours(), new Date().getMinutes()]
}
const onConfirmOutTime = (value) => {
showOutTimePicker.value = false
form.outTime = value.selectedValues.join(':')
}
const formatterTime = (type, option) => {
if (type === 'hour') {
option.text += ' 时';
}
if (type === 'minute') {
option.text += ' 分';
}
return option;
};
// 重置表单
const reset = () => {
myForm.value.resetValidation()
errorSalaryMessage.value = ''
erroIncidentalMessage.value = ''
form.storeName = ''
form.date = ''
form.pattern = ''
form.employeeName = ''
form.inTime = ''
form.outTime = ''
form.salary = 0
form.incidentals = 0
}
// Picker 选择器
const showPicker = ref(false) // 是否展示选择器
const showPickerSearch = ref(true) // 是否展示搜索
const searchPlaceholder = ref('') // 搜索框提示文字
const columns = ref([]) // 选择器列数据
const isType = ref('') // 0: 门店 1: 计划 2: 归属人
// 搜索
const search = (value) => {
if (isType.value === '门店') {
getStoreList(value)
} else if (isType.value === '归属人') {
// 模糊搜索
columns.value = allBelongList.value.filter(item => item.text.includes(value))
}
}
// 确认
const confirm = (value) => {
if (isType.value === '门店') {
const { selectedOptions } = value
form.storeName = selectedOptions[0]?.text
form.storeCode = selectedOptions[0]?.value
} else if (isType.value === '计划') {
const { selectedOptions } = value
form.pattern = selectedOptions[0]?.text
} else if (isType.value === '归属人') {
const { selectedOptions } = value
form.employeeName = selectedOptions[0]?.text
form.employeeNo = selectedOptions[0]?.value
}
}
</script>
<style scoped
lang="scss">
.mobile-container {
background: #f5f5f5;
min-height: 100vh;
padding-top: 20px;
.van-cell-group {
background: #f5f5f5;
}
--van-field-label-width: 100px !important;
}
</style>
\ No newline at end of file
<template>
<div class="mobile-container">
<van-nav-bar right-text="搜索"
left-text="新增计划"
@click-left="$router.push('/promotion_add')"
@click-right="showSearch = true"
placeholder
fixed />
......@@ -14,21 +16,32 @@
finished-text="没有更多了"
@load="onLoad">
<van-cell-group inset>
<van-cell v-for="item in planList"
:key="item.id"
:title="item.storeName"
:to="`/promotion_detail/${item.id}`">
<template #label>
<p class="employee">{{ item.employeeName }}</p>
<p v-if="item.planStatus === 0">未执行</p>
<p v-else
class="plan-go">执行</p>
<van-swipe-cell v-for="item in planList"
:key="item.id">
<van-cell :title="item.storeName"
:to="`/promotion_detail/${item.id}`">
<template #label>
<p class="employee">{{ item.employeeName }}</p>
<p v-if="item.planStatus === 0">未执行</p>
<p v-else
class="plan-go">执行</p>
</template>
<template #value>
<span>{{ item.pattern }}</span>
<p>{{ parseTime(item.date, '{y}-{m}-{d} (周{a})') }}</p>
</template>
</van-cell>
<template #right>
<van-button square
type="success"
text="编辑"
@click="editPlan(item)"/>
<van-button square
type="danger"
text="删除"
@click="deletePlan(item)" />
</template>
<template #value>
<span>{{ item.pattern }}</span>
<p>{{ parseTime(item.date, '{y}-{m}-{d} (周{a})') }}</p>
</template>
</van-cell>
</van-swipe-cell>
</van-cell-group>
</van-list>
</van-pull-refresh>
......@@ -152,8 +165,10 @@ defineOptions({
})
import { parseTime } from '@/utils'
import { useDatePickerOptions } from '@/hooks'
import { getChargeListAPI, getPlanListAPI } from '@/api'
import { getChargeListAPI, getPlanListAPI, deletePlanAPI } from '@/api'
import store from '@/store'
import useUserStore from '@/store/modules/user'
const { proxy } = getCurrentInstance();
const { recentPickerOptions: pickerOptions, thisYearDate } = useDatePickerOptions(0)
const router = useRouter()
......@@ -355,6 +370,25 @@ const resetFn = () => {
getPlanList()
}
// 编辑计划
const editPlan = (row) => {
router.push(`/promotion_add/${row.id}`)
}
// 删除计划
const deletePlan = (row) => {
proxy.$modal.confirm(`确认删除计划吗?`).then(async () => {
await deletePlanAPI({
planIds: [row.id],
employeeNo: useUserStore().getEmployeeNo
})
proxy.$modal.msgSuccess('删除成功')
// 找到 row 在数组里第几条删除
const index = planList.value.findIndex(item => item.id === row.id)
planList.value.splice(index, 1)
})
}
</script>
<style scoped
......@@ -421,4 +455,12 @@ const resetFn = () => {
.van-search {
width: 60%;
}
::v-deep(.van-swipe-cell__right) {
display: flex;
button {
height: 100%;
}
}
</style>
\ No newline at end of file
......@@ -95,22 +95,28 @@ export const constantMobileRoutes = [
},
{
path: '/promotion',
component: () => import('@/mobile_views/promotion/promotion'),
name: 'Mobile_promotion', // 和组件内的名字保持一致
component: () => import('@/mobile_views/promotion/plan/index'),
name: 'Mobile_promotion',
hidden: true,
meta: { keepAlive: true } // 标记该路由需要缓存
},
{
path: '/promotion_detail/:id',
component: () => import('@/mobile_views/promotion/detail'),
path: '/promotion_detail/:id', // 促销计划详情页
component: () => import('@/mobile_views/promotion/plan/detail'),
name: 'Detail',
hidden: true,
},
{
path: '/examine/:examineId',
component: () => import('@/mobile_views/examine'),
path: '/examine/:examineId', // 稽查任务页
component: () => import('@/mobile_views/promotion/examine'),
name: 'Examine',
hidden: true,
},
{
path: '/promotion_add/:planId?', // 新增促销计划页
component: () => import('@/mobile_views/promotion/plan/add-edit'),
name: 'AddAndEdit',
hidden: true,
}
]
......
......@@ -98,9 +98,21 @@ export default defineStore(
getPromotionIdentity(state) {
return getPromotionRole(state.userInfo.privilegeId) === CITY_MANAGER
},
// 获取员工工号,姓名,id
getEmployeeInfo(state) {
return {
operNo: state.userInfo.userName,
operName: state.userInfo.nickName,
operId: state.userInfo.userId
}
},
// 获取员工工号
getEmployeeNo(state) {
return state.userInfo.userName
},
// 获取员工姓名
getEmployeeName(state) {
return state.userInfo.nickName
}
}
})
......
......@@ -162,7 +162,7 @@ export function toggleClass(element, className) {
* @param {boolean} immediate
* @return {*}
*/
export function debounce(func, wait, immediate) {
export function debounce(func, wait = 500, immediate) {
let timeout, args, context, timestamp, result
const later = function () {
......@@ -189,6 +189,7 @@ export function debounce(func, wait, immediate) {
// 如果延时不存在,重新设定延时
if (!timeout) timeout = setTimeout(later, wait)
if (callNow) {
console.log(args, 'args')
result = func.apply(context, args)
context = args = null
}
......
......@@ -266,8 +266,8 @@
inline>
<el-row>
<el-col :span="12">
<!-- 门店列表 -->
<el-form-item label="门店列表"
<!-- 选择门店 -->
<el-form-item label="选择门店"
prop="storeCode">
<el-select v-model="addOrEditPlanForm.storeCode"
placeholder="请选择门店"
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论