index.vue 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389
  1. <template>
  2. <div id="goods">
  3. <span v-show="view === 'list'">
  4. <el-row>
  5. <el-col
  6. :span="24"
  7. class="main animate__animated animate__backInRight"
  8. v-loading="loadings"
  9. element-loading-text="拼命加载中"
  10. element-loading-spinner="el-icon-loading"
  11. >
  12. <el-col :span="24" class="one"> <span>商品管理</span> </el-col>
  13. <data-search :fields="searchFields" v-model="searchInfo" @query="toSearch">
  14. <template #status>
  15. <el-option v-for="i in goodsStatusList" :key="i.value" :label="i.label" :value="i.value"></el-option>
  16. </template>
  17. <template #tags="{ item }">
  18. <el-cascader
  19. v-model="searchInfo[item.model]"
  20. :options="tagsList"
  21. :props="propss"
  22. clearable
  23. filterable
  24. :show-all-levels="false"
  25. @change="changeTags"
  26. ></el-cascader>
  27. </template>
  28. </data-search>
  29. <data-btn :fields="btnList" @add="toAdd"></data-btn>
  30. <data-table
  31. ref="dataTable"
  32. :fields="fields"
  33. :opera="opera"
  34. :data="list"
  35. :total="total"
  36. @query="search"
  37. @edit="toEdit"
  38. @puton="toPuton"
  39. @delete="toDelete"
  40. @spec="toSpec"
  41. @copy="toCopy"
  42. >
  43. <template #source="{ row }">
  44. <span style="color: blue; cursor: pointer" @click="toUrl(row.url)">
  45. {{ row.source }}
  46. </span>
  47. </template>
  48. </data-table>
  49. </el-col>
  50. </el-row>
  51. </span>
  52. <el-row v-if="view === 'info'">
  53. <el-col :span="24" style="margin: 0 0 10px 0">
  54. <el-col :span="2">
  55. <el-button type="primary" size="mini" @click="toBack()">返回</el-button>
  56. </el-col>
  57. <el-col :span="2">
  58. <el-button type="primary" size="mini" @click="toSave(form)">保存</el-button>
  59. </el-col>
  60. </el-col>
  61. <el-col :span="24">
  62. <data-form :fields="infoFields" :rules="rules" v-model="form" labelWidth="150px" @save="toSave">
  63. <template #tags="{ item }">
  64. <el-cascader v-model="form[item.model]" :options="tagsList" :props="props" clearable filterable :show-all-levels="false"></el-cascader>
  65. </template>
  66. <template #act_tags>
  67. <el-option v-for="i in act_tagsList" :key="i.value" :label="i.label" :value="i.value">
  68. {{ i.label }}{{ i.show_goods === '0' ? '(商品脚标)' : '' }}
  69. </el-option>
  70. </template>
  71. <template #status>
  72. <el-option v-for="i in goodsStatusList" :key="i.value" :label="i.label" :value="i.value"></el-option>
  73. </template>
  74. <template #is_cashBack>
  75. <el-option v-for="i in statusList" :key="i.value" :label="i.label" :value="i.value"></el-option>
  76. </template>
  77. <template #cb_config>
  78. <el-form :model="cb_config" ref="cb_config" label-width="180px" @save="toSave" v-if="form.is_cashBack == '0'">
  79. <el-col :span="24">
  80. <el-form-item label="返现金额类型" prop="back_type">
  81. <el-select v-model="cb_config.back_type" clearable filterable placeholder="请选择返现金额类型" size="small" style="width: 100%">
  82. <el-option v-for="i in typeList" :key="i.value" :label="i.label" :value="i.value"> </el-option>
  83. </el-select>
  84. </el-form-item>
  85. </el-col>
  86. <el-col :span="24" v-if="cb_config.back_type == 'fixed'">
  87. <el-form-item label="具体返现金额设置" prop="money">
  88. <el-input v-model="cb_config.money" placeholder="请输入金额" type="number" size="small"></el-input>
  89. </el-form-item>
  90. </el-col>
  91. <el-col :span="24" v-if="cb_config.back_type == 'percent'">
  92. <el-form-item label="具体返现金额设置" prop="money">
  93. <el-input v-model="cb_config.money" placeholder="请输入百分比" type="number" size="small"></el-input>
  94. </el-form-item>
  95. </el-col>
  96. </el-form>
  97. </template>
  98. <template #brief>
  99. <editor v-model="form.brief" url="/files/point/goods/upload" />
  100. </template>
  101. </data-form>
  102. </el-col>
  103. </el-row>
  104. </div>
  105. </template>
  106. <script>
  107. const _ = require('lodash');
  108. import methodsUtil from '@/util/opera';
  109. import { mapState, createNamespacedHelpers } from 'vuex';
  110. const { mapActions: goods } = createNamespacedHelpers('goods');
  111. const { mapActions: goodsSpec } = createNamespacedHelpers('goodsSpec');
  112. const { mapActions: goodsTags } = createNamespacedHelpers('goodsTags');
  113. const { mapActions: dictData } = createNamespacedHelpers('dictData');
  114. const { mapActions: actTags } = createNamespacedHelpers('actTags');
  115. export default {
  116. name: 'index',
  117. props: {},
  118. components: {
  119. editor: () => import('@/components/editor.vue'),
  120. },
  121. data: function () {
  122. return {
  123. loadings: true,
  124. view: 'list',
  125. fields: [
  126. { label: '商品名称', model: 'name', showTip: false },
  127. // { label: '店铺名称', model: 'shop.name' },
  128. { label: '商品分类', model: 'tags', format: (i) => this.getTags(i) },
  129. { label: '活动标签', model: 'act_tags', format: (i) => this.getAct_tags(i) },
  130. { label: '商品状态', model: 'status', format: (i) => this.getStatus(i) },
  131. { label: '供应商', model: 'source', custom: true },
  132. { label: '排序', model: 'sort' },
  133. ],
  134. opera: [
  135. { label: '修改', method: 'edit' },
  136. { label: '上架', method: 'puton', display: (i) => i.status == '0', type: 'success' },
  137. { label: '下架', method: 'puton', display: (i) => i.status == '1', type: 'warning' },
  138. { label: '库存管理', method: 'spec' },
  139. { label: '复制', method: 'copy' },
  140. { label: '删除', method: 'delete', confirm: true, type: 'danger' },
  141. ],
  142. btnList: [{ label: '添加', method: 'add' }],
  143. searchFields: [
  144. { label: '商品名称', model: 'name' },
  145. { label: '规格名称', model: 'spec_name' },
  146. { label: '商品状态', model: 'status', type: 'select' },
  147. { label: '商品分类', model: 'tags', custom: true },
  148. { label: '供应商', model: 'source' },
  149. ],
  150. searchInfo: {},
  151. list: [],
  152. total: 0,
  153. // info部分
  154. infoFields: [
  155. { label: '商品名称', model: 'name' },
  156. { label: '商品分类', model: 'tags', custom: true },
  157. { label: '活动标签', model: 'act_tags', type: 'selectMany' },
  158. { label: '简短简介', model: 'shot_brief', maxLength: 50 },
  159. { label: '预计发货时间', model: 'send_time' },
  160. // { label: '商品状态', model: 'status', type: 'select' },
  161. { label: '商品来源', model: 'source' },
  162. { label: '网址', model: 'url' },
  163. { label: '排序', model: 'sort', type: 'number' },
  164. { label: '是否返现', model: 'is_cashBack', type: 'select' },
  165. { label: '返现设置', model: 'cb_config', custom: true },
  166. { label: '商品图片', model: 'file', type: 'upload', url: '/files/point/goods/upload' },
  167. { label: '商品介绍', model: 'brief', custom: true },
  168. ],
  169. rules: {},
  170. form: {},
  171. // 返现设置
  172. cb_config: {},
  173. // 商品分类
  174. tagsList: [],
  175. props: { multiple: true, label: 'label', value: 'code' },
  176. propss: { multiple: false, label: 'label', value: 'code' },
  177. // 活动标签
  178. act_tagsList: [],
  179. goodsStatusList: [],
  180. // 是否返现
  181. statusList: [],
  182. // 返现金额类型
  183. typeList: [{ label: '固定金额' }, { label: '百分比' }],
  184. // 查询条件
  185. searchQuery: {},
  186. };
  187. },
  188. created() {
  189. this.searchOthers();
  190. let route = JSON.parse(sessionStorage.getItem(this.$route.path));
  191. if (route) {
  192. this.search(route);
  193. this.$nextTick(() => {
  194. this.$refs.dataTable.setPage(route);
  195. });
  196. } else this.search();
  197. },
  198. methods: {
  199. ...dictData({ getDict: 'query' }),
  200. ...actTags({ actQuery: 'query' }),
  201. ...goodsTags(['tree']),
  202. ...goods(['copy', 'query', 'delete', 'fetch', 'update', 'create']),
  203. ...goodsSpec({ specQuery: 'query' }),
  204. ...methodsUtil,
  205. toSearch() {
  206. this.$refs.dataTable.resetPage();
  207. let res = this.$refs.dataTable.getPageConfig();
  208. this.search(res);
  209. },
  210. // 查询
  211. async search({ skip = 0, limit = this.$limit, ...others } = {}) {
  212. let query = { skip, limit, ...others, shop: this.user.shop.id };
  213. if (Object.keys(this.searchInfo).length > 0) query = { ...query, ...this.searchInfo };
  214. const res = await this.query(query);
  215. if (this.$checkRes(res)) {
  216. this.$set(this, `list`, res.data);
  217. this.$set(this, `total`, res.total);
  218. this.$set(this, `searchQuery`, query);
  219. sessionStorage.removeItem(this.$route.path);
  220. }
  221. this.loadings = false;
  222. },
  223. toUrl(url) {
  224. if (url) window.open(url, '_blank');
  225. else this.$message.error('该商品还未添加来源网址,无法跳转');
  226. },
  227. // 去编辑
  228. async toEdit({ data }) {
  229. const res = await this.fetch(data._id);
  230. if (this.$checkRes(res)) {
  231. this.$set(this, `form`, res.data);
  232. if (res.data.cb_config) this.cb_config = res.data.cb_config;
  233. this.view = 'info';
  234. } else this.$message.error('未找到指定数据');
  235. },
  236. // 添加自定义
  237. initAddData() {
  238. const obj = { status: '1', shop: _.get(this.user.shop, '_id') };
  239. this.$set(this, 'form', obj);
  240. },
  241. // 商品分类查询
  242. changeTags(value) {
  243. let tags = _.last(value);
  244. this.$set(this.searchInfo, `tags`, tags);
  245. },
  246. // 复制
  247. async toCopy({ data }) {
  248. this.$confirm('是否确认复制该商品?', '提示', {
  249. confirmButtonText: '确定',
  250. cancelButtonText: '取消',
  251. type: 'warning',
  252. }).then(async () => {
  253. let res;
  254. res = await this.copy(data._id);
  255. if (this.$checkRes(res)) {
  256. this.$message({ type: `success`, message: `复制成功` });
  257. this.toBack();
  258. }
  259. });
  260. },
  261. // 保存
  262. async toSave({ data }) {
  263. let res;
  264. if (data == '' || data == undefined) {
  265. let form = this.form;
  266. form.cb_config = this.cb_config;
  267. form.cb_config.money = Number(form.cb_config.money);
  268. if (form.id) res = await this.update(form);
  269. else res = await this.create(form);
  270. } else {
  271. data.cb_config = this.cb_config;
  272. data.cb_config.money = Number(data.cb_config.money);
  273. if (data.id) res = await this.update(data);
  274. else res = await this.create(data);
  275. }
  276. if (this.$checkRes(res)) {
  277. this.$message({ type: `success`, message: `维护信息成功` });
  278. this.toBack();
  279. }
  280. },
  281. // 执行返回
  282. toBack() {
  283. this.form = {};
  284. this.view = 'list';
  285. this.search(this.searchQuery);
  286. },
  287. // 上架
  288. async toPuton({ data }) {
  289. const res = await this.specQuery({ goods: data._id });
  290. if (this.$checkRes(res)) {
  291. if (res.total > 0) {
  292. this.$confirm('是否确认上架/下架该商品?', '提示', {
  293. confirmButtonText: '确定',
  294. cancelButtonText: '取消',
  295. type: 'warning',
  296. }).then(async () => {
  297. if (data.status == '1') data.status = '0';
  298. else data.status = '1';
  299. let res;
  300. if (data._id) res = await this.update(data);
  301. if (this.$checkRes(res)) this.$message({ type: `success`, message: `修改成功` });
  302. this.search(this.searchQuery);
  303. });
  304. } else {
  305. this.$message({ type: `warning`, message: `该商品没有规格,不允许上架` });
  306. }
  307. }
  308. },
  309. // 库存管理
  310. toSpec({ data }) {
  311. sessionStorage.setItem(this.$route.path, JSON.stringify(this.searchQuery));
  312. this.$router.push({ path: `/selfShop/spec/${data._id}` });
  313. },
  314. // 查询其他信息
  315. async searchOthers() {
  316. // 商品分类
  317. let res = await this.tree();
  318. if (this.$checkRes(res)) this.$set(this, `tagsList`, res.data);
  319. // 商品状态
  320. res = await this.getDict({ code: 'goods_status' });
  321. if (this.$checkRes(res)) this.$set(this, `goodsStatusList`, res.data);
  322. // 活动标签
  323. res = await this.actQuery();
  324. if (this.$checkRes(res)) this.$set(this, `act_tagsList`, res.data);
  325. // 是否返现
  326. res = await this.getDict({ code: 'use' });
  327. if (this.$checkRes(res)) this.$set(this, `statusList`, res.data);
  328. // 返现金额类型
  329. res = await this.getDict({ code: 'cashBack_type' });
  330. if (this.$checkRes(res)) this.$set(this, `typeList`, res.data);
  331. },
  332. // 商品状态
  333. getStatus(data) {
  334. const res = this.goodsStatusList.find((f) => f.value === data);
  335. if (res) return res.label;
  336. return '';
  337. },
  338. // 活动标签
  339. getAct_tags(data) {
  340. const arr = [];
  341. for (const val of data) {
  342. const r = this.act_tagsList.find((f) => f.value === val);
  343. if (r) arr.push(r.label);
  344. }
  345. return arr.join(';');
  346. },
  347. // 商品分类
  348. getTags(data) {
  349. let list = this.tagsList;
  350. const getChildren = (list) =>
  351. list.map((i) => {
  352. if (i.children) return getChildren(i.children);
  353. return i;
  354. });
  355. list = _.flattenDeep(getChildren(list));
  356. const arr = [];
  357. for (const ts of data) {
  358. const last = _.last(ts);
  359. const r = list.find((f) => f.code === last);
  360. if (r) arr.push(r.label);
  361. }
  362. return arr.join(';');
  363. },
  364. },
  365. computed: {
  366. ...mapState(['user']),
  367. },
  368. };
  369. </script>
  370. <style lang="less" scoped>
  371. .main {
  372. .one {
  373. margin: 0 0 10px 0;
  374. span:nth-child(1) {
  375. font-size: 20px;
  376. font-weight: 700;
  377. margin-right: 10px;
  378. }
  379. }
  380. }
  381. .el-col {
  382. margin: 10px 0;
  383. }
  384. </style>