index.vue 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539
  1. <template>
  2. <div>
  3. <el-row>
  4. <el-col :span="6" 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="18">
  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(['goodsAddForm'])" type="primary"
  46. @click="handleAddGoods('sail')">新增售卖商品</el-button>
  47. <el-button size="mini" v-if="checkPermission(['goodsAddForm'])" type="primary"
  48. @click="handleAddGoods('hire')">新增租赁商品</el-button>
  49. </div>
  50. <div class="toolbar-search" style="display: flex; align-items: center;">
  51. <span style=" white-space:nowrap;overflow:hidden;text-overflow:ellipsis;">商品编号/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" v-if="checkPermission(['goods:update'])">
  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(['goodsEditForm'])"
  85. @click="handleEditGoods(scope.row)">编辑</el-button>
  86. <el-button size="mini" v-if="checkPermission(['goods:delete'])" 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.GET_ProductList();
  179. this.GET_Category();
  180. this.timer = setInterval(function () {
  181. document.querySelectorAll(".el-cascader-node__label").forEach((el) => {
  182. el.onclick = function () {
  183. if (this.previousElementSibling) this.previousElementSibling.click();
  184. };
  185. });
  186. }, 1000);
  187. },
  188. methods: {
  189. sailOrHire(val) {
  190. let name = "";
  191. if (val == "sail") {
  192. name = "售卖";
  193. return name;
  194. } else {
  195. name = "租赁";
  196. return name;
  197. }
  198. },
  199. brand(val) {
  200. let name = "";
  201. this.brandList.map((el) => {
  202. if (el.brand_id == val) {
  203. return (name = el.name);
  204. }
  205. });
  206. return name;
  207. },
  208. //获取类别名称
  209. categorysName(val) {
  210. /* let name = "";
  211. this.categorys.map((el) => {
  212. if (el.category_id == val) {
  213. return (name = el.name);
  214. }
  215. });
  216. return name; */
  217. let name = "";
  218. const findCategoryName = (categories, categoryId) => {
  219. for (const category of categories) {
  220. if (category.category_id === categoryId) {
  221. name = category.name;
  222. break;
  223. }
  224. if (category.children && category.children.length > 0) {
  225. findCategoryName(category.children, categoryId);
  226. }
  227. }
  228. };
  229. findCategoryName(this.categorys, val);
  230. return name;
  231. },
  232. getRowKeys(row) {
  233. return row.id;
  234. },
  235. // 行拖拽
  236. rowDrop() {
  237. const tbody = document.querySelector(".draggable-table .el-table__body-wrapper tbody");
  238. const _this = this;
  239. Sortable.create(tbody, {
  240. animation: 300,
  241. onEnd({ newIndex, oldIndex }) {
  242. const currRow = _this.tableData.data.splice(oldIndex, 1)[0];
  243. _this.tableData.data.splice(newIndex, 0, currRow);
  244. /* console.log("拖拽后的行详情:", currRow); */
  245. _this.updateList(currRow.id, newIndex);
  246. },
  247. });
  248. },
  249. // 更新列表顺序
  250. updateList(id, newIndex) {
  251. // 假设API需要商品ID和新的索引位置
  252. console.log(this.tableData.data);
  253. /* API_Goods.updateSort({data:[{goods_id: id, sort: newIndex}]}).then((res) => {
  254. this.$message.success("更新成功!");
  255. this.GET_ProductList();
  256. }).catch(()=>{ this.$message.error("更新失败!");}) */
  257. },
  258. /* updateList(id, sort) {
  259. API_Goods.updateSort({ goods_id: id, sort: sort }).then((res) => {
  260. if (res.errno != 0) {
  261. this.getList();
  262. }
  263. });
  264. },
  265. //行拖拽
  266. rowDrop() {
  267. const tbody = document.querySelector(
  268. ".draggable-table .el-table__body-wrapper tbody"
  269. );
  270. const _this = this;
  271. Sortable.create(tbody, {
  272. animation: 300,
  273. onEnd({ newIndex, oldIndex }) {
  274. const currRow = _this.tableData.data.splice(oldIndex, 1)[0];
  275. _this.tableData.data.splice(newIndex, 0, currRow);
  276. console.log(currRow);
  277. _this.updateList(currRow.id, currRow.sort);
  278. },
  279. });
  280. },*/
  281. switchVal(val, row) {
  282. API_Goods.updateGoods({ id: row.id, market_enable: val }).then((res) => {
  283. this.GET_ProductList();
  284. });
  285. // console.log(val,row);
  286. },
  287. handleChange(val) {
  288. this.categoryForm.parent_id = val;
  289. this.$refs.cascaderHandle.dropDownVisible = false; // 监听值发生变化就关闭它
  290. },
  291. handleEditGoods(row) {
  292. this.$router.push({
  293. name: "goodsEditForm",
  294. params: { id: row.id, hire_or_sail: row.hire_or_sail },
  295. });
  296. },
  297. /** 编辑关联规格 */
  298. handleEditSpecs(cat) {
  299. API_Category.getSpecsByCategoryId(cat.category_id).then((response) => {
  300. this.specsForm = {
  301. ...this.specsForm,
  302. category_id: cat.category_id,
  303. specsList: response,
  304. selectedSpecsList: response
  305. .filter((item) => item.selected)
  306. .map((item) => item.id),
  307. };
  308. this.dialogSpecsVisible = true;
  309. });
  310. },
  311. /** 编辑分类参数 */
  312. handleEditParams(cat) {
  313. this.$router.push({
  314. name: "categoryParams",
  315. params: { id: cat.category_id },
  316. });
  317. },
  318. /** 编辑关联规格 表单提交 */
  319. submitSpecsForm() {
  320. API_Category.updateCategorySpecs(
  321. this.specsForm.category_id,
  322. this.specsForm.selectedSpecsList
  323. ).then((response) => {
  324. this.dialogSpecsVisible = false;
  325. this.$message.success("保存成功!");
  326. });
  327. },
  328. refresh() {
  329. this.params.page_no = 1;
  330. this.params.category_id = "";
  331. this.GET_ProductList();
  332. },
  333. handleSeachShop(data) {
  334. this.params.page_no = 1;
  335. this.params.category_id = data.category_id;
  336. this.GET_ProductList();
  337. },
  338. submitCategoryForm(formName) {
  339. const { category_id } = this.categoryForm;
  340. if (!category_id) {
  341. API_Category.addCategory(this.categoryForm).then(() => {
  342. this.dialogCategoryVisible = false;
  343. this.$message.success("保存成功!");
  344. this.$refs[formName].resetFields();
  345. this.GET_Category();
  346. });
  347. } else {
  348. API_Category.editCategory(category_id, this.categoryForm).then(
  349. (response) => {
  350. this.$message.success("保存成功!");
  351. this.dialogCategoryVisible = false;
  352. this.$refs[formName].resetFields();
  353. this.GET_Category();
  354. }
  355. );
  356. }
  357. },
  358. /* handleDeleteCategory(category) {
  359. this.$confirm("确定要删除这个分类吗?", "提示", { type: "warning" })
  360. .then(() => {
  361. API_Category.deleteCategory(category.category_id).then(() => {
  362. this.$message.success("删除成功!");
  363. this.GET_Category();
  364. });
  365. })
  366. .catch(() => {});
  367. } */handleDeleteCategory(data) {
  368. this.$nextTick(() => {
  369. const node = this.$refs.categoryTree.getNode(data.category_id);
  370. if (!node) {
  371. console.error('Node not found for category_id:', data.category_id);
  372. return;
  373. }
  374. // 保持父节点展开
  375. const parentNode = node.parent;
  376. if (parentNode && !this.expandedKeys.includes(parentNode.data.category_id)) {
  377. this.expandedKeys.push(parentNode.data.category_id);
  378. }
  379. // 执行删除操作
  380. this.$confirm("确定要删除这个分类吗?", "提示", { type: "warning" })
  381. .then(() => {
  382. API_Category.deleteCategory(data.category_id).then(() => {
  383. this.$message.success("删除成功!");
  384. this.GET_Category(); // 重新获取分类数据
  385. // 重新设置展开的节点,以防止收缩
  386. this.$nextTick(() => {
  387. this.$refs.categoryTree.store.expandedKeys = this.expandedKeys;
  388. });
  389. });
  390. })
  391. .catch(() => { });
  392. });
  393. },
  394. /** 添加类别 */
  395. handleAddCategary(category) {
  396. this.categoryForm = {
  397. parent_id: category ? category.category_id : 0,
  398. };
  399. this.dialogCategoryVisible = true;
  400. },
  401. /** 编辑类别 */
  402. handleEditCategary(category) {
  403. this.categoryForm = {
  404. parent_id: category.parent_id,
  405. category_id: category.category_id,
  406. category_name: category.name,
  407. custom_sn: category.custom_sn,
  408. };
  409. this.dialogCategoryVisible = true;
  410. },
  411. /** 获取类别 */
  412. GET_Category() {
  413. API_Category.getCategoryAllChildren("0").then((response) => {
  414. this.categorys = response;
  415. });
  416. },
  417. /** 分页大小发生改变 */
  418. handlePageSizeChange(size) {
  419. this.params.page_size = size;
  420. this.GET_ProductList();
  421. },
  422. /** 分页页数发生改变 */
  423. handlePageCurrentChange(page) {
  424. this.params.page_no = page;
  425. this.GET_ProductList();
  426. },
  427. /** 添加商品档案 */
  428. handleAddGoods(val) {
  429. console.log(val);
  430. this.$router.push({
  431. name: "goodsAddForm",
  432. params: { hire_or_sail: val },
  433. });
  434. },
  435. /** 删除商品档案 */
  436. handleDeleteGoods(row) {
  437. this.$confirm("确定要删除这个商品档案吗?", "提示", { type: "warning" })
  438. .then(() => {
  439. API_Goods.deleteGoods(row.id).then(() => {
  440. this.$message.success("删除成功!");
  441. this.GET_ProductList();
  442. });
  443. })
  444. .catch((error) => {
  445. console.log(error);
  446. });
  447. },
  448. /** 搜索事件触发 */
  449. searchEvent(data) {
  450. this.params = {
  451. ...this.params,
  452. keyword: data,
  453. };
  454. this.params.page_no = 1;
  455. this.GET_ProductList();
  456. },
  457. /** 获取商品档案列表 */
  458. GET_ProductList() {
  459. this.loading = true;
  460. API_Goods.getGoodsList(this.params)
  461. .then((response) => {
  462. this.loading = false;
  463. this.tableData = response;
  464. })
  465. .catch(() => {
  466. this.loading = false;
  467. });
  468. API_brand.getBrandList(this.params).then((response) => {
  469. this.loading = false;
  470. this.brandList = response.data;
  471. });
  472. },
  473. },
  474. watch: {
  475. $route: {
  476. immediate: true,
  477. handler(newVal) {
  478. if (newVal.name === "productFile") {
  479. this.GET_ProductList();
  480. }
  481. },
  482. },
  483. },
  484. };
  485. </script>
  486. <style type="text/scss" lang="scss" scoped>
  487. .toolbar {
  488. display: flex;
  489. -webkit-box-align: center;
  490. -ms-flex-align: center;
  491. align-items: center;
  492. height: 45px;
  493. border-bottom: 1px solid #e6ebf5;
  494. background-color: #fff;
  495. }
  496. .inner-toolbar {
  497. padding: 0 20px;
  498. .categary {
  499. padding-right: 10px;
  500. font-size: 14px;
  501. }
  502. }
  503. .face-image {
  504. display: block;
  505. width: 50px;
  506. height: 50px;
  507. margin: 0 auto;
  508. }
  509. /deep/ .el-dialog__body {
  510. padding: 10px 20px;
  511. }
  512. .custom-tree-node {
  513. flex: 1;
  514. display: flex;
  515. align-items: center;
  516. justify-content: space-between;
  517. font-size: 14px;
  518. padding-right: 8px;
  519. width: 500px;
  520. }
  521. </style>