| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366 |
- <template>
- <div class="detail-props-editor">
- <a-form :labelCol="{ span: 6 }" size="small">
- <a-form-item label="测试内容ID">
- <a-input-number
- :value="props.testDetailId"
- @update:value="emit('update:testDetailId', $event)"
- style="width: 100%"
- />
- </a-form-item>
- <a-form-item label="显示头部">
- <a-checkbox v-model:checked="props.props.showHead" :indeterminate="props.props.showHead === undefined">
- 默认:显示
- </a-checkbox>
- </a-form-item>
- <a-form-item label="显示级别标签">
- <a-checkbox v-model:checked="props.props.showTag" />
- </a-form-item>
- <a-form-item label="显示死亡框">
- <a-checkbox v-model:checked="props.props.showDeadBox" />
- </a-form-item>
- </a-form>
- <a-collapse v-model:activeKey="activeKeys" class="props-collapse">
- <a-collapse-panel key="introBlockDescs" header="简介块描述项 (introBlockDescs)">
- <div v-for="(item, i) in introBlockDescsList" :key="`desc-${i}`" class="nested-item">
- <div class="item-actions">
- <ArrowUpOutlined title="上移" @click.stop="moveIntroBlockDescUp(i)" />
- <ArrowDownOutlined title="下移" @click.stop="moveIntroBlockDescDown(i)" />
- <CopyOutlined title="复制" @click.stop="copyIntroBlockDesc(i)" />
- </div>
- <a-form :labelCol="{ span: 6 }" size="small">
- <a-form-item label="label">
- <a-input v-model:value="item.label" placeholder="显示标签" />
- </a-form-item>
- <a-form-item label="key">
- <a-input v-model:value="item.key" placeholder="数据键名" />
- </a-form-item>
- <a-form-item label="映射关系">
- <KeyValueEditor v-model:value="item.map" />
- </a-form-item>
- <a-popconfirm title="确定删除?" @confirm="removeIntroBlockDesc(i)">
- <a-button type="link" danger size="small">删除</a-button>
- </a-popconfirm>
- </a-form>
- </div>
- <div class="section-footer">
- <a-button style="flex: 4;" type="dashed" block size="small" @click="addIntroBlockDesc">+ 添加描述项</a-button>
- <a-button style="flex: 2;" type="dashed" block size="small" @click="pasteIntroBlockDesc">粘贴</a-button>
- </div>
- </a-collapse-panel>
- <a-collapse-panel key="introBlocks" header="简介下方块 (introBlocks)">
- <div v-for="(item, i) in introBlocksList" :key="`block-${i}`" class="nested-item">
- <div class="item-actions">
- <ArrowUpOutlined title="上移" @click.stop="moveIntroBlockUp(i)" />
- <ArrowDownOutlined title="下移" @click.stop="moveIntroBlockDown(i)" />
- <CopyOutlined title="复制" @click.stop="copyIntroBlock(i)" />
- </div>
- <a-form :labelCol="{ span: 6 }" size="small">
- <a-form-item label="类型">
- <a-input v-model:value="item.type" placeholder="块类型" />
- </a-form-item>
- <a-form-item label="属性">
- <KeyValueEditor v-model:value="item.props" />
- </a-form-item>
- <a-popconfirm title="确定删除?" @confirm="removeIntroBlock(i)">
- <a-button type="link" danger size="small">删除</a-button>
- </a-popconfirm>
- </a-form>
- </div>
- <div class="section-footer">
- <a-button style="flex: 4;" type="dashed" block size="small" @click="addIntroBlock">+ 添加块</a-button>
- <a-button style="flex: 2;" type="dashed" block size="small" @click="pasteIntroBlock">粘贴</a-button>
- </div>
- </a-collapse-panel>
- <a-collapse-panel key="tabs" header="详情 Tab (tabs)">
- <div v-for="(tab, i) in tabItems" :key="tabKey(tab, i)" class="nested-item tab-item">
- <a-collapse>
- <a-collapse-panel :key="i" :header="tabHeader(tab)">
- <template #extra>
- <div class="item-actions">
- <ArrowUpOutlined title="上移" @click.stop="moveTabUp(i)" />
- <ArrowDownOutlined title="下移" @click.stop="moveTabDown(i)" />
- <CopyOutlined title="复制" @click.stop="copyTab(i)" />
- <a-popconfirm title="确认删除该 Tab?" @confirm="removeTab(i)">
- <a-button type="text" danger size="small" @click.stop="">
- <DeleteOutlined title="删除" />
- </a-button>
- </a-popconfirm>
- </div>
- </template>
- <a-form :labelCol="{ span: 4 }" size="small">
- <a-form-item label="文本">
- <a-input v-model:value="tab.text" />
- </a-form-item>
- <a-form-item label="类型">
- <a-select
- v-model:value="tab.type"
- style="width: 100%"
- :options="detailTabTypeOptions"
- @change="(v: string) => onTabTypeChange(tab, v)"
- />
- </a-form-item>
- <a-form-item v-if="tab.type === 'images'" label="前缀文字">
- <a-input v-model:value="tab.prefix" />
- </a-form-item>
- <a-form-item label="数据键 key" help="用于根据数据判断当前 TAB 是否显示,如果需要一直显示,可填写 “id“">
- <a-input v-model:value="tab.key" placeholder="对应内容数据键" />
- </a-form-item>
- <a-form-item label="TAB 宽度" help="TAB 宽度,单位:像素(推荐130~300之间)">
- <a-input-number v-model:value="tab.width" style="width: 100%" />
- </a-form-item>
- <a-form-item label="可见">
- <a-checkbox v-model:checked="tab.visible" :indeterminate="tab.visible === undefined">
- 默认可见
- </a-checkbox>
- </a-form-item>
- <template v-if="tab.type === 'list'">
- <a-divider>列表配置</a-divider>
- <CommonListPropsEditor :props="ensureListDefine(tab).define.props" />
- </template>
- <template v-else-if="tab.type === 'nestCategory'">
- <a-form-item label="子栏目">
- <NestCategoryEditor v-model:categorys="(tab as IHomeCommonCategoryDetailTabItemNestCategoryDefine).categorys" />
- </a-form-item>
- </template>
- <template v-else-if="tab.type === 'rich'">
- <a-form-item label="富文本数据键">
- <a-input v-model:value="tab.key" placeholder="与上方数据键一致" />
- </a-form-item>
- </template>
- </a-form>
- </a-collapse-panel>
- </a-collapse>
- </div>
- <div class="section-footer">
- <a-button style="flex: 4;" type="dashed" block size="small" @click="addTab">+ 添加 Tab</a-button>
- <a-button style="flex: 2;" type="dashed" block size="small" @click="pasteTab">粘贴</a-button>
- </div>
- </a-collapse-panel>
- </a-collapse>
- </div>
- </template>
- <script setup lang="ts">
- import { ref, computed } from 'vue';
- import type {
- IHomeCommonCategoryDetailDefine,
- IHomeCommonCategoryDetailTabItemDefine,
- IHomeCommonCategoryDetailTabItemListDefine,
- IHomeCommonCategoryDetailTabItemNestCategoryDefine,
- } from '../../defines/Details';
- import type { IHomeCommonCategoryListDefine } from '../../CommonCategoryDefine';
- import CommonListPropsEditor from './CommonListPropsEditor.vue';
- import NestCategoryEditor from '../subpart/NestCategoryEditor.vue';
- import KeyValueEditor from '../components/KeyValueEditor.vue';
- import { ArrowUpOutlined, ArrowDownOutlined, CopyOutlined, DeleteOutlined } from '@ant-design/icons-vue';
- import { ArrayUtils } from '@imengyu/imengyu-utils';
- const props = defineProps<{
- props: IHomeCommonCategoryDetailDefine['props'];
- testDetailId: number;
- }>();
- const emit = defineEmits<{
- (e: 'update:testDetailId', value: number): void;
- }>();
- const activeKeys = ref<string[]>(['introBlockDescs', 'introBlocks', 'tabs']);
- const detailTabTypeOptions = [
- { value: 'intro', label: '简介' },
- { value: 'images', label: '相册图片' },
- { value: 'video', label: '视频' },
- { value: 'audio', label: '音频' },
- { value: 'list', label: '列表' },
- { value: 'rich', label: '富文本' },
- { value: 'map', label: '地图' },
- { value: 'nestCategory', label: '嵌套子分类' },
- ];
- const introBlockDescsList = computed(() => props.props?.introBlockDescs ?? []);
- const introBlocksList = computed(() => props.props?.introBlocks ?? []);
- const tabItems = computed(() => (props.props?.tabs || []) as IHomeCommonCategoryDetailTabItemDefine[]);
- function tabKey(tab: IHomeCommonCategoryDetailTabItemDefine, i: number) {
- return `detail-tab-${i}-${tab.text}-${tab.type}`;
- }
- function tabHeader(tab: IHomeCommonCategoryDetailTabItemDefine) {
- return `${tab.text || 'Tab'} (${tab.type || '?'})`;
- }
- function getIntroBlockDescs() {
- if (!props.props.introBlockDescs) props.props.introBlockDescs = [];
- return props.props.introBlockDescs;
- }
- function addIntroBlockDesc() {
- getIntroBlockDescs().push({ label: '', key: '' });
- }
- function removeIntroBlockDesc(i: number) {
- getIntroBlockDescs().splice(i, 1);
- }
- function moveIntroBlockDescUp(i: number) {
- ArrayUtils.upData(getIntroBlockDescs(), i);
- }
- function moveIntroBlockDescDown(i: number) {
- ArrayUtils.downData(getIntroBlockDescs(), i);
- }
- function copyIntroBlockDesc(i: number) {
- const item = getIntroBlockDescs()[i];
- uni.setClipboardData({
- data: JSON.stringify({ type: 'Copy:DetailIntroBlockDesc', data: item }),
- });
- uni.showToast({ title: '复制成功', icon: 'success' });
- }
- async function pasteIntroBlockDesc() {
- const res = await uni.getClipboardData();
- const data = (res as { data?: string })?.data;
- if (typeof data === 'string') {
- try {
- const json = JSON.parse(data);
- if (json.type === 'Copy:DetailIntroBlockDesc') getIntroBlockDescs().push(json.data);
- } catch (_) {}
- }
- }
- function getIntroBlocks() {
- if (!props.props.introBlocks) props.props.introBlocks = [];
- return props.props.introBlocks;
- }
- function addIntroBlock() {
- getIntroBlocks().push({ type: '', props: {} });
- }
- function removeIntroBlock(i: number) {
- getIntroBlocks().splice(i, 1);
- }
- function moveIntroBlockUp(i: number) {
- ArrayUtils.upData(getIntroBlocks(), i);
- }
- function moveIntroBlockDown(i: number) {
- ArrayUtils.downData(getIntroBlocks(), i);
- }
- function copyIntroBlock(i: number) {
- const item = getIntroBlocks()[i];
- uni.setClipboardData({
- data: JSON.stringify({ type: 'Copy:DetailIntroBlock', data: item }),
- });
- uni.showToast({ title: '复制成功', icon: 'success' });
- }
- async function pasteIntroBlock() {
- const res = await uni.getClipboardData();
- const data = (res as { data?: string })?.data;
- if (typeof data === 'string') {
- try {
- const json = JSON.parse(data);
- if (json.type === 'Copy:DetailIntroBlock') getIntroBlocks().push(json.data);
- } catch (_) {}
- }
- }
- /** 创建默认列表定义 */
- function createDefaultListDefine(): IHomeCommonCategoryListDefine {
- return {
- type: 'CommonList',
- props: {
- showTab: true,
- showSearch: true,
- showTotal: true,
- tabs: [],
- },
- };
- }
- /** 确保 list 类型 tab 有 define,并返回 define */
- function ensureListDefine(tab: IHomeCommonCategoryDetailTabItemDefine): IHomeCommonCategoryDetailTabItemListDefine {
- const t = tab as IHomeCommonCategoryDetailTabItemListDefine;
- if (!t.define) {
- t.define = createDefaultListDefine();
- }
- return t;
- }
- function onTabTypeChange(tab: IHomeCommonCategoryDetailTabItemDefine, newType: string) {
- if (newType === 'list') {
- (tab as IHomeCommonCategoryDetailTabItemListDefine).define = createDefaultListDefine();
- } else if (newType === 'nestCategory') {
- (tab as IHomeCommonCategoryDetailTabItemNestCategoryDefine).categorys =
- (tab as IHomeCommonCategoryDetailTabItemNestCategoryDefine).categorys ?? [];
- }
- }
- function getTabs() {
- if (!props.props.tabs) props.props.tabs = [];
- return props.props.tabs;
- }
- function addTab() {
- const newTab: IHomeCommonCategoryDetailTabItemDefine = {
- text: '新 Tab',
- key: '',
- type: 'intro',
- visible: true,
- };
- getTabs().push(newTab);
- }
- function removeTab(i: number) {
- getTabs().splice(i, 1);
- }
- function moveTabUp(i: number) {
- ArrayUtils.upData(getTabs(), i);
- }
- function moveTabDown(i: number) {
- ArrayUtils.downData(getTabs(), i);
- }
- function copyTab(i: number) {
- const tab = getTabs()[i];
- uni.setClipboardData({
- data: JSON.stringify({ type: 'Copy:DetailTabItem', data: tab }),
- });
- uni.showToast({ title: '复制成功', icon: 'success' });
- }
- async function pasteTab() {
- const res = await uni.getClipboardData();
- const data = (res as { data?: string })?.data;
- if (typeof data === 'string') {
- try {
- const json = JSON.parse(data);
- if (json.type === 'Copy:DetailTabItem') getTabs().push(json.data);
- } catch (_) {}
- }
- }
- </script>
- <style scoped>
- .detail-props-editor {
- font-size: 12px;
- }
- .props-collapse {
- margin-top: 8px;
- }
- .nested-item {
- margin-bottom: 12px;
- padding: 8px;
- background: #fafafa;
- border-radius: 4px;
- }
- .item-actions {
- display: flex;
- flex-direction: row;
- gap: 8px;
- margin-bottom: 8px;
- }
- .section-footer {
- display: flex;
- gap: 8px;
- }
- .section-footer .ant-btn:first-child {
- flex: 1;
- }
- .tab-item :deep(.ant-collapse) {
- border: none;
- background: transparent;
- }
- </style>
|