links.vue 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446
  1. <template>
  2. <view class="app-page flex flex-col">
  3. <view v-if="tab.list.length > 3" class="e-fixed shadow-2">
  4. <tm-tabs color="light-blue" :shadow="0" v-model="tab.activeIndex" :list="tab.list" align="center" @change="fnOnTabChange"></tm-tabs>
  5. </view>
  6. <!-- 占位区域 -->
  7. <view v-if="tab.list.length > 3" style="width: 100vw;height: 92rpx;"></view>
  8. <view v-if="loading != 'success'" class="loading-wrap">
  9. <block v-if="loading == 'loading'">
  10. <tm-skeleton model="listAvatr"></tm-skeleton>
  11. <tm-skeleton model="listAvatr"></tm-skeleton>
  12. <tm-skeleton model="listAvatr"></tm-skeleton>
  13. <tm-skeleton model="listAvatr"></tm-skeleton>
  14. <tm-skeleton model="listAvatr"></tm-skeleton>
  15. </block>
  16. <view v-else-if="loading == 'error'" class="loading-error flex flex-col flex-center">
  17. <tm-empty icon="icon-wind-cry" label="加载失败"><tm-button theme="light-blue" size="m" @click="fnGetData()">重新加载</tm-button></tm-empty>
  18. </view>
  19. </view>
  20. <view v-else class="app-page-content">
  21. <!-- 内容区域 -->
  22. <view v-if="filterList.lenght == 0" class="empty"><tm-empty icon="icon-wind-cry" label="无数据"></tm-empty></view>
  23. <block v-else>
  24. <block v-for="(item, index) in filterList" :key="index">
  25. <tm-translate animation-name="fadeUp" :wait="(index + 1) * 50">
  26. <view class="link-card round-3 bg-white flex flex-col mt-24 ml-24 mr-24 pa-24">
  27. <view class="head flex">
  28. <view class="left round-2 flex flex-col flex-center"><image class="logo round-2 " :src="item.logo" mode="aspectFill"></image></view>
  29. <view class="right pl-24 text-size-m">
  30. <view class="title flex flex-between ">
  31. <view class="name flex text-overflow mr-6 text-weight-b">
  32. <tm-tags :dense="true" :shadow="0" color="bg-gradient-blue-accent" size="xs" model="fill">ID:{{ item.id }}</tm-tags>
  33. <text class="text-size-l ml-12">{{ item.name }}</text>
  34. </view>
  35. <view v-if="false" class="icon text-grey-darken-2">
  36. <tm-button :shadow="0" :round="2" theme="light-blue" text size="xs" @click="fnShowFormModal(item)">修改</tm-button>
  37. <tm-button :shadow="0" :round="2" theme="red" text size="xs" @click="fnDelete(item)">删除</tm-button>
  38. </view>
  39. </view>
  40. <view class="mt-6 flex text-size-m">
  41. <view class="label text-grey-darken-2">分组:</view>
  42. <view class="value">{{ item.team || '无分组' }}</view>
  43. </view>
  44. <view class="mt-6 flex text-size-m">
  45. <view class="label text-grey-darken-2">地址:</view>
  46. <view class="value">
  47. <text>{{ item.url }}</text>
  48. <text class="ml-6 text-grey-darken-1 iconfont icon-copy" @click="$utils.copyText(item.url, '网站地址已复制')"></text>
  49. </view>
  50. </view>
  51. <view class="mt-6 flex text-size-m">
  52. <view class="label text-grey-darken-2">描述:</view>
  53. <view class="value text-overflow">{{ item.description || '该博主很懒,没有提供描述' }}</view>
  54. </view>
  55. </view>
  56. </view>
  57. <view class="foot flex flex-between mt-20 pt-16 text-size-m">
  58. <view class="e-btn update-btn flex-1 round-2 pa-12 text-blue text-align-center mr-12" @click="fnShowFormModal(item)">修改</view>
  59. <view class="e-btn del-btn flex-1 round-2 pa-12 text-red text-align-center ml-12" @click="fnDelete(item)">删除</view>
  60. </view>
  61. </view>
  62. </tm-translate>
  63. </block>
  64. </block>
  65. <tm-flotbutton @click="fnToTopPage" :offset="[16, 80]" size="m" color="light-blue" icon="icon-angle-up"></tm-flotbutton>
  66. <tm-flotbutton @click="fnShowFormModal()" size="m" color="orange" icon="icon-plus"></tm-flotbutton>
  67. <!-- 编辑或新增 -->
  68. <tm-poup v-model="poupShow" position="bottom" height="85vh" @change="fnOnPoupChange">
  69. <view class="poup-content">
  70. <view class="poup-head text-align-center text-weight-b text-size-g ma-24">{{ form.id != undefined ? '编辑友链' : '新增友链' }}</view>
  71. <scroll-view class="poup-body pa-24 pt-0" :scroll-y="true" @touchmove.stop>
  72. <tm-input
  73. required
  74. :adjust-position="true"
  75. :round="3"
  76. :borderBottom="false"
  77. title="网站名称"
  78. bg-color="grey-lighten-5"
  79. v-model="form.name"
  80. placeholder="请输入网站名称"
  81. ></tm-input>
  82. <tm-input
  83. required
  84. :borderBottom="false"
  85. :adjust-position="true"
  86. :round="3"
  87. title="网站地址"
  88. bg-color="grey-lighten-5"
  89. v-model="form.url"
  90. placeholder="请输入网站地址"
  91. ></tm-input>
  92. <view class="pl-32 mb-24 input-tips text-grey text-size-s">填写提示:需要加上 http://</view>
  93. <tm-input :borderBottom="false" :round="3" bg-color="grey-lighten-5" title="网站分组" placeholder="请输入选择网站分组" :value="form.team">
  94. <template v-slot:rightBtn>
  95. <tm-pickers :default-value.sync="selectTeam" :list="teamList" @confirm="fnOnSelectTeam">
  96. <tm-button class="ml-12" theme="bg-gradient-blue-accent" :round="3" :font-size="24" :height="70" block :width="120">选择</tm-button>
  97. </tm-pickers>
  98. </template>
  99. </tm-input>
  100. <tm-input
  101. input-type="number"
  102. :borderBottom="false"
  103. :adjust-position="true"
  104. :round="3"
  105. title="排序编号"
  106. bg-color="grey-lighten-5"
  107. v-model.number="form.priority"
  108. placeholder="请输入排序"
  109. ></tm-input>
  110. <tm-input
  111. :borderBottom="false"
  112. :vertical="true"
  113. :adjust-position="true"
  114. inputType="textarea"
  115. :round="3"
  116. title="网站描述"
  117. :height="120"
  118. bg-color="grey-lighten-5"
  119. v-model="form.description"
  120. placeholder="请输入描述"
  121. ></tm-input>
  122. <tm-input
  123. :borderBottom="false"
  124. :adjust-position="true"
  125. :round="3"
  126. title="LOGO"
  127. bg-color="grey-lighten-5"
  128. v-model="form.logo"
  129. placeholder="请输入LOGO地址"
  130. ></tm-input>
  131. <view class="ma-30 mt-12 pb-12 bg-grey">
  132. <image v-if="form.logo" class="thumbnail round-3" :src="form.logo" mode="aspectFill" @click="$utils.previewImage([form.logo])"></image>
  133. <view v-else class="thumbnail round-3 text-grey grey-lighten-5 flex flex-col flex-center ">
  134. <text class="iconfont icon-picture" style="font-size: 46rpx;"></text>
  135. <text class="mt-12 text-size-m">LOGO预览图</text>
  136. </view>
  137. </view>
  138. </scroll-view>
  139. <view class="btn-wrap flex flex-center">
  140. <tm-button size="m" theme="bg-gradient-blue-accent" @click="fnSubmit()">保存</tm-button>
  141. <tm-button v-if="form.id != undefined" size="m" theme="bg-gradient-red-accent" @click="fnDelete()">删除</tm-button>
  142. <tm-button size="m" theme="bg-gradient-blue-grey-accent" @click="poupShow = false">关 闭</tm-button>
  143. </view>
  144. </view>
  145. </tm-poup>
  146. </view>
  147. </view>
  148. </template>
  149. <script>
  150. import tmSkeleton from '@/tm-vuetify/components/tm-skeleton/tm-skeleton.vue';
  151. import tmButton from '@/tm-vuetify/components/tm-button/tm-button.vue';
  152. import tmEmpty from '@/tm-vuetify/components/tm-empty/tm-empty.vue';
  153. import tmFlotbutton from '@/tm-vuetify/components/tm-flotbutton/tm-flotbutton.vue';
  154. import tmPoup from '@/tm-vuetify/components/tm-poup/tm-poup.vue';
  155. import tmInput from '@/tm-vuetify/components/tm-input/tm-input.vue';
  156. import tmPickers from '@/tm-vuetify/components/tm-pickers/tm-pickers.vue';
  157. import tmTags from '@/tm-vuetify/components/tm-tags/tm-tags.vue';
  158. import tmTabs from '@/tm-vuetify/components/tm-tabs/tm-tabs.vue';
  159. import tmTranslate from '@/tm-vuetify/components/tm-translate/tm-translate.vue';
  160. export default {
  161. components: {
  162. tmSkeleton,
  163. tmButton,
  164. tmEmpty,
  165. tmFlotbutton,
  166. tmPoup,
  167. tmInput,
  168. tmPickers,
  169. tmTags,
  170. tmTabs,
  171. tmTranslate
  172. },
  173. data() {
  174. return {
  175. loading: 'loading',
  176. queryParams: {
  177. size: 10,
  178. page: 0
  179. },
  180. dataList: [],
  181. filterList: [],
  182. poupShow: false,
  183. form: {
  184. id: undefined,
  185. description: '',
  186. logo: '',
  187. name: '',
  188. team: '',
  189. url: '',
  190. priority: 0
  191. },
  192. tab: {
  193. activeIndex: 0,
  194. list: []
  195. },
  196. selectTeam: [],
  197. teamList: []
  198. };
  199. },
  200. onLoad() {
  201. this.fnSetPageTitle('友链管理');
  202. this.fnGetTeamData();
  203. },
  204. created() {
  205. this.fnGetData();
  206. },
  207. onPullDownRefresh() {
  208. this.fnGetData();
  209. },
  210. methods: {
  211. fnOnTabChange(index) {
  212. this.fnToTopPage();
  213. let _filterData = [];
  214. if (index == 0) {
  215. _filterData = this.dataList;
  216. } else {
  217. _filterData = this.dataList.filter(x => x.team == this.teamList[index - 1]);
  218. }
  219. this.filterList = JSON.parse(JSON.stringify(_filterData));
  220. },
  221. fnGetTeamData() {
  222. this.$httpApi.admin
  223. .getLinkTeamList()
  224. .then(res => {
  225. if (res.status == 200) {
  226. this.tab.list = ['全部', ...res.data];
  227. this.teamList = res.data;
  228. } else {
  229. uni.$tm.toast('友链分组数据加载失败!');
  230. }
  231. })
  232. .catch(err => {
  233. uni.$tm.toast('友链分组数据加载失败!');
  234. });
  235. },
  236. fnGetData() {
  237. this.loading = 'loading';
  238. uni.showLoading({
  239. mask: true,
  240. title: '加载中...'
  241. });
  242. this.tab.activeIndex = 0;
  243. this.$httpApi.admin
  244. .getLinkList(this.queryParams)
  245. .then(res => {
  246. if (res.status == 200) {
  247. const _dataList = res.data.map(item => {
  248. item.logo = this.$utils.checkUrl(item.logo);
  249. return item;
  250. });
  251. this.dataList = _dataList;
  252. this.filterList = _dataList;
  253. this.loading = 'success';
  254. } else {
  255. this.loading = 'error';
  256. uni.$tm.toast('加载失败,请重试!');
  257. }
  258. })
  259. .catch(err => {
  260. console.error(err);
  261. this.loading = 'error';
  262. uni.$tm.toast('加载失败,请重试!');
  263. })
  264. .finally(() => {
  265. uni.hideLoading();
  266. uni.stopPullDownRefresh();
  267. });
  268. },
  269. fnOnPoupChange(e) {
  270. if (!e) {
  271. this.fnResetForm();
  272. }
  273. },
  274. fnShowFormModal(link) {
  275. if (link) {
  276. this.form = Object.assign({}, {}, link);
  277. if (link.team) {
  278. this.selectTeam = [link.team];
  279. }
  280. } else {
  281. this.fnResetForm();
  282. }
  283. this.poupShow = true;
  284. },
  285. fnOnSelectTeam(e) {
  286. this.form.team = e[0].data;
  287. this.selectTeam = [e[0].data];
  288. },
  289. fnResetForm() {
  290. this.form = {
  291. id: undefined,
  292. description: '',
  293. logo: '',
  294. name: '',
  295. team: '',
  296. url: '',
  297. priority: 0
  298. };
  299. },
  300. fnSubmit() {
  301. if (this.form.name.trim() == '') {
  302. return uni.$tm.toast('友链名称未填写!');
  303. }
  304. if (this.form.id == undefined) {
  305. this.$httpApi.admin
  306. .addLink(this.form)
  307. .then(res => {
  308. if (res.status == 200) {
  309. uni.$tm.toast(`保存成功!`);
  310. setTimeout(() => {
  311. uni.startPullDownRefresh();
  312. }, 1200);
  313. } else {
  314. uni.$tm.toast('操作失败,请重试!');
  315. }
  316. })
  317. .catch(err => {
  318. uni.$tm.toast('操作失败,请重试!');
  319. });
  320. } else {
  321. this.$httpApi.admin
  322. .updateLink(this.form.id, this.form)
  323. .then(res => {
  324. if (res.status == 200) {
  325. uni.$tm.toast(`保存成功!`);
  326. let updateIndex = this.dataList.findIndex(x => x.id == this.form.id);
  327. this.$set(this.dataList, updateIndex, this.form);
  328. } else {
  329. uni.$tm.toast('操作失败,请重试!');
  330. }
  331. })
  332. .catch(err => {
  333. uni.$tm.toast('操作失败,请重试!');
  334. });
  335. }
  336. },
  337. // 删除
  338. fnDelete(link) {
  339. const _link = link || this.form;
  340. uni.$eShowModal({
  341. title: '提示',
  342. content: `您是否要将 ${_link.name} 删除?`,
  343. showCancel: true,
  344. cancelText: '否',
  345. cancelColor: '#999999',
  346. confirmText: '是',
  347. confirmColor: '#03a9f4'
  348. })
  349. .then(res => {
  350. this.$httpApi.admin
  351. .deleteLink(_link.id)
  352. .then(res => {
  353. if (res.status == 200) {
  354. uni.$tm.toast(`${_link.name} 已删除!`);
  355. const delIndex = this.dataList.findIndex(x => x.id == _link.id);
  356. this.dataList.splice(delIndex, 1);
  357. } else {
  358. uni.$tm.toast('操作失败,请重试!');
  359. }
  360. })
  361. .catch(err => {
  362. uni.$tm.toast('操作失败,请重试!');
  363. });
  364. })
  365. .catch(err => {});
  366. }
  367. }
  368. };
  369. </script>
  370. <style lang="scss" scoped>
  371. .app-page {
  372. width: 100vw;
  373. min-height: 100vh;
  374. }
  375. .loading-wrap {
  376. padding: 24rpx;
  377. .loading-error {
  378. height: 65vh;
  379. }
  380. }
  381. .link-card {
  382. box-sizing: border-box;
  383. box-shadow: 0rpx 4rpx 24rpx rgba(0, 0, 0, 0.03);
  384. .head {
  385. .left {
  386. width: 166rpx;
  387. height: 166rpx;
  388. .logo {
  389. width: 100%;
  390. height: 100%;
  391. }
  392. }
  393. .right {
  394. width: 0;
  395. flex-grow: 1;
  396. .name {
  397. width: 0;
  398. flex-grow: 1;
  399. display: flex;
  400. align-items: center;
  401. }
  402. .label {
  403. width: 84rpx;
  404. }
  405. .value {
  406. width: 0;
  407. flex-grow: 1;
  408. }
  409. }
  410. }
  411. .foot {
  412. box-sizing: border-box;
  413. // border-top: 2rpx solid rgba(0, 0, 0, 0.03);
  414. }
  415. }
  416. .poup-content {
  417. overflow: hidden;
  418. }
  419. .poup-body {
  420. height: 71vh;
  421. box-sizing: border-box;
  422. touch-action: none;
  423. }
  424. .thumbnail {
  425. width: 100%;
  426. height: 260rpx;
  427. }
  428. .update-btn {
  429. background-color: rgba(240, 250, 255, 1);
  430. }
  431. .del-btn {
  432. background-color: rgba(254, 241, 240, 1) !important;
  433. }
  434. </style>