list.vue 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665
  1. <template>
  2. <div
  3. :class="mobile ? 'mobileMain' : ''"
  4. :style="{
  5. marginTop: mainMarginTop,
  6. marginBottom: mobile ? '0px' : '30px !important',
  7. }"
  8. >
  9. <div class="skill-page-main">
  10. <div class="skill-wrapper" v-if="!mobile">
  11. <div class="skill-top">
  12. <!-- <div class="tabs">
  13. <div class="tabs-item active">技能服务</div>
  14. <a href="/consult/" class="tabs-item">咨询服务</a>
  15. <a href="/learn/" class="tabs-item">客栈学院</a>
  16. <el-button class="add-btn" @click="handleClickAdd">添加技能赚钱</el-button>
  17. </div> -->
  18. <div class="skill-category-wrapper">
  19. <!-- <div class="category-title">技术分类</div> -->
  20. <!-- 一级分类内容 -->
  21. <div class="skill-category-wrapper-title">
  22. <img class="icon" src="~@/assets/img/skill/skill.png" />
  23. <span class="title">技能服务</span>
  24. <span class="desc">专业人才为你服务</span>
  25. <div class="expand"></div>
  26. <el-button class="add-btn" @click="handleClickAdd"
  27. >添加技能赚钱</el-button
  28. >
  29. </div>
  30. <div
  31. class="category-one-wrapper"
  32. :class="categoryExpanded ? 'expand' : ''"
  33. >
  34. <!-- 更多 -->
  35. <!-- <div
  36. class="category-more"
  37. @click="handleClickExpandCategory">{{ categoryExpanded ? '收起' : '更多' }}</div> -->
  38. <!-- 全部 -->
  39. <a
  40. href="/skill/"
  41. class="category-one-item"
  42. :class="pagination.selectedCateIdOne == '' ? 'active' : ''"
  43. >全部</a
  44. >
  45. <!-- 一级分类 -->
  46. <a
  47. v-for="categoryOne in skillCate"
  48. :key="categoryOne.value"
  49. class="category-one-item"
  50. :class="
  51. pagination.selectedCateIdOne == categoryOne.value
  52. ? 'active'
  53. : ''
  54. "
  55. :href="`/skill/${categoryOne.value}/`"
  56. >{{ categoryOne.label }}</a
  57. >
  58. </div>
  59. <!-- 二级分类内容 -->
  60. <div class="category-two-wrapper">
  61. <div
  62. class="category-two-content"
  63. v-for="categoryOne in skillCate"
  64. :key="`cate-two-parnet${categoryOne.value}`"
  65. v-show="pagination.selectedCateIdOne == categoryOne.value"
  66. >
  67. <a
  68. class="category-two-item"
  69. :class="
  70. pagination.selectedCateIdTwo == categoryTwo.value
  71. ? 'active'
  72. : ''
  73. "
  74. :href="`/skill/${categoryTwo.value}/`"
  75. v-for="categoryTwo in categoryOne.children"
  76. :key="`cate-two-${categoryTwo.value}`"
  77. >{{ categoryTwo.label }}</a
  78. >
  79. </div>
  80. </div>
  81. </div>
  82. </div>
  83. <div class="skill-content">
  84. <div class="skill-list-wrapper" v-if="skillList.length">
  85. <div
  86. class="skill-item"
  87. v-for="item in skillList"
  88. :key="item.sale_id"
  89. >
  90. <a
  91. class="cover"
  92. :href="`/s/${item.sale_id}`"
  93. :style="{
  94. backgroundImage:
  95. 'url(' +
  96. item.coverImage +
  97. '?x-oss-process=image/resize,w_800)',
  98. }"
  99. >
  100. <!-- <img
  101. class="cover"
  102. :src="`${item.coverImage}?x-oss-process=image/resize,w_800`"
  103. alt="skillCover,cover"
  104. /> -->
  105. </a>
  106. <a class="title" :href="`/s/${item.sale_id}`">{{ item.title }}</a>
  107. <a class="owner-wrapper" :href="`/wo/${item.user.uid}/skill`">
  108. <img class="avatar" :src="item.user.icon_url" alt="avatar" />
  109. <div class="nickname">{{ item.user.nickname }}</div>
  110. </a>
  111. <div class="price-wrapper">
  112. <div class="price-text">¥{{ item.price }}</div>
  113. <div v-if="item.buy_num > 0" class="buy-num">
  114. {{ item.buy_num }}人已学习
  115. </div>
  116. </div>
  117. </div>
  118. </div>
  119. <div class="result-empty-wrapper" v-else>
  120. <img src="@/assets/img/common/empty@2x.png" alt="empty" />
  121. <span>暂无搜索内容</span>
  122. </div>
  123. <div
  124. class="pagination-wrapper"
  125. v-if="pagination.total > pagination.pagesize"
  126. >
  127. <el-pagination
  128. background
  129. layout="prev, pager, next"
  130. :current-page="pagination.page"
  131. :total="pagination.total"
  132. :page-size="pagination.pagesize"
  133. @current-change="handlePageChange"
  134. >
  135. </el-pagination>
  136. </div>
  137. </div>
  138. </div>
  139. <div class="skill-wrapper-mobile" v-else>
  140. <div class="skill-category">
  141. <div class="skill-category-one">
  142. <div class="category-scroller">
  143. <div
  144. class="skill-category-one-item"
  145. :class="!pagination.selectedCateIdOne ? 'active' : ''"
  146. @click="handleClickCategoryOne(0)"
  147. >
  148. 全部
  149. </div>
  150. <div
  151. class="skill-category-one-item"
  152. :class="
  153. pagination.selectedCateIdOne == category.value ? 'active' : ''
  154. "
  155. v-for="category in skillCate"
  156. :key="`category-one-${category.value}`"
  157. @click="handleClickCategoryOne(category.value)"
  158. >
  159. {{ category.label }}
  160. </div>
  161. </div>
  162. <!-- filter -->
  163. <div class="filter-bg"></div>
  164. <div class="filter-wrapper" @click="handleShowCategoryDrawer">
  165. <img src="@/assets/img/works/filter-icon@2x.png" alt="filter" />
  166. </div>
  167. </div>
  168. <div class="skill-category-two">
  169. <!-- 全部二级分类 -->
  170. <div
  171. class="skill-category-two-wrapper"
  172. v-show="!pagination.selectedCateIdOne"
  173. >
  174. <div
  175. class="skill-category-two-item"
  176. :class="
  177. pagination.selectedCateIdTwo == category.value ? 'active' : ''
  178. "
  179. v-for="category in skillCateAll"
  180. :key="`category-all-${category.value}`"
  181. @click="handleClickCategoryTwo(category.value)"
  182. >
  183. {{ category.label }}
  184. </div>
  185. </div>
  186. <!-- 接口返回的二级分类 -->
  187. <div
  188. class="skill-category-two-wrapper"
  189. v-for="category in skillCate"
  190. :key="`category-two-wrapper-${category.value}`"
  191. v-show="pagination.selectedCateIdOne === category.value"
  192. >
  193. <div
  194. class="skill-category-two-item"
  195. :class="
  196. pagination.selectedCateIdTwo == categoryChild.value
  197. ? 'active'
  198. : ''
  199. "
  200. v-for="categoryChild in category.children"
  201. :key="`category-two-${categoryChild.value}`"
  202. @click="handleClickCategoryTwo(categoryChild.value)"
  203. >
  204. {{ categoryChild.label }}
  205. </div>
  206. </div>
  207. </div>
  208. </div>
  209. <div
  210. class="skill-list"
  211. :class="showWxHeader ? 'skill-list__showWxHeader' : ''"
  212. >
  213. <ul
  214. class="skill-list-wrapper"
  215. v-infinite-scroll="handleLoadMoreSkill"
  216. :infinite-scroll-disabled="pagination.noMore"
  217. :infinite-scroll-immediate="false"
  218. >
  219. <div
  220. class="skill-item"
  221. v-for="item in skillList"
  222. :key="item.sale_id"
  223. @click="handleClickSkillItem(item.sale_id)"
  224. >
  225. <img
  226. class="cover"
  227. v-lazy="`${item.coverImage}?x-oss-process=image/resize,w_800`"
  228. alt="skillCover,cover"
  229. />
  230. <div class="owner-wrapper">
  231. <img class="avatar" v-lazy="item.user.icon_url" alt="avatar" />
  232. <div class="nickname">{{ item.user.nickname }}</div>
  233. </div>
  234. <div class="title">{{ item.title }}</div>
  235. <div class="price-wrapper">
  236. <div class="price-text">¥{{ item.price }}</div>
  237. <div class="right-info">{{ item.buy_num }}人已学习</div>
  238. </div>
  239. </div>
  240. <!-- 空数据 -->
  241. <div
  242. class="result-empty-wrapper"
  243. v-if="!skillList.length && !pagination.loading"
  244. >
  245. <img src="@/assets/img/common/empty@2x.png" alt="empty" />
  246. <span>暂无搜索内容</span>
  247. </div>
  248. <p v-if="pagination.loading" class="skill-list-tips">加载中...</p>
  249. <p
  250. v-if="skillList.length && pagination.noMore"
  251. class="skill-list-tips"
  252. >
  253. 没有更多了
  254. </p>
  255. </ul>
  256. </div>
  257. <!-- 弹出的分类选择 -->
  258. <el-drawer
  259. ref="categoryDrawer"
  260. class="category-drawer"
  261. :visible.sync="showCategoryDrawer"
  262. direction="ttb"
  263. :withHeader="false"
  264. >
  265. <div class="drawer-category-one">
  266. <div
  267. class="drawer-category-one-item"
  268. :class="currentDrawerCategoryIndex === 0 ? 'active' : ''"
  269. @click="handleClickDrawerCategoryOne(0)"
  270. >
  271. 全部
  272. </div>
  273. <div
  274. class="drawer-category-one-item"
  275. :class="currentDrawerCategoryIndex === index + 1 ? 'active' : ''"
  276. v-for="(category, index) in skillCate"
  277. :key="`drawer-category-one-${category.value}`"
  278. @click="handleClickDrawerCategoryOne(index + 1)"
  279. >
  280. {{ category.label }}
  281. </div>
  282. </div>
  283. <div class="drawer-category-two">
  284. <!-- 全部二级分类 -->
  285. <div
  286. class="drawer-category-two-wrapper"
  287. v-show="currentDrawerCategoryIndex === 0"
  288. >
  289. <div
  290. class="drawer-category-two-item"
  291. :class="
  292. currentDrawerCategoryId == category.value ? 'active' : ''
  293. "
  294. v-for="category in skillCateAll"
  295. :key="`drawer-category-all-${category.value}`"
  296. @click="handleClickDrawerCategoryTwo(category.value)"
  297. >
  298. {{ category.label }}
  299. </div>
  300. </div>
  301. <!-- 接口返回的二级分类 -->
  302. <div
  303. class="drawer-category-two-wrapper"
  304. v-for="(category, index) in skillCate"
  305. :key="`drawer-category-two-wrapper-${category.value}`"
  306. v-show="currentDrawerCategoryIndex === index + 1"
  307. >
  308. <div
  309. class="drawer-category-two-item"
  310. :class="
  311. currentDrawerCategoryId == categoryChild.value ? 'active' : ''
  312. "
  313. v-for="categoryChild in category.children"
  314. :key="`drawer-category-two-${categoryChild.value}`"
  315. @click="handleClickDrawerCategoryTwo(categoryChild.value)"
  316. >
  317. {{ categoryChild.label }}
  318. </div>
  319. </div>
  320. </div>
  321. </el-drawer>
  322. </div>
  323. <!--
  324. <aside class="skill-side">
  325. <div class="section"></div>
  326. <div class="section"></div>
  327. </aside> -->
  328. </div>
  329. </div>
  330. </template>
  331. <script>
  332. import { mapState } from "vuex";
  333. import DealSeoList from "@/components/skill/dealSeoList";
  334. import qs from "qs";
  335. export default {
  336. name: "SeoSkillList",
  337. data() {
  338. return {
  339. baseUrl: "",
  340. // firstLoad: true,
  341. isWeixinApp: true,
  342. categoryExpanded: true, // 更多按钮不要,默认为展开状态
  343. showCategoryDrawer: false,
  344. currentDrawerCategoryId: 0,
  345. currentDrawerCategoryIndex: 0,
  346. };
  347. },
  348. head() {
  349. const {
  350. title = "",
  351. keyword = "",
  352. description = "",
  353. h1 = "",
  354. canonical = "",
  355. metaLocation,
  356. } = this.head || {};
  357. let obj = {
  358. title: title,
  359. meta: [
  360. {
  361. name: "keywords",
  362. content: keyword,
  363. },
  364. {
  365. name: "description",
  366. content: description,
  367. },
  368. {
  369. name: "h1",
  370. content: h1,
  371. },
  372. ],
  373. link: [{ rel: "canonical", href: canonical }],
  374. };
  375. if (metaLocation) {
  376. obj.meta.push({ name: "location", content: metaLocation });
  377. }
  378. return obj;
  379. },
  380. computed: {
  381. ...mapState(["deviceType"]),
  382. showWxHeader() {
  383. return (
  384. !this.deviceType.app &&
  385. !this.isWeixinApp &&
  386. (this.deviceType.android || this.deviceType.ios)
  387. );
  388. },
  389. mainMarginTop() {
  390. if (this.mobile && this.showWxHeader) {
  391. return "64px !important";
  392. } else if (this.mobile) {
  393. return "0px !important";
  394. } else {
  395. return "20px !important";
  396. }
  397. },
  398. },
  399. async asyncData({ ...params }) {
  400. let dealDataObj = new DealSeoList(params);
  401. let ans = await dealDataObj.dealData();
  402. return {
  403. ...ans,
  404. };
  405. },
  406. mounted() {
  407. this.baseUrl = this.$store.state.domainConfig.siteUrl;
  408. this.isWeixinApp = navigator.userAgent.indexOf("miniProgram") > -1;
  409. },
  410. methods: {
  411. /** 分页获取技能列表数据 */
  412. _getSkillList() {
  413. const self = this;
  414. const data = {
  415. type: 1,
  416. page: this.pagination.page,
  417. page_size: this.pagination.pagesize,
  418. cate_id: this.pagination.selectedCateIdTwo,
  419. status: 2,
  420. owner_type: 1,
  421. root_type: this.root_type,
  422. };
  423. this.pagination.loading = true;
  424. this.pagination.noMore = false;
  425. this.$axios
  426. .$post("/api/sale/saleList", data)
  427. .then((res) => {
  428. if (Number(res.status) === 1) {
  429. let skillList = res.data.list || [];
  430. skillList.forEach((item) => {
  431. let imageList = item.image.split(",");
  432. item.coverImage = imageList[0] || "";
  433. imageList.splice(0, 1);
  434. item.imageList = imageList;
  435. });
  436. if (self.mobile) {
  437. self.skillList = self.skillList.concat(skillList);
  438. } else {
  439. self.skillList = skillList;
  440. }
  441. self.pagination.total = res.data.total;
  442. self.pagination.pagesize = res.data.page_size || 9;
  443. if (
  444. self.pagination.page * self.pagination.pagesize >=
  445. self.pagination.total
  446. ) {
  447. console.log("noMore true", self.pagination);
  448. self.pagination.noMore = true;
  449. } else {
  450. console.log("noMore false", self.pagination);
  451. self.pagination.noMore = false;
  452. }
  453. }
  454. })
  455. .then(() => {
  456. self.pagination.loading = false;
  457. });
  458. },
  459. /** 点击展开、收起 */
  460. handleClickExpandCategory() {
  461. this.categoryExpanded = !this.categoryExpanded;
  462. },
  463. /** 点击一级分类时 */
  464. handleClickCategoryOne(id) {
  465. if (id === 0) {
  466. // 点击全部时,移除筛选分类
  467. this.pagination.selectedCateIdOne = id;
  468. this.pagination.selectedCateIdTwo = "";
  469. this.currentDrawerCategoryId = "";
  470. this.pagination.page = 1;
  471. this.skillList = [];
  472. window.scroll(0, 0);
  473. this._getSkillList();
  474. return;
  475. }
  476. if (this.pagination.selectedCateIdOne !== id) {
  477. this.pagination.selectedCateIdOne = id;
  478. }
  479. },
  480. /** 点击二级分类时:移动端 */
  481. handleClickCategoryTwo(id) {
  482. if (this.pagination.selectedCateIdTwo === id) {
  483. this.pagination.selectedCateIdTwo = "";
  484. this.currentDrawerCategoryId = "";
  485. } else {
  486. this.pagination.selectedCateIdTwo = id;
  487. this.currentDrawerCategoryId = id;
  488. }
  489. this.pagination.page = 1;
  490. this.skillList = [];
  491. window.scroll(0, 0);
  492. this._getSkillList();
  493. },
  494. /** 分页页码改变时 */
  495. handlePageChange(val) {
  496. let query = {
  497. page: val,
  498. };
  499. if (this.root_type && Number(this.root_type) > 0) {
  500. query.root_type = this.root_type;
  501. }
  502. window.location.href = `${window.location.origin}${
  503. window.location.pathname
  504. }?${qs.stringify(query)}`;
  505. },
  506. /** mobile 加载更多 */
  507. handleLoadMoreSkill() {
  508. if (this.pagination.loading) {
  509. return;
  510. }
  511. this.pagination.page++;
  512. this._getSkillList();
  513. },
  514. /** 点击筛选时 */
  515. handleShowCategoryDrawer() {
  516. this.showCategoryDrawer = true;
  517. },
  518. /**
  519. * 点击 mobile 分类 drawer 一级分类
  520. */
  521. handleClickDrawerCategoryOne(id) {
  522. if (id === 0) {
  523. this.showCategoryDrawer = false;
  524. return;
  525. }
  526. if (id !== this.currentDrawerCategoryIndex) {
  527. this.currentDrawerCategoryIndex = id;
  528. }
  529. },
  530. /**
  531. * 点击 mobile 分类 drawer 二级分类
  532. */
  533. handleClickDrawerCategoryTwo(id) {
  534. if (this.currentDrawerCategoryId === id) {
  535. this.pagination.selectedCateIdTwo = "";
  536. } else {
  537. this.pagination.selectedCateIdTwo = id;
  538. }
  539. this.currentDrawerCategoryId = id;
  540. this.showCategoryDrawer = false;
  541. this.pagination.page = 1;
  542. this.skillList = [];
  543. window.scroll(0, 0);
  544. this._getSkillList();
  545. },
  546. /**
  547. * 点击 mobile 的一项技能时
  548. */
  549. handleClickSkillItem(saleId) {
  550. if (this.deviceType.android || this.deviceType.ios) {
  551. // 端跳转
  552. let jumpUrl = `${this.baseUrl}/s/${saleId}`;
  553. location.href = `proginn://webview?url=${jumpUrl}`;
  554. } else {
  555. // web 跳转
  556. location.href = `/s/${saleId}`;
  557. }
  558. },
  559. /**
  560. * 点击成为讲师
  561. */
  562. handleClickAdd() {
  563. location.href = "/workbench/skill/index";
  564. },
  565. },
  566. };
  567. </script>
  568. <style lang="scss" scoped>
  569. @import "@/assets/css/skill/list.scss";
  570. </style>
  571. <style lang="scss">
  572. .category-drawer {
  573. .el-drawer {
  574. height: 100vh !important;
  575. .el-drawer__body {
  576. position: relative;
  577. width: 100%;
  578. display: flex;
  579. }
  580. }
  581. .drawer-category-one {
  582. width: 100px;
  583. height: 100vh;
  584. padding-bottom: 34px;
  585. background: #f4f5f9;
  586. overflow-x: hidden;
  587. overflow-y: auto;
  588. -webkit-overflow-scrolling: touch;
  589. &::-webkit-scrollbar {
  590. display: none;
  591. }
  592. .drawer-category-one-item {
  593. width: 100%;
  594. height: 50px;
  595. line-height: 50px;
  596. text-align: center;
  597. font-size: 15px;
  598. font-family: PingFangSC, PingFangSC-Medium;
  599. font-weight: 500;
  600. color: #222222;
  601. background: inherit;
  602. &.active {
  603. color: #308eff;
  604. background: #ffffff;
  605. }
  606. }
  607. }
  608. .drawer-category-two {
  609. width: calc(100% - 100px);
  610. height: 100vh;
  611. padding: 4px 10px 34px;
  612. background: #ffffff;
  613. overflow-x: hidden;
  614. overflow-y: auto;
  615. -webkit-overflow-scrolling: touch;
  616. &::-webkit-scrollbar {
  617. display: none;
  618. }
  619. .drawer-category-two-wrapper {
  620. width: 100%;
  621. display: flex;
  622. flex-wrap: wrap;
  623. .drawer-category-two-item {
  624. margin: 8px 8px 0 0;
  625. padding: 0 12px;
  626. height: 35px;
  627. line-height: 35px;
  628. background: rgba(244, 245, 249, 0.8);
  629. border-radius: 4px;
  630. // opacity: 0.8;
  631. font-size: 13px;
  632. font-family: PingFangSC, PingFangSC-Regular;
  633. font-weight: 400;
  634. color: #222222;
  635. &.active {
  636. height: 33px;
  637. line-height: 33px;
  638. border: 1px solid #308eff;
  639. background: #ffffff;
  640. font-size: 12px;
  641. font-family: PingFangSC, PingFangSC-Medium;
  642. font-weight: 500;
  643. color: #308eff;
  644. }
  645. }
  646. }
  647. }
  648. }
  649. .wx-header-custom-list {
  650. position: fixed !important;
  651. z-index: 11 !important;
  652. }
  653. </style>