AddGood.vue 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476
  1. <template>
  2. <div class="add">
  3. <el-card class="add-container">
  4. <el-form
  5. :model="state.goodForm"
  6. :rules="state.rules"
  7. ref="goodRef"
  8. label-width="140px"
  9. class="goodForm"
  10. >
  11. <el-form-item required label="商品所属店铺">
  12. <el-select
  13. style="width: 300px"
  14. v-model="state.goodForm.shopIds"
  15. filterable
  16. clearable
  17. placeholder="请选择店铺"
  18. @change="getShopDeatil"
  19. >
  20. <el-option
  21. v-for="item in state.shopList"
  22. :key="item.id"
  23. :label="item.name"
  24. :value="item.id"
  25. />
  26. </el-select>
  27. </el-form-item>
  28. <el-form-item required label="商品分类">
  29. <!-- <el-select
  30. style="width: 300px"
  31. v-model="state.goodForm.goodsCategoryId"
  32. filterable
  33. clearable
  34. placeholder="请选择分类"
  35. >
  36. <el-option
  37. v-for="item in categoryList"
  38. :key="item.categoryId"
  39. :label="item.categoryName"
  40. :value="item.categoryId"
  41. />
  42. </el-select> -->
  43. <el-cascader
  44. :props="props"
  45. collapse-tags
  46. clearable
  47. v-model="state.goodForm.goodsCategoryId"
  48. ></el-cascader>
  49. </el-form-item>
  50. <el-form-item label="商品名称" prop="goodsName">
  51. <el-input
  52. style="width: 300px"
  53. v-model="state.goodForm.goodsName"
  54. placeholder="请输入商品名称"
  55. ></el-input>
  56. </el-form-item>
  57. <el-form-item label="商品简介" prop="goodsIntro">
  58. <el-input
  59. style="width: 300px"
  60. type="textarea"
  61. v-model="state.goodForm.goodsIntro"
  62. placeholder="请输入商品简介(100字)"
  63. ></el-input>
  64. </el-form-item>
  65. <el-form-item label="商品价格" prop="originalPrice">
  66. <el-input
  67. min="0"
  68. style="width: 300px"
  69. v-model="state.goodForm.originalPrice"
  70. placeholder="请输入商品价格"
  71. ></el-input>
  72. </el-form-item>
  73. <el-form-item label="商品售卖价" prop="sellingPrice">
  74. <el-input
  75. min="0"
  76. style="width: 300px"
  77. v-model="state.goodForm.sellingPrice"
  78. placeholder="请输入商品售价"
  79. ></el-input>
  80. </el-form-item>
  81. <el-form-item label="商品库存" prop="stockNum">
  82. <el-input
  83. type="number"
  84. min="0"
  85. style="width: 300px"
  86. v-model="state.goodForm.stockNum"
  87. placeholder="请输入商品库存"
  88. ></el-input>
  89. </el-form-item>
  90. <el-form-item label="商品标签" prop="tag">
  91. <el-input
  92. style="width: 300px"
  93. v-model="state.goodForm.tag"
  94. placeholder="请输入商品小标签"
  95. ></el-input>
  96. </el-form-item>
  97. <el-form-item label="上架状态" prop="goodsSellStatus">
  98. <el-radio-group v-model="state.goodForm.goodsSellStatus">
  99. <el-radio label="0">上架</el-radio>
  100. <el-radio label="1">下架</el-radio>
  101. </el-radio-group>
  102. </el-form-item>
  103. <el-form-item label="交易方式">
  104. <el-radio-group v-model="state.goodForm.tradeType">
  105. <el-radio :label="0" :disabled="state.shopDetail.tradeType === 1"
  106. >线下</el-radio
  107. >
  108. <el-radio :label="1" :disabled="state.shopDetail.tradeType === 0"
  109. >线上</el-radio
  110. >
  111. <!-- <el-radio :label="2" :disabled="state.shopDetail.tradeType === 2"
  112. >根据商品情况自行设定</el-radio
  113. > -->
  114. </el-radio-group>
  115. </el-form-item>
  116. <el-form-item required label="商品主图" prop="goodsCoverImg">
  117. <el-upload
  118. class="avatar-uploader"
  119. :show-file-list="false"
  120. :before-upload="beforeAvatarUpload"
  121. :http-request="simpleImgHttpUpload"
  122. >
  123. <img
  124. v-if="state.goodForm.goodsCoverImg"
  125. :src="state.goodForm.goodsCoverImg"
  126. class="avatar"
  127. />
  128. <el-icon v-else class="avatar-uploader-icon"><Plus /></el-icon>
  129. </el-upload>
  130. </el-form-item>
  131. <el-form-item label="详情内容" prop="goodsDetailContent">
  132. <div>
  133. <el-button
  134. v-if="state.goodForm.detailType"
  135. @click="state.goodForm.detailType = 0"
  136. >重新设置</el-button
  137. >
  138. <div
  139. v-if="
  140. state.goodForm.detailType == 0 || !state.goodForm.detailType
  141. "
  142. >
  143. <Editor
  144. v-model:value="state.goodForm.goodsDetailContent"
  145. ></Editor>
  146. </div>
  147. <div v-if="state.goodForm.detailType == 2">
  148. <el-image :src="state.goodForm.goodsDetailContent" fit="fill" />
  149. </div>
  150. <div v-if="state.goodForm.detailType == 1">
  151. <el-text>{{ state.goodForm.goodsDetailContent }}</el-text>
  152. </div>
  153. </div>
  154. </el-form-item>
  155. <el-form-item>
  156. <el-button type="primary" @click="submitAdd()">{{
  157. state.id ? "立即修改" : "立即创建"
  158. }}</el-button>
  159. </el-form-item>
  160. </el-form>
  161. </el-card>
  162. </div>
  163. </template>
  164. <script lang="ts" setup>
  165. import {
  166. reactive,
  167. ref,
  168. onMounted,
  169. onBeforeUnmount,
  170. getCurrentInstance
  171. } from "vue";
  172. import { request, uploadToOSS } from "@/utils/index";
  173. import { ElMessage } from "element-plus";
  174. import type { UploadProps } from "element-plus";
  175. import { Plus } from "@element-plus/icons-vue";
  176. import Editor from "@/components/Editor.vue";
  177. import { useRoute, useRouter } from "vue-router";
  178. const { proxy } = getCurrentInstance();
  179. const editor = ref(null);
  180. const goodRef = ref(null);
  181. const route = useRoute();
  182. const router = useRouter();
  183. const { id } = route.query;
  184. const state = reactive({
  185. id: id,
  186. defaultCate: "",
  187. shopList: [],
  188. shopDetail: {},
  189. goodForm: {
  190. DetailType: 0,
  191. shopIds: undefined,
  192. goodsName: "",
  193. goodsCategoryId: undefined,
  194. goodsIntro: "",
  195. originalPrice: "",
  196. sellingPrice: "",
  197. stockNum: "",
  198. goodsSellStatus: "0",
  199. goodsCoverImg: "",
  200. goodsDetailContent: "",
  201. tradeType: 0,
  202. tag: ""
  203. },
  204. rules: {
  205. shopIds: [
  206. { required: "true", message: "请选择商品所属店铺", trigger: ["change"] }
  207. ],
  208. goodsName: [
  209. { required: "true", message: "请填写商品名称", trigger: ["change"] }
  210. ],
  211. originalPrice: [
  212. { required: "true", message: "请填写商品价格", trigger: ["change"] }
  213. ],
  214. sellingPrice: [
  215. { required: "true", message: "请填写商品售价", trigger: ["change"] }
  216. ],
  217. stockNum: [
  218. { required: "true", message: "请填写商品库存", trigger: ["change"] }
  219. ],
  220. // tag: [{ required: "true", message: "请填写商品标签", trigger: ["change"] }],
  221. goodsCoverImg: [
  222. { required: "true", message: "请上传商品图片", trigger: ["change"] }
  223. ],
  224. goodsDetailContent: [
  225. { required: "true", message: "请填写详情内容", trigger: ["change"] }
  226. ]
  227. },
  228. categoryId: "",
  229. category: {
  230. lazy: true,
  231. lazyLoad(node, resolve) {
  232. const { level = 0, value } = node;
  233. request
  234. .get("/mallService/mechanism/category", {
  235. params: {
  236. page: 1,
  237. pageSize: 1000,
  238. categoryLevel: level + 1,
  239. parentId: value || 0
  240. }
  241. })
  242. .then(res => {
  243. const list = res.data.list;
  244. const nodes = list.map(item => ({
  245. value: item.categoryId,
  246. label: item.categoryName,
  247. leaf: level > 1
  248. }));
  249. resolve(nodes);
  250. });
  251. }
  252. }
  253. });
  254. const categoryList = ref([]);
  255. const props = {
  256. lazy: true,
  257. lazyLoad(node, resolve) {
  258. const { level, value } = node;
  259. console.log("node", node);
  260. request
  261. .get("/mallService/mechanism/category", {
  262. params: {
  263. page: 1,
  264. pageSize: 1000,
  265. parentId: value
  266. }
  267. })
  268. .then(resp => {
  269. let nodes = resp.data.list.map(v => {
  270. return {
  271. ...v,
  272. value: v.categoryId,
  273. label: v.categoryName,
  274. leaf: level >= 1
  275. };
  276. });
  277. resolve(nodes);
  278. });
  279. // setTimeout(() => {
  280. // const nodes = Array.from({ length: level + 1 }).map((item) => ({
  281. // value: ++id,
  282. // label: `Option - ${id}`,
  283. // leaf: level >= 2,
  284. // }))
  285. // // Invoke `resolve` callback to return the child nodes data and indicate the loading is finished.
  286. // resolve(nodes)
  287. // }, 1000)
  288. }
  289. };
  290. let instance;
  291. const getShopList = () => {
  292. request
  293. .get("/articleService/serviceProvider/staff/shop/paginate", {
  294. params: {
  295. page: 1,
  296. pageSize: 9999,
  297. type: -1,
  298. status: -1
  299. }
  300. })
  301. .then(resp => {
  302. state.shopList = resp.data.list;
  303. });
  304. };
  305. const getShopDeatil = id => {
  306. request.get(`/articleService/serviceProvider/shop?id=${id}`).then(resp => {
  307. state.shopDetail = resp.data;
  308. });
  309. };
  310. onMounted(async () => {
  311. console.log("加载成功");
  312. await initCategory();
  313. getShopList();
  314. state.goodForm.tradeType = state.shopDetail?.tradeType;
  315. if (id) {
  316. request.get(`/mallService/mechanism/goods/${id}`).then(res => {
  317. const {
  318. goods,
  319. firstCategory,
  320. secondCategory,
  321. thirdCategory,
  322. category,
  323. shopIds
  324. } = res?.data;
  325. state.goodForm = {
  326. shopIds: shopIds?.[0] || undefined,
  327. goodsName: goods.goodsName,
  328. goodsIntro: goods.goodsIntro,
  329. originalPrice: goods.originalPrice / 100,
  330. sellingPrice: goods.sellingPrice / 100,
  331. stockNum: goods.stockNum,
  332. goodsSellStatus: String(goods.goodsSellStatus),
  333. goodsCoverImg: goods.goodsCoverImg,
  334. tag: goods.tag,
  335. goodsDetailContent: goods.goodsDetailContent,
  336. tradeType: goods.tradeType,
  337. goodsCategoryId: category.categoryId,
  338. detailType: goods.DetailType
  339. };
  340. state.categoryId = goods.goodsCategoryId;
  341. state.defaultCate = `${firstCategory?.categoryName}/${secondCategory?.categoryName}/${thirdCategory?.categoryName}`;
  342. getShopDeatil(shopIds[0]);
  343. if (instance) {
  344. // 初始化商品详情 html
  345. // instance.txt.html(goods.goodsDetailContent);
  346. }
  347. });
  348. }
  349. });
  350. onBeforeUnmount(() => {
  351. // instance.destroy();
  352. instance = null;
  353. });
  354. const initCategory = () => {
  355. return new Promise(resolve => {
  356. request
  357. .get("/mallService/mechanism/category", {
  358. params: {
  359. page: 1,
  360. pageSize: 1000
  361. }
  362. })
  363. .then(res => {
  364. const list = res.data.list;
  365. categoryList.value = list;
  366. resolve();
  367. });
  368. });
  369. };
  370. const submitAdd = () => {
  371. goodRef.value.validate(vaild => {
  372. if (vaild) {
  373. // 默认新增用 post 方法
  374. let params = {
  375. shopIds: [state.goodForm.shopIds],
  376. goodsCategoryId: Number(state.goodForm.goodsCategoryId),
  377. goodsCoverImg: state.goodForm.goodsCoverImg,
  378. goodsDetailContent: state.goodForm.goodsDetailContent,
  379. goodsIntro: state.goodForm.goodsIntro,
  380. goodsName: state.goodForm.goodsName,
  381. goodsSellStatus: Number(state.goodForm.goodsSellStatus),
  382. originalPrice: Number(
  383. (Number(state.goodForm.originalPrice) * 100).toFixed(2)
  384. ),
  385. sellingPrice: Number(
  386. (Number(state.goodForm.sellingPrice) * 100).toFixed(2)
  387. ),
  388. stockNum: Number(state.goodForm.stockNum),
  389. tag: state.goodForm.tag,
  390. tradeType: state.goodForm.tradeType,
  391. detailType: state.goodForm.detailType
  392. };
  393. console.log("params", params);
  394. if (id) {
  395. params.goodsId = Number(id);
  396. // 修改商品使用 put 方法
  397. request.put("/mallService/mechanism/goods", params).then(resp => {
  398. ElMessage.success(resp.message);
  399. router.push({ path: "/shop/goods" });
  400. });
  401. } else {
  402. request.post("/mallService/mechanism/goods", params).then(resp => {
  403. ElMessage.success(resp.message);
  404. router.push({ path: "/shop/goods" });
  405. });
  406. }
  407. }
  408. });
  409. };
  410. const handleBeforeUpload = file => {
  411. const sufix = file.name.split(".")[1] || "";
  412. if (!["jpg", "jpeg", "png"].includes(sufix)) {
  413. ElMessage.error("请上传 jpg、jpeg、png 格式的图片");
  414. return false;
  415. }
  416. };
  417. const handleUrlSuccess = val => {
  418. state.goodForm.goodsCoverImg = val.data || "";
  419. };
  420. const handleChangeCate = val => {
  421. state.categoryId = val[2] || 0;
  422. };
  423. const beforeAvatarUpload: UploadProps["beforeUpload"] = rawFile => {
  424. if (!["image/png", "image/jpeg"].includes(rawFile.type)) {
  425. ElMessage.error("Avatar picture must be JPG format!");
  426. return false;
  427. } else if (rawFile.size / 1024 / 1024 > 2) {
  428. ElMessage.error("Avatar picture size can not exceed 2MB!");
  429. return false;
  430. }
  431. return true;
  432. };
  433. const simpleImgHttpUpload = options => {
  434. console.log("options", options);
  435. uploadToOSS(options.file, options.file.name).then(resp => {
  436. state.goodForm.goodsCoverImg = resp;
  437. });
  438. };
  439. </script>
  440. <style scoped>
  441. .add {
  442. display: flex;
  443. }
  444. .add-container {
  445. flex: 1;
  446. height: 100%;
  447. }
  448. .avatar-uploader {
  449. width: 160px;
  450. height: 160px;
  451. color: #ddd;
  452. font-size: 30px;
  453. }
  454. .avatar-uploader-icon {
  455. display: block;
  456. width: 160px;
  457. height: 160px;
  458. border: 1px solid #e9e9e9;
  459. padding: 32px 17px;
  460. display: flex;
  461. justify-content: center;
  462. }
  463. </style>