ソースを参照

fix: 营养素管理

sh 4 年 前
コミット
3b8c042f7c

+ 18 - 4
src/router/config.js

@@ -123,15 +123,29 @@ export const asyncRouterMap = [
     ]
   },
 
+
+  {
+    path: '/food2',
+    component: Layout,
+    children: [
+      {
+        path: '',
+        name: '食物管理',
+        component: () => import('@/views/food/index'),
+        meta: { title: '食物管理', icon: 'food', admin: true }
+      }
+    ]
+  },
+
   {
     path: '/nutrient',
     component: Layout,
     children: [
       {
         path: '',
-        name: '营养素列表',
+        name: '营养素管理',
         component: () => import('@/views/nutrient/index'),
-        meta: { title: '营养素列表', icon: 'nutrient', admin: true }
+        meta: { title: '营养素管理', icon: 'nutrient', admin: true }
       }
     ]
   },
@@ -142,9 +156,9 @@ export const asyncRouterMap = [
     children: [
       {
         path: '',
-        name: '单位列表',
+        name: '单位管理',
         component: () => import('@/views/unit/index'),
-        meta: { title: '单位列表', icon: 'unit', admin: true }
+        meta: { title: '单位管理', icon: 'unit', admin: true }
       }
     ]
   },

+ 93 - 0
src/views/components/DataSelect/index.vue

@@ -0,0 +1,93 @@
+<template>
+  <span>
+    <el-select
+      v-model="selectValue"
+      filterable
+      remote
+      :placeholder="placeholder||''"
+      :remote-method="search"
+      size="mini"
+      style="width:100%"
+      clearable
+      @change="onChange">
+      <el-option v-if="!!value&&!!label&&!options.find(v=>v.value==value)" :key="value" :label="label" :value="value" />
+      <el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value" />
+    </el-select>
+  </span>
+</template>
+
+<script>
+import { fGet } from '@/api/rest'
+
+export default {
+  props: {
+    url: {
+      type: String,
+      required: true
+    },
+    placeholder: {
+      type: String,
+      required: false
+    },
+    value: {
+      type: String,
+      required: false
+    },
+    label: {
+      type: String,
+      required: false
+    }
+  },
+  watch: {
+    url(newVal) {
+      this.search()
+    },
+    value(newVal) {
+      this.selectValue = newVal
+      this.search()
+    }
+  },
+  data() {
+    return {
+      options: [],
+      selectValue: undefined
+    }
+  },
+  created() {
+    this.selectValue = this.value
+    this.search()
+  },
+  methods: {
+    search(query) {
+      if (!!!this.url) {
+        return
+      }
+      fGet(this.url, { name: query || '' }).then(res => {
+        this.options = res.data.map(v => {
+          return {
+            value: v.id,
+            label: v.name,
+            _data: v
+          }
+        })
+      })
+    },
+    onChange() {
+      if (!!this.selectValue) {
+        this.$emit('update:value', this.selectValue)
+        const data = this.options.find(v => v.value == this.selectValue)
+        if (!!data) {
+          this.$emit('update:label', data.label)
+          this.$emit('select', data)
+        } else {
+          this.$emit('update:label', data.label)
+          this.$emit('select', data)
+        }
+        return
+      }
+      this.$emit('update:value', undefined)
+      this.$emit('update:label', undefined)
+    }
+  }
+}
+</script>

+ 443 - 0
src/views/food/index-bak.vue

@@ -0,0 +1,443 @@
+<template>
+  <div v-loading="uploadLoading" class="app-container">
+    <div class="filter-container">
+      <el-row>
+        <el-input
+          v-model="listQuery.query"
+          placeholder="请输入检索词"
+          style="width: 200px;"
+          class="filter-item"
+          @keyup.enter.native="fetchData()"
+        />
+        <el-select v-if="isUsda" v-model="selectType" style="width: 160px;" class="filter-item">
+          <el-option v-for="item in usdaTypes" :key="item.value" :label="item.label" :value="item.value" />
+        </el-select>
+        <el-select v-if="foodSourceIsAll" v-model="selectType" clearable style="width: 160px;" class="filter-item">
+          <el-option v-for="item in allTypes" :key="item.value" :label="item.label" :value="item.value" />
+        </el-select>
+        <el-button class="filter-item" type="primary" icon="el-icon-search" @click="fetchData">
+          检索
+        </el-button>
+        <el-select v-model="listQuery.orderBy" style="width: 160px;margin-left: 10px;" class="filter-item" @change="fetchData">
+          <el-option v-for="item in sortOptions" :key="item.key" :label="item.label" :value="item.key" />
+        </el-select>
+
+        <!-- <span style="color:gray;font-size:14px;margin-left:15px;">营养物语环境</span>
+        <el-select v-model="yywyEnv" style="width:100px;margin-left:10px;" class="filter-item" placeholder="营养物语环境">
+          <el-option label="dev" value="dev" />
+          <el-option label="prod" value="prod" />
+          <el-option label="test" value="test" />
+          <el-option label="local" value="local" />
+        </el-select> -->
+
+        <el-button size="mini" class="filter-item" @click="queryToYYWYProgrees">查询最后一条同步结果(F12查看详情)</el-button>
+
+        <el-button
+          class="filter-item"
+          style="float: right;"
+          type="success"
+          icon="el-icon-circle-plus-outline"
+          @click="handleCreate"
+        >
+          新建
+        </el-button>
+        <el-button
+          class="filter-item"
+          style="margin-right: 10px; float: right;"
+          type="primary"
+          @click="handleImport"
+        >
+          食物导入
+        </el-button>
+      </el-row>
+    </div>
+
+    <el-table
+      :key="tableKey"
+      v-loading="listLoading"
+      :data="list"
+      border
+      fit
+      highlight-current-row
+      style="width: 100%;"
+      :max-height="maxHeight"
+      @row-dblclick="handleFoodClick"
+    >
+      <el-table-column type="index" label="序号" align="center" fixed width="60px" />
+      <el-table-column label="名称" fixed width="200px">
+        <template slot-scope="{row}">
+          <span>{{ row.name }}</span>
+          <br>
+          <el-button :loading="row._to_yywy" size="mini" @click="toYYWY(row)">同步至营养物语</el-button>
+        </template>
+      </el-table-column>
+      <el-table-column label="图片" align="center" width="180">
+        <template slot-scope="{row}">
+          <el-image
+            v-if="row.mainImage"
+            style="width: 150px; height: 100px"
+            :src="row.mainImage"
+            fit="contain"
+          />
+        </template>
+      </el-table-column>
+      <el-table-column label="自定义ID" align="center" width="100px">
+        <template slot-scope="{row}">
+          <span>{{ row.id2 }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="描述" align="center" width="200">
+        <template slot-scope="{row}">
+          <span>{{ row.description }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="食物类型" align="center" width="80">
+        <template slot-scope="{row}">
+          <span>{{ row.foodType | foodTypeFilter }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="ep(%)" align="center" width="70px">
+        <template slot-scope="{row}">
+          <span>{{ row.ep }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="GI" align="center" width="70px">
+        <template slot-scope="{row}">
+          <span>{{ row.gi }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="GL" align="center" width="70px">
+        <template slot-scope="{row}">
+          <span>{{ row.gl ? row.gl : row.glCalcd }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="数据来源" align="center" width="100px">
+        <template slot-scope="{row}">
+          <span>{{ row.dataSource }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="创建人" align="center" width="100px">
+        <template slot-scope="{row}">
+          <span>{{ row.userName }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="创建时间" width="180px" align="center">
+        <template slot-scope="{row}">
+          <span>{{ row.createTime }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="更新时间" width="180px" align="center">
+        <template slot-scope="{row}">
+          <span>{{ row.updateTime }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="操作" align="center" class-name="small-padding" fixed="right" width="90">
+        <template slot-scope="{row}">
+          <el-dropdown @command="handleCommand">
+            <el-button type="primary" @click.stop="{}">
+              操作<i class="el-icon-arrow-down el-icon--right" />
+            </el-button>
+            <el-dropdown-menu slot="dropdown">
+              <el-dropdown-item :command="{row: row, command: 'update'}">更新/查看</el-dropdown-item>
+              <el-dropdown-item :command="{row: row, command: 'copy'}">复制</el-dropdown-item>
+              <el-dropdown-item :command="{row: row, command: 'manageNutrients'}">营养素关联</el-dropdown-item>
+              <el-dropdown-item :command="{row: row, command: 'manageUnits'}">单位管理</el-dropdown-item>
+              <el-dropdown-item :command="{row: row, command: 'manageModifiers'}">规格管理</el-dropdown-item>
+              <el-dropdown-item :disabled="!canDelete(row)" :command="{row: row, command: 'delete'}">删除</el-dropdown-item>
+            </el-dropdown-menu>
+          </el-dropdown>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <pagination
+      v-show="total>0"
+      :total="total"
+      :page.sync="listQuery.pageNum"
+      :limit.sync="listQuery.pageSize"
+      @pagination="fetchData"
+    />
+
+    <el-dialog title="食物导入" width="40%" :visible.sync="dialogFormVisible">
+      <el-upload
+        ref="upload"
+        action=""
+        :auto-upload="false"
+        :multiple="false"
+        drag
+        :limit="1"
+        :show-file-list="true"
+        :on-change="fileChanged"
+      >
+        <i class="el-icon-upload" />
+        <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
+      </el-upload>
+      <div slot="footer">
+        <el-button @click="dialogFormVisible = false">
+          取消
+        </el-button>
+        <el-button type="primary" @click="importFoods">
+          开始导入
+        </el-button>
+      </div>
+    </el-dialog>
+
+    <el-dialog title="复制食物" width="40%" :visible.sync="copyDialogVisible">
+      <el-form ref="dialogForm" label-position="left" label-width="90px" style="width: 80%; margin-left:50px;">
+        <el-form-item label="新食物名称: " prop="foodName">
+          <el-input v-model="params.foodName" placeholder="请输入新食物名称" />
+        </el-form-item>
+      </el-form>
+      <div slot="footer">
+        <el-button @click="copyDialogVisible = false">
+          取消
+        </el-button>
+        <el-button type="primary" @click="copyFood">
+          确认
+        </el-button>
+      </div>
+    </el-dialog>
+
+    <el-dialog title="食物二维码" width="250" :visible.sync="foodQRDialogVisible">
+      <vue-qr
+        :text="foodQRParams.url"
+        :bg-src="foodQRParams.bgImg"
+        :logo-src="foodQRParams.logoImg"
+        :size="200"
+        :correct-level="3"
+        color-dark="#000"
+        color-light="#fff"
+        :dot-scale="1"
+      />
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import Pagination from '@/components/Pagination'
+import { getList, remove, copyFromSimilarFood } from '@/api/food'
+import axios from 'axios'
+import { getToken } from '@/utils/auth'
+import { getFoodSourceByUrl, isUsda } from '@/utils/food-utils'
+import store from '@/store'
+import VueQr from 'vue-qr'
+
+const foodTypes = { 0: '主材', 1: '辅材' }
+
+export default {
+  name: 'FoodList',
+  components: { Pagination, VueQr },
+  filters: {
+    foodTypeFilter(value) {
+      return foodTypes[value]
+    }
+  },
+  data() {
+    return {
+      tableKey: 0,
+      total: 0,
+      dialogStatus: '',
+      dialogFormVisible: false,
+      sortOptions: [{ key: 0, label: '按创建时间倒序排列' }, { key: 1, label: '按ID顺序排列' }],
+      list: [],
+      listLoading: true,
+      maxHeight: 600,
+      listQuery: {
+        query: '',
+        orderBy: 0,
+        pageNum: 1,
+        pageSize: 20
+      },
+      uploadFile: null,
+      uploadLoading: false,
+      copyDialogVisible: false,
+      params: {},
+      foodSource: '',
+      foodQRDialogVisible: false,
+      foodQRParams: {},
+      isUsda: false,
+      foodSourceIsAll: false,
+      usdaTypes: [{ 'label': 'Foundation', 'value': 'USDA_FOUNDATION' },
+        { 'label': 'SR Legacy', 'value': 'USDA_SR_LEGACY' }, { 'label': 'Branded', 'value': 'USDA_BRANDED' },
+        { 'label': 'FNDDS', 'value': 'USDA_FNDDS' }],
+      allTypes: [{ 'label': '全部', 'value': 'ALL' }, { 'label': '录入', 'value': 'ENTRY' },
+        { 'label': 'CFCT', 'value': 'CFCT' }, { 'label': 'Foundation', 'value': 'USDA_FOUNDATION' },
+        { 'label': 'SR Legacy', 'value': 'USDA_SR_LEGACY' }, { 'label': 'Branded', 'value': 'USDA_BRANDED' },
+        { 'label': 'FNDDS', 'value': 'USDA_FNDDS' }],
+      selectType: '',
+      yywyEnv: 'dev'
+    }
+  },
+  created() {
+    this.maxHeight = document.documentElement.clientHeight - 205
+    this.foodSource = getFoodSourceByUrl(this.$route.path)
+    if (this.foodSource === 'ENTRY' || this.foodSource === '') {
+      this.listQuery.orderBy = 0
+    } else {
+      this.listQuery.orderBy = 1
+    }
+    this.isUsda = isUsda(this.foodSource)
+    if (this.isUsda) {
+      this.foodSource = this.foodSource === 'USDA' ? 'USDA_SR_LEGACY' : this.foodSource
+      this.selectType = this.foodSource
+    } else {
+      this.foodSourceIsAll = this.foodSource === ''
+    }
+    this.fetchData()
+    if (location.href.indexOf('test') > 0) {
+      this.yywyEnv = 'test'
+    }
+  },
+  methods: {
+    fetchData() {
+      this.listLoading = true
+      this.listQuery.foodSource = this.foodSource
+      if (this.isUsda) {
+        if (!this.selectType || this.selectType === 'ALL') {
+          this.listQuery.foodSource = 'USDA'
+        } else {
+          this.listQuery.foodSource = this.selectType
+        }
+      } else if (this.foodSourceIsAll) {
+        if (!this.selectType || this.selectType === 'ALL') {
+          this.listQuery.foodSource = ''
+        } else {
+          this.listQuery.foodSource = this.selectType
+        }
+      }
+      getList(this.listQuery).then(response => {
+        this.list = response.data.list.map(v => {
+          v._to_yywy = false
+          return v
+        })
+        this.total = response.data.count
+        this.listLoading = false
+      })
+    },
+    toYYWY(row) {
+      row._to_yywy = true
+      const headers = {
+        headers: { 'Authorization': getToken() },
+        timeout: 1000 * 60 * 3
+      }
+      axios.post(`/api/foods/${row.id}/to-yywy?env=${this.yywyEnv}`, {}, headers).then(res => {
+        const data = res.data || {}
+        this.$notify.success(`同步结果:${res.status};` + (data.foodId || '') + ',' + (data.rsp || ''))
+      }).catch(res => {
+        this.$message.error(res.response.data.message)
+      }).finally(() => {
+        row._to_yywy = false
+      })
+    },
+    queryToYYWYProgrees() {
+      const headers = {
+        headers: { 'Authorization': getToken() },
+        timeout: 1000 * 60 * 3
+      }
+      axios.get(`/api/foods/last-to-yywy`, headers).then(res => {
+        const data = res.data || {}
+        console.log(data)
+        this.$notify.success(`同步结果:${res.status};` + (data.foodId || '') + ',' + (data.rsp || ''))
+      })
+    },
+    handleCreate() {
+      this.$router.push({ path: '/food/create' })
+    },
+    handleUpdate(row) {
+      this.$router.push({ path: '/food/edit/' + row.id })
+    },
+    manageNutrients(row) {
+      this.$router.push({ path: `/food/${row.id}/nutrient` })
+    },
+    manageUnits(row) {
+      this.$router.push({ path: `/food/${row.id}/unit` })
+    },
+    manageModifiers(row) {
+      this.$router.push({ path: `/food/${row.id}/modifier` })
+    },
+    handleDelete(row, index) {
+      remove(row.id).then(res => {
+        this.$notify({ title: '成功', message: '删除成功', type: 'success', duration: 2000 })
+        this.list.splice(index, 1)
+        this.fetchData()
+      }).catch(res => {
+        this.$message.error(res.data.message)
+      })
+    },
+    handleImport() {
+      if (this.$refs.upload) {
+        this.$refs.upload.clearFiles()
+      }
+      this.uploadFile = null
+      this.dialogFormVisible = true
+    },
+    importFoods() {
+      const reqConfig = { headers: { 'Authorization': getToken(), 'Content-Type': 'multipart/form-data' },
+        timeout: 1000 * 60 * 3 }
+      const importUrl = process.env.VUE_APP_BASE_API + '/api/foods/import'
+      this.uploadLoading = true
+      this.dialogFormVisible = false
+      axios.post(importUrl, this.uploadFile, reqConfig).then(res => {
+        this.$notify.success('导入成功')
+        this.uploadLoading = false
+      }).catch(res => {
+        this.$message.error(res.response.data.message)
+        this.uploadLoading = false
+      })
+    },
+    fileChanged(file, fileList) {
+      const fromData = new FormData()
+      fromData.append('file', file.raw)
+      this.uploadFile = fromData
+    },
+    handleCopy(data) {
+      this.params = { foodId: data.id }
+      this.copyDialogVisible = true
+    },
+    copyFood() {
+      copyFromSimilarFood(this.params).then(res => {
+        this.$notify.success('提交成功')
+        this.$router.push({ path: `/food/edit/${res.data}` })
+      }).catch(res => {
+        this.$message.error(res.data.message)
+      })
+    },
+    handleCommand(data) {
+      switch (data.command) {
+        case 'update':
+          this.handleUpdate(data.row)
+          break
+        case 'copy':
+          this.handleCopy(data.row)
+          break
+        case 'manageNutrients':
+          this.manageNutrients(data.row)
+          break
+        case 'manageUnits':
+          this.manageUnits(data.row)
+          break
+        case 'manageModifiers':
+          this.manageModifiers(data.row)
+          break
+        case 'delete':
+          this.handleDelete(data.row)
+          break
+      }
+    },
+    canDelete(data) {
+      return store.getters.isAdmin || data && data.userId === store.getters.userId
+    },
+    handleFoodClick(row) {
+      this.foodQRParams = { url: process.env.VUE_APP_H5_URL + `/food/index.html?id=${row.id}`,
+        bgImg: row.mainImage ? row.mainImage : '',
+        logoImg: row.mainImage ? row.mainImage : ''
+      }
+      this.foodQRDialogVisible = true
+    }
+  }
+}
+</script>
+
+<style scoped>
+
+</style>

+ 206 - 363
src/views/food/index.vue

@@ -1,443 +1,286 @@
 <template>
-  <div v-loading="uploadLoading" class="app-container">
+  <div class="app-container">
     <div class="filter-container">
-      <el-row>
-        <el-input
-          v-model="listQuery.query"
-          placeholder="请输入检索词"
-          style="width: 200px;"
-          class="filter-item"
-          @keyup.enter.native="fetchData()"
-        />
-        <el-select v-if="isUsda" v-model="selectType" style="width: 160px;" class="filter-item">
-          <el-option v-for="item in usdaTypes" :key="item.value" :label="item.label" :value="item.value" />
-        </el-select>
-        <el-select v-if="foodSourceIsAll" v-model="selectType" clearable style="width: 160px;" class="filter-item">
-          <el-option v-for="item in allTypes" :key="item.value" :label="item.label" :value="item.value" />
-        </el-select>
-        <el-button class="filter-item" type="primary" icon="el-icon-search" @click="fetchData">
-          检索
-        </el-button>
-        <el-select v-model="listQuery.orderBy" style="width: 160px;margin-left: 10px;" class="filter-item" @change="fetchData">
-          <el-option v-for="item in sortOptions" :key="item.key" :label="item.label" :value="item.key" />
-        </el-select>
-
-        <!-- <span style="color:gray;font-size:14px;margin-left:15px;">营养物语环境</span>
-        <el-select v-model="yywyEnv" style="width:100px;margin-left:10px;" class="filter-item" placeholder="营养物语环境">
-          <el-option label="dev" value="dev" />
-          <el-option label="prod" value="prod" />
-          <el-option label="test" value="test" />
-          <el-option label="local" value="local" />
-        </el-select> -->
+      <el-select v-model="list.main.query.baseUnit" class="filter-item" size="mini" placeholder="食物类型" style="width:150px;margin-right:5px;" clearable>
+        <el-option label="是" :value="true" />
+        <el-option label="否" :value="false" />
+      </el-select>
+      <el-select v-model="list.main.query.baseUnit" class="filter-item" size="mini" placeholder="食物类别" style="width:150px;margin-right:5px;" clearable>
+        <el-option label="是" :value="true" />
+        <el-option label="否" :value="false" />
+      </el-select>
+      <el-input v-model="list.main.query.name" class="filter-item" size="mini" placeholder="名称" style="width:150px;margin-right:5px;" clearable />
+      <el-input v-model="list.main.query.enName" class="filter-item" size="mini" placeholder="英文名称" style="width:150px;margin-right:5px;" clearable />
+      <el-select v-model="list.main.query.baseUnit" class="filter-item" size="mini" placeholder="状态" style="width:150px;margin-right:5px;" clearable>
+        <el-option label="是" :value="true" />
+        <el-option label="否" :value="false" />
+      </el-select>
+      <el-button class="filter-item" size="mini" type="primary" @click="M.list()">查询</el-button>
+      <el-button class="filter-item" size="mini" @click="() => {M.reset({ data: { page: 1, limit: list.main.query.limit } })}">重置</el-button>
 
-        <el-button size="mini" class="filter-item" @click="queryToYYWYProgrees">查询最后一条同步结果(F12查看详情)</el-button>
-
-        <el-button
-          class="filter-item"
-          style="float: right;"
-          type="success"
-          icon="el-icon-circle-plus-outline"
-          @click="handleCreate"
-        >
-          新建
-        </el-button>
-        <el-button
-          class="filter-item"
-          style="margin-right: 10px; float: right;"
-          type="primary"
-          @click="handleImport"
-        >
-          食物导入
-        </el-button>
-      </el-row>
+      <div style="float:right;">
+        <el-button class="filter-item" size="mini" type="success" style="margin-right:15px;" @click="create">新增</el-button>
+      </div>
     </div>
-
-    <el-table
-      :key="tableKey"
-      v-loading="listLoading"
-      :data="list"
-      border
-      fit
-      highlight-current-row
-      style="width: 100%;"
-      :max-height="maxHeight"
-      @row-dblclick="handleFoodClick"
-    >
-      <el-table-column type="index" label="序号" align="center" fixed width="60px" />
-      <el-table-column label="名称" fixed width="200px">
+    <el-table v-loading="list.main.loading" :data="list.main.list" border fit highlight-current-row style="width:100%" :max-height="maxHeight">
+      <el-table-column width="50" header-align="center" label="权重">
         <template slot-scope="{row}">
-          <span>{{ row.name }}</span>
-          <br>
-          <el-button :loading="row._to_yywy" size="mini" @click="toYYWY(row)">同步至营养物语</el-button>
+          <span>{{ row.sortOrder }}</span>
         </template>
       </el-table-column>
-      <el-table-column label="图片" align="center" width="180">
+      <el-table-column width="70" align="center" label="图片">
         <template slot-scope="{row}">
-          <el-image
-            v-if="row.mainImage"
-            style="width: 150px; height: 100px"
-            :src="row.mainImage"
-            fit="contain"
-          />
+          <el-image v-if="row.mainImage" style="width:50px;height:50px;" :src="row.mainImage" fit="contain" />
         </template>
       </el-table-column>
-      <el-table-column label="自定义ID" align="center" width="100px">
+      <el-table-column min-width="150" header-align="center" label="名称">
         <template slot-scope="{row}">
-          <span>{{ row.id2 }}</span>
+          <el-tag size="mini" effect="plain">类型</el-tag><span>{{ row.name }}</span>
         </template>
       </el-table-column>
-      <el-table-column label="描述" align="center" width="200">
+      <el-table-column min-width="180" header-align="center" label="英文名称">
         <template slot-scope="{row}">
-          <span>{{ row.description }}</span>
+          <span>{{ row.enName }}</span>
         </template>
       </el-table-column>
-      <el-table-column label="食物类型" align="center" width="80">
+      <el-table-column width="80" header-align="center" align="right" label="可食部(%)">
         <template slot-scope="{row}">
-          <span>{{ row.foodType | foodTypeFilter }}</span>
+          <span v-if="row.ep>0">{{ row.ep }}%</span>
+          <span v-else>-</span>
         </template>
       </el-table-column>
-      <el-table-column label="ep(%)" align="center" width="70px">
+      <el-table-column width="80" header-align="center" align="right" label="GI">
         <template slot-scope="{row}">
-          <span>{{ row.ep }}</span>
+          <span>{{ row.gi }}</span>
         </template>
       </el-table-column>
-      <el-table-column label="GI" align="center" width="70px">
+      <el-table-column width="80" header-align="center" align="right" label="GL">
         <template slot-scope="{row}">
-          <span>{{ row.gi }}</span>
+          <span>{{ row.gl }}</span>
         </template>
       </el-table-column>
-      <el-table-column label="GL" align="center" width="70px">
+      <el-table-column width="100" align="center" label="来源">
         <template slot-scope="{row}">
-          <span>{{ row.gl ? row.gl : row.glCalcd }}</span>
+          <span>{{ row.source }}</span>
         </template>
       </el-table-column>
-      <el-table-column label="数据来源" align="center" width="100px">
+      <el-table-column width="60" align="center" label="状态">
         <template slot-scope="{row}">
-          <span>{{ row.dataSource }}</span>
+          <span v-if="row.shelfStatus=='ONLINE'" style="color:green;">上架</span>
+          <span v-else style="color:gray;">下架</span>
         </template>
       </el-table-column>
-      <el-table-column label="创建人" align="center" width="100px">
+      <el-table-column width="80" align="center" label="创建人">
         <template slot-scope="{row}">
-          <span>{{ row.userName }}</span>
+          <span>{{ row.userName || '-' }}</span>
         </template>
       </el-table-column>
-      <el-table-column label="创建时间" width="180px" align="center">
+      <el-table-column width="140" align="center" label="创建时间">
         <template slot-scope="{row}">
           <span>{{ row.createTime }}</span>
         </template>
       </el-table-column>
-      <el-table-column label="更新时间" width="180px" align="center">
+      <el-table-column width="140" align="center" label="更新时间">
         <template slot-scope="{row}">
           <span>{{ row.updateTime }}</span>
         </template>
       </el-table-column>
-      <el-table-column label="操作" align="center" class-name="small-padding" fixed="right" width="90">
+      <el-table-column width="60" align="center" label="操作" fixed="right">
         <template slot-scope="{row}">
-          <el-dropdown @command="handleCommand">
-            <el-button type="primary" @click.stop="{}">
-              操作<i class="el-icon-arrow-down el-icon--right" />
-            </el-button>
-            <el-dropdown-menu slot="dropdown">
-              <el-dropdown-item :command="{row: row, command: 'update'}">更新/查看</el-dropdown-item>
-              <el-dropdown-item :command="{row: row, command: 'copy'}">复制</el-dropdown-item>
-              <el-dropdown-item :command="{row: row, command: 'manageNutrients'}">营养素关联</el-dropdown-item>
-              <el-dropdown-item :command="{row: row, command: 'manageUnits'}">单位管理</el-dropdown-item>
-              <el-dropdown-item :command="{row: row, command: 'manageModifiers'}">规格管理</el-dropdown-item>
-              <el-dropdown-item :disabled="!canDelete(row)" :command="{row: row, command: 'delete'}">删除</el-dropdown-item>
-            </el-dropdown-menu>
-          </el-dropdown>
+          <i class="el-icon-edit" style="cursor:pointer;color:#999999;margin-right:5px;" @click="edit(row)"></i>
+          &nbsp;&nbsp;
+          <i class="el-icon-delete" style="cursor:pointer;color:#999999;m"></i>
         </template>
       </el-table-column>
     </el-table>
+    <pagination v-show="list.main.total>0" :total="list.main.total" :page.sync="list.main.query.page" :limit.sync="list.main.query.limit" @pagination="M.list()" />
 
-    <pagination
-      v-show="total>0"
-      :total="total"
-      :page.sync="listQuery.pageNum"
-      :limit.sync="listQuery.pageSize"
-      @pagination="fetchData"
-    />
-
-    <el-dialog title="食物导入" width="40%" :visible.sync="dialogFormVisible">
-      <el-upload
-        ref="upload"
-        action=""
-        :auto-upload="false"
-        :multiple="false"
-        drag
-        :limit="1"
-        :show-file-list="true"
-        :on-change="fileChanged"
-      >
-        <i class="el-icon-upload" />
-        <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
-      </el-upload>
-      <div slot="footer">
-        <el-button @click="dialogFormVisible = false">
-          取消
-        </el-button>
-        <el-button type="primary" @click="importFoods">
-          开始导入
-        </el-button>
-      </div>
-    </el-dialog>
-
-    <el-dialog title="复制食物" width="40%" :visible.sync="copyDialogVisible">
-      <el-form ref="dialogForm" label-position="left" label-width="90px" style="width: 80%; margin-left:50px;">
-        <el-form-item label="新食物名称: " prop="foodName">
-          <el-input v-model="params.foodName" placeholder="请输入新食物名称" />
-        </el-form-item>
+    <el-dialog :title="dlg.form.title" :visible.sync="dlg.form.visible" width="80%" top="3vh">
+      <el-form ref="form" :rules="dlg.form.rules" :model="dlg.form.data" size="mini" label-position="right" label-width="100px">
+        <el-tabs v-model="dlg.form.infoTab">
+          <el-tab-pane label="食物基本信息" name="base">
+            <el-form-item label="主/辅材" prop="category">
+              <el-input v-model="dlg.form.data.category" placeholder="请输入单位类别" />
+            </el-form-item>
+            <el-form-item label="食物类别" prop="name">
+              <el-input v-model="dlg.form.data.name" placeholder="请输入单位名称,不能重复" />
+            </el-form-item>
+            <el-form-item label="名称" prop="name">
+              <el-input v-model="dlg.form.data.name" placeholder="请输入单位名称,不能重复" />
+            </el-form-item>
+            <el-form-item label="描述">
+              <el-input v-model="dlg.form.data.code" type="textarea" rows="3" placeholder="请输入单位编码,可为空,但不能重复" />
+            </el-form-item>
+            <el-form-item v-if="!dlg.form.data.baseUnit" label="可食部" required>
+              <el-input v-model="dlg.form.data.ratio" placeholder="百分比">
+                <span slot="append">%</span>
+              </el-input>
+            </el-form-item>
+            <el-form-item label="GI">
+              <el-input v-model="dlg.form.data.name" placeholder="请输入单位名称,不能重复" />
+            </el-form-item>
+            <el-form-item label="GL">
+              <el-input v-model="dlg.form.data.name" placeholder="请输入单位名称,不能重复" />
+            </el-form-item>
+            <el-form-item label="密度">
+              <el-input v-model="dlg.form.data.name" placeholder="请输入单位名称,不能重复" />
+            </el-form-item>
+            <el-form-item label="堆密度">
+              <el-input v-model="dlg.form.data.name" placeholder="请输入单位名称,不能重复" />
+            </el-form-item>
+            <el-form-item label="食物图">
+            </el-form-item>
+            <el-form-item label="营养素图">
+            </el-form-item>
+            <el-form-item label="其他图">
+            </el-form-item>
+          </el-tab-pane>
+          <el-tab-pane label="规格" name="modifier">
+            <div style="margin-bottom:5px;">
+              <el-button size="mini" type="primary" plain>添加</el-button>
+            </div>
+            <el-table :data="tempList" :show-header="true" border fit highlight-current-row style="width:100%">
+              <el-table-column type="index" width="50" align="center" />
+              <el-table-column label="包装名称" width="70" align="center">
+                <el-input size="mini" />
+              </el-table-column>
+              <el-table-column label="包装单位" width="70" align="center">
+                <el-select size="mini" style="width:100%;">
+                </el-select>
+              </el-table-column>
+              <el-table-column label="CNVR" width="70" align="center">
+                <el-input size="mini" />
+              </el-table-column>
+              <el-table-column label="规格转换" width="180" align="center">
+                <el-input size="mini">
+                  <span slot="prepend">1随身包装=</span>
+                </el-input>
+              </el-table-column>
+              <el-table-column label="净含量" width="70" align="center">
+                <el-input size="mini" />
+              </el-table-column>
+              <el-table-column label="状态" width="70" align="center">
+                <el-input size="mini" />
+              </el-table-column>
+              <el-table-column label="卡尺" width="70" align="center">
+                <el-input size="mini" />
+              </el-table-column>
+              <el-table-column label="规格图片" width="70" align="center">
+              </el-table-column>
+              <el-table-column width="70" align="center">
+              </el-table-column>
+            </el-table>
+          </el-tab-pane>
+          <el-tab-pane label="营养素" name="nutrient">
+            <el-table :data="tempList" :show-header="false" border fit highlight-current-row style="width:100%">
+              <el-table-column type="index" width="50" align="center" />
+              <el-table-column>
+                <template slot-scope="{row}">
+                  名称选择<br>
+                  计量方式<br>
+                  计量单位<br>
+                  转换关系<br>
+                  NVR(%)<br>
+                  nv_spec<br>
+                  nv_spec_unit<br>
+                  来源
+                </template>
+              </el-table-column>
+            </el-table>
+          </el-tab-pane>
+          <el-tab-pane label="CF" name="cf">
+          </el-tab-pane>
+          <el-tab-pane label="营养素含量计算" name="calc">
+          </el-tab-pane>
+        </el-tabs>
       </el-form>
-      <div slot="footer">
-        <el-button @click="copyDialogVisible = false">
+      <div slot="footer" class="dialog-footer">
+        <el-button size="mini" @click="dlg.form.visible = false">
           取消
         </el-button>
-        <el-button type="primary" @click="copyFood">
+        <el-button :loading="dlg.form.saveLoading" size="mini" type="primary" @click="M.save()">
           确认
         </el-button>
       </div>
     </el-dialog>
-
-    <el-dialog title="食物二维码" width="250" :visible.sync="foodQRDialogVisible">
-      <vue-qr
-        :text="foodQRParams.url"
-        :bg-src="foodQRParams.bgImg"
-        :logo-src="foodQRParams.logoImg"
-        :size="200"
-        :correct-level="3"
-        color-dark="#000"
-        color-light="#fff"
-        :dot-scale="1"
-      />
-    </el-dialog>
   </div>
 </template>
 
 <script>
 import Pagination from '@/components/Pagination'
-import { getList, remove, copyFromSimilarFood } from '@/api/food'
-import axios from 'axios'
-import { getToken } from '@/utils/auth'
-import { getFoodSourceByUrl, isUsda } from '@/utils/food-utils'
-import store from '@/store'
-import VueQr from 'vue-qr'
-
-const foodTypes = { 0: '主材', 1: '辅材' }
+import { fGet, fPost } from '@/api/rest'
+import { M } from '@/api/op'
 
 export default {
-  name: 'FoodList',
-  components: { Pagination, VueQr },
-  filters: {
-    foodTypeFilter(value) {
-      return foodTypes[value]
-    }
-  },
+  components: { Pagination },
   data() {
     return {
-      tableKey: 0,
-      total: 0,
-      dialogStatus: '',
-      dialogFormVisible: false,
-      sortOptions: [{ key: 0, label: '按创建时间倒序排列' }, { key: 1, label: '按ID顺序排列' }],
-      list: [],
-      listLoading: true,
+      M: new M(this),
+      URI: '/api/foods',
       maxHeight: 600,
-      listQuery: {
-        query: '',
-        orderBy: 0,
-        pageNum: 1,
-        pageSize: 20
+      baseUnitOptions: [],
+      tempList: [{},{}],
+      list: {
+        main: {
+          loading: true,
+          total: 0,
+          list: [],
+          render(row) {
+            return row
+          },
+          query: {
+            page: 1,
+            limit: 50
+          }
+        }
       },
-      uploadFile: null,
-      uploadLoading: false,
-      copyDialogVisible: false,
-      params: {},
-      foodSource: '',
-      foodQRDialogVisible: false,
-      foodQRParams: {},
-      isUsda: false,
-      foodSourceIsAll: false,
-      usdaTypes: [{ 'label': 'Foundation', 'value': 'USDA_FOUNDATION' },
-        { 'label': 'SR Legacy', 'value': 'USDA_SR_LEGACY' }, { 'label': 'Branded', 'value': 'USDA_BRANDED' },
-        { 'label': 'FNDDS', 'value': 'USDA_FNDDS' }],
-      allTypes: [{ 'label': '全部', 'value': 'ALL' }, { 'label': '录入', 'value': 'ENTRY' },
-        { 'label': 'CFCT', 'value': 'CFCT' }, { 'label': 'Foundation', 'value': 'USDA_FOUNDATION' },
-        { 'label': 'SR Legacy', 'value': 'USDA_SR_LEGACY' }, { 'label': 'Branded', 'value': 'USDA_BRANDED' },
-        { 'label': 'FNDDS', 'value': 'USDA_FNDDS' }],
-      selectType: '',
-      yywyEnv: 'dev'
+      dlg: {
+        form: {
+          infoTab: 'modifier',
+          visible: false,
+          title: '',
+          data: {},
+          saveLoading: false,
+          rules: {
+            category: [{ required: true, message: '类别不能为空', trigger: 'blur' }],
+            name: [{ required: true, message: '名称不能为空', trigger: 'blur' }]
+          }
+        }
+      }
     }
   },
   created() {
-    this.maxHeight = document.documentElement.clientHeight - 205
-    this.foodSource = getFoodSourceByUrl(this.$route.path)
-    if (this.foodSource === 'ENTRY' || this.foodSource === '') {
-      this.listQuery.orderBy = 0
-    } else {
-      this.listQuery.orderBy = 1
-    }
-    this.isUsda = isUsda(this.foodSource)
-    if (this.isUsda) {
-      this.foodSource = this.foodSource === 'USDA' ? 'USDA_SR_LEGACY' : this.foodSource
-      this.selectType = this.foodSource
-    } else {
-      this.foodSourceIsAll = this.foodSource === ''
-    }
-    this.fetchData()
-    if (location.href.indexOf('test') > 0) {
-      this.yywyEnv = 'test'
-    }
+    this.maxHeight = document.documentElement.clientHeight - 135
+    this.M.list()
+    this.dlg.form.visible = true
   },
   methods: {
-    fetchData() {
-      this.listLoading = true
-      this.listQuery.foodSource = this.foodSource
-      if (this.isUsda) {
-        if (!this.selectType || this.selectType === 'ALL') {
-          this.listQuery.foodSource = 'USDA'
-        } else {
-          this.listQuery.foodSource = this.selectType
-        }
-      } else if (this.foodSourceIsAll) {
-        if (!this.selectType || this.selectType === 'ALL') {
-          this.listQuery.foodSource = ''
-        } else {
-          this.listQuery.foodSource = this.selectType
-        }
-      }
-      getList(this.listQuery).then(response => {
-        this.list = response.data.list.map(v => {
-          v._to_yywy = false
-          return v
+    loadBaseUnit() {
+      fGet(this.URI, { baseUnit: true, size: 10000 }).then(res => {
+        this.baseUnitOptions = res.data.map(v => {
+          return {
+            label: v.name,
+            value: v.id
+          }
         })
-        this.total = response.data.count
-        this.listLoading = false
-      })
-    },
-    toYYWY(row) {
-      row._to_yywy = true
-      const headers = {
-        headers: { 'Authorization': getToken() },
-        timeout: 1000 * 60 * 3
-      }
-      axios.post(`/api/foods/${row.id}/to-yywy?env=${this.yywyEnv}`, {}, headers).then(res => {
-        const data = res.data || {}
-        this.$notify.success(`同步结果:${res.status};` + (data.foodId || '') + ',' + (data.rsp || ''))
-      }).catch(res => {
-        this.$message.error(res.response.data.message)
-      }).finally(() => {
-        row._to_yywy = false
-      })
-    },
-    queryToYYWYProgrees() {
-      const headers = {
-        headers: { 'Authorization': getToken() },
-        timeout: 1000 * 60 * 3
-      }
-      axios.get(`/api/foods/last-to-yywy`, headers).then(res => {
-        const data = res.data || {}
-        console.log(data)
-        this.$notify.success(`同步结果:${res.status};` + (data.foodId || '') + ',' + (data.rsp || ''))
       })
     },
-    handleCreate() {
-      this.$router.push({ path: '/food/create' })
-    },
-    handleUpdate(row) {
-      this.$router.push({ path: '/food/edit/' + row.id })
-    },
-    manageNutrients(row) {
-      this.$router.push({ path: `/food/${row.id}/nutrient` })
-    },
-    manageUnits(row) {
-      this.$router.push({ path: `/food/${row.id}/unit` })
+    create() {
+      this.M.create({ form: 'form', title: '新增', data: {} })
     },
-    manageModifiers(row) {
-      this.$router.push({ path: `/food/${row.id}/modifier` })
+    edit(row) {
+      this.M.edit({ form: 'form', title: 'name', row: row })
     },
-    handleDelete(row, index) {
-      remove(row.id).then(res => {
-        this.$notify({ title: '成功', message: '删除成功', type: 'success', duration: 2000 })
-        this.list.splice(index, 1)
-        this.fetchData()
-      }).catch(res => {
-        this.$message.error(res.data.message)
-      })
-    },
-    handleImport() {
-      if (this.$refs.upload) {
-        this.$refs.upload.clearFiles()
-      }
-      this.uploadFile = null
-      this.dialogFormVisible = true
-    },
-    importFoods() {
-      const reqConfig = { headers: { 'Authorization': getToken(), 'Content-Type': 'multipart/form-data' },
-        timeout: 1000 * 60 * 3 }
-      const importUrl = process.env.VUE_APP_BASE_API + '/api/foods/import'
-      this.uploadLoading = true
-      this.dialogFormVisible = false
-      axios.post(importUrl, this.uploadFile, reqConfig).then(res => {
-        this.$notify.success('导入成功')
-        this.uploadLoading = false
-      }).catch(res => {
-        this.$message.error(res.response.data.message)
-        this.uploadLoading = false
-      })
-    },
-    fileChanged(file, fileList) {
-      const fromData = new FormData()
-      fromData.append('file', file.raw)
-      this.uploadFile = fromData
-    },
-    handleCopy(data) {
-      this.params = { foodId: data.id }
-      this.copyDialogVisible = true
-    },
-    copyFood() {
-      copyFromSimilarFood(this.params).then(res => {
-        this.$notify.success('提交成功')
-        this.$router.push({ path: `/food/edit/${res.data}` })
-      }).catch(res => {
-        this.$message.error(res.data.message)
-      })
-    },
-    handleCommand(data) {
-      switch (data.command) {
-        case 'update':
-          this.handleUpdate(data.row)
-          break
-        case 'copy':
-          this.handleCopy(data.row)
-          break
-        case 'manageNutrients':
-          this.manageNutrients(data.row)
-          break
-        case 'manageUnits':
-          this.manageUnits(data.row)
-          break
-        case 'manageModifiers':
-          this.manageModifiers(data.row)
-          break
-        case 'delete':
-          this.handleDelete(data.row)
-          break
-      }
-    },
-    canDelete(data) {
-      return store.getters.isAdmin || data && data.userId === store.getters.userId
-    },
-    handleFoodClick(row) {
-      this.foodQRParams = { url: process.env.VUE_APP_H5_URL + `/food/index.html?id=${row.id}`,
-        bgImg: row.mainImage ? row.mainImage : '',
-        logoImg: row.mainImage ? row.mainImage : ''
+    baseUnitChange() {
+      if (!!this.dlg.form.data.baseUnitId) {
+        const baseUnit = this.baseUnitOptions.find(v => v.value == this.dlg.form.data.baseUnitId)
+        if (!!baseUnit) {
+          this.dlg.form.data.baseUnitName = baseUnit.label
+        }
+      } else {
+        this.dlg.form.data.baseUnitName = undefined
       }
-      this.foodQRDialogVisible = true
     }
   }
 }
 </script>
-
-<style scoped>
-
-</style>

+ 298 - 0
src/views/nutrient/index-bak.vue

@@ -0,0 +1,298 @@
+<template>
+  <div class="app-container">
+    <div class="filter-container">
+      <el-input
+        v-model="listQuery.query"
+        placeholder="请输入检索词"
+        style="width: 60%;"
+        class="filter-item"
+        @keyup.enter.native="fetchData()"
+      />
+      <el-button class="filter-item" style="margin-left: 10px;" type="primary" icon="el-icon-search" @click="fetchData">
+        检索
+      </el-button>
+      <el-button
+        class="filter-item"
+        style="margin: 0 10px 20px 0; float: right;"
+        type="success"
+        icon="el-icon-circle-plus-outline"
+        @click="handleCreateOrUpdate('create')"
+      >
+        新建
+      </el-button>
+    </div>
+
+    <el-table
+      ref="table"
+      :key="tableKey"
+      v-loading="listLoading"
+      :data="list"
+      :max-height="maxHeight"
+      border
+      fit
+      highlight-current-row
+    >
+      <el-table-column type="index" label="序号" align="center" fixed width="60" />
+      <el-table-column label="名称" align="center" fixed width="250">
+        <template slot-scope="{row}">
+          <span>{{ row.name }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="图片" align="center" width="180">
+        <template slot-scope="{row}">
+          <el-image v-if="row.coverPic" style="width: 150px; height: 180px" :src="row.coverPic" fit="contain" />
+        </template>
+      </el-table-column>
+      <el-table-column label="自定义ID" align="center" width="100">
+        <template slot-scope="{row}">
+          <span>{{ row.id2 }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="英文名称" align="center" width="150">
+        <template slot-scope="{row}">
+          <span>{{ row.enName }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="编码" align="center" width="60">
+        <template slot-scope="{row}">
+          <span>{{ row.code }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="上级营养素名称" align="center" width="150">
+        <template slot-scope="{row}">
+          <span>{{ row.parentName }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="基础单位" align="center" width="150">
+        <template slot-scope="{row}">
+          <span>{{ row.baseUnit }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="创建时间" width="180px" align="center">
+        <template slot-scope="{row}">
+          <span>{{ row.createTime }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="更新时间" width="180px" align="center">
+        <template slot-scope="{row}">
+          <span>{{ row.updateTime }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="操作" align="center" fixed="right" width="90">
+        <template slot-scope="{row}">
+          <el-dropdown @command="handleCommand">
+            <el-button size="small" type="primary" @click.stop="{}">
+              操作<i class="el-icon-arrow-down el-icon--right" />
+            </el-button>
+            <el-dropdown-menu slot="dropdown">
+              <el-dropdown-item :command="{row: row, command: 'update'}">更新</el-dropdown-item>
+              <el-dropdown-item :command="{row: row, command: 'moveUp'}">上移</el-dropdown-item>
+              <el-dropdown-item :command="{row: row, command: 'moveDown'}">下移</el-dropdown-item>
+              <el-dropdown-item :command="{row: row, command: 'moveTop'}">置顶</el-dropdown-item>
+              <el-dropdown-item :command="{row: row, command: 'moveBottom'}">置顶</el-dropdown-item>
+              <el-dropdown-item :command="{row: row, command: 'delete'}">删除</el-dropdown-item>
+            </el-dropdown-menu>
+          </el-dropdown>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <pagination
+      v-show="total>0"
+      :total="total"
+      :page.sync="listQuery.pageNum"
+      :limit.sync="listQuery.pageSize"
+      @pagination="fetchData"
+    />
+
+    <el-dialog :title="textMap[dialogStatus]" :visible.sync="dialogFormVisible">
+      <el-form ref="dataForm" :rules="rules" :model="params" label-position="left" label-width="200px" style="width: 400px; margin-left:50px;">
+        <el-form-item label="自定义ID" prop="id2">
+          <el-input v-model="params.id2" placeholder="请输入自定义ID" />
+        </el-form-item>
+        <el-form-item label="名称" prop="name">
+          <el-input v-model="params.name" placeholder="请输入名称" :disabled="dialogStatus === 'edit'" />
+        </el-form-item>
+        <el-form-item label="英文名称" prop="enName">
+          <el-input v-model="params.enName" placeholder="请输入英文名称" />
+        </el-form-item>
+        <el-form-item label="编码" prop="code">
+          <el-input v-model="params.code" placeholder="编码" :disabled="dialogStatus === 'edit'" />
+        </el-form-item>
+        <el-form-item label="基础单位" prop="baseUnit">
+          <el-autocomplete
+            v-model="params.baseUnit"
+            class="inline-input"
+            :fetch-suggestions="queryUnits"
+            placeholder="单位关键词"
+          />
+        </el-form-item>
+        <el-form-item label="上级营养素" prop="parentId">
+          <el-select
+            v-model="params.parentId"
+            filterable
+            remote
+            reserve-keyword
+            placeholder="请选择上级营养素"
+            :remote-method="queryNutrients"
+            :loading="loading"
+          >
+            <el-option v-for="item in parentNutrients" :key="item.id" :label="item.name" :value="item.id" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="营养素图片" prop="coverPic">
+          <single-image :value="params.coverPic" :show-preview="true" style="width: 100%" @success="updateFile" />
+        </el-form-item>
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button @click="dialogFormVisible = false">
+          取消
+        </el-button>
+        <el-button type="primary" @click="createOrUpdateData">
+          提交
+        </el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import Pagination from '@/components/Pagination'
+import SingleImage from '@/components/Upload/SingleImage'
+import { getList, update, remove, create, updateSortOrder } from '@/api/nutrient'
+import { getList as getUnits } from '@/api/unit'
+
+export default {
+  name: 'NutrientList',
+  components: { Pagination, SingleImage },
+  filters: {
+  },
+  data() {
+    return {
+      tableKey: 0,
+      total: 0,
+      dialogStatus: '',
+      dialogFormVisible: false,
+      list: [{}],
+      listLoading: true,
+      maxHeight: 600,
+      listQuery: {
+        query: '',
+        pageNum: 1,
+        pageSize: 50
+      },
+      textMap: {
+        update: '更新',
+        create: '新建'
+      },
+      rules: {
+        name: [{ required: true, message: '名称不允许为空', trigger: 'blur' }]
+      },
+      params: { },
+      units: [],
+      loading: false,
+      parentNutrients: []
+    }
+  },
+  created() {
+    this.maxHeight = document.documentElement.clientHeight - 205
+    this.fetchData()
+  },
+  methods: {
+    fetchData() {
+      this.listLoading = true
+      getList(this.listQuery).then(response => {
+        this.list = response.data.list
+        this.total = response.data.count
+        this.listLoading = false
+      }).catch(() => {
+        this.$message.error('获取数据失败')
+        this.listLoading = false
+      })
+    },
+    handleCreateOrUpdate(dialogStatus, row) {
+      this.queryUnits()
+      this.queryNutrients()
+      this.params = dialogStatus === 'create' ? {} : Object.assign({}, row)
+      this.dialogStatus = dialogStatus
+      this.dialogFormVisible = true
+      this.$nextTick(() => {
+        this.$refs['dataForm'].clearValidate()
+      })
+    },
+    handleDelete(row) {
+      remove(row.id).then(res => {
+        this.$notify.success('提交成功')
+        this.fetchData()
+      }).catch(res => {
+        this.$message.error(res.data.message)
+      })
+    },
+    createOrUpdateData() {
+      const resultPromise = this.dialogStatus === 'create' ? create(this.params) : update(this.params.id, this.params)
+      resultPromise.then(res => {
+        this.$notify.success('提交成功')
+        this.fetchData()
+        this.dialogFormVisible = false
+      }).catch(res => {
+        this.$message.error(res.data.message)
+        this.fetchData()
+      })
+    },
+    queryUnits(query, cb) {
+      const units = []
+      getUnits({ query }).then(res => {
+        res.data.list.forEach(item => units.push({ value: item.name }))
+        cb(units)
+      })
+    },
+    queryNutrients(query) {
+      this.loading = true
+      getList({ query }).then(res => {
+        this.parentNutrients = res.data.list
+        this.loading = false
+      }).catch(res => {
+        this.parentNutrients = []
+        this.loading = false
+      })
+    },
+    updateFile(data) {
+      this.$set(this.params, 'coverPic', data.fileUrl)
+    },
+    updateSort(row, type) {
+      updateSortOrder(row.id, { type }).then(res => {
+        this.$notify.success('提交成功')
+        this.fetchData()
+      }).catch(res => {
+        this.$message.error(res.data.message)
+        this.fetchData()
+      })
+    },
+    handleCommand(data) {
+      switch (data.command) {
+        case 'update':
+          this.handleCreateOrUpdate('update', data.row)
+          break
+        case 'moveUp':
+          this.updateSort(data.row, 0)
+          break
+        case 'moveDown':
+          this.updateSort(data.row, 1)
+          break
+        case 'moveTop':
+          this.updateSort(data.row, 2)
+          break
+        case 'moveBottom':
+          this.updateSort(data.row, 3)
+          break
+        case 'delete':
+          this.handleDelete(data.row)
+          break
+      }
+    }
+  }
+}
+</script>
+
+<style scoped lang="scss">
+</style>

+ 110 - 225
src/views/nutrient/index.vue

@@ -1,155 +1,102 @@
 <template>
   <div class="app-container">
     <div class="filter-container">
-      <el-input
-        v-model="listQuery.query"
-        placeholder="请输入检索词"
-        style="width: 60%;"
-        class="filter-item"
-        @keyup.enter.native="fetchData()"
-      />
-      <el-button class="filter-item" style="margin-left: 10px;" type="primary" icon="el-icon-search" @click="fetchData">
-        检索
-      </el-button>
-      <el-button
-        class="filter-item"
-        style="margin: 0 10px 20px 0; float: right;"
-        type="success"
-        icon="el-icon-circle-plus-outline"
-        @click="handleCreateOrUpdate('create')"
-      >
-        新建
-      </el-button>
+      <el-input v-model="list.main.query.name" class="filter-item" size="mini" placeholder="名称" style="width:150px;margin-right:5px;" clearable />
+      <el-input v-model="list.main.query.code" class="filter-item" size="mini" placeholder="编码" style="width:150px;margin-right:5px;" clearable />
+      <el-input v-model="list.main.query.enName" class="filter-item" size="mini" placeholder="英文名称" style="width:150px;margin-right:5px;" clearable />
+      <DataSelect url="/api/units" class="filter-item" placeholder="基础单位" :value.sync="list.main.query.baseUnitId" style="width:150px;margin-right:5px;" />
+      <DataSelect :url="URI" class="filter-item" placeholder="上级营养素" :value.sync="list.main.query.parentId" style="width:150px;margin-right:5px;" />
+      <el-button class="filter-item" size="mini" type="primary" @click="M.list()">查询</el-button>
+      <el-button class="filter-item" size="mini" @click="() => {M.reset({ data: { page: 1, limit: list.main.query.limit } })}">重置</el-button>
+      <div style="float:right;">
+        <el-button class="filter-item" size="mini" type="success" style="margin-right:15px;" @click="create">新增</el-button>
+      </div>
     </div>
-
-    <el-table
-      ref="table"
-      :key="tableKey"
-      v-loading="listLoading"
-      :data="list"
-      :max-height="maxHeight"
-      border
-      fit
-      highlight-current-row
-    >
-      <el-table-column type="index" label="序号" align="center" fixed width="60" />
-      <el-table-column label="名称" align="center" fixed width="250">
+    <el-table v-loading="list.main.loading" :data="list.main.list" border fit highlight-current-row style="width:100%" :max-height="maxHeight">
+      <el-table-column type="index" width="50" align="center" />
+      <el-table-column min-width="180" header-align="center" label="名称">
         <template slot-scope="{row}">
           <span>{{ row.name }}</span>
         </template>
       </el-table-column>
-      <el-table-column label="图片" align="center" width="180">
-        <template slot-scope="{row}">
-          <el-image v-if="row.coverPic" style="width: 150px; height: 180px" :src="row.coverPic" fit="contain" />
-        </template>
-      </el-table-column>
-      <el-table-column label="自定义ID" align="center" width="100">
+      <el-table-column width="150" align="center" label="编码">
         <template slot-scope="{row}">
-          <span>{{ row.id2 }}</span>
+          <span>{{ row.code }}</span>
         </template>
       </el-table-column>
-      <el-table-column label="英文名称" align="center" width="150">
+      <el-table-column min-width="180" header-align="center" label="英文名称">
         <template slot-scope="{row}">
           <span>{{ row.enName }}</span>
         </template>
       </el-table-column>
-      <el-table-column label="编码" align="center" width="60">
-        <template slot-scope="{row}">
-          <span>{{ row.code }}</span>
-        </template>
-      </el-table-column>
-      <el-table-column label="上级营养素名称" align="center" width="150">
+      <el-table-column width="110" align="center" label="基础单位">
         <template slot-scope="{row}">
-          <span>{{ row.parentName }}</span>
+          {{ row.baseUnitName }}
         </template>
       </el-table-column>
-      <el-table-column label="基础单位" align="center" width="150">
+      <el-table-column width="130" header-align="center" label="上级营养素">
         <template slot-scope="{row}">
-          <span>{{ row.baseUnit }}</span>
+          {{ row.parentName || '无' }}
         </template>
       </el-table-column>
-      <el-table-column label="创建时间" width="180px" align="center">
+      <el-table-column label="创建时间" align="center" width="150">
         <template slot-scope="{row}">
           <span>{{ row.createTime }}</span>
         </template>
       </el-table-column>
-      <el-table-column label="更新时间" width="180px" align="center">
+      <el-table-column label="更新时间" align="center" width="150">
         <template slot-scope="{row}">
           <span>{{ row.updateTime }}</span>
         </template>
       </el-table-column>
-      <el-table-column label="操作" align="center" fixed="right" width="90">
+      <el-table-column width="160" align="center" label="操作">
         <template slot-scope="{row}">
-          <el-dropdown @command="handleCommand">
-            <el-button size="small" type="primary" @click.stop="{}">
-              操作<i class="el-icon-arrow-down el-icon--right" />
-            </el-button>
-            <el-dropdown-menu slot="dropdown">
-              <el-dropdown-item :command="{row: row, command: 'update'}">更新</el-dropdown-item>
-              <el-dropdown-item :command="{row: row, command: 'moveUp'}">上移</el-dropdown-item>
-              <el-dropdown-item :command="{row: row, command: 'moveDown'}">下移</el-dropdown-item>
-              <el-dropdown-item :command="{row: row, command: 'moveTop'}">置顶</el-dropdown-item>
-              <el-dropdown-item :command="{row: row, command: 'moveBottom'}">置顶</el-dropdown-item>
-              <el-dropdown-item :command="{row: row, command: 'delete'}">删除</el-dropdown-item>
-            </el-dropdown-menu>
-          </el-dropdown>
+          <i class="el-icon-edit" style="cursor:pointer;color:#999999;margin-right:5px;" @click="edit(row)"></i>
+          &nbsp;&nbsp;
+          <i class="el-icon-delete" style="cursor:pointer;color:#999999;margin-right:5px;"></i>
+          &nbsp;&nbsp;
+          <i class="el-icon-top" style="cursor:pointer;color:#999999;margin-right:5px;"></i>
+          &nbsp;&nbsp;
+          <i class="el-icon-bottom" style="cursor:pointer;color:#999999;"></i>
         </template>
       </el-table-column>
     </el-table>
+    <pagination v-show="list.main.total>0" :total="list.main.total" :page.sync="list.main.query.page" :limit.sync="list.main.query.limit" @pagination="M.list()" />
 
-    <pagination
-      v-show="total>0"
-      :total="total"
-      :page.sync="listQuery.pageNum"
-      :limit.sync="listQuery.pageSize"
-      @pagination="fetchData"
-    />
-
-    <el-dialog :title="textMap[dialogStatus]" :visible.sync="dialogFormVisible">
-      <el-form ref="dataForm" :rules="rules" :model="params" label-position="left" label-width="200px" style="width: 400px; margin-left:50px;">
-        <el-form-item label="自定义ID" prop="id2">
-          <el-input v-model="params.id2" placeholder="请输入自定义ID" />
-        </el-form-item>
+    <el-dialog :title="dlg.form.title" :visible.sync="dlg.form.visible" width="500px">
+      <el-form ref="form" :rules="dlg.form.rules" :model="dlg.form.data" size="mini" label-position="right" label-width="100px">
         <el-form-item label="名称" prop="name">
-          <el-input v-model="params.name" placeholder="请输入名称" :disabled="dialogStatus === 'edit'" />
-        </el-form-item>
-        <el-form-item label="英文名称" prop="enName">
-          <el-input v-model="params.enName" placeholder="请输入英文名称" />
+          <el-input v-model="dlg.form.data.name" placeholder="请输入营养素名称,不能重复" />
         </el-form-item>
         <el-form-item label="编码" prop="code">
-          <el-input v-model="params.code" placeholder="编码" :disabled="dialogStatus === 'edit'" />
+          <el-input v-model="dlg.form.data.code" placeholder="请输入营养素编码,不能重复" />
         </el-form-item>
-        <el-form-item label="基础单位" prop="baseUnit">
-          <el-autocomplete
-            v-model="params.baseUnit"
-            class="inline-input"
-            :fetch-suggestions="queryUnits"
-            placeholder="单位关键词"
-          />
+        <el-form-item label="英文名称">
+          <el-input v-model="dlg.form.data.enName" placeholder="请输入营养素英文名称" />
         </el-form-item>
-        <el-form-item label="上级营养素" prop="parentId">
-          <el-select
-            v-model="params.parentId"
-            filterable
-            remote
-            reserve-keyword
-            placeholder="请选择上级营养素"
-            :remote-method="queryNutrients"
-            :loading="loading"
-          >
-            <el-option v-for="item in parentNutrients" :key="item.id" :label="item.name" :value="item.id" />
-          </el-select>
+        <el-form-item label="基础单位" prop="baseUnitId">
+          <DataSelect
+            url="/api/units"
+            placeholder="请选择基础单位"
+            :value.sync="dlg.form.data.baseUnitId"
+            :label.sync="dlg.form.data.baseUnitName"
+          />
         </el-form-item>
-        <el-form-item label="营养素图片" prop="coverPic">
-          <single-image :value="params.coverPic" :show-preview="true" style="width: 100%" @success="updateFile" />
+        <el-form-item label="上级单位">
+          <DataSelect
+            :url="URI"
+            placeholder="请选择上级单位"
+            :value.sync="dlg.form.data.parentId"
+            :label.sync="dlg.form.data.parentName"
+          />
         </el-form-item>
       </el-form>
       <div slot="footer" class="dialog-footer">
-        <el-button @click="dialogFormVisible = false">
+        <el-button size="mini" @click="dlg.form.visible = false">
           取消
         </el-button>
-        <el-button type="primary" @click="createOrUpdateData">
-          提交
+        <el-button :loading="dlg.form.saveLoading" size="mini" type="primary" @click="M.save()">
+          确认
         </el-button>
       </div>
     </el-dialog>
@@ -158,141 +105,79 @@
 
 <script>
 import Pagination from '@/components/Pagination'
-import SingleImage from '@/components/Upload/SingleImage'
-import { getList, update, remove, create, updateSortOrder } from '@/api/nutrient'
-import { getList as getUnits } from '@/api/unit'
+import { fGet, fPost } from '@/api/rest'
+import { M } from '@/api/op'
+import DataSelect from '@/views/components/DataSelect/index'
 
 export default {
-  name: 'NutrientList',
-  components: { Pagination, SingleImage },
-  filters: {
-  },
+  components: { Pagination, DataSelect },
   data() {
     return {
-      tableKey: 0,
-      total: 0,
-      dialogStatus: '',
-      dialogFormVisible: false,
-      list: [{}],
-      listLoading: true,
+      M: new M(this),
+      URI: '/api/nutrients',
       maxHeight: 600,
-      listQuery: {
-        query: '',
-        pageNum: 1,
-        pageSize: 50
-      },
-      textMap: {
-        update: '更新',
-        create: '新建'
+      baseUnitOptions: [],
+      list: {
+        main: {
+          loading: true,
+          total: 0,
+          list: [],
+          render(row) {
+            return row
+          },
+          query: {
+            page: 1,
+            limit: 50
+          }
+        }
       },
-      rules: {
-        name: [{ required: true, message: '名称不允许为空', trigger: 'blur' }]
-      },
-      params: { },
-      units: [],
-      loading: false,
-      parentNutrients: []
+      dlg: {
+        form: {
+          visible: false,
+          title: '',
+          data: {},
+          saveLoading: false,
+          rules: {
+            name: [{ required: true, message: '营养素名称不能为空', trigger: 'blur' }],
+            code: [{ required: true, message: '营养素编码不能为空', trigger: 'blur' }],
+            baseUnitId: [{ required: true, message: '请选择基本单位', trigger: 'blur' }]
+          }
+        }
+      }
     }
   },
   created() {
-    this.maxHeight = document.documentElement.clientHeight - 205
-    this.fetchData()
+    this.maxHeight = document.documentElement.clientHeight - 135
+    this.M.list()
+    this.loadBaseUnit()
   },
   methods: {
-    fetchData() {
-      this.listLoading = true
-      getList(this.listQuery).then(response => {
-        this.list = response.data.list
-        this.total = response.data.count
-        this.listLoading = false
-      }).catch(() => {
-        this.$message.error('获取数据失败')
-        this.listLoading = false
-      })
-    },
-    handleCreateOrUpdate(dialogStatus, row) {
-      this.queryUnits()
-      this.queryNutrients()
-      this.params = dialogStatus === 'create' ? {} : Object.assign({}, row)
-      this.dialogStatus = dialogStatus
-      this.dialogFormVisible = true
-      this.$nextTick(() => {
-        this.$refs['dataForm'].clearValidate()
-      })
-    },
-    handleDelete(row) {
-      remove(row.id).then(res => {
-        this.$notify.success('提交成功')
-        this.fetchData()
-      }).catch(res => {
-        this.$message.error(res.data.message)
+    loadBaseUnit() {
+      fGet(this.URI, { baseUnit: true, size: 10000 }).then(res => {
+        this.baseUnitOptions = res.data.map(v => {
+          return {
+            label: v.name,
+            value: v.id
+          }
+        })
       })
     },
-    createOrUpdateData() {
-      const resultPromise = this.dialogStatus === 'create' ? create(this.params) : update(this.params.id, this.params)
-      resultPromise.then(res => {
-        this.$notify.success('提交成功')
-        this.fetchData()
-        this.dialogFormVisible = false
-      }).catch(res => {
-        this.$message.error(res.data.message)
-        this.fetchData()
-      })
-    },
-    queryUnits(query, cb) {
-      const units = []
-      getUnits({ query }).then(res => {
-        res.data.list.forEach(item => units.push({ value: item.name }))
-        cb(units)
-      })
-    },
-    queryNutrients(query) {
-      this.loading = true
-      getList({ query }).then(res => {
-        this.parentNutrients = res.data.list
-        this.loading = false
-      }).catch(res => {
-        this.parentNutrients = []
-        this.loading = false
-      })
-    },
-    updateFile(data) {
-      this.$set(this.params, 'coverPic', data.fileUrl)
+    create() {
+      this.M.create({ form: 'form', title: '新增', data: {} })
     },
-    updateSort(row, type) {
-      updateSortOrder(row.id, { type }).then(res => {
-        this.$notify.success('提交成功')
-        this.fetchData()
-      }).catch(res => {
-        this.$message.error(res.data.message)
-        this.fetchData()
-      })
+    edit(row) {
+      this.M.edit({ form: 'form', title: 'name', row: row })
     },
-    handleCommand(data) {
-      switch (data.command) {
-        case 'update':
-          this.handleCreateOrUpdate('update', data.row)
-          break
-        case 'moveUp':
-          this.updateSort(data.row, 0)
-          break
-        case 'moveDown':
-          this.updateSort(data.row, 1)
-          break
-        case 'moveTop':
-          this.updateSort(data.row, 2)
-          break
-        case 'moveBottom':
-          this.updateSort(data.row, 3)
-          break
-        case 'delete':
-          this.handleDelete(data.row)
-          break
+    baseUnitChange() {
+      if (!!this.dlg.form.data.baseUnitId) {
+        const baseUnit = this.baseUnitOptions.find(v => v.value == this.dlg.form.data.baseUnitId)
+        if (!!baseUnit) {
+          this.dlg.form.data.baseUnitName = baseUnit.label
+        }
+      } else {
+        this.dlg.form.data.baseUnitName = undefined
       }
     }
   }
 }
 </script>
-
-<style scoped lang="scss">
-</style>

+ 7 - 5
src/views/unit/index.vue

@@ -1,8 +1,6 @@
 <template>
   <div class="app-container">
     <div class="filter-container">
-      <el-button class="filter-item" size="mini" type="success" style="margin-right:15px;" @click="create">新增</el-button>
-
       <el-input v-model="list.main.query.category" class="filter-item" size="mini" placeholder="类别" style="width:150px;margin-right:5px;" clearable />
       <el-input v-model="list.main.query.name" class="filter-item" size="mini" placeholder="名称" style="width:150px;margin-right:5px;" clearable />
       <el-input v-model="list.main.query.code" class="filter-item" size="mini" placeholder="编码" style="width:150px;margin-right:5px;" clearable />
@@ -16,14 +14,18 @@
       </el-select>
       <el-button class="filter-item" size="mini" type="primary" @click="M.list()">查询</el-button>
       <el-button class="filter-item" size="mini" @click="() => {M.reset({ data: { page: 1, limit: list.main.query.limit } })}">重置</el-button>
+
+      <div style="float:right;">
+        <el-button class="filter-item" size="mini" type="success" style="margin-right:15px;" @click="create">新增</el-button>
+      </div>
     </div>
     <el-table v-loading="list.main.loading" :data="list.main.list" border fit highlight-current-row style="width:100%" :max-height="maxHeight">
-      <el-table-column width="100" header-align="center" label="类别">
+      <el-table-column width="100" align="center" label="类别">
         <template slot-scope="{row}">
           <span>{{ row.category }}</span>
         </template>
       </el-table-column>
-      <el-table-column width="150" header-align="center" label="名称">
+      <el-table-column min-width="150" header-align="center" label="名称">
         <template slot-scope="{row}">
           <span>{{ row.name }}</span>
         </template>
@@ -44,7 +46,7 @@
           <span v-else style="color:gray;">否</span>
         </template>
       </el-table-column>
-      <el-table-column width="180" header-align="center" align="right" label="转换比例">
+      <el-table-column width="200" header-align="center" align="right" label="转换比例">
         <span slot="header">
           转换关系
           <el-tooltip effect="dark" content="1 * unit = ratio * baseUnit" placement="top">