|
|
@@ -0,0 +1,505 @@
|
|
|
+<template>
|
|
|
+ <view class="app-content">
|
|
|
+ <view class="search-wrap">
|
|
|
+ <app-search-module
|
|
|
+ ref="searchRef"
|
|
|
+ :value="searchText"
|
|
|
+ :clearOnFocus="false"
|
|
|
+ placeholder="搜索公告标题"
|
|
|
+ @input="onSearchInput"
|
|
|
+ @search="searchFn"
|
|
|
+ :isBtn="false"
|
|
|
+ :isIcon="true"
|
|
|
+ backColor="#ffffff"
|
|
|
+ :height="72"
|
|
|
+ />
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <element-loading :loading="listLoading" text="加载中">
|
|
|
+ <view class="list-wrap">
|
|
|
+ <block v-if="!$util.isEmpty(list.data)">
|
|
|
+ <view class="notice-card" v-for="(item, index) in list.data" :key="item.id">
|
|
|
+ <view class="card-top">
|
|
|
+ <text class="title">{{ item.title }}</text>
|
|
|
+ <view class="status-tag" :class="item.status == 1 ? 'active' : 'hidden'">
|
|
|
+ <text class="status-dot">●</text>
|
|
|
+ <text>{{ item.status == 1 ? '显示中' : '已隐藏' }}</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <view class="position-row" v-if="item.positionLabels && item.positionLabels.length">
|
|
|
+ <text class="position-label">展示位置</text>
|
|
|
+ <view class="position-tags">
|
|
|
+ <text class="tag" v-for="(label, idx) in item.positionLabels" :key="idx">{{ label }}</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <view class="meta-row">
|
|
|
+ <view class="meta-left">
|
|
|
+ <view class="meta-clock"></view>
|
|
|
+ <text class="meta-text">{{ item.createTime }}</text>
|
|
|
+ </view>
|
|
|
+ <text class="meta-sort">序号 {{ item.sort }}</text>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <view class="card-actions">
|
|
|
+ <button class="action-btn edit" @click.stop="editFn(item.id)">编辑</button>
|
|
|
+ <button
|
|
|
+ class="action-btn"
|
|
|
+ :class="item.status == 1 ? 'offline' : 'publish'"
|
|
|
+ @click.stop="toggleStatus(item, index)"
|
|
|
+ >
|
|
|
+ {{ item.status == 1 ? '下架' : '发布' }}
|
|
|
+ </button>
|
|
|
+ <button class="action-btn delete" @click.stop="openDelete(item, index)">删除</button>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </block>
|
|
|
+ <block v-else-if="!listLoading">
|
|
|
+ <app-wrapper-empty title="暂无更多公告" :is-empty="true" />
|
|
|
+ </block>
|
|
|
+ <block v-else>
|
|
|
+ <view class="list-loading-placeholder" />
|
|
|
+ </block>
|
|
|
+ </view>
|
|
|
+ </element-loading>
|
|
|
+
|
|
|
+ <view class="app-footer">
|
|
|
+ <button class="footer-btn" @click="addFn">新增公告</button>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <modal-module
|
|
|
+ :show="delModal"
|
|
|
+ @cancel="delModal = false"
|
|
|
+ @click="delModalClick"
|
|
|
+ content="确定删除该公告吗?"
|
|
|
+ color="#333"
|
|
|
+ :size="32"
|
|
|
+ padding="30rpx 30rpx"
|
|
|
+ />
|
|
|
+ </view>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script>
|
|
|
+import AppSearchModule from '@/components/module/app-search'
|
|
|
+import AppWrapperEmpty from '@/components/app-wrapper-empty'
|
|
|
+import ModalModule from '@/components/plugin/modal'
|
|
|
+import ElementLoading from '@/components/element-loading/index.vue'
|
|
|
+import { list } from '@/mixins'
|
|
|
+import {
|
|
|
+ shopNoticeList,
|
|
|
+ shopNoticeDelete,
|
|
|
+ updateShopNoticeStatus
|
|
|
+} from '@/api/shop-notice'
|
|
|
+
|
|
|
+export default {
|
|
|
+ name: 'shopNoticeList',
|
|
|
+ components: {
|
|
|
+ AppSearchModule,
|
|
|
+ AppWrapperEmpty,
|
|
|
+ ModalModule,
|
|
|
+ ElementLoading
|
|
|
+ },
|
|
|
+ mixins: [list],
|
|
|
+ computed: {
|
|
|
+ listLoading() {
|
|
|
+ return this.searchLoading || (this.list.loading && this.list.page === 1)
|
|
|
+ }
|
|
|
+ },
|
|
|
+ data() {
|
|
|
+ return {
|
|
|
+ searchText: '',
|
|
|
+ appliedKeyword: '',
|
|
|
+ delModal: false,
|
|
|
+ operateData: {},
|
|
|
+ operateIndex: null,
|
|
|
+ searchTimer: null,
|
|
|
+ searchLoading: false
|
|
|
+ }
|
|
|
+ },
|
|
|
+ onLoad() {
|
|
|
+ uni.$on('refreshShopNoticeList', this.refreshList)
|
|
|
+ },
|
|
|
+ onUnload() {
|
|
|
+ uni.$off('refreshShopNoticeList', this.refreshList)
|
|
|
+ if (this.searchTimer) {
|
|
|
+ clearTimeout(this.searchTimer)
|
|
|
+ }
|
|
|
+ },
|
|
|
+ onPullDownRefresh() {
|
|
|
+ this.refreshList().then(() => {
|
|
|
+ uni.stopPullDownRefresh()
|
|
|
+ })
|
|
|
+ },
|
|
|
+ onReachBottom() {
|
|
|
+ if (!this.list.finished) {
|
|
|
+ this.fetchList()
|
|
|
+ }
|
|
|
+ },
|
|
|
+ methods: {
|
|
|
+ init() {
|
|
|
+ this.fetchList()
|
|
|
+ },
|
|
|
+ refreshList() {
|
|
|
+ this.resetList()
|
|
|
+ return this.fetchList()
|
|
|
+ },
|
|
|
+ onSearchInput(val) {
|
|
|
+ this.searchText = val || ''
|
|
|
+ if (this.searchTimer) {
|
|
|
+ clearTimeout(this.searchTimer)
|
|
|
+ this.searchTimer = null
|
|
|
+ }
|
|
|
+ const keyword = this.searchText.trim()
|
|
|
+ if (!keyword) {
|
|
|
+ this.handleKeywordCleared()
|
|
|
+ return
|
|
|
+ }
|
|
|
+ // 从无到有:防抖后带关键词搜索
|
|
|
+ this.searchTimer = setTimeout(() => {
|
|
|
+ this.runSearch()
|
|
|
+ }, 500)
|
|
|
+ },
|
|
|
+ searchFn(keyword) {
|
|
|
+ if (keyword !== undefined && keyword !== null) {
|
|
|
+ this.searchText = keyword || ''
|
|
|
+ }
|
|
|
+ if (this.searchTimer) {
|
|
|
+ clearTimeout(this.searchTimer)
|
|
|
+ this.searchTimer = null
|
|
|
+ }
|
|
|
+ const current = this.searchText.trim()
|
|
|
+ if (!current) {
|
|
|
+ this.handleKeywordCleared()
|
|
|
+ return
|
|
|
+ }
|
|
|
+ this.runSearch()
|
|
|
+ },
|
|
|
+ // 关键词清空:仅当之前有搜索词时恢复全量列表
|
|
|
+ handleKeywordCleared() {
|
|
|
+ if (!this.appliedKeyword) {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ this.appliedKeyword = ''
|
|
|
+ this.startSearchFetch()
|
|
|
+ this.fetchList()
|
|
|
+ },
|
|
|
+ runSearch() {
|
|
|
+ const keyword = this.searchText.trim()
|
|
|
+ if (!keyword) {
|
|
|
+ this.handleKeywordCleared()
|
|
|
+ return
|
|
|
+ }
|
|
|
+ this.appliedKeyword = keyword
|
|
|
+ this.startSearchFetch()
|
|
|
+ this.fetchList()
|
|
|
+ },
|
|
|
+ // 搜索时仅重置分页,保留当前列表直到新数据返回
|
|
|
+ startSearchFetch() {
|
|
|
+ this.searchLoading = true
|
|
|
+ this.list.page = 1
|
|
|
+ this.list.finished = false
|
|
|
+ this.list.loading = true
|
|
|
+ },
|
|
|
+ fetchList() {
|
|
|
+ const params = {
|
|
|
+ page: this.list.page,
|
|
|
+ pageSize: this.list.pageSize
|
|
|
+ }
|
|
|
+ if (this.appliedKeyword) {
|
|
|
+ params.title = this.appliedKeyword
|
|
|
+ }
|
|
|
+ return shopNoticeList(params).then(res => {
|
|
|
+ this.completes(res)
|
|
|
+ }).catch(() => {
|
|
|
+ this.list.loading = false
|
|
|
+ }).finally(() => {
|
|
|
+ this.searchLoading = false
|
|
|
+ })
|
|
|
+ },
|
|
|
+ addFn() {
|
|
|
+ uni.navigateTo({ url: '/admin/shopNotice/add' })
|
|
|
+ },
|
|
|
+ editFn(id) {
|
|
|
+ uni.navigateTo({ url: `/admin/shopNotice/add?id=${id}` })
|
|
|
+ },
|
|
|
+ formatDateTime(date) {
|
|
|
+ const d = date instanceof Date ? date : new Date(date)
|
|
|
+ const pad = n => String(n).padStart(2, '0')
|
|
|
+ return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`
|
|
|
+ },
|
|
|
+ toggleStatus(item, index) {
|
|
|
+ const status = item.status == 1 ? 0 : 1
|
|
|
+ updateShopNoticeStatus({ id: item.id, status }).then(res => {
|
|
|
+ if (res.code == 1) {
|
|
|
+ this.$set(this.list.data[index], 'status', status)
|
|
|
+ if (status === 1) {
|
|
|
+ this.$set(this.list.data[index], 'publishTime', this.formatDateTime(new Date()))
|
|
|
+ }
|
|
|
+ this.$msg(status === 1 ? '发布成功' : '下架成功')
|
|
|
+ }
|
|
|
+ })
|
|
|
+ },
|
|
|
+ openDelete(item, index) {
|
|
|
+ this.operateData = item
|
|
|
+ this.operateIndex = index
|
|
|
+ this.delModal = true
|
|
|
+ },
|
|
|
+ delModalClick(e) {
|
|
|
+ if (e.index === 0) {
|
|
|
+ this.delModal = false
|
|
|
+ return
|
|
|
+ }
|
|
|
+ shopNoticeDelete({ id: this.operateData.id }).then(res => {
|
|
|
+ this.delModal = false
|
|
|
+ if (res.code == 1) {
|
|
|
+ this.$msg('删除成功')
|
|
|
+ this.refreshList()
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+</script>
|
|
|
+
|
|
|
+<style lang="scss" scoped>
|
|
|
+.app-content {
|
|
|
+ min-height: 100vh;
|
|
|
+ padding-bottom: calc(140rpx + env(safe-area-inset-bottom));
|
|
|
+ background: #f3f4f6;
|
|
|
+}
|
|
|
+
|
|
|
+.search-wrap {
|
|
|
+ padding: 24rpx 24rpx 8rpx;
|
|
|
+ background: #f3f4f6;
|
|
|
+
|
|
|
+ ::v-deep .app-search-module .map-search {
|
|
|
+ border-radius: 20rpx;
|
|
|
+ box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.04);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.list-wrap {
|
|
|
+ padding: 16rpx 24rpx 24rpx;
|
|
|
+ min-height: 400rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.list-loading-placeholder {
|
|
|
+ min-height: 400rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.notice-card {
|
|
|
+ background: #fff;
|
|
|
+ border-radius: 20rpx;
|
|
|
+ padding: 28rpx 28rpx 24rpx;
|
|
|
+ margin-bottom: 24rpx;
|
|
|
+ box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04);
|
|
|
+}
|
|
|
+
|
|
|
+.card-top {
|
|
|
+ display: flex;
|
|
|
+ align-items: flex-start;
|
|
|
+ justify-content: space-between;
|
|
|
+ gap: 20rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.title {
|
|
|
+ flex: 1;
|
|
|
+ font-size: 34rpx;
|
|
|
+ font-weight: 600;
|
|
|
+ color: $fontColorMain;
|
|
|
+ line-height: 1.4;
|
|
|
+}
|
|
|
+
|
|
|
+.status-tag {
|
|
|
+ flex-shrink: 0;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 6rpx;
|
|
|
+ font-size: 24rpx;
|
|
|
+ padding: 8rpx 18rpx;
|
|
|
+ border-radius: 24rpx;
|
|
|
+ white-space: nowrap;
|
|
|
+
|
|
|
+ &.active {
|
|
|
+ color: #07a94b;
|
|
|
+ background: #e8f8ee;
|
|
|
+ .status-dot {
|
|
|
+ color: #07a94b;
|
|
|
+ font-size: 18rpx;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ &.hidden {
|
|
|
+ color: $fontColor3;
|
|
|
+ background: #f0f0f0;
|
|
|
+ .status-dot {
|
|
|
+ color: #bfbfbf;
|
|
|
+ font-size: 18rpx;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.position-row {
|
|
|
+ display: flex;
|
|
|
+ align-items: flex-start;
|
|
|
+ margin-top: 24rpx;
|
|
|
+ gap: 16rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.position-label {
|
|
|
+ flex-shrink: 0;
|
|
|
+ font-size: 26rpx;
|
|
|
+ color: #8c8c8c;
|
|
|
+ line-height: 48rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.position-tags {
|
|
|
+ flex: 1;
|
|
|
+ display: flex;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ gap: 12rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.tag {
|
|
|
+ font-size: 24rpx;
|
|
|
+ color: #4a7fd0;
|
|
|
+ background: #eef4fc;
|
|
|
+ padding: 8rpx 20rpx;
|
|
|
+ border-radius: 8rpx;
|
|
|
+ line-height: 1.2;
|
|
|
+}
|
|
|
+
|
|
|
+.meta-row {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: space-evenly;
|
|
|
+ padding:20rpx 0;
|
|
|
+ border-bottom: 1rpx solid #f5f5f5;
|
|
|
+}
|
|
|
+
|
|
|
+.meta-left {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 10rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.meta-clock {
|
|
|
+ width: 24rpx;
|
|
|
+ height: 24rpx;
|
|
|
+ border: 2rpx solid #bfbfbf;
|
|
|
+ border-radius: 50%;
|
|
|
+ position: relative;
|
|
|
+ flex-shrink: 0;
|
|
|
+
|
|
|
+ &::before,
|
|
|
+ &::after {
|
|
|
+ content: '';
|
|
|
+ position: absolute;
|
|
|
+ background: #bfbfbf;
|
|
|
+ left: 50%;
|
|
|
+ top: 50%;
|
|
|
+ transform-origin: bottom center;
|
|
|
+ }
|
|
|
+
|
|
|
+ &::before {
|
|
|
+ width: 2rpx;
|
|
|
+ height: 7rpx;
|
|
|
+ margin-left: -1rpx;
|
|
|
+ margin-top: -7rpx;
|
|
|
+ }
|
|
|
+
|
|
|
+ &::after {
|
|
|
+ width: 2rpx;
|
|
|
+ height: 5rpx;
|
|
|
+ margin-left: -1rpx;
|
|
|
+ margin-top: -5rpx;
|
|
|
+ transform: rotate(90deg);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.meta-text{
|
|
|
+ font-size: 24rpx;
|
|
|
+ color: #999;
|
|
|
+}
|
|
|
+.meta-sort {
|
|
|
+ margin-left:20rpx;
|
|
|
+ font-size: 24rpx;
|
|
|
+ color: #999;
|
|
|
+}
|
|
|
+
|
|
|
+.card-actions {
|
|
|
+ display: flex;
|
|
|
+ justify-content: flex-end;
|
|
|
+ align-items: center;
|
|
|
+ gap: 16rpx;
|
|
|
+ margin-top: 24rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.action-btn {
|
|
|
+ margin: 0;
|
|
|
+ padding: 0 28rpx;
|
|
|
+ height: 56rpx;
|
|
|
+ line-height: 54rpx;
|
|
|
+ font-size: 26rpx;
|
|
|
+ border-radius: 12rpx;
|
|
|
+ background: #fff;
|
|
|
+ border: 1rpx solid #e0e0e0;
|
|
|
+ color: #666;
|
|
|
+
|
|
|
+ &::after {
|
|
|
+ border: none;
|
|
|
+ }
|
|
|
+
|
|
|
+ &.edit {
|
|
|
+ color: $greenColor;
|
|
|
+ border-color: #b8e6c8;
|
|
|
+ background: #f6fcf8;
|
|
|
+ }
|
|
|
+
|
|
|
+ &.publish {
|
|
|
+ color: $greenColor;
|
|
|
+ border-color: #b8e6c8;
|
|
|
+ background: #f6fcf8;
|
|
|
+ }
|
|
|
+
|
|
|
+ &.offline {
|
|
|
+ color: $fontColor3;
|
|
|
+ border-color: #e0e0e0;
|
|
|
+ background: #fafafa;
|
|
|
+ }
|
|
|
+
|
|
|
+ &.delete {
|
|
|
+ color: #e64340;
|
|
|
+ border-color: #f5c4c3;
|
|
|
+ background: #fff8f8;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.app-footer {
|
|
|
+ position: fixed;
|
|
|
+ left: 0;
|
|
|
+ right: 0;
|
|
|
+ bottom: 0;
|
|
|
+ padding: 20rpx 30rpx calc(20rpx + env(safe-area-inset-bottom));
|
|
|
+ background: #fff;
|
|
|
+ box-shadow: 0 -2rpx 16rpx rgba(0, 0, 0, 0.06);
|
|
|
+}
|
|
|
+
|
|
|
+.footer-btn {
|
|
|
+ width: 100%;
|
|
|
+ height: 88rpx;
|
|
|
+ line-height: 88rpx;
|
|
|
+ margin: 0;
|
|
|
+ padding: 0;
|
|
|
+ font-size: 32rpx;
|
|
|
+ color: #fff;
|
|
|
+ background: $greenColor;
|
|
|
+ border-radius: 22rpx;
|
|
|
+ border: none;
|
|
|
+
|
|
|
+ &::after {
|
|
|
+ border: none;
|
|
|
+ }
|
|
|
+}
|
|
|
+</style>
|