index.vue 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520
  1. <template>
  2. <div>
  3. <el-row>
  4. <el-col :span="8" style="padding: 10px 0 0 10px">
  5. <div class="toolbar">
  6. <div class="inner-toolbar">
  7. <div class="toolbar-btns">
  8. <span class="categary">商品类别</span>
  9. <el-button size="mini" v-if="checkPermission(['category:add'])" type="primary"
  10. @click="handleAddCategary">新增类别</el-button>
  11. </div>
  12. </div>
  13. </div>
  14. <el-tree :data="categorys" node-key="category_id" :expand-on-click-node="false"
  15. :style="{ height: categoryHeight }" :default-expanded-keys="expandedKeys" ref="categoryTree">
  16. <span class="custom-tree-node" slot-scope="{ node, data }" @click="handleSeachShop(data)">
  17. <span>{{ data.name }}</span>
  18. <span>
  19. <el-button type="text" size="mini" :disabled="node.level === 3" icon="el-icon-plus"
  20. :title="node.level === 3 ? '类别最多添加三级' : '增加'" v-if="checkPermission(['category:add'])"
  21. @click.stop="handleAddCategary(data)"></el-button>
  22. <el-button type="text" style="color: #f56c6c" size="mini" icon="el-icon-minus"
  23. v-if="checkPermission(['category:del'])" @click.stop="handleDeleteCategory(data)"></el-button>
  24. <el-button type="text" size="mini" icon="el-icon-edit" v-if="checkPermission(['category:edit'])"
  25. @click.stop="handleEditCategary(data)"></el-button>
  26. <el-button type="text" size="mini" icon="el-icon-setting" v-if="checkPermission(['category:spec'])"
  27. @click.stop="handleEditSpecs(data)">规格</el-button>
  28. <!-- <el-button
  29. type="text"
  30. size="mini"
  31. icon="el-icon-setting"
  32. v-if="checkPermission(['category:params'])"
  33. @click.stop="handleEditParams(data)"
  34. >参数</el-button
  35. > -->
  36. </span>
  37. </span>
  38. </el-tree>
  39. </el-col>
  40. <el-col :span="16">
  41. <en-table-layout :tableData="tableData.data" class="draggable-table" :row-key="getRowKeys" :loading="loading"
  42. style="margin: 10px 10px 0 10px">
  43. <div slot="toolbar" class="inner-toolbar">
  44. <div class="toolbar-btns">
  45. <el-button size="mini" v-if="checkPermission(['goods:add'])" type="primary"
  46. @click="handleAddGoods('sail')">新增售卖商品</el-button>
  47. <el-button size="mini" v-if="checkPermission(['goodsLease:add'])" type="primary"
  48. @click="handleAddGoods('hire')">新增租赁商品</el-button>
  49. </div>
  50. <div class="toolbar-search" style="display: flex; align-items: center">
  51. <span>商品编号/sku/序列号:</span>
  52. <en-table-search @search="searchEvent" placeholder="请输入关键字搜索" />
  53. <el-button @click="refresh" icon="el-icon-refresh" type="info" plain></el-button>
  54. </div>
  55. </div>
  56. <template slot="table-columns">
  57. <el-table-column prop="sn" label="商品编号" />
  58. <el-table-column prop="name" label="商品名称" />
  59. <el-table-column prop="category_id" label="商品类别">
  60. <template slot-scope="scope">
  61. {{ categorysName(scope.row.category_id) }}
  62. </template>
  63. </el-table-column>
  64. <el-table-column prop="brand_id" label="品牌">
  65. <template slot-scope="scope">
  66. {{ brand(scope.row.brand_id) }}
  67. </template>
  68. </el-table-column>
  69. <el-table-column prop="" label="商品形式"><template slot-scope="scope">
  70. {{ sailOrHire(scope.row.hire_or_sail) }}
  71. </template></el-table-column>
  72. <!-- <el-table-column prop="price" label="价格" :formatter="MixinFormatPrice"/>
  73. <el-table-column prop="label_names" label="商品标签"/> -->
  74. <el-table-column label="上架" key="market_enable">
  75. <template slot-scope="scope">
  76. <el-switch v-model="scope.row.market_enable" active-color="#13ce66" inactive-color="#ff4949"
  77. @change="switchVal($event, scope.row)">
  78. </el-switch>
  79. <!-- <span>{{ scope.row.market_enable ? '上架' : '下架' }}</span> -->
  80. </template>
  81. </el-table-column>
  82. <el-table-column label="操作" width="200px">
  83. <template slot-scope="scope">
  84. <el-button type="primary" size="mini" v-if="checkPermission(['goods:edit'])"
  85. @click="handleEditGoods(scope.row)">编辑</el-button>
  86. <el-button size="mini" v-if="checkPermission(['goods:del'])" type="danger"
  87. @click="handleDeleteGoods(scope.row)">删除</el-button>
  88. </template>
  89. </el-table-column>
  90. </template>
  91. <el-pagination v-if="tableData" slot="pagination" @size-change="handlePageSizeChange"
  92. @current-change="handlePageCurrentChange" :current-page="tableData.page_no" :page-sizes="[50, 70, 90, 100]"
  93. :page-size="tableData.page_size" layout="total, sizes, prev, pager, next, jumper"
  94. :total="tableData.data_total">
  95. </el-pagination>
  96. </en-table-layout>
  97. </el-col>
  98. </el-row>
  99. <!--编辑分类dialog-->
  100. <el-dialog :title="categoryForm.name ? '编辑类别' : '添加类别'" :visible.sync="dialogCategoryVisible" width="500px"
  101. :close-on-click-modal="false" :close-on-press-escape="false">
  102. <el-form :model="categoryForm" ref="categoryForm" label-width="100px">
  103. <el-form-item label="上级类别名称" prop="parent_id">
  104. <el-cascader v-model="categoryForm.parent_id" ref="cascaderHandle" :options="categorys" :props="{
  105. label: 'name',
  106. value: 'category_id',
  107. emitPath: false,
  108. checkStrictly: true,
  109. }" @change="handleChange" clearable></el-cascader>
  110. </el-form-item>
  111. <el-form-item label="商品类别名称" prop="category_name">
  112. <el-input v-model="categoryForm.category_name" :maxlength="6"></el-input>
  113. </el-form-item>
  114. <el-form-item label="商品类别编号" prop="custom_sn">
  115. <el-input v-model="categoryForm.custom_sn"></el-input>
  116. </el-form-item>
  117. </el-form>
  118. <span slot="footer" class="dialog-footer">
  119. <el-button @click="dialogCategoryVisible = false">取 消</el-button>
  120. <el-button type="primary" @click="submitCategoryForm('categoryForm')">确 定</el-button>
  121. </span>
  122. </el-dialog>
  123. <!--编辑关联规格dialog-->
  124. <el-dialog title="关联规格" width="500px" :visible.sync="dialogSpecsVisible" :close-on-click-modal="false"
  125. :close-on-press-escape="false">
  126. <el-form :model="specsForm" ref="specsForm">
  127. <el-form-item label="选择规格">
  128. <el-select v-model="specsForm.selectedSpecsList" placeholder="请选择关联规格" multiple filterable
  129. style="width: 350px">
  130. <el-option v-for="item in specsForm.specsList" :key="item.id" :label="item.text" :value="item.id">
  131. </el-option>
  132. </el-select>
  133. </el-form-item>
  134. </el-form>
  135. <span slot="footer" class="dialog-footer">
  136. <el-button @click="dialogSpecsVisible = false">取 消</el-button>
  137. <el-button type="primary" @click="submitSpecsForm()">确 定</el-button>
  138. </span>
  139. </el-dialog>
  140. </div>
  141. </template>
  142. <script>
  143. import * as API_Goods from "@/api/goods";
  144. import * as API_Category from "@/api/category";
  145. import Sortable from "sortablejs";
  146. import * as API_brand from "@/api/basicSetting";
  147. export default {
  148. name: "productFile",
  149. data() {
  150. return {
  151. categorys: [],
  152. categoryForm: {},
  153. dialogCategoryVisible: false,
  154. // 列表loading状态
  155. loading: false,
  156. // 列表参数
  157. params: {
  158. page_no: 1,
  159. page_size: 50,
  160. category_id: "",
  161. },
  162. // 列表数据
  163. tableData: "",
  164. dialogSpecsVisible: false,
  165. // 编辑关联规格 表单
  166. specsForm: {
  167. category_id: null,
  168. selectedSpecsList: [],
  169. specsList: [],
  170. },
  171. // 左侧商品分类高度,和table保持一致
  172. categoryHeight: `${document.body.clientHeight - 219}px`,
  173. brandList: [],
  174. expandedKeys: [],
  175. };
  176. },
  177. mounted() {
  178. //this.rowDrop();
  179. this.GET_ProductList();
  180. this.GET_Category();
  181. this.$nextTick(() => {
  182. // 确保组件已渲染
  183. console.log(this.$refs.categoryTree);
  184. });
  185. this.timer = setInterval(function () {
  186. document.querySelectorAll(".el-cascader-node__label").forEach((el) => {
  187. el.onclick = function () {
  188. if (this.previousElementSibling) this.previousElementSibling.click();
  189. };
  190. });
  191. }, 1000);
  192. },
  193. methods: {
  194. sailOrHire(val) {
  195. let name = "";
  196. if (val == "sail") {
  197. name = "售卖";
  198. return name;
  199. } else {
  200. name = "租赁";
  201. return name;
  202. }
  203. },
  204. brand(val) {
  205. let name = "";
  206. this.brandList.map((el) => {
  207. if (el.brand_id == val) {
  208. return (name = el.name);
  209. }
  210. });
  211. return name;
  212. },
  213. //获取类别名称
  214. categorysName(val) {
  215. let name = "";
  216. this.categorys.map((el) => {
  217. if (el.category_id == val) {
  218. return (name = el.name);
  219. }
  220. });
  221. return name;
  222. },
  223. getRowKeys(row) {
  224. return row.id;
  225. },
  226. // 行拖拽
  227. rowDrop() {
  228. const tbody = document.querySelector(".draggable-table .el-table__body-wrapper tbody");
  229. const _this = this;
  230. Sortable.create(tbody, {
  231. animation: 300,
  232. onEnd({ newIndex, oldIndex }) {
  233. const currRow = _this.tableData.data.splice(oldIndex, 1)[0];
  234. _this.tableData.data.splice(newIndex, 0, currRow);
  235. /* console.log("拖拽后的行详情:", currRow); */
  236. _this.updateList(currRow.id, newIndex);
  237. },
  238. });
  239. },
  240. // 更新列表顺序
  241. updateList(id, newIndex) {
  242. // 假设API需要商品ID和新的索引位置
  243. console.log(this.tableData.data);
  244. /* API_Goods.updateSort({data:[{goods_id: id, sort: newIndex}]}).then((res) => {
  245. this.$message.success("更新成功!");
  246. this.GET_ProductList();
  247. }).catch(()=>{ this.$message.error("更新失败!");}) */
  248. },
  249. /* updateList(id, sort) {
  250. API_Goods.updateSort({ goods_id: id, sort: sort }).then((res) => {
  251. if (res.errno != 0) {
  252. this.getList();
  253. }
  254. });
  255. },
  256. //行拖拽
  257. rowDrop() {
  258. const tbody = document.querySelector(
  259. ".draggable-table .el-table__body-wrapper tbody"
  260. );
  261. const _this = this;
  262. Sortable.create(tbody, {
  263. animation: 300,
  264. onEnd({ newIndex, oldIndex }) {
  265. const currRow = _this.tableData.data.splice(oldIndex, 1)[0];
  266. _this.tableData.data.splice(newIndex, 0, currRow);
  267. console.log(currRow);
  268. _this.updateList(currRow.id, currRow.sort);
  269. },
  270. });
  271. },*/
  272. switchVal(val, row) {
  273. API_Goods.updateGoods({ id: row.id, market_enable: val }).then((res) => {
  274. this.GET_ProductList();
  275. });
  276. // console.log(val,row);
  277. },
  278. handleChange(val) {
  279. this.categoryForm.parent_id = val;
  280. this.$refs.cascaderHandle.dropDownVisible = false; // 监听值发生变化就关闭它
  281. },
  282. handleEditGoods(row) {
  283. this.$router.push({
  284. name: "goodsEditForm",
  285. params: { id: row.id, hire_or_sail: row.hire_or_sail },
  286. });
  287. },
  288. /** 编辑关联规格 */
  289. handleEditSpecs(cat) {
  290. API_Category.getSpecsByCategoryId(cat.category_id).then((response) => {
  291. this.specsForm = {
  292. ...this.specsForm,
  293. category_id: cat.category_id,
  294. specsList: response,
  295. selectedSpecsList: response
  296. .filter((item) => item.selected)
  297. .map((item) => item.id),
  298. };
  299. this.dialogSpecsVisible = true;
  300. });
  301. },
  302. /** 编辑分类参数 */
  303. handleEditParams(cat) {
  304. this.$router.push({
  305. name: "categoryParams",
  306. params: { id: cat.category_id },
  307. });
  308. },
  309. /** 编辑关联规格 表单提交 */
  310. submitSpecsForm() {
  311. API_Category.updateCategorySpecs(
  312. this.specsForm.category_id,
  313. this.specsForm.selectedSpecsList
  314. ).then((response) => {
  315. this.dialogSpecsVisible = false;
  316. this.$message.success("保存成功!");
  317. });
  318. },
  319. refresh() {
  320. this.params.page_no = 1;
  321. this.params.category_id = "";
  322. this.GET_ProductList();
  323. },
  324. handleSeachShop(data) {
  325. this.params.page_no = 1;
  326. this.params.category_id = data.category_id;
  327. this.GET_ProductList();
  328. },
  329. submitCategoryForm(formName) {
  330. const { category_id } = this.categoryForm;
  331. if (!category_id) {
  332. API_Category.addCategory(this.categoryForm).then(() => {
  333. this.dialogCategoryVisible = false;
  334. this.$message.success("保存成功!");
  335. this.$refs[formName].resetFields();
  336. this.GET_Category();
  337. });
  338. } else {
  339. API_Category.editCategory(category_id, this.categoryForm).then(
  340. (response) => {
  341. this.$message.success("保存成功!");
  342. this.dialogCategoryVisible = false;
  343. this.$refs[formName].resetFields();
  344. this.GET_Category();
  345. }
  346. );
  347. }
  348. },
  349. /* handleDeleteCategory(category) {
  350. this.$confirm("确定要删除这个分类吗?", "提示", { type: "warning" })
  351. .then(() => {
  352. API_Category.deleteCategory(category.category_id).then(() => {
  353. this.$message.success("删除成功!");
  354. this.GET_Category();
  355. });
  356. })
  357. .catch(() => {});
  358. } */handleDeleteCategory(data) {
  359. this.$nextTick(() => {
  360. const node = this.$refs.categoryTree.getNode(data.category_id);
  361. if (!node) {
  362. console.error('Node not found for category_id:', data.category_id);
  363. return;
  364. }
  365. // 保持父节点展开
  366. const parentNode = node.parent;
  367. if (parentNode && !this.expandedKeys.includes(parentNode.data.category_id)) {
  368. this.expandedKeys.push(parentNode.data.category_id);
  369. }
  370. // 执行删除操作
  371. this.$confirm("确定要删除这个分类吗?", "提示", { type: "warning" })
  372. .then(() => {
  373. API_Category.deleteCategory(data.category_id).then(() => {
  374. this.$message.success("删除成功!");
  375. this.GET_Category(); // 重新获取分类数据
  376. // 重新设置展开的节点,以防止收缩
  377. this.$nextTick(() => {
  378. this.$refs.categoryTree.store.expandedKeys = this.expandedKeys;
  379. });
  380. });
  381. })
  382. .catch(() => { });
  383. });
  384. },
  385. /** 添加类别 */
  386. handleAddCategary(category) {
  387. this.categoryForm = {
  388. parent_id: category ? category.category_id : 0,
  389. };
  390. this.dialogCategoryVisible = true;
  391. },
  392. /** 编辑类别 */
  393. handleEditCategary(category) {
  394. this.categoryForm = {
  395. parent_id: category.parent_id,
  396. category_id: category.category_id,
  397. category_name: category.name,
  398. custom_sn: category.custom_sn,
  399. };
  400. this.dialogCategoryVisible = true;
  401. },
  402. /** 获取类别 */
  403. GET_Category() {
  404. API_Category.getCategoryAllChildren("0").then((response) => {
  405. this.categorys = response;
  406. });
  407. },
  408. /** 分页大小发生改变 */
  409. handlePageSizeChange(size) {
  410. this.params.page_size = size;
  411. this.GET_ProductList();
  412. },
  413. /** 分页页数发生改变 */
  414. handlePageCurrentChange(page) {
  415. this.params.page_no = page;
  416. this.GET_ProductList();
  417. },
  418. /** 添加商品档案 */
  419. handleAddGoods(val) {
  420. console.log(val);
  421. this.$router.push({
  422. name: "goodsAddForm",
  423. params: { hire_or_sail: val },
  424. });
  425. },
  426. /** 删除商品档案 */
  427. handleDeleteGoods(row) {
  428. this.$confirm("确定要删除这个商品档案吗?", "提示", { type: "warning" })
  429. .then(() => {
  430. API_Goods.deleteGoods(row.id).then(() => {
  431. this.$message.success("删除成功!");
  432. this.GET_ProductList();
  433. });
  434. })
  435. .catch((error) => {
  436. console.log(error);
  437. });
  438. },
  439. /** 搜索事件触发 */
  440. searchEvent(data) {
  441. this.params = {
  442. ...this.params,
  443. keyword: data,
  444. };
  445. this.params.page_no = 1;
  446. this.GET_ProductList();
  447. },
  448. /** 获取商品档案列表 */
  449. GET_ProductList() {
  450. this.loading = true;
  451. API_Goods.getGoodsList(this.params)
  452. .then((response) => {
  453. this.loading = false;
  454. this.tableData = response;
  455. })
  456. .catch(() => {
  457. this.loading = false;
  458. });
  459. API_brand.getBrandList(this.params).then((response) => {
  460. this.loading = false;
  461. this.brandList = response.data;
  462. });
  463. },
  464. },
  465. };
  466. </script>
  467. <style type="text/scss" lang="scss" scoped>
  468. .toolbar {
  469. display: flex;
  470. -webkit-box-align: center;
  471. -ms-flex-align: center;
  472. align-items: center;
  473. height: 45px;
  474. border-bottom: 1px solid #e6ebf5;
  475. background-color: #fff;
  476. }
  477. .inner-toolbar {
  478. padding: 0 20px;
  479. .categary {
  480. padding-right: 10px;
  481. font-size: 14px;
  482. }
  483. }
  484. .face-image {
  485. display: block;
  486. width: 50px;
  487. height: 50px;
  488. margin: 0 auto;
  489. }
  490. /deep/ .el-dialog__body {
  491. padding: 10px 20px;
  492. }
  493. .custom-tree-node {
  494. flex: 1;
  495. display: flex;
  496. align-items: center;
  497. justify-content: space-between;
  498. font-size: 14px;
  499. padding-right: 8px;
  500. width: 500px;
  501. }
  502. </style>