feat(cms): docs/16 站点设置整页化+首页布局编辑器- SiteSetting.vue整页7分区(基本信息/皮肤与外观/首页布局/基础展示/页脚信息/SEO与域名/扩展参数)锚点导航sticky+IntersectionObserver+分区卡片三段式每分区独立保存(configJson单一事实源+submitSection统一提交);创建模式仅①②可编辑其余锁定,创建成功原地replace到site/setting/:id;SiteWizard三步向导退役删除(门户预览收编portalPreviewUrl);SiteManage瘦身表格+行内编辑跳整页(adminPathOf子站带站点段);ImageAssetField图片双通道(上传+路径粘贴折叠);SiteConfigForm支持groups/showExtras/siteId+asset字段接ImageAssetField+三旧键表单不再展示(ABSORBED_LEGACY_KEYS静默保留);homeLayout三件套(homeLayout.ts模型parse/serialize/validate/genFromCategories一键生成+blockTypes.ts注册表showIf条件字段+HomeLayoutEditor.vue序列编辑增删显隐上移下移);routes site/new+site/setting/:id+legacy redirect;CategoryManage home_style提示改口指向首页布局;新增siteSetting.spec+homeLayout.spec,重写siteManage.spec,117用例全绿
build / build (push) Successful in 2m19s
build / build (push) Successful in 2m19s
This commit is contained in:
@@ -0,0 +1,152 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from "vue";
|
||||
import { Message } from "view-ui-plus";
|
||||
import { assetUrl, uploadUrl } from "@/api/module/cms";
|
||||
|
||||
/**
|
||||
* 图片类配置字段控件(docs/16 D7):上传 + 手动粘贴路径双通道。
|
||||
* v-model 绑定资产路径(/ 开头相对路径或 http(s) URL);上传成功自动回填(服务端返回相对 url)。
|
||||
* 缩略预览 + 清空;格式校验由外层 FormItem 规则(ASSET_RE)承担,控件只负责取值。
|
||||
*/
|
||||
const props = defineProps<{
|
||||
modelValue: string;
|
||||
/** 上传归属站点(附件场景引用;缺省不携带) */
|
||||
siteId?: number;
|
||||
placeholder?: string;
|
||||
}>();
|
||||
const emit = defineEmits<{ (e: "update:modelValue", v: string): void }>();
|
||||
|
||||
const manualOpen = ref(false);
|
||||
const manualPath = ref("");
|
||||
const uploading = ref(false);
|
||||
|
||||
const previewSrc = computed(() => (props.modelValue ? assetUrl(props.modelValue) : ""));
|
||||
|
||||
const action = computed(() => {
|
||||
const base = uploadUrl();
|
||||
return props.siteId ? `${base}?siteId=${props.siteId}` : base;
|
||||
});
|
||||
|
||||
function onUploaded(res: any) {
|
||||
uploading.value = false;
|
||||
if (res && res.code === 0 && res.data && res.data.url) {
|
||||
emit("update:modelValue", res.data.url);
|
||||
manualOpen.value = false;
|
||||
} else {
|
||||
Message.error(res?.msg || "图片上传失败");
|
||||
}
|
||||
}
|
||||
|
||||
function onUploadError() {
|
||||
uploading.value = false;
|
||||
Message.error("图片上传失败");
|
||||
}
|
||||
|
||||
function confirmManual() {
|
||||
const v = manualPath.value.trim();
|
||||
if (!v) {
|
||||
Message.warning("请先粘贴图片路径");
|
||||
return;
|
||||
}
|
||||
emit("update:modelValue", v);
|
||||
manualPath.value = "";
|
||||
manualOpen.value = false;
|
||||
}
|
||||
|
||||
function clearValue() {
|
||||
emit("update:modelValue", "");
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="image-asset-field">
|
||||
<div class="iaf-row">
|
||||
<Upload
|
||||
v-if="!modelValue"
|
||||
:action="action"
|
||||
:show-upload-list="false"
|
||||
:on-success="onUploaded"
|
||||
:on-error="onUploadError"
|
||||
accept="image/*"
|
||||
>
|
||||
<Button size="small" icon="ios-cloud-upload-outline" :loading="uploading">上传图片</Button>
|
||||
</Upload>
|
||||
<span v-else class="iaf-thumb">
|
||||
<img :src="previewSrc" alt="" />
|
||||
<span class="iaf-thumb-del" title="清除" @click="clearValue">
|
||||
<Icon type="ios-close" />
|
||||
</span>
|
||||
</span>
|
||||
<Button size="small" type="text" class="iaf-toggle" @click="manualOpen = !manualOpen">
|
||||
{{ manualOpen ? "收起" : "手动粘贴路径" }}
|
||||
</Button>
|
||||
</div>
|
||||
<Input
|
||||
v-if="manualOpen"
|
||||
v-model="manualPath"
|
||||
size="small"
|
||||
class="iaf-manual"
|
||||
:placeholder="placeholder || '粘贴已上传图片的路径,如 /yschcms/upload/…/logo.png'"
|
||||
>
|
||||
<Button slot="append" size="small" type="primary" @click="confirmManual">填入</Button>
|
||||
</Input>
|
||||
<p v-else-if="modelValue" class="iaf-path">{{ modelValue }}</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.image-asset-field {
|
||||
.iaf-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
.iaf-toggle {
|
||||
color: #999;
|
||||
&:hover {
|
||||
color: #2d8cf0;
|
||||
}
|
||||
}
|
||||
.iaf-thumb {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
img {
|
||||
width: 64px;
|
||||
height: 40px;
|
||||
object-fit: cover;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #dcdee2;
|
||||
background: #f8f8f9;
|
||||
}
|
||||
.iaf-thumb-del {
|
||||
position: absolute;
|
||||
top: -7px;
|
||||
right: -7px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
line-height: 14px;
|
||||
text-align: center;
|
||||
border-radius: 50%;
|
||||
background: #ed4014;
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
display: none;
|
||||
}
|
||||
&:hover .iaf-thumb-del {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
.iaf-manual {
|
||||
max-width: 420px;
|
||||
margin-top: 6px;
|
||||
}
|
||||
.iaf-path {
|
||||
color: #999;
|
||||
font-size: 12px;
|
||||
margin-top: 2px;
|
||||
word-break: break-all;
|
||||
line-height: 1.5;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user