create.vue 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464
  1. <template>
  2. <div class="create-skill-wrapper">
  3. <div class="create-skill-title">新建技能服务</div>
  4. <div class="progress-wrapper">
  5. <div class="progress-title">完成度: {{ percent }}%</div>
  6. <el-progress
  7. class="progress-bar"
  8. :show-text="false"
  9. color="#308eff"
  10. :percentage="percent"></el-progress>
  11. </div>
  12. <div class="form-wrapper">
  13. <!-- 第一步 -->
  14. <div v-show="currentStep === 1">
  15. <div class="skill-field">
  16. <div class="field-name"><span class="required">*</span> 技能标题</div>
  17. <el-input
  18. class="skill-title-input"
  19. :class="{ 'is-error': titleError }"
  20. v-model="title"
  21. maxlength="50"
  22. show-word-limit
  23. placeholder="请输入"></el-input>
  24. </div>
  25. </div>
  26. <!-- 第二步 -->
  27. <div v-show="currentStep === 2">
  28. <div class="price-field">
  29. <div class="price-wrapper">
  30. <div class="field-name"><span class="required">*</span> 技能价格</div>
  31. <el-input
  32. class="price-input"
  33. :class="{ 'is-error': priceError }"
  34. type="number"
  35. placeholder="请输入价格"
  36. @input="handlePriceInput"
  37. v-model="price"></el-input>
  38. <span class="yuan">元</span>
  39. </div>
  40. <div class="cate-wrapper">
  41. <div class="field-name"><span class="required">*</span> 技能类别</div>
  42. <div class="cate-content">
  43. <el-cascader
  44. class="cate-cascader"
  45. :class="{ 'is-error': cateError }"
  46. v-model="selectedSkillCate"
  47. :options="skillCate"
  48. :props="{ expandTrigger: 'hover' }"
  49. placeholder="请选择类别"></el-cascader>
  50. </div>
  51. </div>
  52. </div>
  53. <div class="content-field">
  54. <div class="field-name"><span class="required">*</span> 技能描述</div>
  55. <div class="content-editor">
  56. <el-input
  57. class="content-input"
  58. type="textarea"
  59. :rows="8"
  60. :autosize="{ minRows: 8 }"
  61. placeholder="请输入技能描述"
  62. v-model="content"></el-input>
  63. </div>
  64. </div>
  65. </div>
  66. <!-- 第三步 -->
  67. <div v-show="currentStep === 3">
  68. <div class="image-field">
  69. <div class="field-name"><span class="required">*</span> 技能介绍图</div>
  70. <multi-uploader
  71. class="skill-image-uploader"
  72. :showTips="false"
  73. v-model="imageList"></multi-uploader>
  74. <div class="upload-tips">建议上传多张能证明技能价值的图片
  75. 第一张默认为封面
  76. 建议比例16:9(如:1920*1080、800*450)支持J(E)PG、PNG格式,小于5MB
  77. </div>
  78. </div>
  79. </div>
  80. </div>
  81. <div class="example-wrapper">
  82. <div class="example-content">
  83. <div class="example-title">
  84. <img class="example-icon" src="@/assets/img/skill/example-icon@2x.png" alt="example-icon">
  85. <span>示例</span>
  86. </div>
  87. <div class="example-text">{{ exampleText }}</div>
  88. </div>
  89. <img class="example-img" :src="exampleImage" alt="example-img">
  90. </div>
  91. <div class="actions-wrapper">
  92. <div class="actions-left">
  93. <el-button
  94. class="prev-btn"
  95. v-show="currentStep > 1"
  96. :disabled="currentStep === 1"
  97. @click="handlePrev">← 上一步</el-button>
  98. </div>
  99. <div class="actions-right">
  100. <el-button
  101. class="next-btn"
  102. :loading="publishIsLoading"
  103. :disabled="pcreateIsLoading"
  104. @click="handleNext">{{ currentStep === 3 ? '提交审核' : '下一步' }}</el-button>
  105. <el-button
  106. class="pcreate-btn"
  107. :loading="pcreateIsLoading"
  108. :disabled="publishIsLoading"
  109. v-show="currentStep === 3"
  110. @click="handlePcreate">存草稿</el-button>
  111. </div>
  112. </div>
  113. </div>
  114. </template>
  115. <script>
  116. import { mapState } from "vuex";
  117. import editor from "@/components/editor";
  118. import multiUploader from '@/components/multi-uploader';
  119. import ExampleImage1 from '@/assets/img/skill/example-img1@2x.png'
  120. import ExampleImage2 from '@/assets/img/skill/example-img2@2x.png'
  121. import ExampleImage3 from '@/assets/img/skill/example-img3@2x.png'
  122. export default {
  123. // async asyncData({ $axios, params }) {
  124. // let res = await $axios.$get(`/api/vip/getList`)
  125. // console.log('init', res)
  126. // },
  127. head: {
  128. title: "新建技能服务"
  129. },
  130. components: {
  131. editor,
  132. multiUploader
  133. },
  134. data() {
  135. return {
  136. ExampleImage1,
  137. ExampleImage2,
  138. ExampleImage3,
  139. currentStep: 1, // 当前的步骤:1、2、3
  140. sale_id: '', // 编辑时的 id
  141. title: '', // 技能标题
  142. price: '', // 技能价格
  143. selectedSkillCate: [], // 选中的技能分类
  144. skillCate: [], // 技能分类数据源
  145. content: '', // 技能描述
  146. defaultContent: '', // 默认的技能描述
  147. imageList: [], // 图片列表
  148. publishIsLoading: false, // 是否在提交审核
  149. pcreateIsLoading: false, // 是否在存草稿中
  150. priceError: false,
  151. titleError: false,
  152. cateError: false,
  153. contentError: false
  154. }
  155. },
  156. computed: {
  157. // 在示例中展示的文字
  158. exampleText () {
  159. let text = ''
  160. if (this.currentStep === 1) {
  161. text = `最好是一个细分类目下具体有场景的服务内容!
  162. 如:
  163. 1、我可以2小时内完成一个VUE公共组件编写
  164. 2、我可以完成小程序、公众号页面(100%)还原
  165. 3、我可以完成网站https证书安装部署,提高网站安全性和用户信任`
  166. } else if (this.currentStep === 2) {
  167. text = `服务内容需要把边界和风险描述清楚!
  168. 如:
  169. 服务包含的内容:
  170. 1、介绍你的服务包含哪些内容
  171. 2、明确好服务边界,需要由具体的量词描述
  172. 服务优势:
  173. 1、你有过哪些相关经验
  174. 2、你比其他人在哪些方面有优势
  175. 需客户提供的信息:
  176. 1、你的工作开展前需要客户提供哪些资料
  177. 其他:
  178. 1、将服务风险前置,如修改次数以及超出服务部分如何收费的规则等`
  179. } else if (this.currentStep === 3) {
  180. text = `您可以上传工作的最终交付物截图,增加技能的吸引力,让您的技能区别于其他人才,提高曝光率和成单率`
  181. }
  182. return text
  183. },
  184. // 在示例中展示的图片
  185. exampleImage () {
  186. let image = null
  187. if (this.currentStep === 1) {
  188. image = this.ExampleImage1
  189. } else if (this.currentStep === 2) {
  190. image = this.ExampleImage2
  191. } else if (this.currentStep === 3) {
  192. image = this.ExampleImage3
  193. }
  194. return image
  195. },
  196. // 新建/编辑技能的进度:0-100
  197. percent () {
  198. let val = 0
  199. if (this.currentStep === 2) {
  200. val = 33.3
  201. } else if (this.currentStep === 3) {
  202. val = 66.7
  203. }
  204. return val
  205. }
  206. },
  207. mounted () {
  208. this.needLogin()
  209. this._getSkillCate()
  210. this.sale_id = this.$route.query.sale_id || ''
  211. if (this.sale_id) {
  212. this._getSkillDetail()
  213. }
  214. },
  215. methods: {
  216. /** 获取技能分类 */
  217. _getSkillCate () {
  218. this.$axios.$post('/api/sale/cateListYes', { type: 2, point: 1 }).then(res => {
  219. if (res.status === 1) {
  220. let skillCate = res.data || []
  221. this.skillCate = skillCate.map(item => {
  222. let children = item.child_list.map(child => {
  223. return {
  224. value: child.category_id,
  225. label: child.name
  226. }
  227. })
  228. return {
  229. value: item.category_id,
  230. label: item.name,
  231. children: children
  232. }
  233. })
  234. }
  235. }).catch(err => {
  236. console.log('get skill cate error: ', err)
  237. })
  238. },
  239. /** 获取技能服务详情 */
  240. _getSkillDetail () {
  241. const self = this
  242. this.$axios.$post('/api/sale/saleInfo', { sale_id: this.sale_id }).then(res => {
  243. if (res.status === 1) {
  244. console.log('获取技能详情成功', res.data)
  245. self.title = res.data.title || ""
  246. self.price = res.data.price || ""
  247. self.content = res.data.content || self.defaultContent
  248. if (res.data.cate_id_one && res.data.cate_id_two) {
  249. self.selectedSkillCate = [res.data.cate_id_one, res.data.cate_id_two]
  250. }
  251. self._setSkillImages(res.data)
  252. }
  253. })
  254. },
  255. /** 创建、编辑技能 */
  256. _publishSkillData (actType) {
  257. const self = this
  258. const data = {
  259. title: this.title,
  260. price: this.price,
  261. content: this.content,
  262. img: this.imageList.map((it) => it.url).join(","),
  263. type: 1,
  264. cate_id_one: this.selectedSkillCate[0],
  265. cate_id_two: this.selectedSkillCate[1],
  266. act: actType,
  267. sale_id: this.sale_id || ''
  268. }
  269. if (actType === 'pcreate') {
  270. this.pcreateIsLoading = true
  271. } else {
  272. this.publishIsLoading = true
  273. }
  274. this.$axios.$post('/api/sale/create', data).then(res => {
  275. if (res.status === 1) {
  276. // self.$message.success(`${self.sale_id ? '编辑' : '创建'}技能成功!`)
  277. if (actType === 'pcreate') {
  278. self.$message.success('保存草稿成功')
  279. } else {
  280. self.$message.success('保存成功,平台将在1-3个工作日内完成审核')
  281. }
  282. setTimeout(() => {
  283. window.location.href = '/workbench/skill/index'
  284. }, 1200)
  285. } else {
  286. self.$message.error('保存失败')
  287. }
  288. }).then(() => {
  289. setTimeout(() => {
  290. self.pcreateIsLoading = false
  291. self.publishIsLoading = false
  292. }, 800)
  293. })
  294. },
  295. /** 编辑时初始化图片列表 */
  296. _setSkillImages (data) {
  297. let images = data.image.split(',')
  298. if (images && images.length > 0) {
  299. images.forEach((image, index) => {
  300. let imageName = this._getImageName(image);
  301. console.log("imageName", imageName);
  302. this.imageList.push({ name: imageName + index, url: image });
  303. })
  304. }
  305. },
  306. /** 通过 image url 获取名字 */
  307. _getImageName (url) {
  308. if (url) {
  309. try {
  310. const lastquotaIndex = url.lastIndexOf("/");
  311. const lastDotIndex = url.lastIndexOf('.');
  312. return url.substring(lastquotaIndex + 1, lastDotIndex);
  313. } catch (e) {
  314. console.log(e);
  315. }
  316. }
  317. return ""
  318. },
  319. /** 技能描述内容改变时 */
  320. handleChange (val) {
  321. this.content = val
  322. },
  323. /** 点击上一步 */
  324. handlePrev () {
  325. if (this.currentStep > 1) {
  326. this.currentStep--
  327. window.scroll(0, 103)
  328. }
  329. },
  330. /** 点击下一步 */
  331. handleNext () {
  332. this.titleError = false
  333. this.priceError = false
  334. this.cateError = false
  335. this.contentError = false
  336. // 在每一步做数据校验
  337. if (this.currentStep === 1) {
  338. if (this.title === '') {
  339. this.$message.error('请输入技能服务名称')
  340. this.titleError = true
  341. return
  342. }
  343. if (this.title.length < 5) {
  344. this.$message.error('技能服务名称不可少于5字符')
  345. this.titleError = true
  346. return
  347. }
  348. if (this.title.length > 50) {
  349. this.$message.error('技能服务名称不可超过50字符,请删减')
  350. this.titleError = true
  351. return
  352. }
  353. this.currentStep++
  354. window.scroll(0, 103)
  355. } else if (this.currentStep === 2) {
  356. if (this.price <= 0) {
  357. this.$message.error('请输入大于0的技能价格')
  358. this.priceError = true
  359. return
  360. }
  361. if (String(this.price).indexOf('.') > -1 &&
  362. String(this.price).length - String(this.price).indexOf('.') - 1 > 2) {
  363. this.priceError = true
  364. this.$message.error('技能价格最多保留两位小数')
  365. return
  366. }
  367. if (this.selectedSkillCate.length !== 2 ||
  368. (!this.selectedSkillCate[0] || !this.selectedSkillCate[1])) {
  369. this.cateError = true
  370. this.$message.error('请选择技能类别')
  371. return
  372. }
  373. if (!this.content) {
  374. this.contentError = true
  375. this.$message.error('请输入技能描述')
  376. return
  377. }
  378. this.currentStep++
  379. window.scroll(0, 103)
  380. } else if (this.currentStep === 3) {
  381. if (this.imageList.length === 0) {
  382. this.$message.error('请上传技能介绍图')
  383. return
  384. }
  385. this._publishSkillData(this.sale_id ? 'update' : 'create')
  386. }
  387. },
  388. /** 点击存草稿 */
  389. handlePcreate () {
  390. if (this.imageList.length === 0) {
  391. this.$message.error('请上传技能介绍图')
  392. return
  393. }
  394. this._publishSkillData('pcreate')
  395. },
  396. handlePriceInput(e){
  397. if(e > 10000){
  398. this.price = 10000
  399. this.$toast("最高金额10000元")
  400. }
  401. }
  402. }
  403. }
  404. </script>
  405. <style scope lang="scss">
  406. @import "@/assets/css/skill/create.scss";
  407. </style>
  408. <style>
  409. input::-webkit-outer-spin-button,
  410. input::-webkit-inner-spin-button {
  411. -webkit-appearance: none !important;
  412. }
  413. input[type="number"]{
  414. -moz-appearance: textfield !important;
  415. }
  416. </style>
  417. <style lang="scss">
  418. // 编辑器样式
  419. .content-editor {
  420. .my-editor {
  421. .ql-snow {
  422. border: 1px solid #d7dfe8 !important;
  423. border-radius: 3px 3px 0 0 !important;
  424. }
  425. .quill-editor {
  426. border-width: 1px !important;
  427. border-top: 0 !important;
  428. border-color: #d7dfe8 !important;
  429. border-radius: 0 0 3px 3px !important;
  430. }
  431. }
  432. }
  433. // 图片上传器样式
  434. .skill-image-uploader {
  435. .el-upload-list__item {
  436. width: 300px !important;
  437. height: 168.75px !important;
  438. line-height: 168.75px !important;
  439. }
  440. .el-upload--picture-card {
  441. width: 300px !important;
  442. height: 168.75px !important;
  443. line-height: 168.75px !important;
  444. .el-upload-list__item-thumbnail {
  445. object-fit: scale-down !important;
  446. }
  447. }
  448. .el-upload-list--picture-card {
  449. .el-upload-list__item-thumbnail {
  450. object-fit: scale-down !important;
  451. }
  452. }
  453. }
  454. </style>