carousel.vue 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613
  1. <script>
  2. import axios from "axios";
  3. import {defineComponent} from "vue";
  4. import fieldIsAllow from "../../../until/fieldIsAllow"
  5. import RoundedTitle from "../../../components/public/roundedTitle.vue";
  6. import {rCode} from "../../../map/rcodeMap_esm";
  7. import handle from "../../../until/handle";
  8. import ImageViewer from "../../../components/public/imageViewer.vue";
  9. import ImageTable from "../../../components/public/imageTable.vue";
  10. import Pop from "../../../components/public/pop.vue";
  11. import PopCard from "../../../components/public/popCard.vue";
  12. import InputRow from "../../../components/public/form/inputRow.vue";
  13. import dbField_esm from "../../../map/dbField_esm";
  14. import {apiMap} from "../../../map/apiMap";
  15. import SearchBox from "../../../components/search/searchBox.vue";
  16. import {pTypes} from "../../../map/productMap";
  17. import {newsType} from "../../../map/newMap";
  18. export default defineComponent({
  19. name: 'carousel',
  20. computed: {
  21. dbField_esm() {
  22. return dbField_esm
  23. },
  24. productTypes(){
  25. let arr = this.$store.getters.productTypes;
  26. // 添加 all
  27. arr.unshift({text: '全部', key: 'all' });
  28. return arr;
  29. },
  30. newsTypes(){// 添加 all
  31. let arr = this.$store.getters.allNewsTypes;
  32. arr.unshift({text: '全部', key: 'all' });
  33. return arr;
  34. },
  35. },
  36. components: {SearchBox, InputRow, PopCard, Pop, ImageTable, ImageViewer, RoundedTitle},
  37. async asyncData(ctx){
  38. // 加载轮播图数据
  39. let [err,res] = await handle(axios.get(apiMap.carouselList.path));
  40. if(err){
  41. return {};
  42. }
  43. let result = res.data;
  44. if(result.code === rCode.OK){
  45. return {carouselList: result.data}
  46. }else{
  47. this.$message.error(result.msg);
  48. return {}
  49. }
  50. return {}
  51. },
  52. data(){
  53. return {
  54. limit: 10,
  55. loading: false,
  56. carouselList: [],
  57. popShow: false,
  58. popLoading: false,
  59. carouselPopShow: false,
  60. carouselPopLoading: false,
  61. isEditCarousel: false,
  62. carouselData: {},
  63. form: {
  64. // 排序
  65. sort: {
  66. val:0,
  67. init: 0,
  68. msg: '',
  69. state: 0,
  70. },
  71. // 状态 0:禁用,1:启用
  72. status: {
  73. val: 0,
  74. init: 0,
  75. msg: '',
  76. state: 0,
  77. },
  78. // 轮播类型 '0:product','1:news','2:page','3:href'
  79. type: {
  80. val: 0,
  81. init: 0,
  82. msg: '',
  83. state: 0,
  84. options: [
  85. {label: '直接链接', value: 0,checkField: 'href'},
  86. {label: '内部产品', value: 1,checkField: 'productId'},
  87. {label: '指向新闻', value: 2,checkField: 'newsId'},
  88. ],
  89. },
  90. // 具体值
  91. value: {
  92. val: '',
  93. init: '',
  94. msg: '',
  95. state: 0,
  96. // 依赖字段
  97. depend: 'type',
  98. showText: '',// 展示用字段
  99. oldShowText: '',
  100. },
  101. // file
  102. fileData: {
  103. val: '',
  104. init: '',
  105. msg: '',
  106. state: 0,
  107. showText: '',// 展示用字段
  108. oldShowText: '',
  109. },
  110. },
  111. productSelectVisible: false,
  112. productSearch: {
  113. type: {
  114. val: '',
  115. oldVal: '',
  116. init: '',
  117. msg: '',
  118. options: [],
  119. }
  120. },
  121. newsSelectVisible: false,
  122. newsVisible: false,
  123. newsSearch: {
  124. type: {
  125. val: '',
  126. oldVal: '',
  127. init: '',
  128. msg: '',
  129. options: [],
  130. }
  131. },
  132. imageSelectVisible: false,
  133. }
  134. },
  135. mounted() {
  136. if(this.carouselList.length === 0){
  137. this.getCarouselList();
  138. }
  139. },
  140. methods: {
  141. async getCarouselList(){
  142. this.loading = true;
  143. let [err,res] = await handle(this.$axios.get(apiMap.carouselList.path));
  144. this.loading = false;
  145. if(err){
  146. if(this.NotificationKey){
  147. this.$notification.close(this.NotificationKey);
  148. }
  149. this.NotificationKey = `open${Date.now()}`;
  150. return this.$notification.error({
  151. message: '轮播数据加载失败',
  152. description:`异常: ${err.message}`,
  153. duration: 0,
  154. btn: h => {
  155. return h(
  156. 'a-button',
  157. {
  158. props: {
  159. type: 'primary',
  160. size: 'small',
  161. },
  162. on: {
  163. click: () => {
  164. this.$notification.close(this.NotificationKey);
  165. this.getCarouselList();
  166. },
  167. },
  168. },
  169. '重试',
  170. );
  171. },
  172. key:this.NotificationKey,
  173. onClose: close,
  174. });
  175. }
  176. let result = res.data;
  177. if(result.code === rCode.OK){
  178. this.carouselList = result.data;
  179. return {carouselList: result.data}
  180. }else{
  181. this.$message.error(result.msg);
  182. return {}
  183. }
  184. },
  185. // 搜索产品,只需要产品名等信息
  186. async getProductSearch(searchParam){
  187. console.log(searchParam)
  188. if(this.productSearch.type.val !== this.productSearch.type.oldVal){
  189. searchParam.p = 1;
  190. }
  191. searchParam.type = this.productSearch.type.val;
  192. searchParam.l = this.limit;
  193. searchParam.p = searchParam.page;
  194. let [err,res] = await handle(
  195. this.$axios.get(
  196. apiMap.searchProductMini.path,
  197. {params:searchParam})
  198. );
  199. if(err){
  200. console.log(err);
  201. return [{message:'请求数据失败'},null];
  202. }
  203. let result = res.data;
  204. if(result.code === rCode.OK){
  205. this.productSearch.type.oldVal = this.productSearch.type.val;
  206. // data 转换
  207. result.data = result.data.map(item=>{
  208. item.showText=item.name;
  209. return item;
  210. });
  211. return [null,result];
  212. }else{
  213. // 可捕获的服务器错误
  214. return [{message:result.msg},null];
  215. }
  216. },
  217. // 加载轮播默认数据
  218. async getNewsSearch(searchParam){
  219. console.log(searchParam)
  220. if(this.newsSearch.type.val !== this.newsSearch.type.oldVal){
  221. searchParam.p = 1;
  222. }
  223. searchParam.type = this.newsSearch.type.val;
  224. searchParam.l = this.limit;
  225. searchParam.p = searchParam.page;
  226. let [err,res] = await handle(
  227. this.$axios.get(
  228. apiMap.searchNewsMini.path,
  229. {params:searchParam})
  230. );
  231. if(err){
  232. console.log(err);
  233. return [{message:'请求数据失败'},null];
  234. }
  235. let result = res.data;
  236. if(result.code === rCode.OK){
  237. this.newsSearch.type.oldVal = this.newsSearch.type.val;
  238. // data 转换
  239. result.data = result.data.map(item=>{
  240. item.showText=item.name;
  241. return item;
  242. });
  243. return [null,result];
  244. }else{
  245. // 可捕获的服务器错误
  246. return [{message:result.msg},null];
  247. }
  248. },
  249. checkFormItem(field,enumOptions,reCheckField){
  250. let formItem = this.form[field];
  251. if (formItem){
  252. if (enumOptions){
  253. // 遍历枚举
  254. for (let i = 0; i < enumOptions.length; i++) {
  255. let enumOption = enumOptions[i];
  256. if (enumOption.value === formItem.val){
  257. return true;
  258. }
  259. }
  260. formItem.msg = '选项不在范围内';
  261. return false;
  262. }
  263. if(reCheckField){
  264. // 检查用字段
  265. formItem.msg = fieldIsAllow({
  266. [reCheckField]:formItem.val,
  267. })
  268. }else{
  269. formItem.msg = fieldIsAllow({
  270. [field]:formItem.val,
  271. })
  272. }
  273. }else{
  274. let r = true;
  275. for (const fieldKey in this.form) {
  276. formItem = this.form[fieldKey];
  277. let depend = this.form[formItem.depend];
  278. let checkField = fieldKey;
  279. if(formItem.reCheckField){
  280. checkField = formItem.reCheckField;
  281. }
  282. // 枚举值判断
  283. if(formItem.options){
  284. // 有枚举字段,只判断是否在枚举中
  285. if(formItem.options.findIndex(item=>item.value == formItem.val) === -1){
  286. formItem.msg = '选项不在范围内';
  287. r = false;
  288. }
  289. // 枚举值判断完毕,继续下一个字段
  290. continue;
  291. }
  292. // 判断是否有依赖字段
  293. if(depend){
  294. if(depend.options){
  295. // 依赖的对象有枚举类型,检查该枚举类型是否有有检测值
  296. let optionItem = depend.options.find(item=>item.value == depend.val);
  297. if(!optionItem){
  298. depend.msg = '选项不在范围内';
  299. formItem.msg = '该值依赖项输入异常';
  300. r = false;
  301. continue;
  302. }
  303. if(optionItem.checkField){
  304. console.log(`采用依赖项的检测字段${optionItem.checkField}`)
  305. checkField = optionItem.checkField;
  306. }
  307. }
  308. }
  309. console.log(`检测字段:${checkField},值:${formItem.val}`);
  310. formItem.msg = fieldIsAllow({
  311. [checkField]:formItem.val,
  312. })
  313. if (formItem.msg){
  314. r = false;
  315. }
  316. }
  317. return r
  318. }
  319. },
  320. initCarouseForm(){
  321. this.carouselData = {};
  322. let keys = Object.keys(this.form);
  323. for(let i = 0; i < keys.length; i++){
  324. let key = keys[i];
  325. this.form[key].val = this.form[key].init;
  326. this.form[key].msg = '';
  327. this.form[key].state = 0;
  328. this.form.value.showText = '';
  329. }
  330. },
  331. openAddCarouselModal(){
  332. // 初始化表单
  333. this.initCarouseForm();
  334. this.carouselPopShow = true;
  335. this.isEditCarousel = false;
  336. this.$nextTick(()=>{
  337. // 打开弹窗. 选择图片,填写链接地址,排序
  338. this.productSearch.type.options = this.productTypes;
  339. this.newsSearch.type.options = this.newsTypes;
  340. // 默认值设置
  341. this.productSearch.type.val = this.productTypes[0].key;
  342. this.productSearch.type.oldVal = this.productTypes[0].key;
  343. this.productSearch.type.init = this.productTypes[0].key;
  344. this.newsSearch.type.val = this.newsTypes[0].key;
  345. this.newsSearch.type.oldVal = this.newsTypes[0].key;
  346. this.newsSearch.type.init = this.newsTypes[0].key;
  347. });
  348. },
  349. onPopOkClickHandle(){
  350. if(this.isEditCarousel){
  351. console.log('保存修改后的轮播数据');
  352. }else{
  353. console.log('新增轮播图');
  354. this.addCarouselExecute();
  355. }
  356. },
  357. addCarouselExecute(){
  358. // 生成新数据表单
  359. let carouselData = {};
  360. let isPass = this.checkFormItem();
  361. if(!isPass){
  362. return console.log('数据验证不通过');
  363. }
  364. },
  365. showPop(){
  366. this.popShow = true;
  367. this.popLoading = false;
  368. },
  369. cancelPop(){
  370. this.imageSelectVisible = false;
  371. },
  372. okHandle(fileItem){
  373. console.log('文件列表');
  374. console.log(fileItem);
  375. this.cancelPop();
  376. this.$nextTick(()=>{
  377. this.form.fileData.val = fileItem.filePath;
  378. this.form.fileData.state = 1;
  379. this.form.fileData.msg = '';
  380. this.form.fileData.showText = fileItem.filePath;
  381. })
  382. },
  383. onProductSearchHandle(e){
  384. console.log(`onProductSearchHandle ${e}`);
  385. console.log(e);
  386. console.log(this.productSearch.type.val);
  387. },
  388. onSelectedItemHandle(item){
  389. console.log(`selected item ${item}`);
  390. console.log(item);
  391. this.form.value.val = item.id;
  392. this.form.value.showText = item.showText;
  393. this.form.value.msg = '';
  394. },
  395. onTypeChangeHandle(e){
  396. console.log(`type change ${e}`);
  397. // 清除其他值
  398. this.form.value.val = '';
  399. this.form.value.msg = '';
  400. this.form.value.showText = '';
  401. },
  402. onNewsSearchHandle(e){
  403. console.log(`onProductSearchHandle ${e}`);
  404. console.log(e);
  405. console.log(this.productSearch.type.val);
  406. },
  407. },
  408. })
  409. </script>
  410. <template>
  411. <div class="w-full p-2">
  412. <rounded-title>轮播图管理</rounded-title>
  413. <div class="mt-2 rounded bg-white p-2">
  414. <!-- 轮播图list , 左侧轮播图片, 右侧 轮播信息 -->
  415. <div class="mt-2 rounded bg-white p-2">
  416. <div class="py-1 border-b border-cyan-300 flex justify-between">
  417. 点击下方快进行管理轮播图数据,一次性不要添加过多轮播图
  418. <!-- 新增按钮-->
  419. <a-button type="primary" class="ant-icon-btn" icon="plus" @click="openAddCarouselModal" :loading="loading"></a-button>
  420. <!-- 刷新按钮-->
  421. <a-button type="primary" class="ant-icon-btn" icon="reload" @click="getCarouselList" :loading="loading"></a-button>
  422. </div>
  423. <div class="w-full h-auto transition">
  424. <div v-show="loading" class="w-full h-64 flex justify-center items-center ">
  425. <a-spin size="large" />
  426. </div>
  427. <div
  428. v-for="(item,index) in carouselList"
  429. :key="'carouse-'+index"
  430. class="mt-2 rounded border flex h-72 overflow-hidden"
  431. >
  432. <div class="media w-1/2 h-full">
  433. <image-viewer :src="item.filePath"></image-viewer>
  434. </div>
  435. <div class="w-1/2 h-full box-border pl-2">
  436. <a-button @click="showPop">编辑</a-button>
  437. 排序{{item.sort}},数字越小越靠前
  438. 链接地址: {{item.href}}
  439. </div>
  440. </div>
  441. </div>
  442. </div>
  443. </div>
  444. <pop :show="carouselPopShow" :loading="carouselPopLoading">
  445. <pop-card>
  446. <template slot="header" class="w-full">
  447. {{isEditCarousel ? '编辑轮播图' : '新增轮播图'}}
  448. </template>
  449. <template slot="close-group">
  450. <a-button icon="close" @click="carouselPopShow = false"></a-button>
  451. </template>
  452. <div class="w-full">
  453. <input-row :msg="form.sort.msg"
  454. label="排序">
  455. <a-input-number v-model="form.sort.val"
  456. @focus="form.sort.msg=''"
  457. :min="0"
  458. @blur="checkFormItem('sort')"
  459. />
  460. </input-row>
  461. <!-- 轮播类型选择 -->
  462. <input-row :msg="form.type.msg"
  463. label="轮播类型">
  464. <a-radio-group v-model="form.type.val" @change="onTypeChangeHandle">
  465. <a-radio-button v-for="opt in form.type.options"
  466. :key="'cType'+opt.value"
  467. :value="opt.value">
  468. {{ opt.label }}
  469. </a-radio-button>
  470. </a-radio-group>
  471. </input-row>
  472. <!-- 轮播具体值 -->
  473. <!-- 链接-->
  474. <input-row
  475. v-show="form.type.val === dbField_esm.db_base.carouselType.href"
  476. :msg="form.value.msg"
  477. label="输入链接">
  478. <a-input v-model="form.value.val"
  479. placeholder="输入要指向的链接地址"
  480. @focus="form.value.msg=''"
  481. @blur="checkFormItem('value', null,'href')"
  482. />
  483. </input-row>
  484. <!-- 产品选择-->
  485. <input-row
  486. v-show="form.type.val === dbField_esm.db_base.carouselType.production"
  487. :msg="form.value.msg"
  488. label="选择产品">
  489. {{ form.value.showText }}
  490. <a-popover v-model="productSelectVisible" title="选择产品" trigger="click">
  491. <div slot="content" class="searchBox" >
  492. <search-box
  493. class="h-72"
  494. :loadDataApi="getProductSearch"
  495. :limit="limit"
  496. search-placeholder="请输入产品名称关键字"
  497. loadTip="正在搜索产品中..."
  498. @onSelectedItem="onSelectedItemHandle"
  499. >
  500. <div class="w-full" slot="otherSearchItem">
  501. <a-radio-group v-model="productSearch.type.val"
  502. @change="onProductSearchHandle">
  503. <a-radio-button v-for="opt in productSearch.type.options"
  504. :key="'cType'+opt.key"
  505. :value="opt.key">
  506. {{ opt.text }}
  507. </a-radio-button>
  508. </a-radio-group>
  509. </div>
  510. </search-box>
  511. </div>
  512. <a-button type="primary" >
  513. 选择产品
  514. </a-button>
  515. </a-popover>
  516. </input-row>
  517. <!-- 新闻选择-->
  518. <input-row
  519. v-show="form.type.val === dbField_esm.db_base.carouselType.news"
  520. :msg="form.value.msg"
  521. label="文章选择">
  522. {{ form.value.showText }}
  523. <a-popover v-model="newsSelectVisible" title="选择你需要的文章" trigger="click">
  524. <div slot="content" class="searchBox" >
  525. <search-box
  526. class="h-72"
  527. :loadDataApi="getNewsSearch"
  528. :limit="limit"
  529. search-placeholder="请输入产品名称关键字"
  530. loadTip="正在搜索产品中..."
  531. ref="productSearch"
  532. @onSelectedItem="onSelectedItemHandle"
  533. >
  534. <div class="w-full" slot="otherSearchItem">
  535. <a-radio-group v-model="newsSearch.type.val"
  536. @change="onNewsSearchHandle">
  537. <a-radio-button v-for="opt in newsSearch.type.options"
  538. :key="'cType'+opt.key"
  539. :value="opt.key">
  540. {{ opt.text }}
  541. </a-radio-button>
  542. </a-radio-group>
  543. </div>
  544. </search-box>
  545. </div>
  546. <a-button type="primary">
  547. 选择产品
  548. </a-button>
  549. </a-popover>
  550. </input-row>
  551. <!-- 选择图片 -->
  552. <input-row label="轮播图片"
  553. :msg="form.fileData.msg">
  554. <a-popover v-model="imageSelectVisible"
  555. class="w-full"
  556. trigger="click">
  557. <image-table slot="content"
  558. class="w-full h-full"
  559. @cancel="imageSelectVisible = false"
  560. @ok="okHandle"></image-table>
  561. <div class="w-full h-60 rounded relative">
  562. <image-viewer class="" :src="form.fileData.showText"></image-viewer>
  563. <div class="absolute w-full h-full left-0 top-0
  564. justify-center text-white bg-gray-400
  565. items-center text-2xl flex opacity-0 hover:opacity-70">
  566. 点击选择图片
  567. </div>
  568. </div>
  569. </a-popover>
  570. </input-row>
  571. </div>
  572. <template class="w-full" slot="footer">
  573. <a-button @click="onPopOkClickHandle">{{isEditCarousel? '保存': '新增'}}</a-button>
  574. </template>
  575. </pop-card>
  576. </pop>
  577. </div>
  578. </template>
  579. <style scoped>
  580. .searchBox{
  581. width: 420px;
  582. height: 520px;
  583. }
  584. </style>