Ver código fonte

平台管理-商品管理

YY 2 anos atrás
pai
commit
adea0b776c

+ 10 - 4
src/layout/data/menu.js

@@ -21,21 +21,27 @@ export const adminMenu = [
       },
       {
         icon: 'icon-rencai',
-        path: '/platmanag/order',
-        name: '平台订单管理',
+        path: '/platmanag/goods',
+        name: '商品管理',
         index: '2-2',
       },
+      {
+        icon: 'icon-rencai',
+        path: '/platmanag/order',
+        name: '订单管理',
+        index: '2-3',
+      },
       {
         icon: 'icon-rencai',
         path: '/platmanag/goodsRate',
         name: '商品评价',
-        index: '2-3',
+        index: '2-4',
       },
       {
         icon: 'icon-rencai',
         path: '/platmanag/storeAcc',
         name: '店铺管理',
-        index: '2-4',
+        index: '2-5',
       },
     ],
   },

+ 12 - 0
src/router/module/platmanag.js

@@ -5,6 +5,18 @@ export default [
     meta: { title: '平台管理-商品标签设置' },
     component: () => import('@/views/platmanag/goodsTags/index.vue'),
   },
+  {
+    path: '/platmanag/goods',
+    name: 'platmanag_goods',
+    meta: { title: '平台管理-商品管理' },
+    component: () => import('@/views/platmanag/goods/index.vue'),
+  },
+  {
+    path: '/platmanag/spec/:id',
+    name: 'platmanag_spec',
+    meta: { title: '平台管理-商品库存管理' },
+    component: () => import('@/views/platmanag/goods/spec.vue'),
+  },
   {
     path: '/platmanag/order',
     name: 'platmanag_order',

+ 6 - 6
src/router/module/selfShop.js

@@ -11,6 +11,12 @@ export default [
     meta: { title: '自营店铺-商品管理' },
     component: () => import('@/views/selfShop/goods/index.vue'),
   },
+  {
+    path: '/selfShop/spec/:id',
+    name: 'selfShop_spec',
+    meta: { title: '自营店铺-商品库存管理' },
+    component: () => import('@/views/selfShop/goods/spec.vue'),
+  },
   {
     path: '/selfShop/order',
     name: 'selfShop_order',
@@ -29,12 +35,6 @@ export default [
     meta: { title: '自营店铺-订单管理-详细信息' },
     component: () => import('@/views/selfShop/order/detail_orderDetail.vue'),
   },
-  {
-    path: '/selfShop/spec/:id',
-    name: 'selfShop_spec',
-    meta: { title: '自营店铺-商品库存管理' },
-    component: () => import('@/views/selfShop/goods/spec.vue'),
-  },
   {
     path: '/selfShop/sales',
     name: 'selfShop_sales',

+ 283 - 0
src/views/platmanag/goods/index.vue

@@ -0,0 +1,283 @@
+<template>
+  <div id="goods">
+    <template v-if="view === 'list'">
+      <search-1 :form="searchInfo" @onSubmit="search" @querySearch="querySearch" @toReset="toClose" :shopList="shopList"> </search-1>
+      <data-btn :fields="btnList" @add="toAdd"></data-btn>
+      <data-table
+        ref="dataTable"
+        :fields="fields"
+        :opera="opera"
+        :data="list"
+        :total="total"
+        :limit="limit"
+        @query="search"
+        @edit="toEdit"
+        @puton="toPuton"
+        @lower="toLower"
+        @delete="toDelete"
+        @spec="toSpec"
+        @copy="toCopy"
+      ></data-table>
+    </template>
+    <template v-else>
+      <el-row>
+        <el-col :span="24">
+          <el-button type="primary" size="mini" @click="toBack()">返回</el-button>
+        </el-col>
+        <el-col :span="24">
+          <data-form :fields="infoFields" :rules="rules" v-model="form" labelWidth="150px" @save="toSave">
+            <template #tags="{ item }">
+              <el-cascader v-model="form[item.model]" :options="tagsList" :props="props" clearable filterable :show-all-levels="false"></el-cascader>
+            </template>
+            <template #act_tags>
+              <el-option v-for="i in act_tagsList" :key="i.value" :label="i.label" :value="i.value"></el-option>
+            </template>
+            <template #status>
+              <el-option v-for="i in goodsStatusList" :key="i.value" :label="i.label" :value="i.value"></el-option>
+            </template>
+            <template #brief>
+              <editor v-model="form.brief" url="/files/point/goods/upload" />
+            </template>
+          </data-form>
+        </el-col>
+      </el-row>
+    </template>
+  </div>
+</template>
+
+<script>
+const _ = require('lodash');
+import methodsUtil from '@/util/opera';
+import { mapState, createNamespacedHelpers } from 'vuex';
+const { mapActions: goods } = createNamespacedHelpers('goods');
+const { mapActions: goodsTags } = createNamespacedHelpers('goodsTags');
+const { mapActions: dictData } = createNamespacedHelpers('dictData');
+const { mapActions: actTags } = createNamespacedHelpers('actTags');
+const { mapActions: shop } = createNamespacedHelpers('shop');
+
+export default {
+  name: 'index',
+  props: {},
+  components: { editor: () => import('@/components/editor.vue'), search1: () => import('./parts/search-1.vue') },
+  data: function () {
+    return {
+      view: 'list',
+      fields: [
+        { label: '排序', model: 'sort' },
+        { label: '商品名称', model: 'name' },
+        { label: '店铺名称', model: 'shop.name' },
+        { label: '商品分类', model: 'tags', format: (i) => this.getTags(i) },
+        { label: '活动标签', model: 'act_tags', format: (i) => this.getAct_tags(i) },
+        { label: '商品状态', model: 'status', format: (i) => this.getStatus(i) },
+      ],
+      opera: [
+        { label: '修改', method: 'edit' },
+        { label: '上架', method: 'puton', display: (i) => i.status == '0' },
+        { label: '下架', method: 'lower', display: (i) => i.status == '1' },
+        { label: '库存管理', method: 'spec' },
+        { label: '复制', method: 'copy' },
+        { label: '删除', method: 'delete', confirm: true, type: 'danger' },
+      ],
+      btnList: [{ label: '添加', method: 'add' }],
+      searchFields: [
+        { label: '商品名称', model: 'name' },
+        { label: '商品状态', model: 'status', type: 'select' },
+        { label: '店铺名称', model: 'shop', custom: true },
+      ],
+      searchInfo: {},
+      list: [],
+      total: 0,
+      limit: 10,
+      // info部分
+      infoFields: [
+        { label: '商品名称', model: 'name' },
+        { label: '商品分类', model: 'tags', custom: true },
+        { label: '活动标签', model: 'act_tags', type: 'selectMany' },
+        { label: '简短简介', model: 'shot_brief', maxLength: 50 },
+        { label: '预计发货时间', model: 'send_time' },
+        { label: '商品状态', model: 'status', type: 'select' },
+        { label: '商品来源', model: 'source' },
+        { label: '网址', model: 'url' },
+        { label: '排序', model: 'sort', type: 'number' },
+        { label: '商品图片', model: 'file', type: 'upload', url: '/files/point/goods/upload' },
+        { label: '商品介绍', model: 'brief', custom: true },
+      ],
+
+      rules: {},
+      form: {},
+      // 商品分类
+      tagsList: [],
+      props: { multiple: true, label: 'label', value: 'code' },
+      // 活动标签
+      act_tagsList: [],
+
+      goodsStatusList: [],
+      // 商铺列表
+      shopList: [],
+      // 店铺远程搜索加载
+      loading: false,
+    };
+  },
+  created() {
+    this.searchOthers();
+    this.search();
+  },
+  methods: {
+    ...dictData({ getDict: 'query' }),
+    ...actTags({ actQuery: 'query' }),
+    ...goodsTags(['tree']),
+    ...shop({ shopQuery: 'query', shopFetch: 'fetch' }),
+    ...goods(['copy', 'query', 'delete', 'fetch', 'update', 'create']),
+    ...methodsUtil,
+    // 查询
+    async search({ skip = 0, limit = this.limit, ...others } = {}) {
+      let query = { skip, limit, ...others };
+      if (Object.keys(this.searchInfo).length > 0) query = { ...query, ...this.searchInfo };
+      const res = await this.query(query);
+      if (this.$checkRes(res)) {
+        this.$set(this, `list`, res.data);
+        this.$set(this, `total`, res.total);
+      }
+    },
+    // 多选
+    handleSelect(data) {
+      this.$emit('handleSelect');
+    },
+    // 店铺名称远程查询
+    async querySearch(value) {
+      let res = await this.shopQuery({ name: value });
+      if (this.$checkRes(res)) {
+        this.$set(this, 'shopList', res.data);
+      }
+    },
+    toClose() {
+      this.searchInfo = {};
+      this.search();
+    },
+    // 添加自定义
+    initAddData() {
+      const obj = {
+        status: '1',
+        shop: _.get(this.shop, '_id'),
+      };
+      this.$set(this, 'form', obj);
+    },
+    // 复制
+    async toCopy({ data }) {
+      this.$confirm('是否确认复制该商品?', '提示', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning',
+      }).then(async () => {
+        let res;
+        res = await this.copy(data.id);
+        if (this.$checkRes(res)) {
+          this.$message({ type: `success`, message: `复制成功` });
+          this.toBack();
+          this.search();
+        }
+      });
+    },
+    // 查询其他信息
+    async searchOthers() {
+      let res = await this.tree();
+      if (this.$checkRes(res)) this.$set(this, `tagsList`, res.data);
+      res = await this.getDict({ code: 'goods_status' });
+      if (this.$checkRes(res)) this.$set(this, `goodsStatusList`, res.data);
+      res = await this.actQuery();
+      if (this.$checkRes(res)) this.$set(this, `act_tagsList`, res.data);
+    },
+    // 商品状态
+    getStatus(data) {
+      const res = this.goodsStatusList.find((f) => f.value === data);
+      if (res) return res.label;
+      return '';
+    },
+    // 活动标签
+    getAct_tags(data) {
+      const arr = [];
+      for (const val of data) {
+        const r = this.act_tagsList.find((f) => f.value === val);
+        if (r) arr.push(r.label);
+      }
+      return arr.join(';');
+    },
+    // 商品分类
+    getTags(data) {
+      let list = this.tagsList;
+      const getChildren = (list) =>
+        list.map((i) => {
+          if (i.children) return getChildren(i.children);
+          return i;
+        });
+      list = _.flattenDeep(getChildren(list));
+      const arr = [];
+      for (const ts of data) {
+        const last = _.last(ts);
+        const r = list.find((f) => f.code === last);
+        if (r) arr.push(r.label);
+      }
+      return arr.join(';');
+    },
+    // 保存
+    async toSave({ data }) {
+      data.shop = this.user.shop.id;
+      let res;
+      if (data.id) res = await this.update(data);
+      else res = await this.create(data);
+      if (this.$checkRes(res)) {
+        this.$message({ type: `success`, message: `维护信息成功` });
+        this.toBack();
+        this.search();
+      }
+    },
+    // 执行返回
+    toBack() {
+      this.form = {};
+      this.view = 'list';
+    },
+    // 上架
+    async toPuton({ data }) {
+      this.$confirm('是否确认上架该商品?', '提示', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning',
+      }).then(async () => {
+        data.status = '1';
+        let res;
+        if (data.id) res = await this.update(data);
+        if (this.$checkRes(res)) {
+          this.$message({ type: `success`, message: `上架成功` });
+        }
+        this.search();
+      });
+    },
+    // 下架
+    async toLower({ data }) {
+      this.$confirm('是否确认下架该商品?', '提示', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning',
+      }).then(async () => {
+        data.status = '0';
+        let res;
+        if (data._id) res = await this.update(data);
+        if (this.$checkRes(res)) {
+          console.log(res.data);
+          this.$message({ type: `success`, message: `下架成功` });
+        }
+        this.search();
+      });
+    },
+    // 库存管理
+    toSpec({ data }) {
+      this.$router.push({ path: `/selfShop/spec/${data._id}` });
+    },
+  },
+  computed: {
+    ...mapState(['user']),
+  },
+};
+</script>
+
+<style lang="less" scoped></style>

+ 91 - 0
src/views/platmanag/goods/parts/search-1.vue

@@ -0,0 +1,91 @@
+<template>
+  <div id="search-1">
+    <el-row>
+      <el-col :span="24" class="main">
+        <el-form :model="form" ref="form" label-width="130px">
+          <el-col :span="6">
+            <el-form-item label="商品名称" prop="name">
+              <el-input v-model="form.name" placeholder="请输入商品名称" size="small"></el-input>
+            </el-form-item>
+          </el-col>
+          <el-col :span="6">
+            <el-form-item label="店铺名称" prop="type">
+              <el-select
+                v-model="form.shop"
+                filterable
+                remote
+                reserve-keyword
+                placeholder="请输入商铺名称"
+                :remote-method="querySearch"
+                :loading="loading"
+                @change="handleSelect"
+              >
+                <el-option v-for="item in shopList" :key="item.id" :label="item.name" :value="item.id"> </el-option>
+              </el-select>
+            </el-form-item>
+          </el-col>
+          <el-col :span="24" class="btn">
+            <el-button type="primary" icon="el-icon-search" size="mini" @click="onSubmit('form')">搜索</el-button>
+            <el-button icon="el-icon-refresh" size="mini" @click="toReset('form')">重置</el-button>
+          </el-col>
+        </el-form>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script>
+import { mapState, createNamespacedHelpers } from 'vuex';
+export default {
+  name: 'search-1',
+  props: { form: { type: Object }, shopList: { type: Array } },
+  components: {},
+  data: function () {
+    return {
+      loading: false,
+    };
+  },
+  created() {},
+  methods: {
+    querySearch(value) {
+      this.loading = true;
+      this.$emit('querySearch', value);
+      this.loading = false;
+    },
+    handleSelect(value) {
+      this.$set(this.form, `shop`, value);
+    },
+    onSubmit() {
+      this.$emit('onSubmit');
+    },
+    toReset(formName) {
+      this.$refs[formName].resetFields();
+      this.$emit('toReset');
+    },
+  },
+  computed: {},
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+  watch: {
+    test: {
+      deep: true,
+      immediate: true,
+      handler(val) {},
+    },
+  },
+};
+</script>
+
+<style lang="less" scoped>
+.main {
+  .btn {
+    text-align: right;
+  }
+  /deep/.el-form-item {
+    float: left;
+    width: 100%;
+    margin: 0 0 10px 0;
+  }
+}
+</style>

+ 206 - 0
src/views/platmanag/goods/spec.vue

@@ -0,0 +1,206 @@
+<template>
+  <div id="spec">
+    <template v-if="view === 'list'">
+      <el-row>
+        <el-col :span="24" style="padding: 10px">
+          <el-button type="primary" size="mini" @click="toBack()">返回</el-button>
+        </el-col>
+      </el-row>
+      <data-search :fields="searchFields" v-model="searchInfo" @query="search">
+        <template #status>
+          <el-option v-for="i in statusList" :key="i.model" :label="i.label" :value="i.value"></el-option>
+        </template>
+      </data-search>
+      <data-btn :fields="btnFields" @add="toAdd" />
+      <data-table ref="dataTable" :fields="fields" :opera="opera" :data="list" :total="total" @edit="toEdit" @delete="toDelete" @query="search" @copy="toCopy">
+        <template #code="{ row, item }">
+          <el-link type="primary" @click="toData(row)">{{ row[item.model] }}</el-link>
+        </template>
+      </data-table>
+    </template>
+    <template v-else>
+      <el-row>
+        <el-col :span="24">
+          <el-button type="primary" size="mini" @click="toBackList()">返回</el-button>
+        </el-col>
+        <el-col :span="24">
+          <data-form :span="12" :fields="infoFields" :rules="rules" v-model="form" labelWidth="150px" @save="toSave">
+            <template #status>
+              <el-option v-for="i in statusList" :key="i.model" :label="i.label" :value="i.value"></el-option>
+            </template>
+            <template #can_group>
+              <el-option v-for="i in tfList" :key="i.model" :label="i.label" :value="i.value"></el-option>
+            </template>
+          </data-form>
+        </el-col>
+      </el-row>
+    </template>
+  </div>
+</template>
+
+<script>
+// 找到该商品下的规格定义
+const _ = require('lodash');
+import methodUtil from '@/util/opera';
+import { mapState, createNamespacedHelpers } from 'vuex';
+const { mapActions } = createNamespacedHelpers('goodsSpec');
+const { mapActions: dictData } = createNamespacedHelpers('dictData');
+export default {
+  name: 'spec',
+  props: {},
+  components: {},
+  data: function () {
+    return {
+      view: 'list',
+      fields: [
+        { label: '规格名称', model: 'name' },
+        { label: '库存', model: 'num' },
+        { label: '实际销售价格', model: 'sell_money' },
+        { label: '划掉销售价格', model: 'flow_money' },
+        { label: '运费', model: 'freight' },
+        { label: '状态', model: 'status', format: (i) => (i === '0' ? '使用中' : '已禁用') },
+        {
+          label: '是否可以团购',
+          model: 'can_group',
+          format: (i) => {
+            let data = this.tfList.find((f) => f.value == i);
+            if (data) return data.label;
+            else return '暂无';
+          },
+        },
+        { label: '团购金额', model: 'group_config.money' },
+        { label: '开团人数', model: 'group_config.need_person' },
+      ],
+      opera: [
+        { label: '修改', method: 'edit' },
+        { label: '复制', method: 'copy' },
+        { label: '删除', method: 'delete', confirm: true, type: 'danger' },
+      ],
+      list: [],
+      total: 0,
+      limit: 10,
+      btnFields: [{ label: '添加', method: 'add' }],
+      defaultSearch: {},
+      searchInfo: {},
+      searchFields: [
+        { label: '规格名称', model: 'name' },
+        { label: '状态', model: 'status', type: 'select' },
+      ],
+      infoFields: [
+        { label: '规格名称', model: 'name' },
+        { label: '库存', model: 'num', type: 'number' },
+        { label: '实际销售价格', model: 'sell_money', type: 'number' },
+        { label: '划掉销售价格', model: 'flow_money', type: 'number' },
+        { label: '运费', model: 'freight', type: 'number' },
+        { label: '状态', model: 'status', type: 'select' },
+        { label: '是否可以团购', model: 'can_group', type: 'select' },
+        { label: '团购金额', model: 'money', type: 'number' },
+        { label: '开团人数', model: 'need_person', type: 'number' },
+        { label: '图片', model: 'file', type: 'upload', url: '/files/point/goodsSpec/upload' },
+      ],
+      rules: {
+        freight: [{ required: true, message: '请输入运费', trigger: 'blur' }],
+      },
+      form: {},
+
+      statusList: [],
+      // 是否可以团购
+      tfList: [],
+    };
+  },
+  computed: {
+    ...mapState(['user']),
+    goods() {
+      return this.$route.params.id;
+    },
+  },
+  created() {
+    this.defaultSearch.goods = this.goods;
+    this.searchOthers();
+    this.search();
+  },
+  methods: {
+    ...dictData({ getDict: 'query' }),
+    ...mapActions(['query', 'fetch', 'update', 'delete', 'create']),
+    ...methodUtil,
+    // 添加自定义
+    initAddData() {
+      const obj = { goods: this.goods, status: '0', can_group: '1', freight: 0 };
+      this.$set(this, 'form', obj);
+    },
+    // 复制
+    async toCopy({ data }) {
+      this.$confirm('是否确认复制该商品?', '提示', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning',
+      }).then(async () => {
+        data.name = data.name + '-复制';
+        delete data.id;
+        delete data._id;
+        delete data.meta;
+        delete data.__v;
+        let res;
+        res = await this.create(data);
+        if (this.$checkRes(res)) {
+          this.$message({ type: `success`, message: `复制成功` });
+          this.search();
+          this.toBackList();
+        }
+      });
+    },
+    // 保存
+    async toSave({ data }) {
+      let group_config = {};
+      group_config.money = data.money;
+      group_config.need_person = data.need_person;
+      data.group_config = group_config;
+      let res;
+      if (data.id) res = await this.update(data);
+      else res = await this.create(data);
+      if (this.$checkRes(res)) {
+        this.$message({ type: `success`, message: `维护信息成功` });
+        this.search();
+        this.toBackList();
+      }
+    },
+    // 修改
+    async toEdit({ data }) {
+      const res = await this.fetch(data._id);
+      if (this.$checkRes(res)) {
+        let data = res.data;
+        const group_config = _.get(data, 'group_config', {});
+        data = { ...data, ...group_config };
+        delete data.group_config;
+        this.$set(this, `form`, data);
+        this.view = 'info';
+      } else {
+        this.$message.error('未找到指定数据');
+      }
+    },
+    // 返回
+    toBack() {
+      window.history.go('-1');
+    },
+    // 返回列表
+    toBackList() {
+      this.view = 'list';
+      this.form = {};
+    },
+    // 查询其他信息
+    async searchOthers() {
+      // 状态
+      let res = await this.getDict({ code: 'status' });
+      if (this.$checkRes(res)) this.$set(this, 'statusList', res.data);
+      // 是否可以团购
+      res = await this.getDict({ code: 'tf' });
+      if (this.$checkRes(res)) this.$set(this, 'tfList', res.data);
+    },
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+};
+</script>
+
+<style lang="less" scoped></style>