|
|
@@ -0,0 +1,981 @@
|
|
|
+<!--
|
|
|
+ 分类广告设置组件
|
|
|
+ 用途:在分类管理页第三 Tab 内嵌编辑,最多 3 张轮播,不跳转独立页
|
|
|
+ 数据存 xhCategorySetting.advertisement JSON:[{type,img,value}] type 1图文 2商品 3分类
|
|
|
+-->
|
|
|
+<template>
|
|
|
+ <view class="ad-setting">
|
|
|
+ <view class="ad-intro">
|
|
|
+ <text class="ad-intro-title">分类页轮播广告</text>
|
|
|
+ <text class="ad-intro-sub">共 3 个广告位,点表格即可设置图片和跳转</text>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 3 个广告位各一张表,行高加大方便点按 -->
|
|
|
+ <view class="ad-table" v-for="(item, index) in adList" :key="item.slotKey">
|
|
|
+ <view class="ad-table-head">
|
|
|
+ <text class="ad-table-title">广告位 {{ index + 1 }}</text>
|
|
|
+ <text class="ad-status" :class="isSlotSet(item) ? 'is-set' : 'no-set'">
|
|
|
+ {{ isSlotSet(item) ? '已设置' : '未设置' }}
|
|
|
+ </text>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <view class="ad-table-row ad-table-row-img" @click="chooseImage(index)">
|
|
|
+ <text class="ad-label">广告图</text>
|
|
|
+ <view class="ad-value ad-value-img">
|
|
|
+ <block v-if="item.img">
|
|
|
+ <image class="ad-thumb" :src="imgFullUrl(item.img)" mode="aspectFill" />
|
|
|
+ <view class="ad-thumb-remove" @click.stop="removeImage(index)">×</view>
|
|
|
+ </block>
|
|
|
+ <view v-else class="ad-upload-box">
|
|
|
+ <text class="ad-upload-plus">+</text>
|
|
|
+ <text class="ad-upload-text">点击上传</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <view class="ad-table-row">
|
|
|
+ <text class="ad-label">关联类型</text>
|
|
|
+ <view class="ad-value">
|
|
|
+ <view class="type-tabs">
|
|
|
+ <view
|
|
|
+ class="type-tab"
|
|
|
+ v-for="opt in typeOptions"
|
|
|
+ :key="opt.value"
|
|
|
+ :class="{ active: item.type === opt.value }"
|
|
|
+ @click.stop="changeType(index, opt.value)"
|
|
|
+ >{{ opt.label }}</view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <view class="ad-table-row ad-table-row-last" @click="openLinkSelect(index)">
|
|
|
+ <text class="ad-label">关联内容</text>
|
|
|
+ <view class="ad-value ad-value-link">
|
|
|
+ <text
|
|
|
+ class="link-text"
|
|
|
+ :class="{ placeholder: !getActiveLink(item).valueLabel }"
|
|
|
+ >{{ getActiveLink(item).valueLabel || '点击选择' }}</text>
|
|
|
+ <text class="link-arrow">›</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <view class="ad-bottom-space"></view>
|
|
|
+
|
|
|
+ <!-- 分类多选弹层:支持搜索,分类较多时便于查找 -->
|
|
|
+ <view v-if="categoryPickerShow" class="picker-overlay" @touchmove.stop.prevent="preventTouchMove">
|
|
|
+ <view class="picker-mask" @tap.stop="closeCategoryPicker"></view>
|
|
|
+ <view class="picker-panel category-picker-panel">
|
|
|
+ <view class="picker-head">
|
|
|
+ <text class="picker-title">选择分类</text>
|
|
|
+ <text class="picker-count">已选 {{ categoryPickCount }} 个</text>
|
|
|
+ <text class="picker-close" @tap.stop="closeCategoryPicker">×</text>
|
|
|
+ </view>
|
|
|
+ <view class="picker-search-row">
|
|
|
+ <view class="picker-search-box">
|
|
|
+ <text class="picker-search-icon">⌕</text>
|
|
|
+ <input
|
|
|
+ v-model="categorySearchText"
|
|
|
+ class="picker-search-input"
|
|
|
+ placeholder="搜索分类名称"
|
|
|
+ confirm-type="search"
|
|
|
+ />
|
|
|
+ </view>
|
|
|
+ <text
|
|
|
+ v-if="categorySearchText"
|
|
|
+ class="picker-search-clear"
|
|
|
+ @tap.stop="clearCategorySearch"
|
|
|
+ >清空</text>
|
|
|
+ </view>
|
|
|
+ <scroll-view scroll-y class="picker-body">
|
|
|
+ <block v-if="filteredBouquetCategories.length">
|
|
|
+ <view
|
|
|
+ class="picker-item"
|
|
|
+ v-for="cat in filteredBouquetCategories"
|
|
|
+ :key="cat.id"
|
|
|
+ :data-id="cat.id"
|
|
|
+ @tap.stop="toggleCategoryPickByEvent"
|
|
|
+ >
|
|
|
+ <view class="check-box" :class="{ checked: !!categoryPickMap[String(cat.id)] }">
|
|
|
+ <text v-if="categoryPickMap[String(cat.id)]" class="check-mark">✓</text>
|
|
|
+ </view>
|
|
|
+ <text class="picker-item-name">{{ cat.categoryName }}</text>
|
|
|
+ </view>
|
|
|
+ </block>
|
|
|
+ <view v-else class="picker-empty">
|
|
|
+ <text>{{ categorySearchText ? '未找到匹配的分类' : '暂无分类' }}</text>
|
|
|
+ </view>
|
|
|
+ </scroll-view>
|
|
|
+ <view class="picker-footer">
|
|
|
+ <button class="picker-btn cancel" hover-class="none" @click="closeCategoryPicker">取消</button>
|
|
|
+ <button class="picker-btn confirm" hover-class="none" @click="confirmCategoryPick">确定</button>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 已上传广告图预览 -->
|
|
|
+ <view v-if="previewShow" class="picker-overlay" @touchmove.stop.prevent="preventTouchMove">
|
|
|
+ <view class="picker-mask" @click.stop="previewShow = false"></view>
|
|
|
+ <view class="preview-panel">
|
|
|
+ <swiper class="preview-swiper" indicator-dots circular>
|
|
|
+ <swiper-item v-for="slide in previewSlides" :key="slide.slideKey">
|
|
|
+ <image class="preview-img" :src="slide.url" mode="aspectFit" />
|
|
|
+ </swiper-item>
|
|
|
+ </swiper>
|
|
|
+ <button class="preview-close" hover-class="none" @click="previewShow = false">关闭预览</button>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <view class="ad-bottom-bar">
|
|
|
+ <button class="bar-btn bar-btn-preview" hover-class="none" @click="previewAd">预览</button>
|
|
|
+ <button class="bar-btn bar-btn-save" hover-class="none" @click="saveAd">保存</button>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script>
|
|
|
+import { APIHOST } from '@/config';
|
|
|
+import { categoryListInfo } from '@/api/goods';
|
|
|
+import { categorySettingDetail, categorySettingSaveAdvertisement } from '@/api/category-setting';
|
|
|
+
|
|
|
+const EMPTY_LINK = () => ({ value: '', valueLabel: '' });
|
|
|
+
|
|
|
+const EMPTY_SLOT = (index) => ({
|
|
|
+ slotKey: `ad-${index}`,
|
|
|
+ type: 1,
|
|
|
+ img: '',
|
|
|
+ // 各关联类型独立缓存,切换类型时不丢失已选内容
|
|
|
+ links: {
|
|
|
+ 1: EMPTY_LINK(),
|
|
|
+ 2: EMPTY_LINK(),
|
|
|
+ 3: EMPTY_LINK()
|
|
|
+ }
|
|
|
+});
|
|
|
+
|
|
|
+export default {
|
|
|
+ name: 'category-ad-setting',
|
|
|
+ data() {
|
|
|
+ return {
|
|
|
+ constant: this.$constant,
|
|
|
+ settingId: 0,
|
|
|
+ adList: [EMPTY_SLOT(0), EMPTY_SLOT(1), EMPTY_SLOT(2)],
|
|
|
+ typeOptions: [
|
|
|
+ { label: '图文', value: 1 },
|
|
|
+ { label: '商品', value: 2 },
|
|
|
+ { label: '分类', value: 3 }
|
|
|
+ ],
|
|
|
+ bouquetCategories: [],
|
|
|
+ categoryPickerShow: false,
|
|
|
+ categoryPickIndex: 0,
|
|
|
+ categoryPickMap: {},
|
|
|
+ categorySearchText: '',
|
|
|
+ previewShow: false,
|
|
|
+ saving: false
|
|
|
+ };
|
|
|
+ },
|
|
|
+ computed: {
|
|
|
+ previewSlides() {
|
|
|
+ return this.adList
|
|
|
+ .filter((item) => item.img)
|
|
|
+ .map((item, index) => ({
|
|
|
+ slideKey: item.slotKey || `preview-${index}`,
|
|
|
+ url: this.imgFullUrl(item.img)
|
|
|
+ }));
|
|
|
+ },
|
|
|
+ /** 分类弹层内已选数量 */
|
|
|
+ categoryPickCount() {
|
|
|
+ return Object.keys(this.categoryPickMap).length;
|
|
|
+ },
|
|
|
+ /** 按关键词过滤花束分类,分类多时可快速定位 */
|
|
|
+ filteredBouquetCategories() {
|
|
|
+ const keyword = (this.categorySearchText || '').trim().toLowerCase();
|
|
|
+ if (!keyword) return this.bouquetCategories;
|
|
|
+ return this.bouquetCategories.filter((cat) => {
|
|
|
+ const name = (cat.categoryName || '').toLowerCase();
|
|
|
+ return name.indexOf(keyword) > -1;
|
|
|
+ });
|
|
|
+ }
|
|
|
+ },
|
|
|
+ mounted() {
|
|
|
+ this.reload();
|
|
|
+ this.loadBouquetCategories();
|
|
|
+ // 选图文/商品后从子页返回,用全局事件回填当前广告位
|
|
|
+ uni.$on('categoryAdPicTextSelected', this.onPicTextSelected);
|
|
|
+ uni.$on('categoryAdGoodsSelected', this.onGoodsSelected);
|
|
|
+ },
|
|
|
+ beforeDestroy() {
|
|
|
+ uni.$off('categoryAdPicTextSelected', this.onPicTextSelected);
|
|
|
+ uni.$off('categoryAdGoodsSelected', this.onGoodsSelected);
|
|
|
+ },
|
|
|
+ methods: {
|
|
|
+ preventTouchMove() {},
|
|
|
+ /** 图片和关联内容都有才算已设置,给表头状态用 */
|
|
|
+ isSlotSet(item) {
|
|
|
+ const link = this.getActiveLink(item);
|
|
|
+ return !!(item.img && link.value);
|
|
|
+ },
|
|
|
+ imgFullUrl(path) {
|
|
|
+ if (!path) return '';
|
|
|
+ if (path.indexOf('http') === 0) return path;
|
|
|
+ const base = (this.constant.imgUrl || '').replace(/\/$/, '');
|
|
|
+ return `${base}/${path.replace(/^\//, '')}`;
|
|
|
+ },
|
|
|
+ /** 父页下拉刷新或切到广告 Tab 时重新拉 advertisement */
|
|
|
+ reload() {
|
|
|
+ return this.loadSetting();
|
|
|
+ },
|
|
|
+ loadSetting() {
|
|
|
+ return categorySettingDetail().then((res) => {
|
|
|
+ if (res.code != 1) return;
|
|
|
+ const data = res.data || {};
|
|
|
+ this.settingId = data.id || 0;
|
|
|
+ this.adList = this.parseAdvertisement(data.advertisement);
|
|
|
+ });
|
|
|
+ },
|
|
|
+ loadBouquetCategories() {
|
|
|
+ categoryListInfo().then((res) => {
|
|
|
+ const list = res.data || [];
|
|
|
+ this.bouquetCategories = list;
|
|
|
+ // 分类名称依赖列表,加载后刷新各 slot 分类类型展示文案
|
|
|
+ this.adList = this.adList.map((item) => this.refreshSlotLinkLabels(item));
|
|
|
+ });
|
|
|
+ },
|
|
|
+ /** 取当前关联类型下的内容与展示文案 */
|
|
|
+ getActiveLink(item) {
|
|
|
+ const type = item.type || 1;
|
|
|
+ return (item.links && item.links[type]) || EMPTY_LINK();
|
|
|
+ },
|
|
|
+ /** 写入指定类型的关联内容 */
|
|
|
+ setSlotLink(item, type, value, valueLabel) {
|
|
|
+ if (!item.links) {
|
|
|
+ item.links = { 1: EMPTY_LINK(), 2: EMPTY_LINK(), 3: EMPTY_LINK() };
|
|
|
+ }
|
|
|
+ this.$set(item.links, type, { value: value || '', valueLabel: valueLabel || '' });
|
|
|
+ },
|
|
|
+ /** 分类列表就绪后,重算 slot 内各类型展示文案 */
|
|
|
+ refreshSlotLinkLabels(item) {
|
|
|
+ const links = item.links || { 1: EMPTY_LINK(), 2: EMPTY_LINK(), 3: EMPTY_LINK() };
|
|
|
+ [1, 2, 3].forEach((type) => {
|
|
|
+ const link = links[type] || EMPTY_LINK();
|
|
|
+ if (link.value) {
|
|
|
+ links[type] = {
|
|
|
+ value: link.value,
|
|
|
+ valueLabel: this.buildValueLabel(type, link.value, link.valueLabel)
|
|
|
+ };
|
|
|
+ }
|
|
|
+ });
|
|
|
+ return { ...item, links };
|
|
|
+ },
|
|
|
+ parseAdvertisement(raw) {
|
|
|
+ const slots = [EMPTY_SLOT(0), EMPTY_SLOT(1), EMPTY_SLOT(2)];
|
|
|
+ if (!raw) return slots;
|
|
|
+ let arr = [];
|
|
|
+ try {
|
|
|
+ arr = typeof raw === 'string' ? JSON.parse(raw) : raw;
|
|
|
+ } catch (e) {
|
|
|
+ return slots;
|
|
|
+ }
|
|
|
+ if (!Array.isArray(arr)) return slots;
|
|
|
+ arr.slice(0, 3).forEach((item, index) => {
|
|
|
+ const type = parseInt(item.type, 10) || 1;
|
|
|
+ const value = item.value ? String(item.value) : '';
|
|
|
+ const links = {
|
|
|
+ 1: EMPTY_LINK(),
|
|
|
+ 2: EMPTY_LINK(),
|
|
|
+ 3: EMPTY_LINK()
|
|
|
+ };
|
|
|
+ links[type] = {
|
|
|
+ value,
|
|
|
+ valueLabel: this.buildValueLabel(type, value)
|
|
|
+ };
|
|
|
+ slots[index] = {
|
|
|
+ slotKey: `ad-${index}`,
|
|
|
+ type,
|
|
|
+ img: item.img || '',
|
|
|
+ links
|
|
|
+ };
|
|
|
+ });
|
|
|
+ return slots;
|
|
|
+ },
|
|
|
+ buildValueLabel(type, value, fallbackLabel) {
|
|
|
+ if (!value) return '';
|
|
|
+ const ids = value.split(',').filter(Boolean);
|
|
|
+ if (type === 1) {
|
|
|
+ if (fallbackLabel && fallbackLabel.indexOf('图文ID:') !== 0) return fallbackLabel;
|
|
|
+ return ids.length ? `图文ID:${ids.join(',')}` : '';
|
|
|
+ }
|
|
|
+ if (type === 2) return `已选${ids.length}个商品`;
|
|
|
+ if (type === 3) {
|
|
|
+ const names = ids.map((id) => {
|
|
|
+ const cat = this.bouquetCategories.find((c) => String(c.id) === String(id));
|
|
|
+ return cat ? cat.categoryName : id;
|
|
|
+ });
|
|
|
+ return names.join('、') || `已选${ids.length}个分类`;
|
|
|
+ }
|
|
|
+ return '';
|
|
|
+ },
|
|
|
+ /** 仅切换展示类型,不清空各类型已缓存的关联内容 */
|
|
|
+ changeType(index, type) {
|
|
|
+ const item = this.adList[index];
|
|
|
+ if (item.type === type) return;
|
|
|
+ item.type = type;
|
|
|
+ },
|
|
|
+ openLinkSelect(index) {
|
|
|
+ const item = this.adList[index];
|
|
|
+ const link = this.getActiveLink(item);
|
|
|
+ if (item.type === 1) {
|
|
|
+ uni.navigateTo({
|
|
|
+ url: `/admin/picText/picTextList?mode=selectAd&slotIndex=${index}&picTextId=${link.value || 0}`
|
|
|
+ });
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (item.type === 2) {
|
|
|
+ uni.navigateTo({
|
|
|
+ url: `/admin/goods/ad-goods-select?slotIndex=${index}&selectedIds=${link.value || ''}`
|
|
|
+ });
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (item.type === 3) {
|
|
|
+ this.categoryPickIndex = index;
|
|
|
+ this.categorySearchText = '';
|
|
|
+ this.categoryPickMap = {};
|
|
|
+ const ids = link.value ? link.value.split(',').filter(Boolean) : [];
|
|
|
+ ids.forEach((id) => {
|
|
|
+ this.$set(this.categoryPickMap, String(id), true);
|
|
|
+ });
|
|
|
+ this.categoryPickerShow = true;
|
|
|
+ }
|
|
|
+ },
|
|
|
+ onPicTextSelected(payload) {
|
|
|
+ const index = payload.slotIndex;
|
|
|
+ if (index === undefined || !this.adList[index]) return;
|
|
|
+ const valueLabel = payload.title || `图文ID:${payload.picTextId}`;
|
|
|
+ this.setSlotLink(this.adList[index], 1, String(payload.picTextId || ''), valueLabel);
|
|
|
+ },
|
|
|
+ onGoodsSelected(payload) {
|
|
|
+ const index = payload.slotIndex;
|
|
|
+ if (index === undefined || !this.adList[index]) return;
|
|
|
+ const ids = (payload.goodsIds || []).map(String);
|
|
|
+ const value = ids.join(',');
|
|
|
+ this.setSlotLink(
|
|
|
+ this.adList[index],
|
|
|
+ 2,
|
|
|
+ value,
|
|
|
+ ids.length ? `已选${ids.length}个商品` : ''
|
|
|
+ );
|
|
|
+ },
|
|
|
+ /** 从 data-id 切换分类勾选,兼容小程序 tap 传参 */
|
|
|
+ toggleCategoryPickByEvent(e) {
|
|
|
+ const id = e.currentTarget.dataset.id;
|
|
|
+ if (id === undefined || id === null || id === '') return;
|
|
|
+ const key = String(id);
|
|
|
+ if (this.categoryPickMap[key]) {
|
|
|
+ this.$delete(this.categoryPickMap, key);
|
|
|
+ } else {
|
|
|
+ this.$set(this.categoryPickMap, key, true);
|
|
|
+ }
|
|
|
+ },
|
|
|
+ clearCategorySearch() {
|
|
|
+ this.categorySearchText = '';
|
|
|
+ },
|
|
|
+ closeCategoryPicker() {
|
|
|
+ this.categoryPickerShow = false;
|
|
|
+ this.categorySearchText = '';
|
|
|
+ },
|
|
|
+ confirmCategoryPick() {
|
|
|
+ const index = this.categoryPickIndex;
|
|
|
+ const ids = Object.keys(this.categoryPickMap);
|
|
|
+ const value = ids.join(',');
|
|
|
+ this.setSlotLink(this.adList[index], 3, value, this.buildValueLabel(3, value));
|
|
|
+ this.categoryPickerShow = false;
|
|
|
+ this.categorySearchText = '';
|
|
|
+ },
|
|
|
+ chooseImage(index) {
|
|
|
+ uni.chooseImage({
|
|
|
+ count: 1,
|
|
|
+ sizeType: ['compressed'],
|
|
|
+ sourceType: ['album', 'camera'],
|
|
|
+ success: (res) => {
|
|
|
+ const filePath = res.tempFilePaths[0];
|
|
|
+ this.uploadImageFile(filePath, index);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ },
|
|
|
+ /** 通过 multipart 上传广告图,存相对路径 shortUrl 供保存与展示 */
|
|
|
+ uploadImageFile(filePath, index) {
|
|
|
+ uni.showLoading({ title: '上传中', mask: true });
|
|
|
+ const token = uni.getStorageSync('token') || '';
|
|
|
+ uni.uploadFile({
|
|
|
+ url: `${APIHOST}/upload/save-file`,
|
|
|
+ filePath,
|
|
|
+ name: 'file',
|
|
|
+ fileType: 'image',
|
|
|
+ header: { token },
|
|
|
+ success: (uploadRes) => {
|
|
|
+ uni.hideLoading();
|
|
|
+ try {
|
|
|
+ const res = JSON.parse(uploadRes.data || '{}');
|
|
|
+ if (res.code == 1 && res.data) {
|
|
|
+ // 保存相对路径,展示时由 imgFullUrl 拼接域名
|
|
|
+ this.adList[index].img = res.data.shortUrl || res.data.url || '';
|
|
|
+ if (!this.adList[index].img) {
|
|
|
+ this.$msg('上传失败');
|
|
|
+ }
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ this.$msg(res.message || '上传失败');
|
|
|
+ } catch (e) {
|
|
|
+ this.$msg('上传失败');
|
|
|
+ }
|
|
|
+ },
|
|
|
+ fail: () => {
|
|
|
+ uni.hideLoading();
|
|
|
+ this.$msg('上传失败');
|
|
|
+ }
|
|
|
+ });
|
|
|
+ },
|
|
|
+ removeImage(index) {
|
|
|
+ this.adList[index].img = '';
|
|
|
+ },
|
|
|
+ previewAd() {
|
|
|
+ if (!this.previewSlides.length) {
|
|
|
+ this.$msg('请先上传广告图');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ this.previewShow = true;
|
|
|
+ },
|
|
|
+ saveAd() {
|
|
|
+ const payload = [];
|
|
|
+ for (let i = 0; i < this.adList.length; i++) {
|
|
|
+ const item = this.adList[i];
|
|
|
+ const link = this.getActiveLink(item);
|
|
|
+ if (!item.img && !link.value) continue;
|
|
|
+ if (item.img && !link.value) {
|
|
|
+ this.$msg(`请完善广告位${i + 1}的关联内容`);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (link.value && !item.img) {
|
|
|
+ this.$msg(`请上传广告位${i + 1}的图片`);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ payload.push({
|
|
|
+ type: item.type,
|
|
|
+ img: item.img,
|
|
|
+ value: link.value
|
|
|
+ });
|
|
|
+ }
|
|
|
+ if (this.saving) return;
|
|
|
+ this.saving = true;
|
|
|
+ categorySettingSaveAdvertisement({
|
|
|
+ advertisement: JSON.stringify(payload)
|
|
|
+ }).then((res) => {
|
|
|
+ if (res.code == 1) {
|
|
|
+ this.$msg('保存成功');
|
|
|
+ }
|
|
|
+ }).finally(() => {
|
|
|
+ this.saving = false;
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }
|
|
|
+};
|
|
|
+</script>
|
|
|
+
|
|
|
+<style lang="scss" scoped>
|
|
|
+.ad-setting {
|
|
|
+ padding: 20upx 20upx 0;
|
|
|
+ box-sizing: border-box;
|
|
|
+}
|
|
|
+
|
|
|
+.ad-intro {
|
|
|
+ padding: 8upx 8upx 20upx;
|
|
|
+}
|
|
|
+
|
|
|
+.ad-intro-title {
|
|
|
+ display: block;
|
|
|
+ font-size: 32upx;
|
|
|
+ color: #161819;
|
|
|
+ font-weight: 700;
|
|
|
+ line-height: 44upx;
|
|
|
+}
|
|
|
+
|
|
|
+.ad-intro-sub {
|
|
|
+ display: block;
|
|
|
+ margin-top: 8upx;
|
|
|
+ font-size: 24upx;
|
|
|
+ color: #8a9199;
|
|
|
+ line-height: 34upx;
|
|
|
+}
|
|
|
+
|
|
|
+.ad-table {
|
|
|
+ background: #fff;
|
|
|
+ border-radius: 16upx;
|
|
|
+ overflow: hidden;
|
|
|
+ box-shadow: 0 6upx 20upx rgba(0, 0, 0, 0.04);
|
|
|
+}
|
|
|
+
|
|
|
+.ad-table + .ad-table {
|
|
|
+ margin-top: 24upx;
|
|
|
+}
|
|
|
+
|
|
|
+.ad-table-head {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: row;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: space-between;
|
|
|
+ padding: 24upx 28upx;
|
|
|
+ background: #f8fbf8;
|
|
|
+ border-bottom: 1upx solid #f0f0f0;
|
|
|
+}
|
|
|
+
|
|
|
+.ad-table-title {
|
|
|
+ font-size: 30upx;
|
|
|
+ color: #161819;
|
|
|
+ font-weight: 700;
|
|
|
+ line-height: 42upx;
|
|
|
+}
|
|
|
+
|
|
|
+.ad-status {
|
|
|
+ min-width: 96upx;
|
|
|
+ height: 44upx;
|
|
|
+ padding: 0 16upx;
|
|
|
+ line-height: 44upx;
|
|
|
+ text-align: center;
|
|
|
+ font-size: 22upx;
|
|
|
+ font-weight: 600;
|
|
|
+ border-radius: 22upx;
|
|
|
+}
|
|
|
+
|
|
|
+.ad-status.is-set {
|
|
|
+ color: #52c41a;
|
|
|
+ background: #f6ffed;
|
|
|
+}
|
|
|
+
|
|
|
+.ad-status.no-set {
|
|
|
+ color: #999;
|
|
|
+ background: #f5f5f5;
|
|
|
+}
|
|
|
+
|
|
|
+.ad-table-row {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: row;
|
|
|
+ align-items: center;
|
|
|
+ min-height: 112upx;
|
|
|
+ padding: 20upx 28upx;
|
|
|
+ border-bottom: 1upx solid #f3f3f3;
|
|
|
+}
|
|
|
+
|
|
|
+.ad-table-row-last {
|
|
|
+ border-bottom: none;
|
|
|
+}
|
|
|
+
|
|
|
+.ad-table-row-img {
|
|
|
+ align-items: flex-start;
|
|
|
+}
|
|
|
+
|
|
|
+.ad-label {
|
|
|
+ width: 140upx;
|
|
|
+ flex-shrink: 0;
|
|
|
+ font-size: 28upx;
|
|
|
+ color: #666;
|
|
|
+ line-height: 40upx;
|
|
|
+}
|
|
|
+
|
|
|
+.ad-table-row-img .ad-label {
|
|
|
+ padding-top: 12upx;
|
|
|
+}
|
|
|
+
|
|
|
+.ad-value {
|
|
|
+ flex: 1;
|
|
|
+ min-width: 0;
|
|
|
+}
|
|
|
+
|
|
|
+.ad-value-img {
|
|
|
+ position: relative;
|
|
|
+}
|
|
|
+
|
|
|
+.ad-thumb {
|
|
|
+ width: 100%;
|
|
|
+ height: 180upx;
|
|
|
+ border-radius: 12upx;
|
|
|
+ background: #f5f5f5;
|
|
|
+}
|
|
|
+
|
|
|
+.ad-thumb-remove {
|
|
|
+ position: absolute;
|
|
|
+ top: 12upx;
|
|
|
+ right: 12upx;
|
|
|
+ width: 48upx;
|
|
|
+ height: 48upx;
|
|
|
+ line-height: 44upx;
|
|
|
+ text-align: center;
|
|
|
+ background: rgba(0, 0, 0, 0.45);
|
|
|
+ color: #fff;
|
|
|
+ border-radius: 50%;
|
|
|
+ font-size: 32upx;
|
|
|
+}
|
|
|
+
|
|
|
+.ad-upload-box {
|
|
|
+ height: 180upx;
|
|
|
+ border: 2upx dashed #52c41a;
|
|
|
+ border-radius: 12upx;
|
|
|
+ background: #f8fff4;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+}
|
|
|
+
|
|
|
+.ad-upload-plus {
|
|
|
+ font-size: 48upx;
|
|
|
+ color: #52c41a;
|
|
|
+ line-height: 48upx;
|
|
|
+}
|
|
|
+
|
|
|
+.ad-upload-text {
|
|
|
+ margin-top: 8upx;
|
|
|
+ font-size: 26upx;
|
|
|
+ color: #52c41a;
|
|
|
+}
|
|
|
+
|
|
|
+.type-tabs {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: row;
|
|
|
+}
|
|
|
+
|
|
|
+.type-tab {
|
|
|
+ flex: 1;
|
|
|
+ height: 72upx;
|
|
|
+ line-height: 72upx;
|
|
|
+ text-align: center;
|
|
|
+ font-size: 28upx;
|
|
|
+ color: #666;
|
|
|
+ border: 2upx solid #e8e8e8;
|
|
|
+ background: #fff;
|
|
|
+ border-radius: 10upx;
|
|
|
+}
|
|
|
+
|
|
|
+.type-tab + .type-tab {
|
|
|
+ margin-left: 12upx;
|
|
|
+}
|
|
|
+
|
|
|
+.type-tab.active {
|
|
|
+ color: #52c41a;
|
|
|
+ border-color: #52c41a;
|
|
|
+ background: #f6ffed;
|
|
|
+ font-weight: 600;
|
|
|
+}
|
|
|
+
|
|
|
+.ad-value-link {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: row;
|
|
|
+ align-items: center;
|
|
|
+ min-height: 72upx;
|
|
|
+ padding: 0 20upx;
|
|
|
+ border: 2upx solid #e8e8e8;
|
|
|
+ border-radius: 10upx;
|
|
|
+ background: #fafafa;
|
|
|
+}
|
|
|
+
|
|
|
+.link-text {
|
|
|
+ flex: 1;
|
|
|
+ min-width: 0;
|
|
|
+ font-size: 28upx;
|
|
|
+ color: #333;
|
|
|
+ overflow: hidden;
|
|
|
+ text-overflow: ellipsis;
|
|
|
+ white-space: nowrap;
|
|
|
+}
|
|
|
+
|
|
|
+.link-text.placeholder {
|
|
|
+ color: #bbb;
|
|
|
+}
|
|
|
+
|
|
|
+.link-arrow {
|
|
|
+ margin-left: 12upx;
|
|
|
+ color: #bbb;
|
|
|
+ font-size: 40upx;
|
|
|
+ line-height: 40upx;
|
|
|
+}
|
|
|
+
|
|
|
+.ad-bottom-space {
|
|
|
+ height: calc(180upx + env(safe-area-inset-bottom));
|
|
|
+}
|
|
|
+
|
|
|
+.ad-bottom-bar {
|
|
|
+ position: fixed;
|
|
|
+ left: 0;
|
|
|
+ right: 0;
|
|
|
+ bottom: 0;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: row;
|
|
|
+ align-items: center;
|
|
|
+ padding: 20upx 24upx;
|
|
|
+ padding-bottom: calc(20upx + env(safe-area-inset-bottom));
|
|
|
+ background: #fff;
|
|
|
+ box-shadow: 0 -2upx 10upx rgba(0, 0, 0, 0.06);
|
|
|
+ z-index: 10;
|
|
|
+}
|
|
|
+
|
|
|
+.bar-btn {
|
|
|
+ height: 88upx;
|
|
|
+ line-height: 88upx;
|
|
|
+ margin: 0;
|
|
|
+ padding: 0;
|
|
|
+ font-size: 30upx;
|
|
|
+ font-weight: 600;
|
|
|
+ border-radius: 12upx;
|
|
|
+ text-align: center;
|
|
|
+}
|
|
|
+
|
|
|
+.bar-btn::after {
|
|
|
+ border: none;
|
|
|
+}
|
|
|
+
|
|
|
+.bar-btn-preview {
|
|
|
+ width: 200upx;
|
|
|
+ background: #fff;
|
|
|
+ color: #52c41a;
|
|
|
+ border: 2upx solid #52c41a;
|
|
|
+ margin-right: 16upx;
|
|
|
+}
|
|
|
+
|
|
|
+.bar-btn-save {
|
|
|
+ flex: 1;
|
|
|
+ background: #52c41a;
|
|
|
+ color: #fff;
|
|
|
+ border: 2upx solid #52c41a;
|
|
|
+}
|
|
|
+
|
|
|
+.picker-overlay {
|
|
|
+ position: fixed;
|
|
|
+ left: 0;
|
|
|
+ top: 0;
|
|
|
+ right: 0;
|
|
|
+ bottom: 0;
|
|
|
+ z-index: 200;
|
|
|
+ display: flex;
|
|
|
+ align-items: flex-end;
|
|
|
+ justify-content: center;
|
|
|
+}
|
|
|
+
|
|
|
+.picker-mask {
|
|
|
+ position: absolute;
|
|
|
+ left: 0;
|
|
|
+ top: 0;
|
|
|
+ right: 0;
|
|
|
+ bottom: 0;
|
|
|
+ background: rgba(0, 0, 0, 0.5);
|
|
|
+}
|
|
|
+
|
|
|
+.picker-panel {
|
|
|
+ position: relative;
|
|
|
+ z-index: 1;
|
|
|
+ width: 100%;
|
|
|
+ max-height: 70vh;
|
|
|
+ background: #fff;
|
|
|
+ border-radius: 24upx 24upx 0 0;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+}
|
|
|
+
|
|
|
+.category-picker-panel {
|
|
|
+ max-height: 82vh;
|
|
|
+}
|
|
|
+
|
|
|
+.picker-head {
|
|
|
+ position: relative;
|
|
|
+ height: 88upx;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ border-bottom: 1upx solid #f0f0f0;
|
|
|
+ flex-shrink: 0;
|
|
|
+}
|
|
|
+
|
|
|
+.picker-title {
|
|
|
+ font-size: 32upx;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #333;
|
|
|
+}
|
|
|
+
|
|
|
+.picker-count {
|
|
|
+ position: absolute;
|
|
|
+ left: 24upx;
|
|
|
+ font-size: 24upx;
|
|
|
+ color: #52c41a;
|
|
|
+}
|
|
|
+
|
|
|
+.picker-close {
|
|
|
+ position: absolute;
|
|
|
+ right: 24upx;
|
|
|
+ font-size: 36upx;
|
|
|
+ color: #999;
|
|
|
+ padding: 8upx;
|
|
|
+}
|
|
|
+
|
|
|
+.picker-search-row {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ padding: 16upx 24upx;
|
|
|
+ border-bottom: 1upx solid #f5f5f5;
|
|
|
+ flex-shrink: 0;
|
|
|
+}
|
|
|
+
|
|
|
+.picker-search-box {
|
|
|
+ flex: 1;
|
|
|
+ min-width: 0;
|
|
|
+ height: 68upx;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ padding: 0 20upx;
|
|
|
+ border: 2upx solid #e8e8e8;
|
|
|
+ border-radius: 34upx;
|
|
|
+ background: #fafafa;
|
|
|
+ box-sizing: border-box;
|
|
|
+}
|
|
|
+
|
|
|
+.picker-search-icon {
|
|
|
+ font-size: 28upx;
|
|
|
+ color: #999;
|
|
|
+ margin-right: 12upx;
|
|
|
+ flex-shrink: 0;
|
|
|
+}
|
|
|
+
|
|
|
+.picker-search-input {
|
|
|
+ flex: 1;
|
|
|
+ min-width: 0;
|
|
|
+ height: 64upx;
|
|
|
+ font-size: 26upx;
|
|
|
+ color: #333;
|
|
|
+}
|
|
|
+
|
|
|
+.picker-search-clear {
|
|
|
+ margin-left: 16upx;
|
|
|
+ font-size: 24upx;
|
|
|
+ color: #52c41a;
|
|
|
+ flex-shrink: 0;
|
|
|
+}
|
|
|
+
|
|
|
+.picker-body {
|
|
|
+ flex: 1;
|
|
|
+ min-height: 0;
|
|
|
+ max-height: 46vh;
|
|
|
+ padding: 8upx 24upx 16upx;
|
|
|
+ box-sizing: border-box;
|
|
|
+}
|
|
|
+
|
|
|
+.picker-empty {
|
|
|
+ padding: 80upx 0;
|
|
|
+ text-align: center;
|
|
|
+ font-size: 26upx;
|
|
|
+ color: #999;
|
|
|
+}
|
|
|
+
|
|
|
+.picker-item {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ padding: 22upx 0;
|
|
|
+ border-bottom: 1upx solid #f5f5f5;
|
|
|
+}
|
|
|
+
|
|
|
+.picker-item:last-child {
|
|
|
+ border-bottom: none;
|
|
|
+}
|
|
|
+
|
|
|
+.check-box {
|
|
|
+ width: 36upx;
|
|
|
+ height: 36upx;
|
|
|
+ border: 2upx solid #ccc;
|
|
|
+ border-radius: 6upx;
|
|
|
+ margin-right: 16upx;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ flex-shrink: 0;
|
|
|
+}
|
|
|
+
|
|
|
+.check-box.checked {
|
|
|
+ background: #52c41a;
|
|
|
+ border-color: #52c41a;
|
|
|
+}
|
|
|
+
|
|
|
+.check-mark {
|
|
|
+ color: #fff;
|
|
|
+ font-size: 24upx;
|
|
|
+}
|
|
|
+
|
|
|
+.picker-item-name {
|
|
|
+ flex: 1;
|
|
|
+ min-width: 0;
|
|
|
+ font-size: 28upx;
|
|
|
+ color: #333;
|
|
|
+ line-height: 1.4;
|
|
|
+}
|
|
|
+
|
|
|
+.picker-footer {
|
|
|
+ display: flex;
|
|
|
+ padding: 20upx 24upx;
|
|
|
+ padding-bottom: calc(20upx + env(safe-area-inset-bottom));
|
|
|
+ flex-shrink: 0;
|
|
|
+ border-top: 1upx solid #f0f0f0;
|
|
|
+}
|
|
|
+
|
|
|
+.picker-btn {
|
|
|
+ flex: 1;
|
|
|
+ height: 80upx;
|
|
|
+ line-height: 80upx;
|
|
|
+ margin: 0;
|
|
|
+ font-size: 28upx;
|
|
|
+ border-radius: 8upx;
|
|
|
+}
|
|
|
+
|
|
|
+.picker-btn::after {
|
|
|
+ border: none;
|
|
|
+}
|
|
|
+
|
|
|
+.picker-btn.cancel {
|
|
|
+ margin-right: 20upx;
|
|
|
+ background: #fff;
|
|
|
+ color: #333;
|
|
|
+ border: 2upx solid #ddd;
|
|
|
+}
|
|
|
+
|
|
|
+.picker-btn.confirm {
|
|
|
+ background: #52c41a;
|
|
|
+ color: #fff;
|
|
|
+}
|
|
|
+
|
|
|
+.preview-panel {
|
|
|
+ position: relative;
|
|
|
+ z-index: 1;
|
|
|
+ width: 90%;
|
|
|
+ background: #fff;
|
|
|
+ border-radius: 16upx;
|
|
|
+ padding: 24upx;
|
|
|
+ box-sizing: border-box;
|
|
|
+ align-self: center;
|
|
|
+}
|
|
|
+
|
|
|
+.preview-swiper {
|
|
|
+ height: 320upx;
|
|
|
+}
|
|
|
+
|
|
|
+.preview-img {
|
|
|
+ width: 100%;
|
|
|
+ height: 320upx;
|
|
|
+}
|
|
|
+
|
|
|
+.preview-close {
|
|
|
+ margin-top: 20upx;
|
|
|
+ height: 72upx;
|
|
|
+ line-height: 72upx;
|
|
|
+ background: #52c41a;
|
|
|
+ color: #fff;
|
|
|
+ font-size: 28upx;
|
|
|
+ border-radius: 8upx;
|
|
|
+}
|
|
|
+
|
|
|
+.preview-close::after {
|
|
|
+ border: none;
|
|
|
+}
|
|
|
+</style>
|