Browse Source

feat: 新增应季食材管理后台页面

AdminSeasonalController + SeasonalFoods.vue,管理端食材应季月份配置

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
E2E Test Bot 2 months ago
parent
commit
52abf5b3bc

+ 49 - 0
cfc-backend/src/main/java/com/etotem/cfc/controller/admin/AdminSeasonalController.java

@@ -0,0 +1,49 @@
+package com.etotem.cfc.controller.admin;
+
+import com.etotem.cfc.common.Result;
+import com.etotem.cfc.entity.SeasonalFood;
+import com.etotem.cfc.service.SeasonalFoodService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+import java.time.LocalDate;
+import java.util.List;
+import java.util.Map;
+
+@Tag(name = "\u7ba1\u7406\u7aef-\u5e94\u5b63\u98df\u6750\u914d\u7f6e")
+@RestController
+@RequestMapping("/api/admin/seasonal")
+public class AdminSeasonalController {
+
+    @Resource
+    private SeasonalFoodService seasonalFoodService;
+
+    @Operation(summary = "\u5e94\u5b63\u5217\u8868")
+    @PostMapping("/list")
+    public Result<List<SeasonalFood>> list(@RequestBody Map<String, Object> params) {
+        Integer month = params.get("month") != null
+                ? Integer.valueOf(params.get("month").toString()) : null;
+        if (month != null) {
+            return Result.success(seasonalFoodService.listByMonth(month));
+        }
+        return Result.success(seasonalFoodService.listByMonth(LocalDate.now().getMonthValue()));
+    }
+
+    @Operation(summary = "\u65b0\u589e\u5e94\u5b63\u6620\u5c04")
+    @PostMapping("/create")
+    public Result<String> create(@RequestBody SeasonalFood seasonalFood) {
+        seasonalFoodService.create(seasonalFood);
+        return Result.success("ok");
+    }
+
+    @Operation(summary = "\u5220\u9664\u5e94\u5b63\u6620\u5c04")
+    @PostMapping("/delete")
+    public Result<String> delete(@RequestBody Map<String, Object> params) {
+        Long id = params.get("id") != null ? Long.valueOf(params.get("id").toString()) : null;
+        if (id == null) return Result.error("id\u4e0d\u80fd\u4e3a\u7a7a");
+        seasonalFoodService.delete(id);
+        return Result.success("ok");
+    }
+}

+ 169 - 0
cfc-web/src/views/admin/SeasonalFoods.vue

@@ -0,0 +1,169 @@
+<template>
+  <div class="seasonal-foods admin-page">
+    <el-card>
+      <div slot="header" class="admin-page-header">
+        <span class="admin-page-title">应季食材配置</span>
+        <div class="flex items-center gap-sm">
+          <el-select
+            v-model="monthFilter"
+            placeholder="选择月份"
+            size="mini"
+            @change="loadList"
+          >
+            <el-option v-for="m in 12" :key="m" :label="m + '月'" :value="m" />
+          </el-select>
+          <el-input v-model="keyword" placeholder="搜索..." prefix-icon="el-icon-search" clearable @keyup.enter.native="handleSearch" @clear="handleSearch" />
+          <el-button type="primary" size="mini" @click="openCreate">新增应季映射</el-button>
+        </div>
+      </div>
+      <div class="table-scroll-wrap">
+      <el-table :data="list" v-loading="loading" border stripe :max-height="tableHeight">
+        <el-table-column prop="id" label="ID" width="80" />
+        <el-table-column prop="foodId" label="食材ID" width="80" />
+        <el-table-column label="月份" width="80">
+          <template slot-scope="{ row }">{{ row.month }}月</template>
+        </el-table-column>
+        <el-table-column label="推荐等级" width="100">
+          <template slot-scope="{ row }">
+            <el-tag :type="row.peak >= 3 ? 'danger' : row.peak >= 2 ? 'warning' : 'info'" size="mini">
+              {{ row.peak }}
+            </el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column label="操作" width="200" fixed="right">
+          <template slot-scope="{ row }">
+            <el-dropdown trigger="hover" @command="(cmd) => handleActionCmd(row, cmd)">
+              <el-button size="mini">
+                更多<i class="el-icon-arrow-down el-icon--right"></i>
+              </el-button>
+              <el-dropdown-menu slot="dropdown">
+                <el-dropdown-item command="delete" icon="el-icon-delete">删除</el-dropdown-item>
+              </el-dropdown-menu>
+            </el-dropdown>
+          </template>
+        </el-table-column>
+      </el-table></div>
+    </el-card>
+
+    <el-dialog title="新增应季映射" :visible.sync="dialogVisible" width="400px">
+      <el-form label-width="100px">
+        <el-form-item label="食材ID">
+          <el-input-number v-model="form.foodId" :min="1" style="width:100%" />
+        </el-form-item>
+        <el-form-item label="月份">
+          <el-select v-model="form.month" style="width:100%">
+            <el-option v-for="m in 12" :key="m" :label="m + '月'" :value="m" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="推荐等级">
+          <el-select v-model="form.peak" style="width:100%">
+            <el-option label="1-普通" :value="1" />
+            <el-option label="2-推荐" :value="2" />
+            <el-option label="3-强烈推荐" :value="3" />
+          </el-select>
+        </el-form-item>
+      </el-form>
+      <div slot="footer">
+        <el-button @click="dialogVisible = false">取消</el-button>
+        <el-button type="primary" :loading="submitting" @click="saveItem">保存</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'SeasonalFoods',
+  computed: {
+    tableHeight() {
+      return window.innerHeight - 300
+    }
+  },
+  data() {
+    const now = new Date()
+    return {
+      submitting: false,
+      loading: false,
+      list: [],
+      dialogVisible: false,
+      keyword: '',
+      monthFilter: now.getMonth() + 1,
+      form: {
+        foodId: null,
+        month: now.getMonth() + 1,
+        peak: 1
+      }
+    }
+  },
+  created() {
+    this.loadList()
+  },
+  methods: {
+    async loadList() {
+      this.loading = true
+      try {
+        const baseURL = process.env.VUE_APP_BASE_API || ''
+        const token = localStorage.getItem('token')
+        const axios = this.$axios || (await import('axios')).default
+        const res = await axios.post(baseURL + '/api/admin/seasonal/list', { month: this.monthFilter, keyword: this.keyword }, {
+          headers: { Authorization: 'Bearer ' + token }
+        })
+        this.list = res.data && res.data.data || []
+      } catch (e) {
+        this.$message && this.$message.error && this.$message.error('加载失败')
+      } finally {
+        this.loading = false
+      }
+    },
+    handleSearch() {
+      this.loadList()
+    },
+    openCreate() {
+      this.form = { foodId: null, month: new Date().getMonth() + 1, peak: 1 }
+      this.dialogVisible = true
+    },
+    async saveItem() {
+      if (!this.form.foodId) {
+        this.$message && this.$message.warning && this.$message.warning('请输入食材ID')
+        return
+      }
+      try {
+        const baseURL = process.env.VUE_APP_BASE_API || ''
+        const token = localStorage.getItem('token')
+        const axios = this.$axios || (await import('axios')).default
+        await axios.post(baseURL + '/api/admin/seasonal/create', this.form, {
+          headers: { Authorization: 'Bearer ' + token }
+        })
+        this.$message && this.$message.success && this.$message.success('创建成功')
+        this.dialogVisible = false
+        this.loadList()
+      } catch (e) {
+        this.$message && this.$message.error && this.$message.error('保存失败')
+      }
+    },
+    async deleteItem(row) {
+      this.$confirm('确定删除该应季映射吗?', '提示', { type: 'warning' }).then(async () => {
+        try {
+          const baseURL = process.env.VUE_APP_BASE_API || ''
+          const token = localStorage.getItem('token')
+          const axios = this.$axios || (await import('axios')).default
+          await axios.post(baseURL + '/api/admin/seasonal/delete', { id: row.id }, {
+            headers: { Authorization: 'Bearer ' + token }
+          })
+          this.$message && this.$message.success && this.$message.success('删除成功')
+          this.loadList()
+        } catch (e) {
+          this.$message && this.$message.error && this.$message.error('删除失败')
+        }
+      }).catch(() => {})
+    },
+    handleActionCmd(row, cmd) {
+      switch (cmd) {
+        case 'delete':
+          this.deleteItem(row)
+          break
+      }
+    }
+  }
+}
+</script>