|
@@ -0,0 +1,365 @@
|
|
|
|
|
+<template>
|
|
|
|
|
+ <div class="page">
|
|
|
|
|
+ <el-tabs type="card" class="my-tabs" v-model="activeName" @tab-change="handleTabClick">
|
|
|
|
|
+ <el-tab-pane label="当前菜单" name="current">
|
|
|
|
|
+ <el-form :inline="true" v-if="searchVisible" class="query-form m-b-10" ref="searchForm"
|
|
|
|
|
+ :model="searchForm" @keyup.enter.native="refreshList()" @submit.native.prevent>
|
|
|
|
|
+ <el-form-item label="菜品名称" prop="dishName">
|
|
|
|
|
+ <el-input v-model="searchForm.dishName" placeholder="请输入菜品名称" clearable></el-input>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ <el-form-item label="菜品分类" prop="typeId">
|
|
|
|
|
+ <SelectTree ref="searchDishTypeTree" :props="{
|
|
|
|
|
+ value: 'id',
|
|
|
|
|
+ label: 'name',
|
|
|
|
|
+ children: 'children'
|
|
|
|
|
+ }" url="/psi-management-server/psi/dishType/treeData" :value="searchForm.typeId" :clearable="true"
|
|
|
|
|
+ :accordion="true" size="default" style="width: 180px"
|
|
|
|
|
+ @getValue="(value) => { searchForm.typeId = value }" />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ <el-form-item label="状态" prop="status">
|
|
|
|
|
+ <el-select v-model="searchForm.status" placeholder="请选择状态" clearable style="width: 120px">
|
|
|
|
|
+ <el-option label="启用" value="0"></el-option>
|
|
|
|
|
+ <el-option label="停用" value="1"></el-option>
|
|
|
|
|
+ </el-select>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ <el-form-item>
|
|
|
|
|
+ <el-button type="primary" @click="refreshList()" icon="el-icon-search">查询</el-button>
|
|
|
|
|
+ <el-button @click="resetSearch()" icon="el-icon-refresh-right">重置</el-button>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ </el-form>
|
|
|
|
|
+
|
|
|
|
|
+ <div class="jp-table top">
|
|
|
|
|
+ <vxe-toolbar :refresh="{ query: refreshList }" custom>
|
|
|
|
|
+ <template #buttons>
|
|
|
|
|
+
|
|
|
|
|
+ <el-button type="primary" icon="el-icon-plus" v-if="hasPermission('psi:menu:add')"
|
|
|
|
|
+ @click="openDishDialog()">添加菜品</el-button>
|
|
|
|
|
+ <el-button type="primary" icon="el-icon-refresh" v-if="hasPermission('psi:menu:edit')"
|
|
|
|
|
+ @click="updateMenu()">更新</el-button>
|
|
|
|
|
+ <el-button type="danger" icon="el-icon-delete" plain @click="del()"
|
|
|
|
|
+ :disabled="$refs.dishTable && $refs.dishTable.getCheckboxRecords().length === 0"
|
|
|
|
|
+ v-if="hasPermission('psi:menu:del')">删除</el-button>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ <template #tools>
|
|
|
|
|
+ <vxe-button text type="primary" :title="searchVisible ? '收起检索' : '展开检索'"
|
|
|
|
|
+ icon="vxe-icon-search" class="tool-btn"
|
|
|
|
|
+ @click="searchVisible = !searchVisible"></vxe-button>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </vxe-toolbar>
|
|
|
|
|
+ <div style="height: calc(100% - 50px)">
|
|
|
|
|
+ <vxe-table border="inner" auto-resize resizable height="auto" :loading="loading" ref="dishTable"
|
|
|
|
|
+ show-header-overflow show-overflow highlight-hover-row :menu-config="{}" :data="dataList"
|
|
|
|
|
+ :tree-config="{ transform: true, rowField: 'treeId', parentField: 'treeParentId', expandAll: true }"
|
|
|
|
|
+ :checkbox-config="{ checkMethod: ({ row }) => !row.isCategory }">
|
|
|
|
|
+ <vxe-column type="seq" width="60" title="序号"></vxe-column>
|
|
|
|
|
+ <vxe-column type="checkbox" width="60"></vxe-column>
|
|
|
|
|
+ <vxe-column min-width="220" align="left" title="菜品名称" field="dishName"
|
|
|
|
|
+ tree-node></vxe-column>
|
|
|
|
|
+ <vxe-column width="80" align="center" title="菜品图片" field="imageUrl">
|
|
|
|
|
+ <template #default="scope">
|
|
|
|
|
+ <el-image v-if="!scope.row.isCategory && scope.row.previewImageUrl"
|
|
|
|
|
+ :src="scope.row.previewImageUrl" :preview-src-list="scope.row.previewImageList"
|
|
|
|
|
+ fit="cover" style="width: 44px; height: 44px; border-radius: 4px;"
|
|
|
|
|
+ hide-on-click-modal append-to-body :z-index="9999"></el-image>
|
|
|
|
|
+ <span v-else-if="!scope.row.isCategory">暂无图片</span>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </vxe-column>
|
|
|
|
|
+ <vxe-column min-width="150" align="center" title="菜品编码" field="dishCode"></vxe-column>
|
|
|
|
|
+ <vxe-column min-width="150" align="center" title="菜品分类" field="typeName"></vxe-column>
|
|
|
|
|
+ <vxe-column width="90" align="center" title="单位" field="unit"></vxe-column>
|
|
|
|
|
+ <vxe-column width="120" align="center" title="菜品库价格" field="librarySalePrice"></vxe-column>
|
|
|
|
|
+ <vxe-column width="120" align="center" title="菜单价格" field="salePrice"></vxe-column>
|
|
|
|
|
+ <vxe-column width="100" align="center" title="状态" field="status">
|
|
|
|
|
+ <template #default="scope">
|
|
|
|
|
+ <el-tag v-if="!scope.row.isCategory"
|
|
|
|
|
+ :type="scope.row.status === '0' ? 'success' : 'info'">
|
|
|
|
|
+ {{ scope.row.status === '0' ? '启用' : '停用' }}
|
|
|
|
|
+ </el-tag>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </vxe-column>
|
|
|
|
|
+ <vxe-column min-width="130" align="center" title="创建人" field="createName"></vxe-column>
|
|
|
|
|
+ <vxe-column min-width="180" align="center" title="创建时间" field="createTime"></vxe-column>
|
|
|
|
|
+ <vxe-column title="操作" width="180px" fixed="right" align="center">
|
|
|
|
|
+ <template #default="scope">
|
|
|
|
|
+ <el-button text type="primary" size="small"
|
|
|
|
|
+ v-if="!scope.row.isCategory && hasPermission('psi:menu:edit')"
|
|
|
|
|
+ @click="openPriceDialog(scope.row)">修改价格</el-button>
|
|
|
|
|
+ <el-button text type="primary" size="small" @click="del(scope.row.id)"
|
|
|
|
|
+ v-if="!scope.row.isCategory && hasPermission('psi:menu:del')">删除</el-button>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </vxe-column>
|
|
|
|
|
+ </vxe-table>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </el-tab-pane>
|
|
|
|
|
+ <el-tab-pane label="历史菜单" name="history">
|
|
|
|
|
+ <div class="jp-table history-table">
|
|
|
|
|
+ <vxe-toolbar :refresh="{ query: refreshHistoryList }" custom></vxe-toolbar>
|
|
|
|
|
+ <vxe-table border="inner" auto-resize resizable height="auto" :loading="historyLoading"
|
|
|
|
|
+ show-header-overflow show-overflow highlight-hover-row :data="historyList">
|
|
|
|
|
+ <vxe-column type="seq" width="60" title="序号"></vxe-column>
|
|
|
|
|
+ <vxe-column min-width="180" align="center" title="更新时间" field="historyTime"></vxe-column>
|
|
|
|
|
+ <vxe-column min-width="180" align="center" title="创建时间" field="createTime"></vxe-column>
|
|
|
|
|
+ <vxe-column min-width="130" align="center" title="操作人" field="createName"></vxe-column>
|
|
|
|
|
+ <vxe-column width="120" align="center" title="菜品数量" field="dishCount"></vxe-column>
|
|
|
|
|
+ <vxe-column title="操作" width="120px" fixed="right" align="center">
|
|
|
|
|
+ <template #default="scope">
|
|
|
|
|
+ <el-button text type="primary" size="small"
|
|
|
|
|
+ @click="openHistoryDetail(scope.row)">详情</el-button>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </vxe-column>
|
|
|
|
|
+ </vxe-table>
|
|
|
|
|
+ <vxe-pager background :current-page="historyPage.currentPage" :page-size="historyPage.pageSize"
|
|
|
|
|
+ :total="historyPage.total" :page-sizes="[10, 20, 100, 1000]"
|
|
|
|
|
+ :layouts="['PrevPage', 'JumpNumber', 'NextPage', 'FullJump', 'Sizes', 'Total']"
|
|
|
|
|
+ @page-change="historyPageChange">
|
|
|
|
|
+ </vxe-pager>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </el-tab-pane>
|
|
|
|
|
+ </el-tabs>
|
|
|
|
|
+
|
|
|
|
|
+ <DishMenuDishDialog ref="dishMenuDishDialog" @refreshList="refreshList"></DishMenuDishDialog>
|
|
|
|
|
+ <DishMenuPriceDialog ref="dishMenuPriceDialog" @refreshList="refreshList"></DishMenuPriceDialog>
|
|
|
|
|
+ <DishMenuHistoryDetailDialog ref="dishMenuHistoryDetailDialog"></DishMenuHistoryDetailDialog>
|
|
|
|
|
+ </div>
|
|
|
|
|
+</template>
|
|
|
|
|
+
|
|
|
|
|
+<script>
|
|
|
|
|
+import SelectTree from '@/components/treeSelect/treeSelect.vue'
|
|
|
|
|
+import OSSSerivce from '@/api/sys/OSSService'
|
|
|
|
|
+import DishMenuService from '@/api/psi/DishMenuService'
|
|
|
|
|
+import DishTypeService from '@/api/psi/DishTypeService'
|
|
|
|
|
+import DishMenuDishDialog from './DishMenuDishDialog'
|
|
|
|
|
+import DishMenuPriceDialog from './DishMenuPriceDialog'
|
|
|
|
|
+import DishMenuHistoryDetailDialog from './DishMenuHistoryDetailDialog'
|
|
|
|
|
+
|
|
|
|
|
+export default {
|
|
|
|
|
+ data() {
|
|
|
|
|
+ return {
|
|
|
|
|
+ activeName: 'current',
|
|
|
|
|
+ searchVisible: true,
|
|
|
|
|
+ searchForm: {
|
|
|
|
|
+ dishName: '',
|
|
|
|
|
+ typeId: '',
|
|
|
|
|
+ status: ''
|
|
|
|
|
+ },
|
|
|
|
|
+ dataList: [],
|
|
|
|
|
+ tablePage: {
|
|
|
|
|
+ total: 0,
|
|
|
|
|
+ currentPage: 1,
|
|
|
|
|
+ pageSize: 10,
|
|
|
|
|
+ orders: []
|
|
|
|
|
+ },
|
|
|
|
|
+ loading: false,
|
|
|
|
|
+ historyList: [],
|
|
|
|
|
+ historyPage: {
|
|
|
|
|
+ total: 0,
|
|
|
|
|
+ currentPage: 1,
|
|
|
|
|
+ pageSize: 10
|
|
|
|
|
+ },
|
|
|
|
|
+ historyLoading: false
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ dishMenuService: null,
|
|
|
|
|
+ dishTypeService: null,
|
|
|
|
|
+ ossService: null,
|
|
|
|
|
+ created() {
|
|
|
|
|
+ this.dishMenuService = new DishMenuService()
|
|
|
|
|
+ this.dishTypeService = new DishTypeService()
|
|
|
|
|
+ this.ossService = new OSSSerivce()
|
|
|
|
|
+ },
|
|
|
|
|
+ components: {
|
|
|
|
|
+ SelectTree,
|
|
|
|
|
+ DishMenuDishDialog,
|
|
|
|
|
+ DishMenuPriceDialog,
|
|
|
|
|
+ DishMenuHistoryDetailDialog
|
|
|
|
|
+ },
|
|
|
|
|
+ mounted() {
|
|
|
|
|
+ this.refreshList()
|
|
|
|
|
+ },
|
|
|
|
|
+ activated() {
|
|
|
|
|
+ this.refreshList()
|
|
|
|
|
+ },
|
|
|
|
|
+ methods: {
|
|
|
|
|
+ handleTabClick() {
|
|
|
|
|
+ if (this.activeName === 'history') {
|
|
|
|
|
+ this.refreshHistoryList()
|
|
|
|
|
+ } else {
|
|
|
|
|
+ this.refreshList()
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ refreshList() {
|
|
|
|
|
+ this.loading = true
|
|
|
|
|
+ Promise.all([
|
|
|
|
|
+ this.dishTypeService.list({}),
|
|
|
|
|
+ this.dishMenuService.list({
|
|
|
|
|
+ current: 1,
|
|
|
|
|
+ size: 1000000,
|
|
|
|
|
+ orders: this.tablePage.orders,
|
|
|
|
|
+ ...this.searchForm
|
|
|
|
|
+ })
|
|
|
|
|
+ ]).then(([typeList, data]) => {
|
|
|
|
|
+ const dishList = data && data.records ? data.records : []
|
|
|
|
|
+ this.dataList = this.buildCurrentMenuTree(typeList || [], dishList)
|
|
|
|
|
+ this.loadImagePreview(this.dataList.filter(item => !item.isCategory))
|
|
|
|
|
+ this.loading = false
|
|
|
|
|
+ this.$nextTick(() => {
|
|
|
|
|
+ if (this.$refs.dishTable) {
|
|
|
|
|
+ this.$refs.dishTable.setAllTreeExpand(true)
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+ }).catch(() => {
|
|
|
|
|
+ this.loading = false
|
|
|
|
|
+ })
|
|
|
|
|
+ },
|
|
|
|
|
+ buildCurrentMenuTree(typeList, dishList) {
|
|
|
|
|
+ const typeMap = new Map(typeList.map(item => [item.id, item]))
|
|
|
|
|
+ const usedTypeIdSet = new Set()
|
|
|
|
|
+ dishList.forEach(item => {
|
|
|
|
|
+ let typeId = item.typeId
|
|
|
|
|
+ while (typeId && typeId !== '0' && typeMap.has(typeId) && !usedTypeIdSet.has(typeId)) {
|
|
|
|
|
+ usedTypeIdSet.add(typeId)
|
|
|
|
|
+ typeId = typeMap.get(typeId).parentId
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ const rows = typeList.filter(item => usedTypeIdSet.has(item.id)).map(item => {
|
|
|
|
|
+ return {
|
|
|
|
|
+ treeId: `type:${item.id}`,
|
|
|
|
|
+ treeParentId: usedTypeIdSet.has(item.parentId) ? `type:${item.parentId}` : null,
|
|
|
|
|
+ isCategory: true,
|
|
|
|
|
+ dishName: item.name
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ const hasUncategorized = dishList.some(item => !item.typeId || !typeMap.has(item.typeId))
|
|
|
|
|
+ if (hasUncategorized) {
|
|
|
|
|
+ rows.push({
|
|
|
|
|
+ treeId: 'type:uncategorized',
|
|
|
|
|
+ treeParentId: null,
|
|
|
|
|
+ isCategory: true,
|
|
|
|
|
+ dishName: '未分类'
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ dishList.forEach(item => {
|
|
|
|
|
+ rows.push({
|
|
|
|
|
+ ...item,
|
|
|
|
|
+ treeId: `dish:${item.id}`,
|
|
|
|
|
+ treeParentId: item.typeId && typeMap.has(item.typeId)
|
|
|
|
|
+ ? `type:${item.typeId}`
|
|
|
|
|
+ : 'type:uncategorized',
|
|
|
|
|
+ isCategory: false
|
|
|
|
|
+ })
|
|
|
|
|
+ })
|
|
|
|
|
+ return rows
|
|
|
|
|
+ },
|
|
|
|
|
+ refreshHistoryList() {
|
|
|
|
|
+ this.historyLoading = true
|
|
|
|
|
+ this.dishMenuService.historyList({
|
|
|
|
|
+ current: this.historyPage.currentPage,
|
|
|
|
|
+ size: this.historyPage.pageSize
|
|
|
|
|
+ }).then((data) => {
|
|
|
|
|
+ this.historyList = data.records
|
|
|
|
|
+ this.historyPage.total = data.total
|
|
|
|
|
+ this.historyLoading = false
|
|
|
|
|
+ }).catch(() => {
|
|
|
|
|
+ this.historyLoading = false
|
|
|
|
|
+ })
|
|
|
|
|
+ },
|
|
|
|
|
+ openHistoryDetail(row) {
|
|
|
|
|
+ this.$refs.dishMenuHistoryDetailDialog.init(row.id)
|
|
|
|
|
+ },
|
|
|
|
|
+ openDishDialog() {
|
|
|
|
|
+ this.$refs.dishMenuDishDialog.init('add')
|
|
|
|
|
+ },
|
|
|
|
|
+ openPriceDialog(row) {
|
|
|
|
|
+ this.$refs.dishMenuPriceDialog.init(row)
|
|
|
|
|
+ },
|
|
|
|
|
+ updateMenu() {
|
|
|
|
|
+ this.$refs.dishMenuDishDialog.init('update')
|
|
|
|
|
+ },
|
|
|
|
|
+ del(id) {
|
|
|
|
|
+ const ids = id || this.$refs.dishTable.getCheckboxRecords().map(item => item.id).join(',')
|
|
|
|
|
+ if (!ids) {
|
|
|
|
|
+ this.$message.warning('请选择要删除的菜品')
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ this.$confirm('确定删除所选菜品吗?', '提示', {
|
|
|
|
|
+ confirmButtonText: '确定',
|
|
|
|
|
+ cancelButtonText: '取消',
|
|
|
|
|
+ type: 'warning'
|
|
|
|
|
+ }).then(() => {
|
|
|
|
|
+ this.loading = true
|
|
|
|
|
+ this.dishMenuService.delete(ids).then((data) => {
|
|
|
|
|
+ this.$message.success(data)
|
|
|
|
|
+ this.refreshList()
|
|
|
|
|
+ }).finally(() => {
|
|
|
|
|
+ this.loading = false
|
|
|
|
|
+ })
|
|
|
|
|
+ })
|
|
|
|
|
+ },
|
|
|
|
|
+ loadImagePreview(list) {
|
|
|
|
|
+ list.forEach((row) => {
|
|
|
|
|
+ const urls = this.getImageUrls(row.imageUrl)
|
|
|
|
|
+ if (urls.length === 0) {
|
|
|
|
|
+ row.previewImageUrl = ''
|
|
|
|
|
+ row.previewImageList = []
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ row.previewImageList = urls
|
|
|
|
|
+ row.previewImageUrl = urls[0]
|
|
|
|
|
+ Promise.all(urls.map(url => this.getPreviewUrl(url))).then((previewList) => {
|
|
|
|
|
+ row.previewImageList = previewList
|
|
|
|
|
+ row.previewImageUrl = previewList[0]
|
|
|
|
|
+ })
|
|
|
|
|
+ })
|
|
|
|
|
+ },
|
|
|
|
|
+ getImageUrls(value) {
|
|
|
|
|
+ if (!value) {
|
|
|
|
|
+ return []
|
|
|
|
|
+ }
|
|
|
|
|
+ return value.split(',').map(item => item.trim()).filter(item => item)
|
|
|
|
|
+ },
|
|
|
|
|
+ getPreviewUrl(url) {
|
|
|
|
|
+ if (url.indexOf('http') === 0) {
|
|
|
|
|
+ return Promise.resolve(url)
|
|
|
|
|
+ }
|
|
|
|
|
+ return this.ossService.getTemporaryUrl(url).catch(() => url)
|
|
|
|
|
+ },
|
|
|
|
|
+ historyPageChange({ currentPage, pageSize }) {
|
|
|
|
|
+ this.historyPage.currentPage = currentPage
|
|
|
|
|
+ this.historyPage.pageSize = pageSize
|
|
|
|
|
+ this.refreshHistoryList()
|
|
|
|
|
+ },
|
|
|
|
|
+ resetSearch() {
|
|
|
|
|
+ this.$refs.searchForm.resetFields()
|
|
|
|
|
+ this.searchForm.typeId = ''
|
|
|
|
|
+ this.tablePage.currentPage = 1
|
|
|
|
|
+ this.refreshList()
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+</script>
|
|
|
|
|
+
|
|
|
|
|
+<style lang="less" scoped>
|
|
|
|
|
+.my-tabs {
|
|
|
|
|
+ height: 100%;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.my-tabs :deep(.el-tabs__content) {
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+ overflow: hidden;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.my-tabs :deep(.el-tab-pane) {
|
|
|
|
|
+ height: 100%;
|
|
|
|
|
+ overflow: auto;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.history-table {
|
|
|
|
|
+ height: calc(100% - 4px);
|
|
|
|
|
+}
|
|
|
|
|
+</style>
|