增加门店管理,会员管理,订单管理模块

master
wyy 3 years ago
parent 552513a534
commit 96310a0deb

@ -0,0 +1,88 @@
<template>
<div>
<el-upload
class="pic-uploader-component"
:action="http.adornUrl('/admin/file/upload/element')"
accept=".png,.jpg,.jpeg,.gif"
:headers="{Authorization: $cookie.get('Authorization')}"
:show-file-list="false"
:on-success="handleUploadSuccess"
:before-upload="beforeAvatarUpload"
>
<img
v-if="value"
alt=""
:src="resourcesUrl + value"
class="pic"
>
<i
v-else
class="el-icon-plus pic-uploader-icon"
/>
</el-upload>
</div>
</template>
<script setup>
import $cookie from 'vue-cookies'
import { ElMessage } from 'element-plus'
const emit = defineEmits(['update:modelValue'])
// eslint-disable-next-line no-unused-vars
const props = defineProps({
value: {
default: '',
type: String
}
})
const resourcesUrl = import.meta.env.VITE_APP_RESOURCES_URL
/**
* 图片上传
*/
// eslint-disable-next-line no-unused-vars
const handleUploadSuccess = (response, file) => {
emit('update:modelValue', file.response.data)
}
/**
* 限制图片上传大小
*/
const beforeAvatarUpload = (file) => {
const isLt2M = file.size / 1024 / 1024 < 2
const isJPG = file.type === 'image/jpeg' || file.type === 'image/png' || file.type === 'image/gif' || file.type === 'image/jpg'
if (!isJPG) {
ElMessage.error('上传图片只能是jpeg/jpg/png/gif 格式!')
}
if (!isLt2M) {
ElMessage.error('上传图片大小不能超过 2MB!')
}
return isLt2M && isJPG
}
</script>
<style lang="scss" scoped>
.pic-uploader-component :deep(.el-upload) {
border: 1px dashed #d9d9d9;
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
.pic-uploader-icon {
font-size: 28px;
color: #8c939d;
width: 178px;
height: 178px;
line-height: 178px;
text-align: center;
}
.pic {
width: 178px;
height: 178px;
display: block;
}
}
.pic-uploader-component :deep(.el-upload:hover) {
border-color: #409EFF;
}
</style>

@ -0,0 +1,226 @@
<template>
<el-dialog
v-model="visible"
title="选择商品"
:modal="false"
:close-on-click-modal="false"
>
<el-table
ref="prodTableRef"
v-loading="dataListLoading"
:data="dataList"
border
style="width: 100%;"
@selection-change="selectChangeHandle"
>
<el-table-column
v-if="isSingle"
width="50"
header-align="center"
align="center"
>
<template #default="scope">
<div>
<el-radio
v-model="singleSelectProdId"
:label="scope.row.prodId"
@change="getSelectProdRow(scope.row)"
>
&nbsp;
</el-radio>
</div>
</template>
</el-table-column>
<el-table-column
v-if="!isSingle"
type="selection"
header-align="center"
align="center"
width="50"
/>
<el-table-column
prop="prodName"
header-align="center"
align="center"
label="产品名称"
/>
<el-table-column
align="center"
width="140"
label="产品图片"
>
<template #default="scope">
<img
alt=""
:src="scope.row.pic"
width="100"
height="100"
>
</template>
</el-table-column>
</el-table>
<el-pagination
:current-page="pageIndex"
:page-sizes="[10, 20, 50, 100]"
:page-size="pageSize"
:total="totalPage"
layout="total, sizes, prev, pager, next, jumper"
@size-change="sizeChangeHandle"
@current-change="currentChangeHandle"
/>
<template #footer>
<span>
<el-button @click="visible = false">取消</el-button>
<el-button
type="primary"
@click="submitProds()"
>确定</el-button>
</span>
</template>
</el-dialog>
</template>
<script setup>
import { ElMessage } from 'element-plus'
const emit = defineEmits(['refreshSelectProds'])
// eslint-disable-next-line no-unused-vars
const props = defineProps({
isSingle: {
default: false,
type: Boolean
}
})
const visible = ref(false)
const dataForm = reactive({
product: ''
})
const singleSelectProdId = ref(0)
const selectProds = ref([])
const dataList = ref([])
const pageIndex = ref(1)
const pageSize = ref(10)
const totalPage = ref(0)
const dataListLoading = ref(false)
const dataListSelections = ref([])
onMounted(() => {
getDataList()
})
/**
* 获取数据列表
*/
const init = (selectProdParam) => {
selectProds.value = selectProdParam
visible.value = true
dataListLoading.value = true
if (selectProds.value) {
selectProds.value?.forEach(row => {
dataListSelections.value.push(row)
})
}
getDataList()
}
defineExpose({ init })
const prodTableRef = ref(null)
const getDataList = () => {
http({
url: http.adornUrl('/prod/prod/page'),
method: 'get',
params: http.adornParams(
Object.assign(
{
current: pageIndex.value,
size: pageSize.value
},
{
prodName: dataForm.prodName
}
)
)
})
.then(({ data }) => {
dataList.value = data.records
totalPage.value = data.total
dataListLoading.value = false
if (selectProds.value) {
nextTick(() => {
selectProds.value?.forEach(row => {
const index = dataList.value?.findIndex((prodItem) => prodItem.prodId === row.prodId)
prodTableRef.value?.toggleRowSelection(dataList.value[index])
})
})
}
})
}
/**
* 每页数
* @param val
*/
const sizeChangeHandle = (val) => {
pageSize.value = val
pageIndex.value = 1
getDataList()
}
/**
* 当前页
* @param val
*/
const currentChangeHandle = (val) => {
pageIndex.value = val
getDataList()
}
/**
* 单选商品事件
* @param row
*/
const getSelectProdRow = (row) => {
dataListSelections.value = [row]
}
/**
* 多选点击事件
* @param selection
*/
const selectChangeHandle = (selection) => {
dataList.value?.forEach((tableItem) => {
const selectedProdIndex = selection.findIndex((selectedProd) => {
if (!selectedProd) {
return false
}
return selectedProd.prodId === tableItem.prodId
})
const dataSelectedProdIndex = dataListSelections.value?.findIndex((dataSelectedProd) => dataSelectedProd.prodId === tableItem.prodId)
if (selectedProdIndex > -1 && dataSelectedProdIndex === -1) {
dataListSelections.value.push(tableItem)
} else if (selectedProdIndex === -1 && dataSelectedProdIndex > -1) {
dataListSelections.value.splice(dataSelectedProdIndex, 1)
}
})
}
/**
* 确定事件
*/
const submitProds = () => {
if (!dataListSelections.value.length) {
ElMessage({
message: '请选择商品',
type: 'error',
duration: 1000,
onClose: () => {}
})
return
}
const prods = []
dataListSelections.value.forEach(item => {
const prodIndex = prods.findIndex((prod) => prod.prodId === item.prodId)
if (prodIndex === -1) {
prods.push({ prodId: item.prodId, prodName: item.prodName, pic: item.pic })
}
})
emit('refreshSelectProds', prods)
dataListSelections.value = []
visible.value = false
}
</script>

@ -0,0 +1,81 @@
<template>
<div class="components-add-or-upload">
<el-tooltip
v-if="props.tinymceUploadType === 'prod'"
content="内容"
placement="top"
>
<el-button
type="primary"
@click="clickUpload()"
>
<el-icon><UploadFilled /></el-icon>
上传图片
</el-button>
</el-tooltip>
<el-button
v-else
:style="{ background: props.color, borderColor: props.color }"
type="primary"
@click="clickUpload()"
>
<el-icon><UploadFilled /></el-icon>
上传图片
</el-button>
<!-- 弹窗, 新增图片 -->
<!-- <elx-imgbox-->
<!-- v-if="elxImgboxVisible"-->
<!-- ref="elxImgboxRef"-->
<!-- @refresh-pic="refreshPic"-->
<!-- />-->
</div>
</template>
<script setup>
// const emit = defineEmits(['successCBK'])
const props = defineProps({
color: {
type: String,
default: '#155bd4'
},
tinymceUploadType: {
default: '',
type: String
}
})
const elxImgboxVisible = ref(false)
// const maxNum = ref(15) // 可选择的最大图片数量
const imgUrls = ref([])
// const resourcesUrl = import.meta.env.VITE_APP_RESOURCES_URL
// const elxImgboxRef = ref()
// const dialogVisible = ref()
/**
* 打开图片选择窗
*/
const clickUpload = () => {
imgUrls.value = ''
elxImgboxVisible.value = true
// nextTick(() => {
// elxImgboxRef.value?.init(0, maxNum.value)
// })
}
/**
* 接收回调的图片数据
*/
// const refreshPic = imagePath => {
// const imageArray = imagePath.split(',')
// const data = []
// imageArray.forEach(img => {
// data.push(resourcesUrl + img)
// })
// imgUrls.value = ''
// dialogVisible.value = false
// emit('successCBK', data)
// }
</script>
<style lang="scss" scoped>
.components-add-or-upload{
margin-bottom: 20px;
}
</style>

@ -0,0 +1,163 @@
<template>
<div class="components-tiny-mce">
<div
class="tinymce-container"
:class="{ 'tox-fullscreen': toxFullscreen }"
>
<Editor
:id="props.id"
:init="init"
api-key="i6mv006qcwsfu1t7ebisntg5w261vpowkwirnx9cnse3ho5o"
/>
<div
class="add-or-upload"
>
<add-or-upload
v-show="isLoaded"
@success-c-b-k="imageSuccessCBK"
/>
</div>
</div>
</div>
</template>
<script setup>
import Editor from '@tinymce/tinymce-vue'
import AddOrUpload from './add-or-upload.vue'
const props = defineProps({
modelValue: {
type: String,
default: ''
},
id: {
type: String,
default: function () {
return 'vue-tinymce-' + +new Date() + ((Math.random() * 1000).toFixed(0) + '')
}
},
height: {
type: [Number, String],
required: false,
default: 360
},
width: {
type: [Number, String],
required: false,
default: 'auto'
}
})
const toxFullscreen = ref(false)
const hasChange = ref(false)
const hasInit = ref(false)
watch(
() => props.modelValue,
val => {
if (!hasChange.value && hasInit.value) {
nextTick(() => window.tinymce.get(props.id).setContent(val || ''))
}
}
)
const language = computed(() => {
return localStorage.getItem('b2cLang') || 'zh_CN'
})
watch(language, () => {
destroyTinymce()
})
const emit = defineEmits(['update:modelValue'])
const isLoaded = ref(false)
const init = reactive({
plugins: 'preview anchor autolink codesample emoticons image link lists media searchreplace table visualblocks pagebreak insertdatetime fullscreen', // 插件 preview anchor autolink codesample emoticons image link lists media searchreplace table visualblocks wordcount pagebreak insertdatetime fullscreen
content_css: 'default', // 主题tinymce-5-dark || tinymce-5 || default || writer || document || dark
custom_undo_redo_levels: 50, // 回退数量
end_container_on_empty_block: true, // 块级文本是否换行
keep_styles: false, // 回车是否保存原有样式,例如code块回车插件初始化失败: tinycomments是否截断
menubar: true, // 是否开启顶部菜单 > false 关闭菜单 | 'edit insert view format table tools help' 菜单按照这里排序 | 参考:https://www.tiny.cloud/docs/tinymce/6/menus-configuration-options/
toolbar_mode: 'wrap', // 功能栏是否换行 > | wrap 换行 | scrolling 滚动 | sliding 省略
toolbar: ' searchreplace bold italic underline strikethrough alignleft aligncenter alignright outdent indent blockquote undo redo removeformat subscript superscript code codesample hr numlist link image preview anchor pagebreak insertdatetime media table emoticons forecolor backcolor fullscreen',
powerpaste_word_import: 'clean',
toolbar_location: 'top', // 菜单栏位置 > bottom 底部 | top 顶部
style_formats_merge: true, // 是否开启默认功能
elementpath: false, // 是否展示编辑层级 > p span
advlist_bullet_styles: 'square',
resize: true, // 调整宽高 > true 调整高 | false 不可调整宽高 | both 宽高可调
language, // 中文
init_instance_callback: editor => {
isLoaded.value = true
if (props.modelValue) {
editor.setContent(props.modelValue)
}
hasInit.value = true
editor.on('NodeChange Change KeyUp SetContent', () => {
hasChange.value = true
emit('update:modelValue', editor.getContent())
})
},
setup (editor) {
const addOrupload = document.querySelectorAll('.components-tiny-mce .tinymce-container .add-or-upload')
addOrupload.forEach(v => {
v.style.zIndex = 10
})
editor.on('FullscreenStateChanged', e => {
toxFullscreen.value = e.state
handleFullScreenStateChange(e.state)
})
}
})
const handleFullScreenStateChange = (isFullscreen) => {
const addOrupload = document.querySelectorAll('.components-tiny-mce .tinymce-container .add-or-upload')
if (isFullscreen) {
// 进入全屏模式时执行的代码
addOrupload.forEach(v => {
v.style.zIndex = 0
})
} else {
// 退出全屏模式时执行的代码
addOrupload.forEach(v => {
v.style.zIndex = 10
})
}
}
const destroyTinymce = () => {
const tinymce = window.tinymce?.get(props.id)
if (toxFullscreen.value) {
tinymce.execCommand('mceFullScreen')
}
onUnmounted(() => {
destroyTinymce()
})
if (tinymce) {
tinymce.destroy()
}
}
const imageSuccessCBK = (arr) => {
arr.forEach(v => {
window.tinymce.get(props.id).insertContent(`<img alt="" class="wscnph" src="${v}" >`)
})
}
</script>
<!--eslint-disable-next-line vue-scoped-css/enforce-style-type -->
<style lang="scss">
.components-tiny-mce {
.tox-fullscreen .add-or-upload {
z-index: 9999 !important;
position:fixed !important;
right: 0;
top: 0;
}
.tinymce-container {
position: relative;
.add-or-upload {
position: absolute;
top: 10px;
right: 10px;
}
}
}
.tox-tinymce-aux {
z-index: 9999 !important;
}
</style>

@ -0,0 +1,253 @@
<template>
<div class="mod-index-img">
<el-dialog
v-model="visible"
:title="!dataForm.imgId ? '新增' : '修改'"
:close-on-click-modal="false"
>
<el-form
ref="dataFormRef"
:model="dataForm"
:rules="dataRule"
label-width="100px"
>
<el-form-item
label="轮播图片"
prop="imgUrl"
>
<pic-upload v-model="dataForm.imgUrl" />
</el-form-item>
<el-form-item
label="顺序"
prop="seq"
:rules="[
{ required: false, pattern: /\s\S+|S+\s|\S/, message: '请输入正确的顺序', trigger: 'blur' }
]"
>
<el-input v-model="dataForm.seq" />
</el-form-item>
<el-form-item
label="状态"
prop="status"
>
<el-radio-group v-model="dataForm.status">
<el-radio :label="0">
禁用
</el-radio>
<el-radio :label="1">
正常
</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="类型">
<el-radio-group
v-model="dataForm.type"
@change="deleteRelation"
>
<el-radio :label="-1">
无
</el-radio>
<el-radio :label="0">
商品
</el-radio>
</el-radio-group>
<div v-if="dataForm.relation!=null">
<el-card
:body-style="{ padding: '0px' }"
style="height: 160px;width: 120px"
>
<img
alt=""
:src="card.pic"
style="height:104px;width:100%"
>
<div class="card-prod-bottom">
<span class="card-prod-name">{{ card.name }}</span>
<el-button
type="text"
class="card-prod-name-button"
@click="deleteRelation"
>
删除
</el-button>
</div>
</el-card>
</div>
<div v-if="dataForm.relation==null">
<el-button
v-if=" dataForm.type == 0"
@click="addProd"
>
选择商品
</el-button>
</div>
</el-form-item>
<el-form-item>
<el-button
type="primary"
@click="onSubmit()"
>
确定
</el-button>
</el-form-item>
</el-form>
</el-dialog>
<!-- 商品选择弹窗-->
<prods-select
v-if="prodsSelectVisible"
ref="prodsSelectRef"
:is-single="true"
@refresh-select-prods="selectCouponProds"
/>
</div>
</template>
<script setup>
import { ElMessage } from 'element-plus'
import { Debounce } from '@/utils/debounce'
const emit = defineEmits(['refreshDataList'])
const dataForm = ref({
status: 1,
des: '',
imgUrl: '',
seq: 0,
imgId: 0,
type: -1,
relation: null
})
const dataRule = reactive({
imgUrl: [
{ required: true, message: '轮播图片不能为空', trigger: 'blur' }
]
})
// 关联数据
const card = ref({
id: 0,
pic: '',
name: '',
realData: {
prod: [],
shop: [],
activity: []
}
})
const page = reactive({
total: 0, // 总页数
currentPage: 1, // 当前页数
pageSize: 10 // 每页显示多少条
})
const prodsSelectVisible = ref(false)
const visible = ref(false)
const dataFormRef = ref(null)
/**
* 获取分类数据
* @param id
*/
const init = (id) => {
visible.value = true
dataForm.value.imgId = id || 0
if (dataForm.value.imgId) {
// 获取产品数据
http({
url: http.adornUrl(`/admin/indexImg/info/${dataForm.value.imgId}`),
method: 'get'
})
.then(({ data }) => {
dataForm.value = data
if (data.relation) {
card.value.pic = data.pic
card.value.name = data.prodName
card.value.id = data.relation
}
})
} else {
nextTick(() => {
dataFormRef.value?.resetFields()
dataForm.value.imgUrl = ''
})
}
}
defineExpose({ init })
/**
* 表单提交
*/
const onSubmit = Debounce(() => {
dataFormRef.value?.validate((valid) => {
if (!valid) {
return
}
const param = dataForm.value
http({
url: http.adornUrl('/admin/indexImg'),
method: param.imgId ? 'put' : 'post',
data: http.adornData(param)
})
.then(() => {
ElMessage({
message: '操作成功',
type: 'success',
duration: 1500,
onClose: () => {
visible.value = false
emit('refreshDataList', page)
}
})
})
})
})
/**
* 删除关联数据
*/
const deleteRelation = () => {
dataForm.value.relation = null
}
const prodsSelectRef = ref(null)
/**
* 打开选择商品
*/
const addProd = () => {
prodsSelectVisible.value = true
nextTick(() => {
prodsSelectRef.value?.init(card.value.realData.prod)
})
}
/**
* 添加指定商品
*/
const selectCouponProds = (prods) => {
card.value.realData.prods = prods
if (prods.length) {
const selectProd = prods[0]
dataForm.value.relation = selectProd.prodId
card.value.pic = selectProd.pic
card.value.name = selectProd.prodName
card.value.id = selectProd.prodId
} else {
card.value = {}
}
}
</script>
<style lang="scss" scoped>
//card样式
.card-prod-bottom {
position: relative;
text-align: left;
.card-prod-name {
margin: auto;
padding: 0 6px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
width: 118px;
display: inline-block;
}
.card-prod-name-button {
position: absolute;
top: 24px;
right: 10px;
}
}
</style>

@ -0,0 +1,177 @@
<template>
<div class="mod-prod">
<avue-crud
ref="crudRef"
:page="page"
:data="dataList"
:table-loading="dataListLoading"
:option="tableOption"
@search-change="onSearch"
@selection-change="selectionChange"
@on-load="getDataList"
>
<template #menu-left>
<el-button
v-if="isAuth('admin:indexImg:save')"
type="primary"
icon="el-icon-plus"
@click.stop="onAddOrUpdate()"
>
新增
</el-button>
<el-button
v-if="isAuth('admin:indexImg:delete')"
type="danger"
:disabled="dataListSelections.length <= 0"
@click="onDelete()"
>
批量删除
</el-button>
</template>
<template #imgUrl="scope">
<img
v-if="scope.row.imgUrl"
alt=""
:src="scope.row.imgUrl"
width="100"
height="100"
>
<img
v-else
alt=""
src="~@/assets/img/def.png"
width="100"
height="100"
>
</template>
<template #menu="scope">
<el-button
v-if="isAuth('admin:indexImg:update')"
type="primary"
icon="el-icon-edit"
@click="onAddOrUpdate(scope.row.imgId)"
>
修改
</el-button>
<el-button
v-if="isAuth('admin:indexImg:delete')"
type="danger"
icon="el-icon-delete"
@click="onDelete(scope.row.imgId)"
>
删除
</el-button>
</template>
</avue-crud>
<!-- 弹窗, 新增 / 修改 -->
<add-or-update
v-if="addOrUpdateVisible"
ref="addOrUpdateRef"
@refresh-data-list="getDataList"
/>
</div>
</template>
<script setup>
import { isAuth } from '@/utils'
import { ElMessage, ElMessageBox } from 'element-plus'
import AddOrUpdate from './add-or-update.vue'
import { tableOption } from '@/crud/admin/indexImg.js'
const dataList = ref([])
const dataListLoading = ref(false)
const dataListSelections = ref([])
const resourcesUrl = import.meta.env.VITE_APP_RESOURCES_URL
const page = reactive({
total: 0, // 总页数
currentPage: 1, // 当前页数
pageSize: 10 // 每页显示多少
})
/**
* 获取数据列表
*/
const getDataList = (pageParam, params, done) => {
dataListLoading.value = true
http({
url: http.adornUrl('/admin/indexImg/page'),
method: 'get',
params: http.adornParams(
Object.assign(
{
current: pageParam == null ? page.currentPage : pageParam.currentPage,
size: pageParam == null ? page.pageSize : pageParam.pageSize
},
params
)
)
})
.then(({ data }) => {
data.records.forEach(item => {
item.imgUrl = item.imgUrl ? resourcesUrl + item.imgUrl : ''
})
dataList.value = data.records
page.total = data.total
dataListLoading.value = false
if (done) done()
})
}
const addOrUpdateVisible = ref(false)
const addOrUpdateRef = ref(null)
/**
* 新增 / 修改
* @param id
*/
const onAddOrUpdate = (id) => {
addOrUpdateVisible.value = true
nextTick(() => {
addOrUpdateRef.value?.init(id)
})
}
/**
* 删除
* @param id
*/
const onDelete = (id) => {
const ids = id ? [id] : dataListSelections.value?.map(item => {
return item.imgId
})
ElMessageBox.confirm(`确定进行[${id ? '删除' : '批量删除'}]操作?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
})
.then(() => {
http({
url: http.adornUrl('/admin/indexImg'),
method: 'delete',
data: http.adornData(ids, false)
})
.then(() => {
ElMessage({
message: '操作成功',
type: 'success',
duration: 1500,
onClose: () => {
getDataList()
}
})
})
})
}
/**
* 条件查询
*/
const onSearch = (params, done) => {
getDataList(page, params, done)
}
/**
* 多选变化
*/
const selectionChange = (val) => {
dataListSelections.value = val
}
</script>

@ -0,0 +1,101 @@
<template>
<!-- 发货信息,用于导出代发货订单的excel交给快递公司 -->
<el-dialog
v-model="visible"
:modal="false"
title="请输入发货信息"
:close-on-click-modal="false"
width="38%"
>
<el-form
ref="dataFormRef"
:model="dataForm"
:rules="dataRule"
label-width="120px"
@keyup.enter="onSubmit()"
>
<el-form-item
label="发件人姓名"
prop="consignmentName"
>
<el-input
v-model="dataForm.consignmentName"
controls-position="right"
label="发件人姓名"
/>
</el-form-item>
<el-form-item
label="发货人手机号"
prop="consignmentMobile"
>
<el-input
v-model="dataForm.consignmentMobile"
controls-position="right"
label="发货人手机号"
/>
</el-form-item>
<el-form-item
label="发货地址"
prop="consignmentAddr"
>
<el-input
v-model="dataForm.consignmentAddr"
controls-position="right"
label="发货地址"
/>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button
@click="visible = false"
>取消</el-button>
<el-button
type="primary"
@click="onSubmit()"
>确定</el-button>
</span>
</template>
</el-dialog>
</template>
<script setup>
const emit = defineEmits(['inputCallback'])
const visible = ref(false)
const dataForm = reactive({
consignmentName: '',
consignmentMobile: '',
consignmentAddr: ''
})
const dataRule = {
consignmentName: [
{ required: true, message: '不能为空', trigger: 'blur' }
],
consignmentMobile: [
{ required: true, message: '不能为空', trigger: 'blur' }
],
consignmentAddr: [
{ required: true, message: '不能为空', trigger: 'blur' }
]
}
const dataFormRef = ref(null)
const init = () => {
visible.value = true
nextTick(() => {
dataFormRef.value?.resetFields()
})
}
defineExpose({ init })
/**
* 表单提交
*/
const onSubmit = () => {
dataFormRef.value?.validate((valid) => {
if (valid) {
visible.value = false
emit('inputCallback', dataForm)
}
})
}
</script>

@ -0,0 +1,124 @@
<template>
<el-dialog
v-model="visible"
:modal="false"
title="选择发货地址"
:close-on-click-modal="false"
>
<el-form
ref="dataFormRef"
:model="dataForm"
:rules="dataRule"
label-width="80px"
@keyup.enter="onSubmit()"
>
<el-form-item label="快递公司">
<el-select
v-model="dataForm.dvyId"
placeholder="请选择"
>
<el-option
v-for="item in dataForm.dvyNames"
:key="item.dvyId"
:label="item.dvyName"
:value="item.dvyId"
/>
</el-select>
</el-form-item>
<el-form-item
label="快递单号"
prop="dvyFlowId"
>
<el-input
v-model="dataForm.dvyFlowId"
controls-position="right"
:min="0"
label="快递单号"
/>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="visible = false">取消</el-button>
<el-button
type="primary"
@click="onSubmit()"
>确定</el-button>
</span>
</template>
</el-dialog>
</template>
<script setup>
import { ElMessage } from 'element-plus'
const emit = defineEmits(['refreshDataList'])
// eslint-disable-next-line no-unused-vars
const validDvyFlowId = (rule, value, callback) => {
if (!value.trim()) {
callback(new Error('不能为空'))
} else {
callback()
}
}
const dataRule = {
dvyFlowId: [
{ required: true, message: '不能为空', trigger: 'blur' },
{ validator: validDvyFlowId, trigger: 'blur' }
]
}
const visible = ref(false)
const dataForm = reactive({
dvyId: '',
dvyFlowId: 0,
dvyNames: [],
orderNumber: 0
})
const init = (orderNumber, dvyId, dvyFlowId) => {
visible.value = true
dataForm.orderNumber = orderNumber || ''
dataForm.dvyId = dvyId || ''
dataForm.dvyFlowId = dvyFlowId || ''
http({
url: http.adornUrl('/admin/delivery/list'),
method: 'get',
params: http.adornParams()
}).then(({ data }) => {
dataForm.dvyNames = data
})
}
defineExpose({ init })
const dataFormRef = ref(null)
/**
* 表单提交
*/
const onSubmit = () => {
dataFormRef.value?.validate((valid) => {
if (valid) {
http({
url: http.adornUrl('/order/order/delivery'),
method: 'put',
data: http.adornData({
orderNumber: dataForm.orderNumber,
dvyId: dataForm.dvyId,
dvyFlowId: dataForm.dvyFlowId
})
})
.then(() => {
ElMessage({
message: '操作成功',
type: 'success',
duration: 1500,
onClose: () => {
visible.value = false
emit('refreshDataList')
}
})
})
}
})
}
</script>

@ -0,0 +1,514 @@
<template>
<el-dialog
v-model="visible"
:title="!dataForm.orderNumber ? '新增' : '查看'"
:close-on-click-modal="false"
width="80%"
>
<el-form
ref="dataFormRef"
:model="dataForm"
label-width="80px"
@keyup.enter="onSubmit()"
>
<div class="main">
<div class="content">
<div class="order-number">
<div class="num-cont">
<el-form-item label="订单编号:">
<span class="text">{{ dataForm.orderNumber }}</span>
</el-form-item>
<el-form-item>
<el-steps
:active="stepsStatus"
align-center
:process-status="dataForm.status == 6 ? 'error':'wait'"
>
<el-step
title="提交订单"
:description="dataForm.orderTime"
/>
<el-step
title="买家已付款"
:description="dataForm.payTime"
/>
<el-step
v-if="dataForm.orderType !== 1"
title="卖家已发货"
:description="dataForm.dvyTime"
/>
<el-step
v-if="dataForm.orderType !== 1"
title="买家已收货"
:description="dataForm.finallyTime"
/>
</el-steps>
</el-form-item>
</div>
</div>
<div class="order-state">
<div class="state-cont">
<div class="state-title">
<el-form-item label="订单状态:">
<template #default>
<el-tag
v-if="dataForm.status === 1"
type="warning"
>
待付款
</el-tag>
<el-tag
v-if="dataForm.status === 2 && dataForm.orderType !== 1"
type="warning"
>
待发货
</el-tag>
<el-tag
v-if="dataForm.status === 3 && dataForm.orderType !== 1"
type="warning"
>
待收货
</el-tag>
<el-tag
v-if="dataForm.status === 4 && dataForm.orderType !== 1"
type="warning"
>
待评价
</el-tag>
<el-tag
v-if="dataForm.status === 5"
type="success"
>
成功
</el-tag>
<el-tag
v-if="dataForm.status === 6"
type="danger"
>
失败
</el-tag>
</template>
</el-form-item>
<el-form-item>
<el-row>
<el-button
v-if="dataForm.status === 2 && dataForm.orderType !== 1"
type="primary"
plain
@click="changeOrder(dataForm.orderNumber)"
>
发货
</el-button>
</el-row>
</el-form-item>
</div>
<div class="order-info">
<div class="order-details">
<div class="detail-title">
<img
src="~@/assets/img/car.png"
alt=""
>
<span class="prompt">买家付款后才可以发货</span>
</div>
<div class="detail-cont">
<div class="detail01">
<img
src="~@/assets/img/address.png"
alt=""
>
<div class="text-width">
<el-form-item label="收货人:">
<span>{{ dataForm.userAddrOrder.receiver }}</span>
</el-form-item>
<el-form-item label="手机:">
<span>{{ dataForm.userAddrOrder.mobile }}</span>
</el-form-item>
<el-form-item label="收货地址">
<span>{{ dataForm.userAddrOrder.province }}{{ dataForm.userAddrOrder.city }}{{ dataForm.userAddrOrder.area }}{{ dataForm.userAddrOrder.addr }}</span>
</el-form-item>
</div>
</div>
<div class="detail01">
<img
src="~@/assets/img/invoice.png"
alt=""
>
<span>不开发票</span>
</div>
</div>
</div>
<div class="buyers">
<div class="detail-title">
<img
src="~@/assets/img/buyer.png"
alt=""
style="margin-right:15px"
>
<el-form-item
label="买家:"
style="margin-top:22px"
label-width="44px"
>
<span>{{ dataForm.nickName }}</span>
</el-form-item>
</div>
<div class="buyers-info">
<div class="detail02">
<img
src="~@/assets/img/message.png"
alt=""
>
<div class="text-width">
<span>买家备注:</span>
<br>
<span>{{ dataForm.remarks }}</span>
</div>
</div>
</div>
</div>
</div>
<div class="item-list">
<el-table
:data="dataForm.orderItems"
border
>
<el-table-column
prop=""
label="商品"
>
<template #default="scope">
<div class="prod-con">
<img
:src="resourcesUrl + scope.row.pic"
class="prod-img"
>
<span>{{ scope.row.prodName }}</span>
</div>
</template>
</el-table-column>
<el-table-column
prop="price"
label="单价"
width="180"
align="center"
>
<template #default="scope">
<span>{{ scope.row.price }}</span>
</template>
</el-table-column>
<el-table-column
prop="count"
label="数量"
width="180"
align="center"
>
<template #default="scope">
<span>{{ scope.row.prodCount }}</span>
</template>
</el-table-column>
<el-table-column
prop="totalPrice"
label="总价"
width="180"
align="center"
>
<template #default="scope">
<span>{{ scope.row.productTotalAmount }}</span>
</template>
</el-table-column>
</el-table>
</div>
<div class="item-info">
<el-form-item label="商品总价:">
<span class="text">¥{{ dataForm.total }}</span>
</el-form-item>
<el-form-item
v-if="dataForm.freightAmount"
label="配送费用:"
>
<span class="text">¥{{ dataForm.freightAmount }}</span>
</el-form-item>
<el-form-item label="应付金额:">
<span class="text">¥{{ dataForm.actualTotal }}</span>
</el-form-item>
</div>
</div>
</div>
<div class="order-log">
<div class="log-title">
<span>订单日志</span>
</div>
<div class="log-cont">
<el-form-item
v-if="dataForm.orderTime"
label-width="10px"
>
<span>{{ dataForm.orderTime }} {{ dataForm.nickName }} 创建订单(成功)</span>
</el-form-item>
<el-form-item
v-if="dataForm.updateTime"
label-width="10px"
>
<span>{{ dataForm.updateTime }} {{ dataForm.nickName }} 订单更新(成功)</span>
</el-form-item>
<el-form-item
v-if="dataForm.payTime"
label-width="10px"
>
<span>{{ dataForm.payTime }} {{ dataForm.nickName }} 订单付款(成功)</span>
</el-form-item>
<el-form-item
v-if="dataForm.dvyTime"
label-width="10px"
>
<span>{{ dataForm.dvyTime }} {{ dataForm.nickName }} 订单发货(成功)</span>
</el-form-item>
<el-form-item
v-if="dataForm.finallyTime"
label-width="10px"
>
<span>{{ dataForm.finallyTime }} {{ dataForm.nickName }} 完成订单(成功)</span>
</el-form-item>
<el-form-item
v-if="dataForm.cancelTime"
label-width="10px"
>
<span>{{ dataForm.cancelTime }} {{ dataForm.nickName }} 取消订单(成功)</span>
</el-form-item>
</div>
</div>
</div>
</div>
</el-form>
<!-- 弹窗, 新增 / 修改 -->
<devy-add
v-if="devyVisible"
ref="devyAddRef"
@refresh-data-list="getDataList"
/>
</el-dialog>
</template>
<script setup>
import DevyAdd from './order-devy.vue'
const dataForm = ref({
orderId: 0,
orderNumber: '',
remarks: '',
total: 0,
actualTotal: 0,
dvyType: '',
status: 1,
addrOrderId: 0,
nickName: '',
orderItems: [],
orderTime: '',
updateTime: '',
payTime: '',
dvyTime: '',
finallyTime: '',
cancelTime: '',
userAddrOrder: {}
})
const stepsStatus = computed(() => {
if (dataForm.value.finallyTime) {
return 4
}
if (dataForm.value.dvyTime) {
return 3
}
if (dataForm.value.payTime) {
return 2
}
if (dataForm.value.orderTime) {
return 1
}
})
const visible = ref(false)
const devyVisible = ref(false)
watch(
() => visible.value,
() => {
if (!visible.value) {
devyVisible.value = false
}
}
)
const dataFormRef = ref(null)
const init = (orderNumber) => {
dataForm.value.orderNumber = orderNumber || 0
visible.value = true
nextTick(() => {
dataFormRef.value?.resetFields()
})
if (dataForm.value.orderNumber) {
// 修改
http({
url: http.adornUrl(`/order/order/orderInfo/${dataForm.value.orderNumber}`),
method: 'get',
params: http.adornParams()
})
.then(({ data }) => {
dataForm.value = data
})
}
}
defineExpose({ init })
const getDataList = () => {
http({
url: http.adornUrl(`/order/order/orderInfo/${dataForm.value.orderNumber}`),
method: 'get',
params: http.adornParams()
})
.then(({ data }) => {
dataForm.value = data
})
}
const devyAddRef = ref(null)
/**
* 发货
* @param orderNumber
*/
const changeOrder = (orderNumber) => {
devyVisible.value = true
nextTick(() => {
devyAddRef.value?.init(orderNumber, dataForm.value.dvyId, dataFormRef.value?.dvyFlowId)
})
}
</script>
<style scoped lang="scss">
.main {
height: 100%;
width: 100%;
font: 14px Arial, "PingFang SC", "Hiragino Sans GB", STHeiti, "Microsoft YaHei", "WenQuanYi Micro Hei", sans-serif;
color: #495060;
}
.content {
margin: 0 20px;
.order-state {
position: relative;
margin-top: 50px;
border-bottom: 1px solid #e9eaec;
}
}
.order-number {
.text {
font-size: 14px;
color: #8a8a8a;
}
}
.order-state {
.state-title {
width: 100%;
display: flex;
justify-content: space-between;
align-items: center;
}
.order-info {
width: 100%;
border-top: 1px solid #e9eaec;
margin: 50px 0;
display: flex;
}
.item-info {
padding-left: 80%;
margin: 25px 0;
}
}
.order-info {
img {
width: 18px !important;
height: 18px !important;
margin-right: 15px;
}
.detail-title {
height: 50px;
line-height: 50px;
display: flex;
align-items: center;
}
.order-details {
width: 50%;
border-right: 1px solid #e9eaec;
}
.detail-cont {
position: relative;
border-top: 1px dashed #e9eaec;
margin: 15px 20px 0 0;
}
.buyers {
width: 50%;
margin-left: 20px;
}
}
.detail-cont {
.detail01 {
display: flex;
height: 100%;
line-height: 25px;
margin-top: 15px;
}
}
.detail01 {
.text-width {
width: 100%;
}
}
.detail02 {
.text-width {
width: 100%;
}
}
.buyers {
.buyers-info {
border-top: 1px dashed #e9eaec;
margin-top: 15px;
position: relative;
}
.detail02 {
display: flex;
height: 100%;
line-height: 25px;
margin-top: 15px;
}
}
.item-info {
span {
margin-bottom: 15px;
line-height: 30px;
}
.text {
position: absolute;
right: 0;
}
}
.order-log {
.log-title {
height: 50px;
width: 100%;
line-height: 50px;
font-weight: bold;
}
.log-cont {
color: #4395ff;
}
}
.item-list {
.prod-con {
display: flex;
.prod-img {
width: 100px;
height: 100px;
margin-right: 8px;
}
}
}
</style>

@ -0,0 +1,556 @@
<template>
<div class="mod-order-order">
<el-form
:inline="true"
:model="dataForm"
@keyup.enter="getDataList(page)"
>
<el-form-item label="订单编号:">
<el-input
v-model="dataForm.orderNumber"
placeholder="订单编号"
clearable
/>
</el-form-item>
<el-form-item label="下单时间:">
<el-date-picker
v-model="dateRange"
type="datetimerange"
range-separator="至"
value-format="yyyy-MM-dd HH:mm:ss"
start-placeholder="开始日期"
end-placeholder="结束日期"
/>
</el-form-item>
<el-form-item label="订单状态:">
<el-select
v-model="dataForm.status"
clearable
placeholder="请选择订单状态"
>
<el-option
v-for="item in options"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item>
<el-button
type="primary"
icon="el-icon-search"
@click="getDataList()"
>
查询
</el-button>
<el-button
v-if="isAuth('order:order:waitingConsignmentExcel')"
type="primary"
@click="showConsignmentInfo()"
>
导出待发货订单
</el-button>
<el-button
v-if="isAuth('order:order:soldExcel')"
type="primary"
@click="getSoldExcel()"
>
导出销售记录
</el-button>
<el-button @click="clearDatas()">
清空
</el-button>
</el-form-item>
</el-form>
<div class="main">
<div class="content">
<div class="tit">
<el-row style="width:100%">
<el-col :span="10">
<span class="item product">商品</span>
</el-col>
<el-col :span="3">
<span class="item">成交单价/购买数量</span>
</el-col>
<el-col :span="3">
<span class="item">实付金额</span>
</el-col>
<el-col :span="3">
<span class="item">支付方式</span>
</el-col>
<el-col :span="3">
<span class="item">订单状态</span>
</el-col>
<el-col :span="2">
<span class="item">操作</span>
</el-col>
</el-row>
</div>
<div
v-for="order in dataList"
:key="order.orderId"
class="prod"
>
<div class="prod-tit">
<span>订单编号:{{ order.orderNumber }}</span>
<span>下单时间:{{ order.createTime }}</span>
</div>
<div class="prod-cont">
<el-row style="width:100%">
<el-col :span="12">
<div class="prod-item">
<div
v-for="orderItem in order.orderItems"
:key="orderItem.orderItemId"
class="items name"
>
<div class="prod-image">
<img
alt=""
:src="resourcesUrl + orderItem.pic"
style="height:100px;width: 100px;"
>
</div>
<div class="prod-name">
<span>{{ orderItem.prodName }}</span>
<span class="prod-info">{{ orderItem.skuName }}</span>
</div>
<div class="prod-price">
<span>¥{{ orderItem.price }}</span>
<span>×{{ orderItem.prodCount }}</span>
</div>
</div>
</div>
</el-col>
<el-col
:span="3"
style="height: 100%;"
>
<div class="item">
<div>
<span class="totalprice">¥{{ order.actualTotal }}</span>
<span v-if="order.freightAmount">(含运费:¥{{ order.freightAmount }})</span>
<span>共{{ order.productNums }}件</span>
</div>
</div>
</el-col>
<el-col
:span="3"
style="height: 100%;"
>
<div class="item">
<div>
<span v-if="order.payType === 1">微信支付</span>
<span v-else-if="order.payType === 2">支付宝</span>
<span v-else>手动代付</span>
</div>
</div>
</el-col>
<el-col
:span="3"
style="height: 100%;"
>
<div class="item">
<span
v-if="order.status === 1"
type="danger"
>待付款</span>
<span
v-else-if="order.status === 2"
type="danger"
>待发货</span>
<span
v-else-if="order.status === 3"
type="danger"
>待收货</span>
<span
v-else-if="order.status === 4"
type="danger"
>待评价</span>
<span
v-else-if="order.status === 5"
type="danger"
>成功</span>
<span
v-else
>失败</span>
</div>
</el-col>
<el-col
:span="3"
style="height: 100%;"
>
<div class="item">
<div class="operate">
<el-button
v-if="isAuth('order:order:update')"
type="text"
@click="onAddOrUpdate(order.orderNumber)"
>
查看
</el-button>
</div>
</div>
</el-col>
</el-row>
</div>
<div class="remark">
<div class="buyer-remark">
<span>备注:{{ order.remarks }}</span>
</div>
</div>
</div>
</div>
</div>
<!-- 空 -->
<div class="empty-tips">
暂无数据
</div>
<el-pagination
:current-page="page.pageIndex"
:page-sizes="[10, 20, 50, 100]"
:page-size="page.pageSize"
:total="page.total"
layout="total, sizes, prev, pager, next, jumper"
@size-change="sizeChangeHandle"
@current-change="currentChangeHandle"
/>
<!-- 弹窗, 新增 / 修改 -->
<add-or-update
v-if="addOrUpdateVisible"
ref="addOrUpdateRef"
@refresh-data-list="getDataList"
/>
<consignment-info
v-if="consignmentInfoVisible"
ref="consignmentInfoRef"
@input-callback="getWaitingConsignmentExcel"
/>
</div>
</template>
<script setup>
import AddOrUpdate from './components/order-info.vue'
import ConsignmentInfo from './components/consignment-info.vue'
import { isAuth } from '@/utils'
const resourcesUrl = import.meta.env.VITE_APP_RESOURCES_URL
const dataForm = ref({})
const dateRange = ref([])
const options = [{
value: 1,
label: '待付款'
},
{
value: 2,
label: '待发货'
},
{
value: 3,
label: '待收货'
},
{
value: 4,
label: '待评价'
},
{
value: 5,
label: '成功'
},
{
value: 6,
label: '失败'
}]
const dataList = ref([])
const page = reactive({
total: 0, // 总页数
currentPage: 1, // 当前页数
pageSize: 10 // 每页显示多少条
})
onMounted(() => {
getDataList(page)
})
/**
* 获取数据列表
*/
const getDataList = (pageParam, params, done) => {
pageParam = (pageParam === undefined ? page : pageParam)
http({
url: http.adornUrl('/order/order/page'),
method: 'get',
params: http.adornParams(
Object.assign(
{
current: pageParam == null ? page.currentPage : pageParam.currentPage,
size: pageParam == null ? page.pageSize : pageParam.pageSize,
orderNumber: dataForm.value.orderNumber,
status: dataForm.value.status,
startTime: dateRange.value === null ? null : dateRange.value[0], // 开始时间
endTime: dateRange.value === null ? null : dateRange.value[1] // 结束时间
},
params
), false
)
})
.then(({ data }) => {
dataList.value = data.records
page.total = data.total
if (done) done()
})
}
/**
* 清除数据
*/
const clearDatas = () => {
dataForm.value = {}
dateRange.value = []
}
/**
* 每页数
* @param val
*/
const sizeChangeHandle = (val) => {
page.pageSize = val
page.currentPage = 1
getDataList(page)
}
/**
* 当前页
* @param val
*/
const currentChangeHandle = (val) => {
page.currentPage = val
getDataList(page)
}
const addOrUpdateRef = ref(null)
const addOrUpdateVisible = ref(false)
/**
* 新增 / 修改
* @param val
*/
const onAddOrUpdate = (val) => {
addOrUpdateVisible.value = true
nextTick(() => {
addOrUpdateRef.value?.init(val)
})
}
const consignmentInfoRef = ref(null)
const consignmentInfoVisible = ref(false)
const showConsignmentInfo = () => {
consignmentInfoVisible.value = true
nextTick(() => {
consignmentInfoRef.value?.init()
})
}
const getWaitingConsignmentExcel = (consignmentInfo) => {
http({
url: http.adornUrl('/order/order/waitingConsignmentExcel'),
method: 'get',
params: http.adornParams({
consignmentName: consignmentInfo.consignmentName,
consignmentMobile: consignmentInfo.consignmentMobile,
consignmentAddr: consignmentInfo.consignmentAddr,
startTime: dateRange.value === null ? null : dateRange.value[0], // 开始时间
endTime: dateRange.value === null ? null : dateRange.value[1] // 结束时间
}),
responseType: 'blob' // 解决文件下载乱码问题
})
.then(({ data }) => {
const blob = new Blob([data], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8' })
const fileName = '待发货信息整理.xls'
const elink = document.createElement('a')
if ('download' in elink) { // 非IE下载
elink.download = fileName
elink.style.display = 'none'
elink.href = URL.createObjectURL(blob)
document.body.appendChild(elink)
elink.click()
URL.revokeObjectURL(elink.href) // 释放URL 对象
document.body.removeChild(elink)
} else { // IE10+下载
navigator.msSaveBlob(blob, fileName)
}
})
}
const getSoldExcel = () => {
http({
url: http.adornUrl('/order/order/soldExcel'),
method: 'get',
params: http.adornParams({
startTime: dateRange.value === null ? null : dateRange.value[0], // 开始时间
endTime: dateRange.value === null ? null : dateRange.value[1] // 结束时间
}),
responseType: 'blob' // 解决文件下载乱码问题
})
.then(({ data }) => {
const blob = new Blob([data], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8' })
const fileName = '销售信息整理.xls'
const elink = document.createElement('a')
if ('download' in elink) { // 非IE下载
elink.download = fileName
elink.style.display = 'none'
elink.href = URL.createObjectURL(blob)
document.body.appendChild(elink)
elink.click()
URL.revokeObjectURL(elink.href) // 释放URL 对象
document.body.removeChild(elink)
} else { // IE10+下载
navigator.msSaveBlob(blob, fileName)
}
})
}
</script>
<style lang="scss" scoped>
.mod-order-order {
.tit {
display: flex;
height: 45px;
align-items: center;
}
.tit {
.item {
padding: 0 10px;
width: 10%;
text-align: center;
}
.product {
width: 25%;
}
}
.prod-tit {
padding: 10px;
background: #f8f8f9;
border-left: 1px solid #dddee1;
border-top: 1px solid #dddee1;
border-right: 1px solid #dddee1;
span {
margin-right: 15px;
}
}
.prod-cont {
display: flex;
border-top: 1px solid #dddee1;
border-bottom: 1px solid #dddee1;
border-left: 1px solid #dddee1;
color: #495060;
.item {
display: flex;
display: -webkit-flex;
align-items: center;
justify-content: center;
padding: 10px;
border-right: 1px solid #dddee1;
text-align: center;
height: 100%;
span {
display: block;
}
}
.prod-item {
display: flex;
flex-direction: column;
border-right: 1px solid #dddee1;
}
.items.name {
display: flex;
position: relative;
padding: 20px;
border-bottom: 1px solid #dddee1;
&:last-child {
border-bottom: none;
}
}
}
.prod-name {
width: 55%;
text-align: left;
.prod-info {
display: block;
color: #80848f;
margin-top: 30px;
}
}
.prod-price {
position: absolute;
right: 40px;
text-align: right;
span {
display: block;
margin-bottom: 10px;
}
}
.prod-image {
margin-right: 20px;
width: 100px;
height: 100px;
img {
width: 100px;
height: 100px;
}
}
.item {
span {
display: block;
margin-bottom: 10px;
}
.operate {
color: #2d8cf0;
}
.totalprice {
color: #c00;
}
}
.prod {
.remark {
width: 100%;
height: 50px;
line-height: 50px;
background-color: #e8f7f6;
border-left: 1px solid #dddee1;
border-right: 1px solid #dddee1;
border-bottom: 1px solid #dddee1;
margin-bottom: 20px;
}
}
.buyer-remark {
padding: 0 20px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.empty-tips {
display: block;
width: 100%;
text-align: center;
margin: 50px 0;
color: #999;
}
}
</style>

@ -0,0 +1,163 @@
<template>
<div class="mod-hotSearch-add-or-update">
<el-dialog
v-model="visible"
:title="!dataForm.hotSearchId ? '新增' : '修改'"
:close-on-click-modal="false"
>
<el-form
ref="dataFormRef"
:model="dataForm"
:rules="dataRule"
label-width="80px"
@keyup.enter="onSubmit()"
>
<el-form-item
label="标题"
prop="title"
>
<el-input
v-model="dataForm.title"
controls-position="right"
:min="0"
maxlength="50"
show-word-limit
label="标题"
/>
</el-form-item>
<el-form-item
label="内容"
prop="content"
>
<el-input
v-model="dataForm.content"
controls-position="right"
type="textarea"
:min="0"
maxlength="255"
show-word-limit
label="内容"
/>
</el-form-item>
<el-form-item
label="排序号"
prop="seq"
>
<el-input-number
v-model="dataForm.seq"
controls-position="right"
:min="0"
label="排序号"
/>
</el-form-item>
<el-form-item
label="状态"
prop="status"
>
<el-radio-group v-model="dataForm.status">
<el-radio :label="0">
下线
</el-radio>
<el-radio :label="1">
正常
</el-radio>
</el-radio-group>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="visible = false">
取消
</el-button>
<el-button
type="primary"
@click="onSubmit()"
>
确定
</el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup>
import { ElMessage } from 'element-plus'
import { Debounce } from '@/utils/debounce'
const emit = defineEmits(['refreshDataList'])
const dataForm = ref({
hotSearchId: 0,
title: '',
content: '',
recDate: '',
seq: 0,
status: 0
})
const page = reactive({
total: 0, // 总页数
currentPage: 1, // 当前页数
pageSize: 10 // 每页显示多少条
})
const visible = ref(false)
const dataRule = {
title: [
{ required: true, message: '标题不能为空', trigger: 'blur' },
{ min: 1, max: 50, message: '长度在1到50个字符内', trigger: 'blur' },
{ pattern: /\s\S+|S+\s|\S/, message: '标题不能为空', trigger: 'blur' }
],
content: [
{ required: true, message: '内容不能为空', trigger: 'blur' },
{ min: 1, max: 255, message: '长度在1到255个字符内', trigger: 'blur' },
{ pattern: /\s\S+|S+\s|\S/, message: '内容不能为空', trigger: 'blur' }
]
}
const dataFormRef = ref(null)
const init = (id) => {
dataForm.value.hotSearchId = id || 0
visible.value = true
nextTick(() => {
dataFormRef.value?.resetFields()
if (dataForm.value.hotSearchId) {
http({
url: http.adornUrl('/admin/hotSearch/info/' + dataForm.value.hotSearchId),
method: 'get',
params: http.adornParams()
})
.then(({ data }) => {
dataForm.value = data
})
}
})
}
defineExpose({ init })
/**
* 表单提交
*/
const onSubmit = Debounce(() => {
dataFormRef.value?.validate(valid => {
if (valid) {
const param = dataForm.value
http({
url: http.adornUrl('/admin/hotSearch'),
method: param.hotSearchId ? 'put' : 'post',
data: http.adornData(param)
})
.then(() => {
ElMessage({
message: '操作成功',
type: 'success',
duration: 1500,
onClose: () => {
visible.value = false
emit('refreshDataList', page)
}
})
})
}
})
})
</script>

@ -0,0 +1,172 @@
<template>
<div class="mod-hotSearcch">
<avue-crud
ref="crudRef"
:page="page"
:data="dataList"
:table-loading="dataListLoading"
:option="tableOption"
@search-change="onSearch"
@on-load="getDataList"
@refresh-change="refreshChange"
@selection-change="selectionChange"
>
<template #menu-left>
<el-button
v-if="isAuth('admin:hotSearch:save')"
type="primary"
icon="el-icon-plus"
@click="onAddOrUpdate()"
>
新增
</el-button>
<el-button
type="danger"
:disabled="dataListSelections.length <= 0"
@click.stop="onDeconste"
>
批量删除
</el-button>
</template>
<template #status="scope">
<el-tag
v-if="scope.row.status === 0"
type="danger"
>
未启用
</el-tag>
<el-tag v-else>
启用
</el-tag>
</template>
<template #menu="scope">
<el-button
v-if="isAuth('admin:hotSearch:update')"
type="primary"
icon="el-icon-edit"
@click="onAddOrUpdate(scope.row.hotSearchId)"
>
修改
</el-button>
<el-button
v-if="isAuth('admin:hotSearch:deconste')"
type="danger"
icon="el-icon-deconste"
@click.stop="onDeconste(scope.row,scope.index)"
>
删除
</el-button>
</template>
</avue-crud>
<!-- 弹窗, 新增 / 修改 -->
<add-or-update
v-if="addOrUpdateVisible"
ref="addOrUpdateRef"
@refresh-data-list="getDataList"
/>
</div>
</template>
<script setup>
import { isAuth } from '@/utils'
import { ElMessage, ElMessageBox } from 'element-plus'
import { tableOption } from '@/crud/shop/hotSearch.js'
import AddOrUpdate from './add-or-update.vue'
const dataList = ref([])
const page = reactive({
total: 0, // 总页数
currentPage: 1, // 当前页数
pageSize: 10 // 每页显示多少条
})
const dataListLoading = ref(false)
const dataListSelections = ref([])
/**
* 获取数据列表
*/
const getDataList = (pageParam, params, done) => {
dataListLoading.value = true
http({
url: http.adornUrl('/admin/hotSearch/page'),
method: 'get',
params: http.adornParams(Object.assign({
current: pageParam ? pageParam.currentPage : 1,
size: pageParam ? pageParam.pageSize : 20
}, params))
})
.then(({ data }) => {
page.total = data.total
page.pageSize = data.size
page.currentPage = data.current
dataList.value = data.records
dataListLoading.value = false
if (done) done()
})
}
/**
* 多选回调
* @param list
*/
const selectionChange = (list) => {
dataListSelections.value = list
}
const addOrUpdateVisible = ref(false)
const addOrUpdateRef = ref(null)
/**
* 新增 / 修改
* @param id
*/
const onAddOrUpdate = (id) => {
addOrUpdateVisible.value = true
nextTick(() => {
addOrUpdateRef.value?.init(id)
})
}
/**
* 点击查询
*/
const onSearch = (params, done) => {
getDataList(page, params, done)
}
/**
* 删除
*/
const onDeconste = (row) => {
const ids = row.hotSearchId ? [row.hotSearchId] : dataListSelections.value?.map(item => {
return item.hotSearchId
})
ElMessageBox.confirm(`确定进行[${row.hotSearchId ? '删除' : '批量删除'}]操作?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
})
.then(() => {
http({
url: http.adornUrl('/admin/hotSearch'),
method: 'delete',
data: http.adornData(ids, false)
})
.then(() => {
ElMessage({
message: '操作成功',
type: 'success',
duration: 1500,
onClose: () => {
getDataList()
}
})
})
}).catch(() => { })
}
const refreshChange = () => {
getDataList(page)
}
</script>

@ -0,0 +1,148 @@
<template>
<el-dialog
v-model="visible"
:title="!dataForm.id ? '新增' : '修改'"
:close-on-click-modal="false"
>
<el-form
ref="dataFormRef"
:model="dataForm"
:rules="dataRule"
label-width="80px"
@keyup.enter="onSubmit()"
>
<el-form-item
label="公告标题"
prop="title"
>
<el-input v-model="dataForm.title" />
</el-form-item>
<el-form-item
label="状态"
prop="status"
>
<el-radio-group v-model="dataForm.status">
<el-radio :label="1">
公布
</el-radio>
<el-radio :label="0">
撤销
</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item
label="置顶"
prop="isTop"
>
<el-radio-group v-model="dataForm.isTop">
<el-radio :label="1">
是
</el-radio>
<el-radio :label="0">
否
</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item
label="公告内容"
prop="content"
>
<TinyMce
ref="contentEnRef"
v-model="dataForm.content"
/>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button
@click="visible = false"
>取消</el-button>
<el-button
type="primary"
@click="onSubmit()"
>确定</el-button>
</span>
</template>
</el-dialog>
</template>
<script setup>
import { ElMessage } from 'element-plus'
import { Debounce } from '@/utils/debounce'
const emit = defineEmits(['refreshDataList'])
const visible = ref(false)
const contentEnRef = ref(null)
// eslint-disable-next-line no-unused-vars
const validateTitle = (rule, value, callback) => {
if (!value.trim()) {
dataForm.value.title = ''
callback(new Error('公告标题不能为空'))
} else {
callback()
}
}
const dataRule = {
title: [
{ required: true, message: '公告标题不能为空', trigger: 'blur' },
{ validator: validateTitle, trigger: 'blur' }
]
}
const dataForm = ref({
title: null,
content: null,
url: null,
status: 1,
isTop: 0
})
const dataFormRef = ref(null)
const init = (id) => {
dataForm.value.id = id || 0
visible.value = true
nextTick(() => {
dataFormRef.value?.resetFields()
if (dataForm.value.id) {
http({
url: http.adornUrl('/shop/notice/info/' + dataForm.value.id),
method: 'get',
params: http.adornParams()
})
.then(({ data }) => {
dataForm.value = data
})
}
})
}
defineExpose({ init })
/**
* 表单提交
*/
const onSubmit = Debounce(() => {
dataFormRef.value?.validate((valid) => {
if (valid) {
http({
url: http.adornUrl('/shop/notice'),
method: dataForm.value.id ? 'put' : 'post',
data: http.adornData(dataForm.value)
})
.then(() => {
ElMessage({
message: '操作成功',
type: 'success',
duration: 1500,
onClose: () => {
visible.value = false
emit('refreshDataList')
dataForm.value.content = ''
}
})
})
}
})
})
</script>

@ -0,0 +1,152 @@
<template>
<div class="mod-shop-notice">
<avue-crud
ref="crudRef"
:page="page"
:data="dataList"
:table-loading="dataListLoading"
:option="tableOption"
@search-change="onSearch"
@on-load="getDataList"
@refresh-change="refreshChange"
>
<template #status="scope">
<el-tag
v-if="scope.row.status === 0"
type="danger"
>
撤销
</el-tag>
<el-tag v-else>
公布
</el-tag>
</template>
<template #isTop="scope">
<el-tag v-if="scope.row.isTop === 0">
否
</el-tag>
<el-tag v-else>
是
</el-tag>
</template>
<template #menuLeft>
<el-button
v-if="isAuth('shop:notice:save')"
type="primary"
icon="el-icon-plus"
@click="onAddOrUpdate()"
>
新增
</el-button>
</template>
<template #menu="scope">
<el-button
v-if="isAuth('shop:notice:update')"
type="primary"
icon="el-icon-edit"
@click="onAddOrUpdate(scope.row.id)"
>
修改
</el-button>
<el-button
v-if="isAuth('shop:notice:delete')"
type="danger"
icon="el-icon-delete"
@click.stop="onDelete(scope.row.id)"
>
删除
</el-button>
</template>
</avue-crud>
<add-or-update
v-if="addOrUpdateVisible"
ref="addOrUpdateRef"
@refresh-data-list="refreshChange"
/>
</div>
</template>
<script setup>
import { isAuth } from '@/utils'
import { ElMessage, ElMessageBox } from 'element-plus'
import { tableOption } from '@/crud/shop/notice'
import AddOrUpdate from './add-or-update.vue'
const dataList = ref([])
const page = reactive({
total: 0, // 总页数
currentPage: 1, // 当前页数
pageSize: 10 // 每页显示多少条
})
const dataListLoading = ref(false)
const addOrUpdateVisible = ref(false)
const getDataList = (pageParam, params, done) => {
dataListLoading.value = true
http({
url: http.adornUrl('/shop/notice/page'),
method: 'get',
params: http.adornParams(Object.assign({
current: pageParam == null ? page.currentPage : pageParam.currentPage,
size: pageParam == null ? page.pageSize : pageParam.pageSize
}, params))
})
.then(({ data }) => {
dataList.value = data.records
page.total = data.total
dataListLoading.value = false
if (done) done()
})
}
const addOrUpdateRef = ref(null)
/**
* 新增 / 修改
* @param id
*/
const onAddOrUpdate = (id) => {
addOrUpdateVisible.value = true
nextTick(() => {
addOrUpdateRef.value?.init(id)
})
}
const onDelete = (id) => {
ElMessageBox.confirm('确定进行删除操作?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
})
.then(() => {
http({
url: http.adornUrl('/shop/notice/' + id),
method: 'delete',
data: http.adornData({})
})
.then(() => {
ElMessage({
message: '操作成功',
type: 'success',
duration: 1500,
onClose: () => {
getDataList()
}
})
})
}).catch(() => { })
}
/**
* 刷新回调
*/
const refreshChange = () => {
getDataList(page)
}
const onSearch = (params, done) => {
getDataList(page, params, done)
}
</script>
<style lang="scss" scoped>
</style>

@ -0,0 +1,289 @@
<template>
<el-dialog
v-model="visible"
:title="!dataForm.addrId ? '新增' : '修改'"
:close-on-click-modal="false"
>
<el-form
ref="dataFormRef"
:model="dataForm"
:rules="dataRule"
label-width="80px"
@keyup.enter="onSubmit()"
>
<el-form-item
label="名称"
prop="addrName"
>
<el-input
v-model="dataForm.addrName"
placeholder="自提点名称"
/>
</el-form-item>
<el-form-item label="省份">
<el-col :span="8">
<el-form-item prop="province">
<el-select
v-model="dataForm.provinceId"
placeholder="请选择"
@change="selectProvince"
>
<el-option
v-for="province in provinceList"
:key="province.areaId"
:label="province.areaName"
:value="province.areaId"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item prop="city">
<el-select
v-model="dataForm.cityId"
placeholder="请选择"
@change="selectCity"
>
<el-option
v-for="city in cityList"
:key="city.areaId"
:label="city.areaName"
:value="city.areaId"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item prop="area">
<el-select
v-model="dataForm.areaId"
placeholder="请选择"
>
<el-option
v-for="area in areaList"
:key="area.areaId"
:label="area.areaName"
:value="area.areaId"
/>
</el-select>
</el-form-item>
</el-col>
</el-form-item>
<el-form-item
label="地址"
prop="addr"
>
<el-input
v-model="dataForm.addr"
placeholder="地址"
/>
</el-form-item>
<el-form-item
label="手机号"
prop="mobile"
>
<el-input
v-model="dataForm.mobile"
maxlength="11"
placeholder="手机号"
/>
</el-form-item>
</el-form>
<template #footer>
<span
class="dialog-footer"
>
<el-button @click="visible = false">取消</el-button>
<el-button
type="primary"
@click="onSubmit()"
>确定</el-button>
</span>
</template>
</el-dialog>
</template>
<script setup>
import { ElMessage } from 'element-plus'
import { isMobile } from '@/utils/validate'
import { Debounce } from '@/utils/debounce'
const emit = defineEmits(['refreshDataList'])
const visible = ref(false)
// eslint-disable-next-line no-unused-vars
const validateMobile = (rule, value, callback) => {
if (!isMobile(value)) {
callback(new Error('手机号格式错误'))
} else {
callback()
}
}
const dataRule = {
addrName: [
{ required: true, message: '自提点名称不能为空', trigger: 'blur' },
{ pattern: /\s\S+|S+\s|\S/, message: '请输入正确的自提点名称', trigger: 'blur' }
],
addr: [
{ required: true, message: '地址不能为空', trigger: 'blur' },
{ pattern: /\s\S+|S+\s|\S/, message: '请输入正确的地址', trigger: 'blur' }
],
city: [{ required: true, message: '城市不能为空', trigger: 'blur' }],
province: [
{ required: true, message: '省份不能为空', trigger: 'blur' }
],
area: [{ required: true, message: '区/县不能为空', trigger: 'blur' }],
mobile: [
{ required: true, message: '手机号不能为空', trigger: 'blur' },
{ validator: validateMobile, trigger: 'blur' }
]
}
const provinceList = ref([])
const dataFormRef = ref(null)
const cityList = ref([])
const areaList = ref([])
const dataForm = reactive({
addrId: 0,
addr: '',
addrName: '',
mobile: '',
area: '',
city: '',
province: '',
areaId: null,
cityId: null,
provinceId: null
})
const page = reactive({
total: 0, // 总页数
currentPage: 1, // 当前页数
pageSize: 10 // 每页显示多少条
})
const init = (id) => {
dataForm.addrId = id || 0
visible.value = true
nextTick(() => {
dataFormRef.value?.resetFields()
cityList.value = []
areaList.value = []
dataForm.provinceId = null
dataForm.cityId = null
dataForm.areaId = null
})
listAreaByParentId().then(({ data }) => {
provinceList.value = data
})
if (dataForm.addrId) {
http({
url: http.adornUrl(
`/shop/pickAddr/info/${dataForm.addrId}`
),
method: 'get',
params: http.adornParams()
})
.then(({ data }) => {
dataForm.addr = data.addr
dataForm.mobile = data.mobile
dataForm.addrName = data.addrName
dataForm.areaId = data.areaId
dataForm.cityId = data.cityId
dataForm.provinceId = data.provinceId
listAreaByParentId(data.provinceId).then(({ data }) => {
cityList.value = data
})
listAreaByParentId(data.cityId).then(({ data }) => {
areaList.value = data
})
})
}
}
defineExpose({ init })
const listAreaByParentId = (pid) => {
if (!pid) pid = 0
return http({
url: http.adornUrl('/admin/area/listByPid'),
method: 'get',
params: http.adornParams({ pid })
})
}
/**
* 选择省
* @param val
*/
const selectProvince = (val) => {
dataForm.cityId = null
dataForm.city = ''
// 获取城市的select
listAreaByParentId(val).then(({ data }) => {
cityList.value = data
})
}
/**
* 选择市
* @param val
*/
const selectCity = (val) => {
dataForm.areaId = null
dataForm.area = ''
// 获取区的select
listAreaByParentId(val).then(({ data }) => {
areaList.value = data
})
}
/**
* 表单提交
*/
const onSubmit = Debounce(() => {
for (let i = 0; i < provinceList.value.length; i++) {
if (provinceList.value[i].areaId === dataForm.provinceId) {
// 将省名字保存起来
dataForm.province = provinceList.value[i].areaName
}
}
for (let i = 0; i < cityList.value.length; i++) {
if (cityList.value[i].areaId === dataForm.cityId) {
// 将市名字保存起来
dataForm.city = cityList.value[i].areaName
}
}
for (let i = 0; i < areaList.value.length; i++) {
if (areaList.value[i].areaId === dataForm.areaId) {
// 将市名字保存起来
dataForm.area = areaList.value[i].areaName
}
}
dataFormRef.value?.validate(valid => {
if (valid) {
http({
url: http.adornUrl('/shop/pickAddr'),
method: dataForm.addrId ? 'put' : 'post',
data: http.adornData({
addrId: dataForm.addrId || undefined,
addr: dataForm.addr,
addrName: dataForm.addrName,
mobile: dataForm.mobile,
area: dataForm.area,
city: dataForm.city,
province: dataForm.province,
areaId: dataForm.areaId,
cityId: dataForm.cityId,
provinceId: dataForm.provinceId
})
})
.then(() => {
ElMessage({
message: '操作成功',
type: 'success',
duration: 1500,
onClose: () => {
visible.value = false
emit('refreshDataList', page)
}
})
})
}
})
})
</script>

@ -0,0 +1,166 @@
<template>
<div class="mod-pickAddr">
<avue-crud
ref="crudRef"
:page="page"
:data="dataList"
:option="tableOption"
:permission="permission"
@search-change="onSearch"
@selection-change="selectionChange"
@on-load="getDataList"
>
<template #menu-left>
<el-button
v-if="isAuth('shop:pickAddr:save')"
type="primary"
icon="el-icon-plus"
@click.stop="onAddOrUpdate()"
>
新增
</el-button>
<el-button
v-if="isAuth('shop:pickAddr:delete')"
type="danger"
:disabled="dataListSelections.length <= 0"
@click="onDelete()"
>
批量删除
</el-button>
</template>
<template #menu="scope">
<el-button
v-if="isAuth('shop:pickAddr:update')"
type="primary"
icon="el-icon-edit"
@click.stop="onAddOrUpdate(scope.row.addrId)"
>
编辑
</el-button>
<el-button
v-if="isAuth('shop:pickAddr:delete')"
type="danger"
icon="el-icon-delete"
@click.stop="onDelete(scope.row.addrId)"
>
删除
</el-button>
</template>
</avue-crud>
<!-- 弹窗, 新增 / 修改 -->
<add-or-update
v-if="addOrUpdateVisible"
ref="addOrUpdateRef"
@refresh-data-list="getDataList"
/>
</div>
</template>
<script setup>
import { isAuth } from '@/utils'
import { ElMessage, ElMessageBox } from 'element-plus'
import AddOrUpdate from './add-or-update.vue'
import { tableOption } from '@/crud/shop/pickAddr.js'
const permission = {
delBtn: isAuth('prod:prod:delete')
}
const dataList = ref([])
const dataListSelections = ref([])
const page = reactive({
total: 0, // 总页数
currentPage: 1, // 当前页数
pageSize: 10 // 每页显示多少条
})
/**
* 获取数据列表
*/
const getDataList = (pageParam, params, done) => {
http({
url: http.adornUrl('/shop/pickAddr/page'),
method: 'get',
params: http.adornParams(
Object.assign(
{
current: pageParam == null ? page.currentPage : pageParam.currentPage,
size: pageParam == null ? page.pageSize : pageParam.pageSize
},
params
)
)
})
.then(({ data }) => {
dataList.value = data.records
page.total = data.total
if (done) done()
})
}
const addOrUpdateVisible = ref(false)
const addOrUpdateRef = ref(null)
/**
* 新增 / 修改
* @param id
*/
const onAddOrUpdate = (id) => {
addOrUpdateVisible.value = true
nextTick(() => {
addOrUpdateRef.value?.init(id)
})
}
/**
* 删除
* @param id
*/
const onDelete = (id) => {
const ids = id ? [id] : dataListSelections.value?.map(item => {
return item.addrId
})
ElMessageBox.confirm(
'确定进行删除操作?', '提示',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}
)
.then(() => {
http({
url: http.adornUrl('/shop/pickAddr'),
method: 'delete',
data: http.adornData(ids, false)
})
.then(() => {
ElMessage({
message: '操作成功',
type: 'success',
duration: 1500,
onClose: () => {
getDataList(page)
}
})
})
})
.catch(() => { })
}
/**
* 条件查询
* @param params
* @param done
*/
const onSearch = (params, done) => {
getDataList(page, params, done)
}
/**
* 多选变化
* @param val
*/
const selectionChange = (val) => {
dataListSelections.value = val
}
</script>

@ -0,0 +1,568 @@
<template>
<el-dialog
v-model="visible"
:title="!dataForm.transportId ? '新增' : '修改'"
:close-on-click-modal="false"
width="1400px"
class="transport-dialog"
>
<el-form
ref="dataFormRef"
:model="dataForm"
label-width="80px"
@keyup.enter="onSubmit()"
>
<el-form-item
label="模板名称"
prop="transName"
:rules="[
{ required: true, message: '模板名称不能为空'},
{ pattern: /\s\S+|S+\s|\S/, message: '请输入正确的模板名称', trigger: 'blur' }
]"
>
<el-input
v-model="dataForm.transName"
placeholder="模板名称"
/>
</el-form-item>
<el-form-item
label="模板类型"
prop="isFreeFee"
required="required"
>
<el-radio-group
v-model="dataForm.isFreeFee"
@change="changeFreeFee"
>
<el-radio :label="0">
买家承担运费
</el-radio>
<el-radio :label="1">
卖家包邮
</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item
label="收费方式"
prop="chargeType"
>
<el-radio-group
v-model="dataForm.chargeType"
:disabled="dataForm.isFreeFee == 1"
>
<el-radio :label="0">
按件数
</el-radio>
<el-radio :label="1">
按重量
</el-radio>
<el-radio :label="2">
按体积
</el-radio>
</el-radio-group>
</el-form-item>
<el-table
:data="dataForm.transfees"
border
style="width: 100%;"
class="table-con"
>
<el-table-column
header-align="center"
align="center"
width="450"
label="可配送区域"
>
<template #default="scope">
<span v-if="scope.$index == 0">所有地区</span>
<span v-if="(!scope.row.cityList || !scope.row.cityList.length) && scope.$index > 0">请选择可配送区域</span>
<span v-if="scope.$index > 0">
<el-tag
v-for="city in scope.row.cityList"
:key="city.areaId"
>{{ city.areaName }}</el-tag>
</span>
<el-button
v-if="isAuth('shop:transfee:update') && scope.$index > 0"
type="text"
@click="onAddOrUpdate(`${scope.$index}`)"
>
编辑
</el-button>
<el-button
v-if="isAuth('shop:transfee:delete') && scope.$index > 0"
type="text"
@click="onDelete(`${scope.$index}`)"
>
删除
</el-button>
</template>
</el-table-column>
<el-table-column
header-align="center"
align="center"
width="180"
:label="tableTitle[0]"
>
<template #default="scope">
<el-form-item
:prop="`transfees.${scope.$index}.firstPiece`"
label-width="0px"
:rules="[{ required: true, message: `${tableTitle[0]}不能为空`}]"
>
<el-input
v-model="scope.row.firstPiece"
type="number"
:disabled="!scope.row.status && scope.$index === 0"
@change="checkNumber(scope.row, 1)"
/>
</el-form-item>
</template>
</el-table-column>
<el-table-column
header-align="center"
align="center"
:label="tableTitle[1]"
>
<template #default="scope">
<el-form-item
:prop="`transfees.${scope.$index}.firstFee`"
label-width="0px"
:rules="[{ required: true, message: `${tableTitle[1]}不能为空`}]"
>
<el-input
v-model="scope.row.firstFee"
type="number"
:min="0"
:disabled="!scope.row.status && scope.$index === 0"
/>
</el-form-item>
</template>
</el-table-column>
<el-table-column
header-align="center"
align="center"
:label="tableTitle[2]"
>
<template #default="scope">
<el-form-item
:prop="`transfees.${scope.$index}.continuousPiece`"
label-width="0px"
:rules="[{ required: true, message: `${tableTitle[2]}不能为空`}]"
>
<el-input
v-model="scope.row.continuousPiece"
type="number"
:disabled="!scope.row.status && scope.$index === 0"
@change="checkNumber(scope.row, 3)"
/>
</el-form-item>
</template>
</el-table-column>
<el-table-column
header-align="center"
align="center"
:label="tableTitle[3]"
>
<template #default="scope">
<el-form-item
:prop="`transfees.${scope.$index}.continuousFee`"
label-width="0px"
:rules="[{ required: true, message: `${tableTitle[3]}不能为空`}]"
>
<el-input
v-model="scope.row.continuousFee"
type="number"
:min="0"
:disabled="!scope.row.status && scope.$index === 0"
/>
</el-form-item>
</template>
</el-table-column>
</el-table>
<div
v-if="dataForm.isFreeFee == 0"
style="margin-top: 20px"
>
<el-button
type="primary"
icon="el-icon-location-outline"
@click="addTransfee()"
>
点击添加可配送的区域和运费
</el-button>
</div>
<el-checkbox
v-if="!dataForm.isFreeFee"
v-model="dataForm.hasFreeCondition"
style="margin-top:10px;font-size:50px"
>
指定条件包邮
</el-checkbox>
<el-table
v-if="dataForm.hasFreeCondition && !dataForm.isFreeFee"
:data="dataForm.transfeeFrees"
border
style="width: 100%;"
>
<el-table-column
header-align="center"
align="center"
width="350"
label="指定区域"
>
<template #default="scope">
<span v-if="!scope.row.freeCityList || !scope.row.freeCityList.length">请选择指定包邮城市</span>
<el-tag
v-for="city in scope.row.freeCityList"
:key="city.areaId"
>
{{ city.areaName }}
</el-tag>
<el-button
v-if="isAuth('shop:transfee:update')"
type="text"
@click="addOrUpdateTransfeeFree(`${scope.$index}`)"
>
编辑
</el-button>
<el-button
v-if="isAuth('shop:transfee:delete')"
type="text"
@click="deleteTransfeeFree(`${scope.$index}`)"
>
删除
</el-button>
</template>
</el-table-column>
<el-table-column
header-align="center"
align="center"
width="600"
label="设置包邮条件"
>
<template #default="scope">
<el-radio-group v-model="scope.row.freeType">
<el-radio :label="0">
满件/重量/体积包邮
</el-radio>
<el-radio :label="1">
满金额包邮
</el-radio>
<el-radio :label="2">
满件/重量/体积且满金额包邮
</el-radio>
</el-radio-group>
</template>
</el-table-column>
<el-table-column
header-align="center"
align="left"
>
<template #default="scope">
<el-form-item
v-if="scope.row.freeType == 1 || scope.row.freeType == 2"
:prop="`transfeeFrees.${scope.$index}.amount`"
label-width="0px"
:rules="[{ required: true, message: `不能为空`}]"
>
满 <el-input
v-model="scope.row.amount"
style="width:100px"
/> 元包邮
</el-form-item>
<el-form-item
v-if="scope.row.freeType == 0 || scope.row.freeType == 2"
:prop="`transfeeFrees.${scope.$index}.piece`"
label-width="0px"
:rules="[{ required: true, message: `不能为空`}]"
>
满 <el-input
v-model="scope.row.piece"
style="width:100px"
/> 件/重量/体积包邮
</el-form-item>
</template>
</el-table-column>
</el-table>
<div
v-if="dataForm.isFreeFee == 0"
style="margin-top: 20px"
>
<el-button
type="primary"
icon="el-icon-location-outline"
@click="addTransfeeFree()"
>
点击添加指定包邮条件
</el-button>
</div>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="visible = false">
取消
</el-button>
<el-button
type="primary"
@click="onSubmit()"
>
确定
</el-button>
</div>
</template>
<!-- 弹窗, 新增 / 修改 -->
<add-or-update
v-if="addOrUpdateVisible"
ref="addOrUpdateRef"
@refresh-data-list="getDataList"
/>
</el-dialog>
</template>
<script setup>
import { isAuth } from '@/utils'
import { ElMessage } from 'element-plus'
import { Debounce } from '@/utils/debounce'
import AddOrUpdate from './add-or-update.vue'
const emit = defineEmits(['refreshDataList'])
const hasFreeCondition = ref(0)
const visible = ref(false)
const dataForm = ref({
hasFreeCondition: false,
transName: '',
createTime: '',
chargeType: 0,
transportId: 0,
isFreeFee: 0,
transfees: [{ cityList: [], status: 1 }],
transfeeFrees: [{ freeCityList: [], freeType: 0 }]
})
const page = reactive({
total: 0, // 总页数
currentPage: 1, // 当前页数
pageSize: 10 // 每页显示多少条
})
const tableTitle = computed(() => {
const titles = [['首件(个)', '运费(元)', '续件(个)', '续费(元)'], ['首重(kg)', '运费(元)', '续重(kg)', '续费(元)'], ['首体积(m³)', '运费(元)', '续体积(m³)', '续费(元)']]
if (dataForm.value.chargeType) {
return titles[dataForm.value.chargeType]
}
return titles[0]
})
// 如果当前对话框不可见,则关闭选择城市的对话框
watch(
() => visible.value,
(val) => {
if (!val) {
addOrUpdateVisible.value = false
}
}
)
const addOrUpdateVisible = ref(false)
const dataFormRef = ref(null)
const init = (id) => {
visible.value = true
dataForm.value.transportId = id || 0
nextTick(() => {
dataFormRef.value?.resetFields()
dataForm.value = {
hasFreeCondition: false,
transName: '',
createTime: '',
chargeType: 0,
transportId: 0,
isFreeFee: 0,
transfees: [{ cityList: [], status: 1 }],
transfeeFrees: [{ freeCityList: [], freeType: 0 }]
}
})
if (dataForm.value.transportId) {
http({
// 获取运费模板数据
url: http.adornUrl(`/shop/transport/info/${dataForm.value.transportId}`),
method: 'get'
})
.then(({ data }) => {
if (data.isFreeFee) {
data.transfees[0].status = 0
} else {
data.transfees[0].status = 1
}
dataForm.value = data
dataForm.value.hasFreeCondition = !!data.hasFreeCondition
})
}
}
defineExpose({ init })
const getDataList = (row, cityList, type) => {
if (type === 0) {
dataForm.value.transfees[row].cityList = cityList
}
if (type === 1) {
dataForm.value.transfeeFrees[row].freeCityList = cityList
}
}
/**
* 添加运费项
*/
const addTransfee = () => {
dataForm.value.transfees.push({ cityList: [], status: 1 })
}
/**
* 删除运费项
*/
const onDelete = (rowIndex) => {
dataForm.value.transfees.splice(rowIndex, 1)
}
const addOrUpdateRef = ref(null)
/**
* 可配送区域和运费编辑
*/
const onAddOrUpdate = (rowIndex) => {
addOrUpdateVisible.value = true
let allSelectCityList = []
for (let i = 1; i < dataForm.value.transfees.length; i++) {
const cityList = dataForm.value.transfees[i].cityList
allSelectCityList = allSelectCityList.concat(cityList)
}
nextTick(() => {
addOrUpdateRef.value?.init(rowIndex, dataForm.value.transfees[rowIndex].cityList || [], allSelectCityList, 0)
})
}
/**
* 添加指定包邮条件
*/
const addTransfeeFree = () => {
if (dataForm.value.hasFreeCondition) {
dataForm.value.transfeeFrees?.push({ freeCityList: [], freeType: 0 })
}
}
/**
* 删除指定包邮条件
*/
const deleteTransfeeFree = (rowIndex) => {
dataForm.value.transfeeFrees?.splice(rowIndex, 1)
}
/**
* 指定包邮条件编辑
*/
const addOrUpdateTransfeeFree = (rowIndex) => {
addOrUpdateVisible.value = true
let allSelectCityList = []
for (let i = 1; i < dataForm.value.transfeeFrees.length; i++) {
const freeCityList = dataForm.value.transfeeFrees[i].freeCityList
allSelectCityList = allSelectCityList.concat(freeCityList)
}
nextTick(() => {
addOrUpdateRef.value?.init(rowIndex, dataForm.value.transfeeFrees[rowIndex].freeCityList || [], allSelectCityList, 1)
})
}
/**
* 改变模板类型, 0 买家承担运费 1 卖家包邮
* @param val
*/
const changeFreeFee = (val) => {
dataForm.value.hasFreeCondition = false
if (val) {
dataForm.value.chargeType = 0
dataForm.value.transfees = [{ cityList: [], status: 0, firstPiece: 1, firstFee: 0, continuousPiece: 1, continuousFee: 0 }]
} else {
dataForm.value.transfees = [{ cityList: [], status: 1 }]
}
}
/**
* 校验输入的数字
*/
const checkNumber = (row, type) => {
if (type === 1) {
row.firstPiece = getNumber(row.firstPiece)
row.firstPiece = row.firstPiece === 0 ? 1 : row.firstPiece
} else if (type === 3) {
row.continuousPiece = getNumber(row.continuousPiece)
row.continuousPiece = row.continuousPiece === 0 ? 1 : row.continuousPiece
}
}
/**
* 保留整数并小于零的数设为0
*/
const getNumber = (num) => {
num = Math.round(num)
return num < 0 ? 0 : num
}
/**
* 表单提交
*/
const onSubmit = Debounce(() => {
dataFormRef.value?.validate((valid) => {
if (valid) {
for (let i = 1; i < dataForm.value.transfees.length; i++) {
const transfee = dataForm.value.transfees[i]
if (transfee.cityList.length === 0) {
ElMessage({
message: '请选择可配送区域',
type: 'error',
duration: 1500
})
return
}
}
if (dataForm.value.hasFreeCondition) {
hasFreeCondition.value = 1
} else {
hasFreeCondition.value = 0
}
dataForm.value.transfees[0].cityList = []
http({
url: http.adornUrl('/shop/transport'),
method: dataForm.value.transportId ? 'put' : 'post',
data: http.adornData({
transportId: dataForm.value.transportId || undefined,
transName: dataForm.value.transName,
chargeType: dataForm.value.chargeType,
isFreeFee: dataForm.value.isFreeFee,
transfees: dataForm.value.transfees,
transfeeFrees: dataForm.value.transfeeFrees,
hasFreeCondition: hasFreeCondition.value
})
})
.then(() => {
ElMessage({
message: '操作成功',
type: 'success',
duration: 1500,
onClose: () => {
visible.value = false
emit('refreshDataList', page)
}
})
})
}
})
})
</script>
<style scoped>
.transport-dialog .table-con :deep(.el-form-item) {
margin-top: 16px;
margin-bottom: 16px!important;
}
</style>

@ -0,0 +1,167 @@
<template>
<div class="mod-transport">
<avue-crud
ref="crudRef"
:page="page"
:data="dataList"
:option="tableOption"
@search-change="onSearch"
@selection-change="selectionChange"
@on-load="getDataList"
>
<template #prod-prop-values="scope">
<el-tag
v-for="item in scope.row.prodPropValues"
:key="item.valueId"
>
{{ item.propValue }}
</el-tag>
</template>
<template #menu-left>
<el-button
v-if="isAuth('shop:transport:save')"
type="primary"
icon="el-icon-plus"
@click.stop="onAddOrUpdate()"
>
新增
</el-button>
<el-button
v-if="isAuth('shop:transport:delete')"
type="danger"
:disabled="dataListSelections.length <= 0"
@click="onDelete()"
>
批量删除
</el-button>
</template>
<template #menu="scope">
<el-button
v-if="isAuth('shop:transport:update')"
type="primary"
icon="el-icon-edit"
@click.stop="onAddOrUpdate(scope.row.transportId)"
>
修改
</el-button>
<el-button
v-if="isAuth('shop:transport:delete')"
type="danger"
icon="el-icon-delete"
@click.stop="onDelete(scope.row.transportId)"
>
删除
</el-button>
</template>
</avue-crud>
<!-- 弹窗, 新增 / 修改 -->
<add-or-update
v-if="addOrUpdateVisible"
ref="addOrUpdateRef"
@refresh-data-list="getDataList"
/>
</div>
</template>
<script setup>
import { isAuth } from '@/utils'
import { ElMessage, ElMessageBox } from 'element-plus'
import { tableOption } from '@/crud/shop/transport'
import AddOrUpdate from './add-or-update.vue'
const dataList = ref([])
const dataListSelections = ref([])
const page = ref({
total: 0, // 总页数
currentPage: 1, // 当前页数
pageSize: 10 // 每页显示多少条
})
/**
* 获取数据列表
*/
const getDataList = (pageParam, params, done) => {
http({
url: http.adornUrl('/shop/transport/page'),
method: 'get',
params: http.adornParams(
Object.assign(
{
current: pageParam == null ? page.value.currentPage : pageParam.currentPage,
size: pageParam == null ? page.value.pageSize : pageParam.pageSize
},
params
)
)
})
.then(({ data }) => {
dataList.value = data.records
page.value.total = data.total
if (done) done()
})
}
const addOrUpdateVisible = ref(false)
const addOrUpdateRef = ref(null)
/**
* 新增 / 修改
* @param id
*/
const onAddOrUpdate = (id) => {
addOrUpdateVisible.value = true
nextTick(() => {
addOrUpdateRef.value?.init(id)
})
}
/**
* 删除
* @param id
*/
const onDelete = (id) => {
const ids = id ? [id] : dataListSelections.value?.map(item => item.transportId)
ElMessageBox.confirm(
`确定进行[${id ? '删除' : '批量删除'}]操作?`,
'提示',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}
)
.then(() => {
http({
url: http.adornUrl('/shop/transport'),
method: 'delete',
data: http.adornData(ids, false)
})
.then(() => {
ElMessage({
message: '操作成功',
type: 'success',
duration: 1500,
onClose: () => {
getDataList()
}
})
})
}).catch(() => {})
}
/**
* 条件查询
*/
const onSearch = (params, done) => {
getDataList(page.value, params, done)
}
/**
* 多选变化
*/
const selectionChange = (val) => {
dataListSelections.value = val
}
</script>

@ -0,0 +1,135 @@
<template>
<el-dialog
v-model="visible"
:title="!dataForm.userId ? '新增' : '修改'"
:close-on-click-modal="false"
>
<el-form
ref="dataFormRef"
:model="dataForm"
:rules="dataRule"
label-width="80px"
@keyup.enter="onSubmit()"
>
<el-form-item
label="用户头像"
prop="pic"
>
<img
:src="dataForm.pic"
class="image"
alt=""
>
</el-form-item>
<el-form-item
label="用户昵称"
prop="nickName"
>
<el-input
v-model="dataForm.nickName"
:disabled="true"
placeholder="用户昵称"
/>
</el-form-item>
<el-form-item
label="状态"
prop="status"
>
<el-radio-group v-model="dataForm.status">
<el-radio :label="0">
禁用
</el-radio>
<el-radio :label="1">
正常
</el-radio>
</el-radio-group>
</el-form-item>
</el-form>
<template #footer>
<span
class="dialog-footer"
>
<el-button @click="visible = false">取消</el-button>
<el-button
type="primary"
@click="onSubmit()"
>确定</el-button>
</span>
</template>
</el-dialog>
</template>
<script setup>
import { ElMessage } from 'element-plus'
import { Debounce } from '@/utils/debounce'
const emit = defineEmits(['refreshDataList'])
const visible = ref(false)
const dataForm = ref({
userId: 0,
nickName: '',
pic: '',
status: 1
})
const page = reactive({
total: 0, // 总页数
currentPage: 1, // 当前页数
pageSize: 10 // 每页显示多少条
})
const dataRule = {
nickName: [
{ required: true, message: '用户名不能为空', trigger: 'blur' }
]
}
const dataFormRef = ref(null)
const init = (id) => {
dataForm.value.userId = id || 0
visible.value = true
nextTick(() => {
dataFormRef.value?.resetFields()
})
if (dataForm.value.userId) {
http({
url: http.adornUrl(`/admin/user/info/${dataForm.value.userId}`),
method: 'get',
params: http.adornParams()
})
.then(({ data }) => {
dataForm.value = data
})
}
}
defineExpose({ init })
/**
* 表单提交
*/
const onSubmit = Debounce(() => {
dataFormRef.value?.validate(valid => {
if (valid) {
http({
url: http.adornUrl('/admin/user'),
method: dataForm.value.userId ? 'put' : 'post',
data: http.adornData({
userId: dataForm.value.userId || undefined,
nickName: dataForm.value.nickName,
status: dataForm.value.status
})
})
.then(() => {
ElMessage({
message: '操作成功',
type: 'success',
duration: 1500,
onClose: () => {
visible.value = false
emit('refreshDataList', page)
}
})
})
}
})
})
</script>

@ -0,0 +1,123 @@
<template>
<div class="mod-user">
<avue-crud
ref="crudRef"
:page="page"
:data="dataList"
:option="tableOption"
@search-change="onSearch"
@selection-change="selectionChange"
@on-load="getDataList"
>
<template #pic="scope">
<span
v-if="scope.row.pic"
class="avue-crud__img"
>
<i
:src="scope.row.pic"
class="el-icon-document"
/>
</span>
<span v-else>-</span>
</template>
<template #status="scope">
<el-tag
v-if="scope.row.status === 0"
type="danger"
>
禁用
</el-tag>
<el-tag v-else>
正常
</el-tag>
</template>
<template #menu="scope">
<el-button
v-if="isAuth('admin:user:update')"
type="primary"
icon="el-icon-edit"
@click.stop="onAddOrUpdate(scope.row.userId)"
>
编辑
</el-button>
</template>
</avue-crud>
<!-- 弹窗, 新增 / 修改 -->
<add-or-update
v-if="addOrUpdateVisible"
ref="addOrUpdateRef"
@refresh-data-list="getDataList"
/>
</div>
</template>
<script setup>
import { isAuth } from '@/utils'
import { tableOption } from '@/crud/user/user.js'
import AddOrUpdate from './add-or-update.vue'
const dataList = ref([])
const dataListLoading = ref(false)
const dataListSelections = ref([])
const addOrUpdateVisible = ref(false)
const page = reactive({
total: 0, // 总页数
currentPage: 1, // 当前页数
pageSize: 10 // 每页显示多少条
})
/**
* 获取数据列表
*/
const getDataList = (pageParam, params, done) => {
dataListLoading.value = true
http({
url: http.adornUrl('/admin/user/page'),
method: 'get',
params: http.adornParams(
Object.assign(
{
current: pageParam == null ? page.currentPage : pageParam.currentPage,
size: pageParam == null ? page.pageSize : pageParam.pageSize
},
params
)
)
})
.then(({ data }) => {
dataList.value = data.records
page.total = data.total
dataListLoading.value = false
if (done) done()
})
}
const addOrUpdateRef = ref(null)
/**
* 新增 / 修改
* @param id
*/
const onAddOrUpdate = (id) => {
addOrUpdateVisible.value = true
nextTick(() => {
addOrUpdateRef.value?.init(id)
})
}
/**
* 条件查询
*/
const onSearch = (params, done) => {
getDataList(page, params, done)
}
/**
* 多选变化
*/
const selectionChange = (val) => {
dataListSelections.value = val
}
</script>
Loading…
Cancel
Save