فهرست منبع

add goods model

e 5 سال پیش
والد
کامیت
574c876fd4

+ 108 - 0
src/layout/components/Settings/index.vue

@@ -0,0 +1,108 @@
+<template>
+  <div class="drawer-container">
+    <div>
+      <h3 class="drawer-title">Page style setting</h3>
+
+      <div class="drawer-item">
+        <span>Theme Color</span>
+        <theme-picker style="float: right;height: 26px;margin: -3px 8px 0 0;" @change="themeChange" />
+      </div>
+
+      <div class="drawer-item">
+        <span>Open Tags-View</span>
+        <el-switch v-model="tagsView" class="drawer-switch" />
+      </div>
+
+      <div class="drawer-item">
+        <span>Fixed Header</span>
+        <el-switch v-model="fixedHeader" class="drawer-switch" />
+      </div>
+
+      <div class="drawer-item">
+        <span>Sidebar Logo</span>
+        <el-switch v-model="sidebarLogo" class="drawer-switch" />
+      </div>
+
+    </div>
+  </div>
+</template>
+
+<script>
+import ThemePicker from '@/components/ThemePicker'
+
+export default {
+  components: { ThemePicker },
+  data() {
+    return {}
+  },
+  computed: {
+    fixedHeader: {
+      get() {
+        return this.$store.state.settings.fixedHeader
+      },
+      set(val) {
+        this.$store.dispatch('settings/changeSetting', {
+          key: 'fixedHeader',
+          value: val
+        })
+      }
+    },
+    tagsView: {
+      get() {
+        return this.$store.state.settings.tagsView
+      },
+      set(val) {
+        this.$store.dispatch('settings/changeSetting', {
+          key: 'tagsView',
+          value: val
+        })
+      }
+    },
+    sidebarLogo: {
+      get() {
+        return this.$store.state.settings.sidebarLogo
+      },
+      set(val) {
+        this.$store.dispatch('settings/changeSetting', {
+          key: 'sidebarLogo',
+          value: val
+        })
+      }
+    }
+  },
+  methods: {
+    themeChange(val) {
+      this.$store.dispatch('settings/changeSetting', {
+        key: 'theme',
+        value: val
+      })
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.drawer-container {
+  padding: 24px;
+  font-size: 14px;
+  line-height: 1.5;
+  word-wrap: break-word;
+
+  .drawer-title {
+    margin-bottom: 12px;
+    color: rgba(0, 0, 0, .85);
+    font-size: 14px;
+    line-height: 22px;
+  }
+
+  .drawer-item {
+    color: rgba(0, 0, 0, .65);
+    font-size: 14px;
+    padding: 12px 0;
+  }
+
+  .drawer-switch {
+    float: right
+  }
+}
+</style>

+ 85 - 0
src/layout/components/TagsView/ScrollPane.vue

@@ -0,0 +1,85 @@
+<template>
+  <el-scrollbar ref="scrollContainer" :vertical="false" class="scroll-container" @wheel.native.prevent="handleScroll">
+    <slot />
+  </el-scrollbar>
+</template>
+
+<script>
+const tagAndTagSpacing = 4 // tagAndTagSpacing
+
+export default {
+  name: 'ScrollPane',
+  data() {
+    return {
+      left: 0
+    }
+  },
+  computed: {
+    scrollWrapper() {
+      return this.$refs.scrollContainer.$refs.wrap
+    }
+  },
+  methods: {
+    handleScroll(e) {
+      const eventDelta = e.wheelDelta || -e.deltaY * 40
+      const $scrollWrapper = this.scrollWrapper
+      $scrollWrapper.scrollLeft = $scrollWrapper.scrollLeft + eventDelta / 4
+    },
+    moveToTarget(currentTag) {
+      const $container = this.$refs.scrollContainer.$el
+      const $containerWidth = $container.offsetWidth
+      const $scrollWrapper = this.scrollWrapper
+      const tagList = this.$parent.$refs.tag
+
+      let firstTag = null
+      let lastTag = null
+
+      // find first tag and last tag
+      if (tagList.length > 0) {
+        firstTag = tagList[0]
+        lastTag = tagList[tagList.length - 1]
+      }
+
+      if (firstTag === currentTag) {
+        $scrollWrapper.scrollLeft = 0
+      } else if (lastTag === currentTag) {
+        $scrollWrapper.scrollLeft = $scrollWrapper.scrollWidth - $containerWidth
+      } else {
+        // find preTag and nextTag
+        const currentIndex = tagList.findIndex(item => item === currentTag)
+        const prevTag = tagList[currentIndex - 1]
+        const nextTag = tagList[currentIndex + 1]
+
+        // the tag's offsetLeft after of nextTag
+        const afterNextTagOffsetLeft = nextTag.$el.offsetLeft + nextTag.$el.offsetWidth + tagAndTagSpacing
+
+        // the tag's offsetLeft before of prevTag
+        const beforePrevTagOffsetLeft = prevTag.$el.offsetLeft - tagAndTagSpacing
+
+        if (afterNextTagOffsetLeft > $scrollWrapper.scrollLeft + $containerWidth) {
+          $scrollWrapper.scrollLeft = afterNextTagOffsetLeft - $containerWidth
+        } else if (beforePrevTagOffsetLeft < $scrollWrapper.scrollLeft) {
+          $scrollWrapper.scrollLeft = beforePrevTagOffsetLeft
+        }
+      }
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.scroll-container {
+  white-space: nowrap;
+  position: relative;
+  overflow: hidden;
+  width: 100%;
+  /deep/ {
+    .el-scrollbar__bar {
+      bottom: 0px;
+    }
+    .el-scrollbar__wrap {
+      height: 49px;
+    }
+  }
+}
+</style>

+ 289 - 0
src/layout/components/TagsView/index.vue

@@ -0,0 +1,289 @@
+<template>
+  <div id="tags-view-container" class="tags-view-container">
+    <scroll-pane ref="scrollPane" class="tags-view-wrapper">
+      <router-link
+        v-for="tag in visitedViews"
+        ref="tag"
+        :key="tag.path"
+        :class="isActive(tag)?'active':''"
+        :to="{ path: tag.path, query: tag.query, fullPath: tag.fullPath }"
+        tag="span"
+        class="tags-view-item"
+        @click.middle.native="!isAffix(tag)?closeSelectedTag(tag):''"
+        @contextmenu.prevent.native="openMenu(tag,$event)"
+      >
+        {{ tag.title }}
+        <span v-if="!isAffix(tag)" class="el-icon-close" @click.prevent.stop="closeSelectedTag(tag)" />
+      </router-link>
+    </scroll-pane>
+    <ul v-show="visible" :style="{left:left+'px',top:top+'px'}" class="contextmenu">
+      <li @click="refreshSelectedTag(selectedTag)">Refresh</li>
+      <li v-if="!isAffix(selectedTag)" @click="closeSelectedTag(selectedTag)">Close</li>
+      <li @click="closeOthersTags">Close Others</li>
+      <li @click="closeAllTags(selectedTag)">Close All</li>
+    </ul>
+  </div>
+</template>
+
+<script>
+import ScrollPane from './ScrollPane'
+import path from 'path'
+
+export default {
+  components: { ScrollPane },
+  data() {
+    return {
+      visible: false,
+      top: 0,
+      left: 0,
+      selectedTag: {},
+      affixTags: []
+    }
+  },
+  computed: {
+    visitedViews() {
+      return this.$store.state.tagsView.visitedViews
+    },
+    routes() {
+      return this.$store.state.permission.routes
+    }
+  },
+  watch: {
+    $route() {
+      this.addTags()
+      this.moveToCurrentTag()
+    },
+    visible(value) {
+      if (value) {
+        document.body.addEventListener('click', this.closeMenu)
+      } else {
+        document.body.removeEventListener('click', this.closeMenu)
+      }
+    }
+  },
+  mounted() {
+    this.initTags()
+    this.addTags()
+  },
+  methods: {
+    isActive(route) {
+      return route.path === this.$route.path
+    },
+    isAffix(tag) {
+      return tag.meta && tag.meta.affix
+    },
+    filterAffixTags(routes, basePath = '/') {
+      let tags = []
+      routes.forEach(route => {
+        if (route.meta && route.meta.affix) {
+          const tagPath = path.resolve(basePath, route.path)
+          tags.push({
+            fullPath: tagPath,
+            path: tagPath,
+            name: route.name,
+            meta: { ...route.meta }
+          })
+        }
+        if (route.children) {
+          const tempTags = this.filterAffixTags(route.children, route.path)
+          if (tempTags.length >= 1) {
+            tags = [...tags, ...tempTags]
+          }
+        }
+      })
+      return tags
+    },
+    initTags() {
+      const affixTags = this.affixTags = this.filterAffixTags(this.routes)
+      for (const tag of affixTags) {
+        // Must have tag name
+        if (tag.name) {
+          this.$store.dispatch('tagsView/addVisitedView', tag)
+        }
+      }
+    },
+    addTags() {
+      const { name } = this.$route
+      if (name) {
+        this.$store.dispatch('tagsView/addView', this.$route)
+      }
+      return false
+    },
+    moveToCurrentTag() {
+      const tags = this.$refs.tag
+      this.$nextTick(() => {
+        for (const tag of tags) {
+          if (tag.to.path === this.$route.path) {
+            this.$refs.scrollPane.moveToTarget(tag)
+            // when query is different then update
+            if (tag.to.fullPath !== this.$route.fullPath) {
+              this.$store.dispatch('tagsView/updateVisitedView', this.$route)
+            }
+            break
+          }
+        }
+      })
+    },
+    refreshSelectedTag(view) {
+      this.$store.dispatch('tagsView/delCachedView', view).then(() => {
+        const { fullPath } = view
+        this.$nextTick(() => {
+          this.$router.replace({
+            path: '/redirect' + fullPath
+          })
+        })
+      })
+    },
+    closeSelectedTag(view) {
+      this.$store.dispatch('tagsView/delView', view).then(({ visitedViews }) => {
+        if (this.isActive(view)) {
+          this.toLastView(visitedViews, view)
+        }
+      })
+    },
+    closeOthersTags() {
+      this.$router.push(this.selectedTag)
+      this.$store.dispatch('tagsView/delOthersViews', this.selectedTag).then(() => {
+        this.moveToCurrentTag()
+      })
+    },
+    closeAllTags(view) {
+      this.$store.dispatch('tagsView/delAllViews').then(({ visitedViews }) => {
+        if (this.affixTags.some(tag => tag.path === view.path)) {
+          return
+        }
+        this.toLastView(visitedViews, view)
+      })
+    },
+    toLastView(visitedViews, view) {
+      const latestView = visitedViews.slice(-1)[0]
+      if (latestView) {
+        this.$router.push(latestView.fullPath)
+      } else {
+        // now the default is to redirect to the home page if there is no tags-view,
+        // you can adjust it according to your needs.
+        if (view.name === 'Dashboard') {
+          // to reload home page
+          this.$router.replace({ path: '/redirect' + view.fullPath })
+        } else {
+          this.$router.push('/')
+        }
+      }
+    },
+    openMenu(tag, e) {
+      const menuMinWidth = 105
+      const offsetLeft = this.$el.getBoundingClientRect().left // container margin left
+      const offsetWidth = this.$el.offsetWidth // container width
+      const maxLeft = offsetWidth - menuMinWidth // left boundary
+      const left = e.clientX - offsetLeft + 15 // 15: margin right
+
+      if (left > maxLeft) {
+        this.left = maxLeft
+      } else {
+        this.left = left
+      }
+
+      this.top = e.clientY
+      this.visible = true
+      this.selectedTag = tag
+    },
+    closeMenu() {
+      this.visible = false
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.tags-view-container {
+  height: 34px;
+  width: 100%;
+  background: #fff;
+  border-bottom: 1px solid #d8dce5;
+  box-shadow: 0 1px 3px 0 rgba(0, 0, 0, .12), 0 0 3px 0 rgba(0, 0, 0, .04);
+  .tags-view-wrapper {
+    .tags-view-item {
+      display: inline-block;
+      position: relative;
+      cursor: pointer;
+      height: 26px;
+      line-height: 26px;
+      border: 1px solid #d8dce5;
+      color: #495060;
+      background: #fff;
+      padding: 0 8px;
+      font-size: 12px;
+      margin-left: 5px;
+      margin-top: 4px;
+      &:first-of-type {
+        margin-left: 15px;
+      }
+      &:last-of-type {
+        margin-right: 15px;
+      }
+      &.active {
+        background-color: #42b983;
+        color: #fff;
+        border-color: #42b983;
+        &::before {
+          content: '';
+          background: #fff;
+          display: inline-block;
+          width: 8px;
+          height: 8px;
+          border-radius: 50%;
+          position: relative;
+          margin-right: 2px;
+        }
+      }
+    }
+  }
+  .contextmenu {
+    margin: 0;
+    background: #fff;
+    z-index: 3000;
+    position: absolute;
+    list-style-type: none;
+    padding: 5px 0;
+    border-radius: 4px;
+    font-size: 12px;
+    font-weight: 400;
+    color: #333;
+    box-shadow: 2px 2px 3px 0 rgba(0, 0, 0, .3);
+    li {
+      margin: 0;
+      padding: 7px 16px;
+      cursor: pointer;
+      &:hover {
+        background: #eee;
+      }
+    }
+  }
+}
+</style>
+
+<style lang="scss">
+//reset element css of el-icon-close
+.tags-view-wrapper {
+  .tags-view-item {
+    .el-icon-close {
+      width: 16px;
+      height: 16px;
+      vertical-align: 2px;
+      border-radius: 50%;
+      text-align: center;
+      transition: all .3s cubic-bezier(.645, .045, .355, 1);
+      transform-origin: 100% 50%;
+      &:before {
+        transform: scale(.6);
+        display: inline-block;
+        vertical-align: -3px;
+      }
+      &:hover {
+        background-color: #b4bccc;
+        color: #fff;
+      }
+    }
+  }
+}
+</style>

+ 1 - 0
src/layout/components/index.js

@@ -1,3 +1,4 @@
 export { default as Navbar } from './Navbar'
 export { default as Sidebar } from './Sidebar'
 export { default as AppMain } from './AppMain'
+export { default as TagsView } from './TagsView'

+ 5 - 3
src/layout/index.vue

@@ -2,9 +2,10 @@
   <div :class="classObj" class="app-wrapper">
     <div v-if="device==='mobile'&&sidebar.opened" class="drawer-bg" @click="handleClickOutside" />
     <sidebar class="sidebar-container" />
-    <div class="main-container">
+    <div :class="{hasTagsView:needTagsView}" class="main-container">
       <div :class="{'fixed-header':fixedHeader}">
         <navbar />
+        <tags-view />
       </div>
       <app-main />
     </div>
@@ -12,7 +13,7 @@
 </template>
 
 <script>
-import { Navbar, Sidebar, AppMain } from './components'
+import { Navbar, Sidebar, AppMain, TagsView } from './components'
 import ResizeMixin from './mixin/ResizeHandler'
 
 export default {
@@ -20,7 +21,8 @@ export default {
   components: {
     Navbar,
     Sidebar,
-    AppMain
+    AppMain,
+    TagsView
   },
   mixins: [ResizeMixin],
   computed: {

+ 14 - 0
src/router/index.js

@@ -90,6 +90,20 @@ export const constantRoutes = [
         component: () => import('@/views/goods/category'),
         meta: { title: '商品分类', icon: 'component' }
       },
+      {
+        path: 'category/edit/:id(\\d+)',
+        name: 'EditCategory',
+        component: () => import('@/views/goods/category-edit'),
+        meta: { title: '编辑分类', noCache: true, activeMenu: '/goods/category' },
+        hidden: true
+      },
+      {
+        path: 'category/create',
+        name: 'CreateCategory',
+        component: () => import('@/views/goods/category-create'),
+        meta: { title: '添加分类', noCache: true, activeMenu: '/goods/category' },
+        hidden: true
+      },
       {
         path: 'list',
         name: 'List',

+ 13 - 1
src/settings.js

@@ -12,5 +12,17 @@ module.exports = {
    * @type {boolean} true | false
    * @description Whether show the logo in sidebar
    */
-  sidebarLogo: true
+  sidebarLogo: true,
+
+  /**
+   * @type {boolean} true | false
+   * @description Whether need tagsView
+   */
+  tagsView: true,
+
+  /**
+   * @type {number}
+   * @description 组织ID
+   */
+  organizeID: 1
 }

+ 2 - 0
src/store/getters.js

@@ -1,6 +1,8 @@
 const getters = {
   sidebar: state => state.app.sidebar,
   device: state => state.app.device,
+  visitedViews: state => state.tagsView.visitedViews,
+  cachedViews: state => state.tagsView.cachedViews,
   token: state => state.user.token,
   avatar: state => state.user.avatar,
   name: state => state.user.name

+ 4 - 2
src/store/modules/settings.js

@@ -1,11 +1,13 @@
 import defaultSettings from '@/settings'
 
-const { showSettings, fixedHeader, sidebarLogo } = defaultSettings
+const { showSettings, tagsView, fixedHeader, sidebarLogo, organizeID } = defaultSettings
 
 const state = {
   showSettings: showSettings,
   fixedHeader: fixedHeader,
-  sidebarLogo: sidebarLogo
+  tagsView: tagsView,
+  sidebarLogo: sidebarLogo,
+  organizeID: organizeID
 }
 
 const mutations = {

+ 160 - 0
src/store/modules/tagsView.js

@@ -0,0 +1,160 @@
+const state = {
+  visitedViews: [],
+  cachedViews: []
+}
+
+const mutations = {
+  ADD_VISITED_VIEW: (state, view) => {
+    if (state.visitedViews.some(v => v.path === view.path)) return
+    state.visitedViews.push(
+      Object.assign({}, view, {
+        title: view.meta.title || 'no-name'
+      })
+    )
+  },
+  ADD_CACHED_VIEW: (state, view) => {
+    if (state.cachedViews.includes(view.name)) return
+    if (!view.meta.noCache) {
+      state.cachedViews.push(view.name)
+    }
+  },
+
+  DEL_VISITED_VIEW: (state, view) => {
+    for (const [i, v] of state.visitedViews.entries()) {
+      if (v.path === view.path) {
+        state.visitedViews.splice(i, 1)
+        break
+      }
+    }
+  },
+  DEL_CACHED_VIEW: (state, view) => {
+    const index = state.cachedViews.indexOf(view.name)
+    index > -1 && state.cachedViews.splice(index, 1)
+  },
+
+  DEL_OTHERS_VISITED_VIEWS: (state, view) => {
+    state.visitedViews = state.visitedViews.filter(v => {
+      return v.meta.affix || v.path === view.path
+    })
+  },
+  DEL_OTHERS_CACHED_VIEWS: (state, view) => {
+    const index = state.cachedViews.indexOf(view.name)
+    if (index > -1) {
+      state.cachedViews = state.cachedViews.slice(index, index + 1)
+    } else {
+      // if index = -1, there is no cached tags
+      state.cachedViews = []
+    }
+  },
+
+  DEL_ALL_VISITED_VIEWS: state => {
+    // keep affix tags
+    const affixTags = state.visitedViews.filter(tag => tag.meta.affix)
+    state.visitedViews = affixTags
+  },
+  DEL_ALL_CACHED_VIEWS: state => {
+    state.cachedViews = []
+  },
+
+  UPDATE_VISITED_VIEW: (state, view) => {
+    for (let v of state.visitedViews) {
+      if (v.path === view.path) {
+        v = Object.assign(v, view)
+        break
+      }
+    }
+  }
+}
+
+const actions = {
+  addView({ dispatch }, view) {
+    dispatch('addVisitedView', view)
+    dispatch('addCachedView', view)
+  },
+  addVisitedView({ commit }, view) {
+    commit('ADD_VISITED_VIEW', view)
+  },
+  addCachedView({ commit }, view) {
+    commit('ADD_CACHED_VIEW', view)
+  },
+
+  delView({ dispatch, state }, view) {
+    return new Promise(resolve => {
+      dispatch('delVisitedView', view)
+      dispatch('delCachedView', view)
+      resolve({
+        visitedViews: [...state.visitedViews],
+        cachedViews: [...state.cachedViews]
+      })
+    })
+  },
+  delVisitedView({ commit, state }, view) {
+    return new Promise(resolve => {
+      commit('DEL_VISITED_VIEW', view)
+      resolve([...state.visitedViews])
+    })
+  },
+  delCachedView({ commit, state }, view) {
+    return new Promise(resolve => {
+      commit('DEL_CACHED_VIEW', view)
+      resolve([...state.cachedViews])
+    })
+  },
+
+  delOthersViews({ dispatch, state }, view) {
+    return new Promise(resolve => {
+      dispatch('delOthersVisitedViews', view)
+      dispatch('delOthersCachedViews', view)
+      resolve({
+        visitedViews: [...state.visitedViews],
+        cachedViews: [...state.cachedViews]
+      })
+    })
+  },
+  delOthersVisitedViews({ commit, state }, view) {
+    return new Promise(resolve => {
+      commit('DEL_OTHERS_VISITED_VIEWS', view)
+      resolve([...state.visitedViews])
+    })
+  },
+  delOthersCachedViews({ commit, state }, view) {
+    return new Promise(resolve => {
+      commit('DEL_OTHERS_CACHED_VIEWS', view)
+      resolve([...state.cachedViews])
+    })
+  },
+
+  delAllViews({ dispatch, state }, view) {
+    return new Promise(resolve => {
+      dispatch('delAllVisitedViews', view)
+      dispatch('delAllCachedViews', view)
+      resolve({
+        visitedViews: [...state.visitedViews],
+        cachedViews: [...state.cachedViews]
+      })
+    })
+  },
+  delAllVisitedViews({ commit, state }) {
+    return new Promise(resolve => {
+      commit('DEL_ALL_VISITED_VIEWS')
+      resolve([...state.visitedViews])
+    })
+  },
+  delAllCachedViews({ commit, state }) {
+    return new Promise(resolve => {
+      commit('DEL_ALL_CACHED_VIEWS')
+      resolve([...state.cachedViews])
+    })
+  },
+
+  updateVisitedView({ commit }, view) {
+    commit('UPDATE_VISITED_VIEW', view)
+  }
+}
+
+export default {
+  namespaced: true,
+  state,
+  mutations,
+  actions
+}

+ 90 - 0
src/views/goods/category-create.vue

@@ -0,0 +1,90 @@
+<template>
+  <div class="app-container">
+    <el-form ref="dataForm" :rules="rules" :model="temp" label-position="left" label-width="120px" style="width: 400px; margin-left:50px;">
+      <el-form-item label="分类名称" prop="classifyName">
+        <el-input v-model="temp.classifyName" />
+      </el-form-item>
+      <el-form-item label="图标" prop="classifyImage">
+        <el-input v-model="temp.classifyImage" />
+      </el-form-item>
+      <el-form-item label="排序值" prop="sort">
+        <el-input v-model="temp.sort" type="number" />
+      </el-form-item>
+      <el-form-item label="邮费是否到付" prop="postage">
+        <el-select v-model="temp.postage" class="filter-item" placeholder="请选择">
+          <el-option label="是" value="1" />
+          <el-option label="否" value="0" />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="状态" prop="status">
+        <el-select v-model="temp.status" class="filter-item" placeholder="请选择">
+          <el-option label="启用" value="1" />
+          <el-option label="停用" value="2" />
+        </el-select>
+      </el-form-item>
+      <el-form-item>
+        <el-button type="primary" @click="createData">提交</el-button>
+      </el-form-item>
+    </el-form>
+  </div>
+</template>
+
+<script>
+import { createCategory } from '@/api/goods'
+export default {
+  data() {
+    return {
+      msg: '编辑分类',
+      rules: {
+        classifyName: [{ required: true, message: '分类名称不能为空', trigger: 'blur' }],
+        sort: [{ required: true, message: '分类名称不能为空', trigger: 'blur' }],
+        classifyImage: [{ type: 'file', required: true, message: '请上传分类图标', trigger: 'change' }],
+        postage: [{ required: true, message: '请选择邮费是否到付', trigger: 'change' }],
+        status: [{ required: true, message: '请选择状态', trigger: 'change' }]
+      },
+      temp: {
+        id: 1,
+        organizeID: this.organizeID,
+        classifyName: '针剂',
+        classifyImage: 'dfgdfsgsd',
+        sort: 60,
+        status: 1,
+        postage: 1,
+        addTime: new Date(),
+        updateTime: new Date(),
+        delFlag: 0
+      }
+    }
+  },
+  computed: {
+    organizeID() {
+      return this.$store.state.settings.organizeID
+    }
+  },
+  methods: {
+    createData() {
+      this.$refs['dataForm'].validate((valid) => {
+        if (valid) {
+          createCategory(this.temp).then(() => {
+            this.list.unshift(this.temp)
+            this.dialogFormVisible = false
+            this.$notify({
+              title: 'Success',
+              message: '添加分类成功',
+              type: 'success',
+              duration: 2000
+            })
+          })
+        }
+      })
+    }
+  }
+}
+</script>
+
+<style scoped>
+  .is-active {
+      border-bottom: 2px solid #409eff;
+      color: #303133;
+  }
+</style>

+ 98 - 0
src/views/goods/category-edit.vue

@@ -0,0 +1,98 @@
+<template>
+  <div class="app-container">
+    <el-form ref="dataForm" :rules="rules" :model="temp" label-position="left" label-width="120px" style="width: 400px; margin-left:50px;">
+      <el-form-item label="分类名称" prop="classifyName">
+        <el-input v-model="temp.classifyName" />
+      </el-form-item>
+      <el-form-item label="图标" prop="classifyImage">
+        <el-input v-model="temp.classifyImage" />
+      </el-form-item>
+      <el-form-item label="排序值" prop="sort">
+        <el-input v-model="temp.sort" type="number" />
+      </el-form-item>
+      <el-form-item label="邮费是否到付" prop="postage">
+        <el-select v-model="temp.postage" class="filter-item" placeholder="请选择">
+          <el-option label="是" value="1" />
+          <el-option label="否" value="0" />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="状态" prop="status">
+        <el-select v-model="temp.status" class="filter-item" placeholder="请选择">
+          <el-option label="启用" value="1" />
+          <el-option label="停用" value="2" />
+        </el-select>
+      </el-form-item>
+      <el-form-item>
+        <el-button type="primary" @click="updateData">提交</el-button>
+      </el-form-item>
+    </el-form>
+  </div>
+</template>
+
+<script>
+import { updateCategory } from '@/api/goods'
+export default {
+  data() {
+    return {
+      msg: '编辑分类',
+      rules: {
+        classifyName: [{ required: true, message: '分类名称不能为空', trigger: 'blur' }],
+        sort: [{ required: true, message: '分类名称不能为空', trigger: 'blur' }],
+        classifyImage: [{ type: 'file', required: true, message: '请上传分类图标', trigger: 'change' }],
+        postage: [{ required: true, message: '请选择邮费是否到付', trigger: 'change' }],
+        status: [{ required: true, message: '请选择状态', trigger: 'change' }]
+      },
+      temp: {
+        id: 1,
+        organizeID: this.organizeID,
+        classifyName: '针剂',
+        classifyImage: 'dfgdfsgsd',
+        sort: 60,
+        status: 1,
+        postage: 1,
+        addTime: new Date(),
+        updateTime: new Date(),
+        delFlag: 0
+      }
+    }
+  },
+  computed: {
+    organizeID() {
+      return this.$store.state.settings.organizeID
+    }
+  },
+  methods: {
+    updateData() {
+      this.$refs['dataForm'].validate((valid) => {
+        if (valid) {
+          const tempData = Object.assign({}, this.temp)
+          tempData.updateTime = +new Date(tempData.updateTime)
+          updateCategory(tempData).then(() => {
+            for (const v of this.list) {
+              if (v.id === this.temp.id) {
+                const index = this.list.indexOf(v)
+                this.list.splice(index, 1, this.temp)
+                break
+              }
+            }
+            this.dialogFormVisible = false
+            this.$notify({
+              title: 'Success',
+              message: '更新成功',
+              type: 'success',
+              duration: 2000
+            })
+          })
+        }
+      })
+    }
+  }
+}
+</script>
+
+<style scoped>
+  .is-active {
+      border-bottom: 2px solid #409eff;
+      color: #303133;
+  }
+</style>

+ 12 - 111
src/views/goods/category.vue

@@ -1,5 +1,9 @@
 <template>
   <div class="app-container">
+    <div>
+      <router-link to="/goods/category/create"><el-button type="primary">添加分类</el-button></router-link>
+      <el-button type="primary">批量更新排序</el-button>
+    </div>
     <el-table
       v-loading="listLoading"
       :data="list"
@@ -7,6 +11,7 @@
       border
       fit
       highlight-current-row
+      style="width:100%;margin-top:20px;"
     >
       <el-table-column align="center" label="序号" width="95">
         <template slot-scope="scope">
@@ -42,52 +47,17 @@
       </el-table-column>
       <el-table-column label="Actions" align="center" width="110" class-name="small-padding fixed-width">
         <template slot-scope="{row}">
-          <el-button type="primary" size="mini" @click="handleEdit(row)">编辑</el-button>
+          <router-link :to="'/goods/category/edit/' + row.id "><el-button type="primary">编辑</el-button></router-link>
         </template>
       </el-table-column>
     </el-table>
 
     <pagination v-show="total>0" :total="total" :page.sync="listQuery.page" :limit.sync="listQuery.limit" @pagination="fetchData" />
-
-    <el-dialog :title="textMap[dialogStatus]" :visible.sync="dialogFormVisible">
-      <el-form ref="dataForm" :rules="rules" :model="temp" label-position="left" label-width="120px" style="width: 400px; margin-left:50px;">
-        <el-form-item label="分类名称" prop="classifyName">
-          <el-input v-model="temp.classifyName" />
-        </el-form-item>
-        <el-form-item label="图标" prop="classifyImage">
-          <el-input v-model="temp.classifyImage" />
-        </el-form-item>
-        <el-form-item label="排序值" prop="sort">
-          <el-input v-model="temp.sort" type="number" />
-        </el-form-item>
-        <el-form-item label="邮费是否到付" prop="postage">
-          <el-select v-model="temp.postage" class="filter-item" placeholder="请选择">
-            <el-option label="是" value="1" />
-            <el-option label="否" value="0" />
-          </el-select>
-        </el-form-item>
-        <el-form-item label="状态" prop="status">
-          <el-select v-model="temp.status" class="filter-item" placeholder="请选择">
-            <el-option label="启用" value="1" />
-            <el-option label="停用" value="2" />
-          </el-select>
-        </el-form-item>
-      </el-form>
-      <div slot="footer" class="dialog-footer">
-        <el-button @click="dialogFormVisible = false">
-          Cancel
-        </el-button>
-        <el-button type="primary" @click="dialogStatus==='create' ? createData() : updateData()">
-          Confirm
-        </el-button>
-      </div>
-    </el-dialog>
-
   </div>
 </template>
 
 <script>
-import { getCategory, updateCategory, createCategory } from '@/api/goods'
+import { getCategory } from '@/api/goods'
 import Pagination from '@/components/Pagination'
 
 export default {
@@ -113,34 +83,14 @@ export default {
         title: undefined,
         type: undefined,
         sort: '+id'
-      },
-      temp: {
-        id: 1,
-        organizeID: 1,
-        classifyName: '针剂',
-        classifyImage: 'dfgdfsgsd',
-        sort: 60,
-        status: 1,
-        postage: 1,
-        addTime: new Date(),
-        updateTime: new Date(),
-        delFlag: 0
-      },
-      dialogFormVisible: false,
-      dialogStatus: '',
-      textMap: {
-        update: 'Edit',
-        create: 'Create'
-      },
-      rules: {
-        classifyName: [{ required: true, message: '分类名称不能为空', trigger: 'blur' }],
-        sort: [{ required: true, message: '分类名称不能为空', trigger: 'blur' }],
-        classifyImage: [{ type: 'file', required: true, message: '请上传分类图标', trigger: 'change' }],
-        postage: [{ required: true, message: '请选择邮费是否到付', trigger: 'change' }],
-        status: [{ required: true, message: '请选择状态', trigger: 'change' }]
       }
     }
   },
+  computed: {
+    organizeID() {
+      return this.$store.state.settings.organizeID
+    }
+  },
   created() {
     this.fetchData()
   },
@@ -180,55 +130,6 @@ export default {
         ]
       })
       this.listLoading = false
-    },
-    handleEdit(row) {
-      this.temp = Object.assign({}, row)
-      this.temp.timestamp = new Date(this.temp.timestamp)
-      this.dialogStatus = 'update'
-      this.dialogFormVisible = true
-      this.$nextTick(() => {
-        this.$refs['dataForm'].clearValidate()
-      })
-    },
-    updateData() {
-      this.$refs['dataForm'].validate((valid) => {
-        if (valid) {
-          const tempData = Object.assign({}, this.temp)
-          tempData.updateTime = +new Date(tempData.updateTime)
-          updateCategory(tempData).then(() => {
-            for (const v of this.list) {
-              if (v.id === this.temp.id) {
-                const index = this.list.indexOf(v)
-                this.list.splice(index, 1, this.temp)
-                break
-              }
-            }
-            this.dialogFormVisible = false
-            this.$notify({
-              title: 'Success',
-              message: '更新成功',
-              type: 'success',
-              duration: 2000
-            })
-          })
-        }
-      })
-    },
-    createData() {
-      this.$refs['dataForm'].validate((valid) => {
-        if (valid) {
-          createCategory(this.temp).then(() => {
-            this.list.unshift(this.temp)
-            this.dialogFormVisible = false
-            this.$notify({
-              title: 'Success',
-              message: '添加分类成功',
-              type: 'success',
-              duration: 2000
-            })
-          })
-        }
-      })
     }
   }
 }