mirror of
https://gitee.com/beijing_hongye_huicheng/lilishop-ui.git
synced 2026-11-13 04:18:55 +08:00
feat(im): 客服聊天支持图片上传、独立OSS存储配置及发送失败状态处理
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
import { post, get, upload, del } from "@/utils/request";
|
import { post, get, upload, del } from "@/utils/request";
|
||||||
|
import config from "@/config/config";
|
||||||
|
|
||||||
// 获取聊天列表服务接口
|
// 获取聊天列表服务接口
|
||||||
export const ServeGetTalkList = (data) => {
|
export const ServeGetTalkList = (data) => {
|
||||||
@@ -69,9 +70,9 @@ export const ServeSendTalkFile = (data) => {
|
|||||||
return post("/im/talk/message/file", data);
|
return post("/im/talk/message/file", data);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 发送聊天图片服务接口
|
// 上传客服图片到独立 OSS;FormData 必须包含会话 ID,店铺及目录由后端推导。
|
||||||
export const ServeSendTalkImage = (data) => {
|
export const ServeSendTalkImage = (data) => {
|
||||||
return upload("/buyer/common/upload/file", data);
|
return upload("/im/message/image", data, { apiBase: config.BASE_API_URL });
|
||||||
};
|
};
|
||||||
|
|
||||||
// 发送表情包服务接口
|
// 发送表情包服务接口
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="image-message no-select">
|
<div class="image-message no-select">
|
||||||
<el-image
|
<el-image
|
||||||
fit="cover"
|
fit="contain"
|
||||||
:src="src"
|
:src="src"
|
||||||
:lazy="true"
|
|
||||||
:style="getImgStyle(src)"
|
:style="getImgStyle(src)"
|
||||||
:preview-src-list="[src]"
|
:preview-src-list="[src]"
|
||||||
|
preview-teleported
|
||||||
>
|
>
|
||||||
<template #error>
|
<template #error>
|
||||||
<div class="image-slot">图片加载失败...</div>
|
<div class="image-slot">图片加载失败...</div>
|
||||||
@@ -29,6 +29,10 @@ defineProps({
|
|||||||
})
|
})
|
||||||
|
|
||||||
function getImgStyle(url) {
|
function getImgStyle(url) {
|
||||||
|
// 商城上传地址不携带尺寸,提供可见的预览区域;原尺寸后缀仍沿用旧展示规则。
|
||||||
|
if (!/\d+x\d+/.test(url.split('_').pop())) {
|
||||||
|
return { width: '200px', height: '200px', maxWidth: '100%' }
|
||||||
|
}
|
||||||
return imgZoom(url, 200)
|
return imgZoom(url, 200)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -295,7 +295,7 @@ function formatDateToString(date) {
|
|||||||
// margin-left: 12px;
|
// margin-left: 12px;
|
||||||
}
|
}
|
||||||
:deep(.el-tabs__nav-scroll) {
|
:deep(.el-tabs__nav-scroll) {
|
||||||
min-width: 362px;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
:deep(.el-tabs__item) {
|
:deep(.el-tabs__item) {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<el-container class="ov-hidden flex-10 full-height ">
|
<el-container class="ov-hidden flex-10 full-height chat-main">
|
||||||
<PanelHeader ref="panelHeader" :data="params" :online="isOnline" :keyboard="inputEvent"
|
<PanelHeader ref="panelHeader" :data="params" :online="isOnline" :keyboard="inputEvent"
|
||||||
@event="handleHeaderEvent" />
|
@event="handleHeaderEvent" />
|
||||||
<!-- 主体信息 -->
|
<!-- 主体信息 -->
|
||||||
@@ -65,7 +65,7 @@
|
|||||||
<pre v-if="!emojistwo.includes(item.text)" v-html="item.text" />
|
<pre v-if="!emojistwo.includes(item.text)" v-html="item.text" />
|
||||||
<pre v-if="emojistwo.includes(item.text)" v-html="textReplaceEmoji(item.text)" />
|
<pre v-if="emojistwo.includes(item.text)" v-html="textReplaceEmoji(item.text)" />
|
||||||
</div>
|
</div>
|
||||||
|
<ImageMessage v-if="item.messageType === 'PICTURE'" :src="item.text" />
|
||||||
</div>
|
</div>
|
||||||
<div v-if="item.webSocketStatus" class="tips">网络异常发送失败,请重新发送。</div>
|
<div v-if="item.webSocketStatus" class="tips">网络异常发送失败,请重新发送。</div>
|
||||||
|
|
||||||
@@ -163,7 +163,7 @@
|
|||||||
<!-- 页脚信息 -->
|
<!-- 页脚信息 -->
|
||||||
<el-footer class="footer-box" height="180">
|
<el-footer class="footer-box" height="180">
|
||||||
<template v-if="multiSelect.isOpen === false">
|
<template v-if="multiSelect.isOpen === false">
|
||||||
<MeEditor @send="submitSendMessage" @keyboard-event="onKeyboardEvent" />
|
<MeEditor :talk-id="params.talkId" @send="submitSendMessage" @send-image="submitSendImage" @keyboard-event="onKeyboardEvent" />
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<PanelToolbar v-model="multiSelect.items.length" @event="handleMultiMode" />
|
<PanelToolbar v-model="multiSelect.items.length" @event="handleMultiMode" />
|
||||||
@@ -185,7 +185,7 @@
|
|||||||
</transition>
|
</transition>
|
||||||
|
|
||||||
<!-- 链接信息 -->
|
<!-- 链接信息 -->
|
||||||
<OtherLink :toUser="toUser" :id="id" :goodsParams="goodsParams" class="flex-4" />
|
<OtherLink :toUser="toUser" :id="id" :goodsParams="goodsParams" class="chat-sidebar" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script setup>
|
<script setup>
|
||||||
@@ -202,6 +202,7 @@ import { textReplaceEmoji, emojistwo } from "@/utils/emojis";
|
|||||||
import OtherLink from "@/components/chat/panel/OtherLink.vue";
|
import OtherLink from "@/components/chat/panel/OtherLink.vue";
|
||||||
import TalkSearchRecord from "@/components/chat/TalkSearchRecord";
|
import TalkSearchRecord from "@/components/chat/TalkSearchRecord";
|
||||||
import MeEditor from "@/components/editor/MeEditor";
|
import MeEditor from "@/components/editor/MeEditor";
|
||||||
|
import ImageMessage from "@/components/chat/messaege/ImageMessage.vue";
|
||||||
import PanelHeader from "./PanelHeader";
|
import PanelHeader from "./PanelHeader";
|
||||||
import PanelToolbar from "./PanelToolbar";
|
import PanelToolbar from "./PanelToolbar";
|
||||||
import SocketInstance from "@/im-server/socket-instance";
|
import SocketInstance from "@/im-server/socket-instance";
|
||||||
@@ -414,16 +415,21 @@ function formateDateAndTimeToString(date) {
|
|||||||
// 重新发送消息
|
// 重新发送消息
|
||||||
function againSendMessage(val) {
|
function againSendMessage(val) {
|
||||||
|
|
||||||
submitSendMessage(val.text)
|
submitSendMessage(val.text, val.messageType)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 回车键发送消息回调事件
|
// 发送已上传图片,复用消息发送和失败重发流程。
|
||||||
function submitSendMessage(content) {
|
function submitSendImage(url) {
|
||||||
|
submitSendMessage(url, "PICTURE");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发送文本或图片,重发保持原类型,发送图片时保留文字草稿。
|
||||||
|
function submitSendMessage(content, messageType = "MESSAGE") {
|
||||||
const record = {
|
const record = {
|
||||||
operation_type: "MESSAGE",
|
operation_type: "MESSAGE",
|
||||||
to: props.params.receiver_id,
|
to: props.params.receiver_id,
|
||||||
from: id.value,
|
from: id.value,
|
||||||
message_type: "MESSAGE",
|
message_type: messageType,
|
||||||
context: content,
|
context: content,
|
||||||
talk_id: props.params.talkId,
|
talk_id: props.params.talkId,
|
||||||
};
|
};
|
||||||
@@ -434,7 +440,7 @@ function submitSendMessage(content) {
|
|||||||
|
|
||||||
talkStore.UPDATE_TALK_ITEM({
|
talkStore.UPDATE_TALK_ITEM({
|
||||||
index_name: index_name.value,
|
index_name: index_name.value,
|
||||||
draft_text: "",
|
...(messageType === "MESSAGE" ? { draft_text: "" } : {}),
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -445,7 +451,8 @@ function submitSendMessage(content) {
|
|||||||
fromUser: id.value,
|
fromUser: id.value,
|
||||||
toUser: record.to,
|
toUser: record.to,
|
||||||
isRead: false,
|
isRead: false,
|
||||||
messageType: "MESSAGE",
|
messageType,
|
||||||
|
talkId: record.talk_id,
|
||||||
text: content,
|
text: content,
|
||||||
float: "right",
|
float: "right",
|
||||||
};
|
};
|
||||||
@@ -465,7 +472,7 @@ function submitSendMessage(content) {
|
|||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
talkStore.SET_TLAK_UNREAD_MESSAGE({
|
talkStore.SET_TLAK_UNREAD_MESSAGE({
|
||||||
content: content,
|
content: messageType === "PICTURE" ? "[图片]" : content,
|
||||||
nickname: record.name,
|
nickname: record.name,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -889,6 +896,17 @@ function talkPanelScrollBottom() {
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
|
/* 固定侧栏宽度,防止 tab 内容最小宽度挤压聊天区域。 */
|
||||||
|
.chat-main {
|
||||||
|
flex: 1 1 0;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
.chat-sidebar {
|
||||||
|
flex: 0 0 362px;
|
||||||
|
width: 362px;
|
||||||
|
min-width: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
.flex-a-c{
|
.flex-a-c{
|
||||||
align-items: center;
|
align-items: center;
|
||||||
>div{
|
>div{
|
||||||
|
|||||||
@@ -21,11 +21,11 @@
|
|||||||
<legacy-el-icon name="el-icon-headset" />
|
<legacy-el-icon name="el-icon-headset" />
|
||||||
<p class="tip-title">语音消息</p>
|
<p class="tip-title">语音消息</p>
|
||||||
</li> -->
|
</li> -->
|
||||||
<!-- #TODO 发图片功能暂时隐藏 此处会涉及到token过期 -->
|
<li role="button" tabindex="0" aria-label="发送图片" @click="restFile.click()"
|
||||||
<!-- <li @click="$refs.restFile.click()">
|
@keydown.enter="restFile.click()" @keydown.space.prevent="restFile.click()">
|
||||||
<legacy-el-icon name="el-icon-picture-outline-round" />
|
<legacy-el-icon name="el-icon-picture-outline-round" />
|
||||||
<p class="tip-title">图片</p>
|
<p class="tip-title">图片</p>
|
||||||
</li> -->
|
</li>
|
||||||
<!-- <li @click="$refs.restFile2.click()">
|
<!-- <li @click="$refs.restFile2.click()">
|
||||||
<legacy-el-icon name="el-icon-folder" />
|
<legacy-el-icon name="el-icon-folder" />
|
||||||
<p class="tip-title">附件</p>
|
<p class="tip-title">附件</p>
|
||||||
@@ -117,8 +117,12 @@ import {
|
|||||||
} from "@/api/chat";
|
} from "@/api/chat";
|
||||||
|
|
||||||
defineOptions({ name: "MeEditor" });
|
defineOptions({ name: "MeEditor" });
|
||||||
|
const props = defineProps({
|
||||||
|
// 后端 li_im_talk.id;不使用接收方 ID 代替会话 ID,避免图片存入错误会话。
|
||||||
|
talkId: { type: String, default: "" },
|
||||||
|
});
|
||||||
|
|
||||||
const emit = defineEmits(["keyboard-event", "send"]);
|
const emit = defineEmits(["keyboard-event", "send", "send-image"]);
|
||||||
|
|
||||||
const dialogueStore = useDialogueStore();
|
const dialogueStore = useDialogueStore();
|
||||||
|
|
||||||
@@ -141,6 +145,10 @@ const imageViewer = reactive({
|
|||||||
isShow: false,
|
isShow: false,
|
||||||
file: null,
|
file: null,
|
||||||
});
|
});
|
||||||
|
// 预览绑定原会话,上传期间切换会话后不发送,避免图片误发。
|
||||||
|
let imageTalk = null;
|
||||||
|
let imageTalkId = null;
|
||||||
|
let uploadingImage = false;
|
||||||
|
|
||||||
const codeBlock = reactive({
|
const codeBlock = reactive({
|
||||||
isShow: false,
|
isShow: false,
|
||||||
@@ -165,6 +173,8 @@ const sendtime = ref(0);
|
|||||||
const interval = ref(1000);
|
const interval = ref(1000);
|
||||||
|
|
||||||
watch(talkUser, (n_index_name) => {
|
watch(talkUser, (n_index_name) => {
|
||||||
|
imageViewer.isShow = false;
|
||||||
|
imageViewer.file = null;
|
||||||
fileManageRef.value?.clear();
|
fileManageRef.value?.clear();
|
||||||
editorText.value = getDraftText(n_index_name);
|
editorText.value = getDraftText(n_index_name);
|
||||||
});
|
});
|
||||||
@@ -253,6 +263,21 @@ function uploadFileChange(e) {
|
|||||||
|
|
||||||
// 打开图片查看器
|
// 打开图片查看器
|
||||||
function openImageViewer(file) {
|
function openImageViewer(file) {
|
||||||
|
if (!file || uploadingImage) return;
|
||||||
|
if (!props.talkId) {
|
||||||
|
ElNotification.warning({ message: "会话尚未就绪,请稍后重试" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!/^image\/(png|jpeg|gif|webp)$/i.test(file.type) || !file.size) {
|
||||||
|
ElNotification.warning({ message: "请选择有效的 PNG、JPG、GIF 或 WebP 图片" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (file.size > 10 * 1024 * 1024) {
|
||||||
|
ElNotification.warning({ message: "图片不能大于10MB" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
imageTalk = talkUser.value;
|
||||||
|
imageTalkId = props.talkId;
|
||||||
imageViewer.isShow = true;
|
imageViewer.isShow = true;
|
||||||
imageViewer.file = file;
|
imageViewer.file = file;
|
||||||
}
|
}
|
||||||
@@ -278,29 +303,34 @@ function confirmCodeBlock(data) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// 确认上传图片消息回调事件
|
// 上传返回地址后通过现有消息通道发送;失败保留预览以便重试。
|
||||||
function confirmUploadImage() {
|
async function confirmUploadImage() {
|
||||||
let fileData = new FormData();
|
if (uploadingImage) return;
|
||||||
fileData.append("file", imageViewer.file);
|
const imageView = imageViewerRef.value;
|
||||||
|
const targetTalk = imageTalk;
|
||||||
let imageView = imageViewerRef.value;
|
const targetTalkId = imageTalkId;
|
||||||
|
uploadingImage = true;
|
||||||
ServeSendTalkImage(fileData)
|
try {
|
||||||
.then((res) => {
|
const fileData = new FormData();
|
||||||
imageView.loading = false;
|
fileData.append("file", imageViewer.file);
|
||||||
if (res.code == 200) {
|
fileData.append("talkId", targetTalkId);
|
||||||
imageView.closeBox();
|
const res = await ServeSendTalkImage(fileData);
|
||||||
} else {
|
if (res.code !== 200 || typeof res.result !== "string" || !/^https?:\/\//i.test(res.result)) {
|
||||||
ElNotification({
|
throw new Error(res.message || "图片上传失败,请重试");
|
||||||
title: "友情提示",
|
}
|
||||||
message: res.message,
|
if (targetTalk !== talkUser.value || targetTalkId !== props.talkId || !imageViewer.isShow) {
|
||||||
type: "warning",
|
ElNotification.warning({ message: "会话已切换,图片未发送,请在目标会话重新选择" });
|
||||||
});
|
return;
|
||||||
}
|
}
|
||||||
})
|
emit("send-image", res.result);
|
||||||
.finally(() => {
|
imageView.loading = false;
|
||||||
imageView.loading = false;
|
imageView.closeBox();
|
||||||
});
|
} catch (error) {
|
||||||
|
ElNotification.error({ message: error.message || "图片上传失败,请重试" });
|
||||||
|
} finally {
|
||||||
|
uploadingImage = false;
|
||||||
|
imageView.loading = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 选中表情包回调事件
|
// 选中表情包回调事件
|
||||||
|
|||||||
@@ -62,7 +62,10 @@ function closeBox() {
|
|||||||
emit('close', false)
|
emit('close', false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 清理切换会话后的预览,仅对有效文件读取内容。
|
||||||
function loadFile(file) {
|
function loadFile(file) {
|
||||||
|
src.value = ''
|
||||||
|
if (!file) return
|
||||||
let reader = new FileReader()
|
let reader = new FileReader()
|
||||||
fileSize.value = Math.ceil(file.size / 1024)
|
fileSize.value = Math.ceil(file.size / 1024)
|
||||||
fileName.value = file.name
|
fileName.value = file.name
|
||||||
@@ -75,6 +78,7 @@ function loadFile(file) {
|
|||||||
|
|
||||||
// 确认按钮事件
|
// 确认按钮事件
|
||||||
function uploadImage() {
|
function uploadImage() {
|
||||||
|
if (loading.value || !src.value) return
|
||||||
loading.value = true
|
loading.value = true
|
||||||
emit('confirm')
|
emit('confirm')
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { useAppStore } from "@/stores/app";
|
import { useAppStore } from "@/stores/app";
|
||||||
|
import { useDialogueStore } from "@/stores/dialogue";
|
||||||
import config from "@/config/config";
|
import config from "@/config/config";
|
||||||
import WsSocket from "@/plugins/ws-socket";
|
import WsSocket from "@/plugins/ws-socket";
|
||||||
import { getToken } from "@/utils/auth";
|
import { getToken } from "@/utils/auth";
|
||||||
@@ -86,6 +87,15 @@ class SocketInstance {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.socket.on("event_error", (data) => {
|
this.socket.on("event_error", (data) => {
|
||||||
|
// 将被服务端拒绝的图片标记为失败,避免乐观展示被误认为发送成功。
|
||||||
|
if (data.pictureUrl) {
|
||||||
|
useDialogueStore().records.forEach((record) => {
|
||||||
|
if (record.messageType === "PICTURE" && record.float === "right"
|
||||||
|
&& record.talkId === data.talkId && record.text === data.pictureUrl) {
|
||||||
|
record.webSocketStatus = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
ElNotification({
|
ElNotification({
|
||||||
title: "友情提示",
|
title: "友情提示",
|
||||||
message: data.message,
|
message: data.message,
|
||||||
|
|||||||
@@ -272,6 +272,12 @@ class WsSocket {
|
|||||||
* @param {Object} evt Websocket 消息
|
* @param {Object} evt Websocket 消息
|
||||||
*/
|
*/
|
||||||
onMessage(evt) {
|
onMessage(evt) {
|
||||||
|
// 图片权限校验失败属于系统反馈,不能当成聊天消息插入记录。
|
||||||
|
const envelope = JSON.parse(evt.data);
|
||||||
|
if (envelope.messageResultType === "SYSTEM_TIPS") {
|
||||||
|
this.onCallBacks["event_error"]?.(envelope.result);
|
||||||
|
return;
|
||||||
|
}
|
||||||
let result = this.onParse(evt);
|
let result = this.onParse(evt);
|
||||||
// 部分推送(在线状态、系统通知等)没有 text 字段,不做商品/群名解析直接透传
|
// 部分推送(在线状态、系统通知等)没有 text 字段,不做商品/群名解析直接透传
|
||||||
const text = result.text;
|
const text = result.text;
|
||||||
|
|||||||
@@ -153,10 +153,12 @@ export const del = (url, data = {}, options = {}) => {
|
|||||||
* @returns {Promise<any>}
|
* @returns {Promise<any>}
|
||||||
*/
|
*/
|
||||||
export const upload = (url, data = {}, options = {}) => {
|
export const upload = (url, data = {}, options = {}) => {
|
||||||
|
// 默认沿用买家上传地址;客服图片显式选择 IM 服务,不将文件发往通用上传入口。
|
||||||
|
const { apiBase = config.BASE_BUYER, ...requestOptions } = options;
|
||||||
return request({
|
return request({
|
||||||
url: config.BASE_BUYER + url,
|
url: apiBase + url,
|
||||||
method: "post",
|
method: "post",
|
||||||
data: data,
|
data: data,
|
||||||
...options,
|
...requestOptions,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,6 +6,25 @@
|
|||||||
<span class="desc">配置买家端联系客服以及商家端登录客服跳转的路径</span>
|
<span class="desc">配置买家端联系客服以及商家端登录客服跳转的路径</span>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-divider content-position="left">客服图片存储</el-divider>
|
||||||
|
<el-form-item label="OSS Bucket" prop="imageOssBucket">
|
||||||
|
<el-input v-model.trim="formValidate.imageOssBucket" placeholder="客服图片专用桶名称" />
|
||||||
|
<span class="desc">使用独立于商品、头像的桶,并在 OSS 控制台设为公共读、私有写。</span>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="OSS Endpoint" prop="imageOssEndpoint">
|
||||||
|
<el-input v-model.trim="formValidate.imageOssEndpoint" placeholder="oss-cn-beijing.aliyuncs.com" />
|
||||||
|
<span class="desc">填写公网节点域名,不含 https:// 或路径。</span>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="AccessKey ID" prop="imageOssAccessKeyId">
|
||||||
|
<el-input v-model.trim="formValidate.imageOssAccessKeyId" autocomplete="off" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="AccessKey Secret" prop="imageOssAccessKeySecret">
|
||||||
|
<el-input v-model.trim="formValidate.imageOssAccessKeySecret" type="password" show-password autocomplete="new-password" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<span class="desc">客户和客服的图片统一按会话所属店铺存储。支持 PNG、JPG、GIF、WebP,单张不超过10MB。四项均留空时无法发送图片,上传失败不会写入通用桶;持有图片链接即可查看。</span>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
<div class="label-btns">
|
<div class="label-btns">
|
||||||
<el-button type="primary" @click="submit('formValidateRef')">保存</el-button>
|
<el-button type="primary" @click="submit('formValidateRef')">保存</el-button>
|
||||||
</div>
|
</div>
|
||||||
@@ -26,7 +45,11 @@ const { proxy } = getCurrentInstance();
|
|||||||
// 只做属性合并的成组对象
|
// 只做属性合并的成组对象
|
||||||
const ruleValidate = reactive({}); // 验证规则
|
const ruleValidate = reactive({}); // 验证规则
|
||||||
const formValidate = ref({ // 表单数据
|
const formValidate = ref({ // 表单数据
|
||||||
httpUrl: ""
|
httpUrl: "",
|
||||||
|
imageOssBucket: "",
|
||||||
|
imageOssEndpoint: "",
|
||||||
|
imageOssAccessKeyId: "",
|
||||||
|
imageOssAccessKeySecret: "",
|
||||||
});
|
});
|
||||||
const result = ref("");
|
const result = ref("");
|
||||||
|
|
||||||
@@ -57,18 +80,30 @@ function setupSetting() {
|
|||||||
function init() {
|
function init() {
|
||||||
result.value = JSON.parse(props.res);
|
result.value = JSON.parse(props.res);
|
||||||
|
|
||||||
formValidate.value = { ...result.value };
|
formValidate.value = { ...formValidate.value, ...result.value };
|
||||||
Object.keys(formValidate.value).forEach((item) => {
|
ruleValidate.httpUrl = [
|
||||||
ruleValidate[item] = [
|
|
||||||
{
|
{
|
||||||
required: true,
|
required: true,
|
||||||
message: "请填写必填项",
|
message: "请填写必填项",
|
||||||
trigger: "blur",
|
trigger: "blur",
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
["imageOssBucket", "imageOssEndpoint", "imageOssAccessKeyId", "imageOssAccessKeySecret"].forEach((key) => {
|
||||||
|
ruleValidate[key] = [{ validator: validateImageStorage, trigger: "blur" }];
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 四项配置可整体留空;填写任一项后必须完整,避免只保存节点或密钥导致上传不可用。
|
||||||
|
function validateImageStorage(rule, value, callback) {
|
||||||
|
const keys = ["imageOssBucket", "imageOssEndpoint", "imageOssAccessKeyId", "imageOssAccessKeySecret"];
|
||||||
|
const values = keys.map((key) => (formValidate.value[key] || "").trim());
|
||||||
|
if (values.some(Boolean) && !values.every(Boolean)) {
|
||||||
|
callback(new Error("请完整填写客服图片 OSS 的四项配置"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
|
||||||
init();
|
init();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user