carousel.vue 15 KB

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