802 lines
23 KiB
Vue
802 lines
23 KiB
Vue
<script lang="ts" setup>
|
|
import { ref } from 'vue';
|
|
import {
|
|
postDelete,
|
|
getGetMapList,
|
|
} from '@/api/InterfaceMappingServices/interfaceMap';
|
|
|
|
import {
|
|
getGetPublishCompare,
|
|
postPublish,
|
|
} from '@/api/InterfaceMappingServices/interfaceMapPublish';
|
|
import useModal from '@/components/ModalPro/useModal';
|
|
import { useI18n } from 'vue-i18n';
|
|
import { useRequest } from 'vue-hooks-plus';
|
|
|
|
import dayjs from 'dayjs';
|
|
import { $baseMessage, $baseConfirm } from '@/utils';
|
|
import { diffJson } from 'diff';
|
|
// import { mapserviceproviderColumns } from '../models/tableColumns';
|
|
import RollBack from './RollBack.vue';
|
|
import MonacoCom from './monacoCom.vue';
|
|
// import { IconSearch } from '@arco-design/web-vue/es/icon';
|
|
|
|
const { t } = useI18n();
|
|
|
|
defineOptions({
|
|
name: 'MapServiceProvider',
|
|
});
|
|
|
|
type IProps = {
|
|
id: number;
|
|
};
|
|
|
|
const props = defineProps<IProps>();
|
|
|
|
const emit = defineEmits(['edit-map', 'row-click']);
|
|
|
|
const tableRef = ref<any>();
|
|
const list = ref<any[]>([]);
|
|
const loading = ref(false);
|
|
const searchKeyword = ref('');
|
|
const selectedId = ref<number | string | null>(null);
|
|
|
|
function getRowKey(row: any, index: number) {
|
|
return row?.interfaceMapId ?? row?.id ?? index;
|
|
}
|
|
|
|
function handleSelectRow(row: any, index: number) {
|
|
selectedId.value = getRowKey(row, index);
|
|
emit('row-click', row);
|
|
}
|
|
|
|
async function loadList() {
|
|
try {
|
|
loading.value = true;
|
|
const requestParams: any = {
|
|
InterfaceId: props.id,
|
|
NameOrCode: searchKeyword.value || undefined,
|
|
PageIndex: 1,
|
|
PageSize: 1000,
|
|
};
|
|
const response: any = await getGetMapList(requestParams);
|
|
// 兼容各种返回结构,规避 TS 报错
|
|
const raw: any = response || {};
|
|
const data: any = raw.items ?? raw.data ?? raw.list ?? raw.records ?? raw;
|
|
list.value = Array.isArray(data) ? data : [];
|
|
// 仅当当前选中项在新列表中不存在时才清空,避免“点击一次又掉选中”
|
|
if (
|
|
selectedId.value !== null &&
|
|
!list.value.some((row: any, index: number) => getRowKey(row, index) === selectedId.value)
|
|
) {
|
|
selectedId.value = null;
|
|
}
|
|
} catch (e) {
|
|
list.value = [];
|
|
selectedId.value = null;
|
|
} finally {
|
|
loading.value = false;
|
|
}
|
|
}
|
|
|
|
watch(() => props.id, () => {
|
|
if (props.id) loadList();
|
|
}, { immediate: true });
|
|
|
|
// 暴露与旧用法兼容的 reload 接口
|
|
tableRef.value = { reload: loadList };
|
|
|
|
function setSearchKeyword(val: string) {
|
|
searchKeyword.value = val || '';
|
|
loadList();
|
|
}
|
|
|
|
defineExpose({ onRelease, tableRef, setSearchKeyword });
|
|
|
|
// 右侧内容
|
|
const modifiedModel = ref<string>();
|
|
const originalValue = ref<string | undefined>();
|
|
|
|
const differenceContent = ref('');
|
|
|
|
|
|
// 删除
|
|
function delRow(row: any) {
|
|
$baseConfirm({
|
|
title: t('system.interface.modal.warningTitle'),
|
|
content: t('system.interface.mapping.modal.deleteConfirm'),
|
|
onOk: async () => {
|
|
await postDelete({
|
|
interfaceId: row.systemInterfaceId,
|
|
serviceProviderId: row.serviceProviderId,
|
|
}).then(() => {
|
|
$baseMessage(t('system.interface.message.deleteSuccess'), 'success');
|
|
loadList();
|
|
});
|
|
},
|
|
});
|
|
}
|
|
|
|
// 发布
|
|
const [registerRelease, registerReleaseMethods] = useModal() as [any, any];
|
|
|
|
const codeRef = ref();
|
|
// 差异数量
|
|
const diffLength = ref(0);
|
|
// 接口映射id
|
|
const interfaceMapId = ref(0);
|
|
// 服务商名称接口名称
|
|
const serverInfo = {
|
|
serviceProviderName: '',
|
|
interfaceName: '',
|
|
};
|
|
const commsData = ref<any>({});
|
|
|
|
const getOriginalValue = computed(() => {
|
|
if (originalValue.value) {
|
|
return JSON.stringify(JSON.parse(originalValue.value || '{}'), null, 2);
|
|
}
|
|
return undefined;
|
|
});
|
|
|
|
const getModifiedModel = computed(() => {
|
|
if (modifiedModel.value) {
|
|
return JSON.stringify(JSON.parse(modifiedModel.value || '{}'), null, 2);
|
|
}
|
|
return '';
|
|
});
|
|
|
|
// 简单差异行列表(无语法高亮,仅标记增删)
|
|
const simpleDiffLines = computed(() => {
|
|
try {
|
|
const originObj = JSON.parse(originalValue.value || '{}');
|
|
const newObj = JSON.parse(modifiedModel.value || '{}');
|
|
const diffParts =
|
|
diffJson(originObj, newObj)?.filter((p) => p && p.value !== undefined) ||
|
|
[];
|
|
const lines: Array<{ text: string; added?: boolean; removed?: boolean }> =
|
|
[];
|
|
diffParts.forEach((part) => {
|
|
const segments = (part.value || '').split('\n');
|
|
segments.forEach((seg, idx) => {
|
|
// 跳过最后一个空行
|
|
if (idx === segments.length - 1 && seg === '') return;
|
|
lines.push({ text: seg, added: part.added, removed: part.removed });
|
|
});
|
|
});
|
|
return lines;
|
|
} catch (err) {
|
|
return [];
|
|
}
|
|
});
|
|
|
|
const showOnlyDiff = ref(false);
|
|
// 将 jsdiff 输出转成行级结构,并尝试把“删+增”配对为“修改”
|
|
const structuredDiff = computed(() => {
|
|
try {
|
|
const originObj = JSON.parse(originalValue.value || '{}');
|
|
const newObj = JSON.parse(modifiedModel.value || '{}');
|
|
const parts =
|
|
diffJson(originObj, newObj)?.filter((p) => p && p.value !== undefined) ||
|
|
[];
|
|
const flat: Array<{ text: string; status: 'add' | 'remove' | 'same' }> = [];
|
|
parts.forEach((p) => {
|
|
const status = p.added ? 'add' : p.removed ? 'remove' : 'same';
|
|
const lines = (p.value || '').split('\n');
|
|
lines.forEach((line, idx) => {
|
|
if (idx === lines.length - 1 && line === '') return;
|
|
flat.push({ text: line, status });
|
|
});
|
|
});
|
|
const merged: Array<
|
|
| { type: 'add' | 'remove' | 'same'; text: string }
|
|
| { type: 'change'; oldText: string; newText: string }
|
|
> = [];
|
|
let i = 0;
|
|
while (i < flat.length) {
|
|
const curr = flat[i];
|
|
const next = flat[i + 1];
|
|
if (curr?.status === 'remove' && next?.status === 'add') {
|
|
merged.push({ type: 'change', oldText: curr.text, newText: next.text });
|
|
i += 2;
|
|
} else {
|
|
merged.push({ type: curr.status, text: curr.text });
|
|
i += 1;
|
|
}
|
|
}
|
|
return merged;
|
|
} catch (err) {
|
|
return [];
|
|
}
|
|
});
|
|
|
|
const visibleDiffRows = computed(() => {
|
|
if (!showOnlyDiff.value) return structuredDiff.value;
|
|
return structuredDiff.value.filter((row) => row.type !== 'same');
|
|
});
|
|
|
|
async function onRelease(row: any) {
|
|
commsData.value = row;
|
|
interfaceMapId.value = row.interfaceMapId!;
|
|
serverInfo.serviceProviderName = row.serviceProviderName;
|
|
serverInfo.interfaceName = row.interfaceName;
|
|
|
|
try {
|
|
const resp = await getGetPublishCompare({
|
|
serviceProviderId: row.serviceProviderId!,
|
|
interfaceCode: row.systemInterfaceCode,
|
|
});
|
|
const res = (resp as any)?.data || resp || {};
|
|
|
|
modifiedModel.value = res.dbContent || '';
|
|
originalValue.value = res.publishedContent || '';
|
|
differenceContent.value = res.differenceContent || '';
|
|
|
|
// 先打开弹窗
|
|
registerReleaseMethods.openModal();
|
|
|
|
// 等待弹窗渲染完成
|
|
await nextTick();
|
|
|
|
// MonacoCom 组件会自动响应 props 变化,无需手动初始化
|
|
|
|
// 计算差异数量
|
|
const len =
|
|
diffJson(
|
|
JSON.parse(originalValue.value || '{}'),
|
|
JSON.parse(modifiedModel.value || '{}')
|
|
)?.filter((item) => item.added || item.removed).length || 0;
|
|
diffLength.value = len;
|
|
} catch (error) {
|
|
$baseMessage(t('system.interface.mapping.message.getPublishCompareFailed'), 'error');
|
|
}
|
|
}
|
|
|
|
const visible = ref(false);
|
|
const description = ref('');
|
|
async function sendMapOk() {
|
|
await run({
|
|
interfaceCode: commsData.value.systemInterfaceCode,
|
|
serviceProviderId: commsData.value.serviceProviderId,
|
|
dbContent: modifiedModel.value || '',
|
|
differenceContent: differenceContent.value,
|
|
description: description.value,
|
|
});
|
|
description.value = '';
|
|
visible.value = false;
|
|
}
|
|
|
|
function handlerSendMap() {
|
|
if (!diffLength.value)
|
|
return $baseMessage(t('system.interface.mapping.message.noContentToPublish'), 'warning');
|
|
|
|
visible.value = true;
|
|
}
|
|
|
|
const { run } = useRequest(postPublish, {
|
|
manual: true,
|
|
onSuccess() {
|
|
$baseMessage(
|
|
t('system.interface.mapping.message.publishCacheSuccess', {
|
|
providerName: serverInfo.serviceProviderName,
|
|
interfaceName: serverInfo.interfaceName,
|
|
count: diffLength.value,
|
|
}),
|
|
'success'
|
|
);
|
|
|
|
registerReleaseMethods.closeModal();
|
|
},
|
|
});
|
|
|
|
/**
|
|
* @description: 切换下一个差异
|
|
* @return {*}
|
|
*/
|
|
|
|
function changeNext() {
|
|
codeRef.value.handleNext();
|
|
}
|
|
|
|
/**
|
|
* @description: 切换上一个差异
|
|
* @return {*}
|
|
*/
|
|
|
|
function changePre() {
|
|
codeRef.value.handlePre();
|
|
}
|
|
|
|
// 回滚
|
|
const rollBackRef = ref();
|
|
|
|
/**
|
|
* @description: 打开回滚弹窗
|
|
* @return {*}
|
|
*/
|
|
|
|
function onBack(row: any) {
|
|
interfaceMapId.value = row.interfaceMapId;
|
|
rollBackRef.value?.handleOpenBack(row);
|
|
}
|
|
|
|
// 处理搜索
|
|
function handleSearch() {
|
|
// 使用 reload 方法重新加载数据
|
|
// searchInfo 是计算属性,会自动响应 searchKeyword 的变化
|
|
if (tableRef.value) {
|
|
tableRef.value.reload();
|
|
}
|
|
}
|
|
|
|
// 处理取消操作
|
|
function handleCancel() {
|
|
visible.value = false;
|
|
}
|
|
|
|
</script>
|
|
|
|
<template>
|
|
<div class="main-container pb-0">
|
|
<div>
|
|
<div class="provider-list">
|
|
<div v-if="!loading && list && list.length > 0">
|
|
<a-spin :loading="loading">
|
|
<div
|
|
v-for="(row, index) in list"
|
|
:key="getRowKey(row, index)"
|
|
class="provider-item"
|
|
:class="{ selected: selectedId === getRowKey(row, index) }"
|
|
@click="handleSelectRow(row, index)"
|
|
>
|
|
<table class="provider-table">
|
|
<tbody>
|
|
<tr>
|
|
<td class="provider-content-cell">
|
|
<div class="provider-content">
|
|
<div class="provider-header">
|
|
<span v-if="selectedId === getRowKey(row, index)" class="selected-dot"></span>
|
|
<span class="provider-number">{{ index + 1 }}</span>
|
|
<div class="provider-name">{{ row.serviceProviderName }}</div>
|
|
<span v-if="row.serviceProviderCode" class="provider-code-inline">{{ row.serviceProviderCode }}</span>
|
|
</div>
|
|
</div>
|
|
</td>
|
|
<td class="provider-actions-cell">
|
|
<div class="provider-actions" @click.stop>
|
|
<a-dropdown trigger="click">
|
|
<a-button
|
|
type="text"
|
|
size="small"
|
|
class="action-btn vertical-dots"
|
|
:title="t('system.interface.mapping.actions.actions')"
|
|
@click="handleSelectRow(row, index)"
|
|
>
|
|
<div class="vertical-dots-icon">
|
|
<span></span>
|
|
<span></span>
|
|
<span></span>
|
|
</div>
|
|
</a-button>
|
|
<template #content>
|
|
<a-doption @click="delRow(row)">{{ t('system.interface.mapping.actions.delete') }}</a-doption>
|
|
<a-doption @click="onRelease(row)">{{ t('system.interface.mapping.actions.publish') }}</a-doption>
|
|
<a-doption @click="onBack(row)">{{ t('system.interface.mapping.actions.rollback') }}</a-doption>
|
|
</template>
|
|
</a-dropdown>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</a-spin>
|
|
</div>
|
|
<div v-else-if="!loading && (!list || list.length === 0)" class="empty">
|
|
<a-empty :description="t('system.interface.mapping.empty.noProvider')" />
|
|
</div>
|
|
<div v-else class="loading-container">
|
|
<a-spin :loading="loading" />
|
|
</div>
|
|
</div>
|
|
|
|
<ModalPro
|
|
:title="t('system.interface.mapping.modal.publishTitle')"
|
|
:width="1280"
|
|
:footer="false"
|
|
@register="registerRelease"
|
|
>
|
|
<div class="release-content slim">
|
|
<div class="diff-header">
|
|
<div class="diff-title">{{ t('system.interface.mapping.modal.updateCache') }}</div>
|
|
<div class="diff-actions">
|
|
<a-button
|
|
type="primary"
|
|
size="small"
|
|
@click="handlerSendMap"
|
|
:disabled="diffLength === 0"
|
|
>
|
|
{{ t('system.interface.mapping.actions.publish') }}
|
|
</a-button>
|
|
<a-checkbox v-model="showOnlyDiff" size="small">
|
|
只看差异
|
|
</a-checkbox>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="diff-boxes">
|
|
<div class="diff-box">
|
|
<div class="diff-box-title">{{ t('system.interface.mapping.modal.onlineCache') }}</div>
|
|
<pre class="diff-pre">{{ getOriginalValue || '{}' }}</pre>
|
|
</div>
|
|
<div class="diff-box">
|
|
<div class="diff-box-title">{{ t('system.interface.mapping.modal.updateCache', { count: diffLength }) }}</div>
|
|
<pre class="diff-pre">{{ getModifiedModel || '{}' }}</pre>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="legend">
|
|
<span class="legend-item add">🟢 新增</span>
|
|
<span class="legend-item remove">🔴 删除</span>
|
|
<span class="legend-item change">🟡 修改</span>
|
|
<span class="legend-item same">⚪ 不变</span>
|
|
</div>
|
|
|
|
<div class="merged-diff">
|
|
<div class="diff-box-title">{{ t('system.interface.mapping.modal.updateCache') }}</div>
|
|
<div class="merged-diff-body">
|
|
<template v-for="(row, idx) in visibleDiffRows" :key="idx">
|
|
<template v-if="row.type === 'change'">
|
|
<div class="merged-line change">
|
|
<span class="line-mark">🟡</span>
|
|
<span class="line-text">
|
|
{{ row.oldText }} → {{ row.newText }}
|
|
</span>
|
|
</div>
|
|
</template>
|
|
<template v-else>
|
|
<div
|
|
class="merged-line"
|
|
:class="{
|
|
added: row.type === 'add',
|
|
removed: row.type === 'remove',
|
|
same: row.type === 'same'
|
|
}"
|
|
>
|
|
<span class="line-mark">
|
|
{{ row.type === 'add' ? '🟢' : row.type === 'remove' ? '🔴' : '⚪' }}
|
|
</span>
|
|
<span class="line-text">{{ row.text }}</span>
|
|
</div>
|
|
</template>
|
|
</template>
|
|
<div v-if="!visibleDiffRows.length" class="diff-empty">
|
|
{{ t('system.interface.mapping.message.noContentToPublish') }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</ModalPro>
|
|
|
|
<RollBack ref="rollBackRef" :server-info="serverInfo" />
|
|
|
|
<a-modal
|
|
v-model:visible="visible"
|
|
:title="t('system.interface.modal.warningTitle')"
|
|
@cancel="handleCancel"
|
|
@ok="sendMapOk"
|
|
>
|
|
<div class="mb-15px">{{ t('system.interface.mapping.modal.publishWarning') }}</div>
|
|
<a-input v-model="description" :placeholder="t('system.interface.mapping.modal.versionRemark')" />
|
|
</a-modal>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<style lang="less" scoped>
|
|
/* 去掉外围容器可能带来的默认左右内边距 */
|
|
.main-container { padding-left: 0 !important; padding-right: 0 !important; }
|
|
.bg-white { padding-left: 0 !important; padding-right: 0 !important; }
|
|
/* 接近下游服务商列表样式:紧凑、仅关键信息 */
|
|
:deep(.vxe-table) {
|
|
font-size: 12px;
|
|
}
|
|
:deep(.vxe-body--row) {
|
|
height: 36px;
|
|
}
|
|
.sp-code {
|
|
color: #8c8c8c;
|
|
}
|
|
.main-box {
|
|
padding-top: 0;
|
|
}
|
|
|
|
.toolbar-buttons {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
width: 100%;
|
|
}
|
|
|
|
.span-style {
|
|
margin-top: 5px;
|
|
font-size: 17px;
|
|
font-weight: 900;
|
|
}
|
|
|
|
.text-style {
|
|
display: flex;
|
|
}
|
|
|
|
.search-box {
|
|
padding: 16px;
|
|
border-bottom: 1px solid #e8e8e8;
|
|
|
|
:deep(.arco-input-wrapper) {
|
|
width: 300px !important;
|
|
max-width: 300px !important;
|
|
}
|
|
|
|
:deep(.arco-input) {
|
|
width: 100% !important;
|
|
}
|
|
}
|
|
|
|
.mapped-list { padding: 0; }
|
|
.provider-list {
|
|
padding: 8px 0;
|
|
display: flex;
|
|
flex-direction: column;
|
|
flex: 1;
|
|
color: #000;
|
|
background: transparent;
|
|
border-radius: 16px;
|
|
overflow-y: auto;
|
|
padding-right: 4px;
|
|
}
|
|
|
|
.provider-list::-webkit-scrollbar {
|
|
width: 6px;
|
|
}
|
|
|
|
.provider-list::-webkit-scrollbar-track {
|
|
background: rgba(0, 0, 0, 0.08);
|
|
border-radius: 8px;
|
|
}
|
|
|
|
.provider-list::-webkit-scrollbar-thumb {
|
|
background: rgba(86, 144, 236, 0.65);
|
|
border-radius: 8px;
|
|
}
|
|
|
|
.provider-list::-webkit-scrollbar-thumb:hover {
|
|
background: rgba(59, 97, 148, 0.85);
|
|
}
|
|
|
|
|
|
.loading-container {
|
|
flex: 1;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
.provider-item {
|
|
width: 100%;
|
|
box-sizing: border-box;
|
|
border: 1px solid rgba(0, 0, 0, 0.1);
|
|
padding: 10px 12px;
|
|
margin: 8px 0;
|
|
border-radius: 8px;
|
|
transition: background-color 0.2s ease, border-color 0.2s ease, transform 0.2s ease;
|
|
cursor: pointer;
|
|
background: #fff;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: flex-start;
|
|
|
|
&:hover {
|
|
background-color: rgba(0, 0, 0, 0.05);
|
|
border-color: rgba(0, 0, 0, 0.2);
|
|
transform: translateX(4px);
|
|
}
|
|
|
|
&.selected {
|
|
background: #FFF394 !important;
|
|
box-shadow: none;
|
|
border-color: #FFF394 !important;
|
|
color: #000 !important;
|
|
|
|
.provider-number,
|
|
.provider-name,
|
|
.provider-code,
|
|
.provider-code-inline {
|
|
color: #000 !important;
|
|
}
|
|
}
|
|
}
|
|
.provider-table { width: 100%; border-collapse: collapse; table-layout: fixed; }
|
|
.provider-content-cell { text-align: left; padding: 0; width: auto; }
|
|
.provider-actions-cell { text-align: right; padding: 0; width: 30px; vertical-align: middle; }
|
|
.provider-code-cell { text-align: left; padding: 0; width: auto; padding-left: 28px; }
|
|
.provider-content { cursor: pointer; }
|
|
.provider-actions { opacity: 1; transition: opacity 0.2s; margin-top: 2px; }
|
|
.action-btn { width: 24px; height: 24px; padding: 0; display: flex; align-items: center; justify-content: center; border: none; background: transparent; cursor: pointer; }
|
|
.action-btn:hover { background-color: #f0f8ff; border-radius: 4px; }
|
|
.vertical-dots-icon { display: flex; flex-direction: column; gap: 2px; }
|
|
.vertical-dots-icon span { width: 3px; height: 3px; background-color: #8c8c8c; border-radius: 50%; display: block; }
|
|
.provider-header { display: flex; align-items: center; margin-bottom: 4px; }
|
|
.provider-number { color: rgba(0, 0, 0, 0.6); font-size: 12px; font-weight: normal; margin-right: 8px; display: inline-block; min-width: 16px; text-align: center; flex-shrink: 0; }
|
|
.provider-name { font-size: 14px; font-weight: 600; color: #000; flex-shrink: 0; }
|
|
.provider-code { font-size: 12px; color: rgba(0, 0, 0, 0.65); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
.provider-code-inline { font-size: 11px; color: rgba(0, 0, 0, 0.65); margin-left: 6px; }
|
|
/* 选中态与下游一致 */
|
|
.selected-dot { display: none; }
|
|
.empty {
|
|
flex: 1;
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
}
|
|
|
|
/* 发布弹窗样式 */
|
|
.release-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
margin-bottom: 12px;
|
|
font-size: 12px;
|
|
color: #232A33;
|
|
}
|
|
|
|
.release-title {
|
|
flex: 1;
|
|
text-align: center;
|
|
}
|
|
|
|
.release-content.slim {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 12px;
|
|
}
|
|
|
|
.diff-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
}
|
|
|
|
.diff-title {
|
|
font-size: 13px;
|
|
color: #232A33;
|
|
}
|
|
|
|
.diff-actions {
|
|
display: flex;
|
|
gap: 8px;
|
|
}
|
|
|
|
.diff-boxes {
|
|
display: grid;
|
|
grid-template-columns: 1fr 1fr;
|
|
gap: 12px;
|
|
}
|
|
|
|
.diff-box {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 8px;
|
|
}
|
|
|
|
.diff-box-title {
|
|
font-size: 12px;
|
|
color: #666;
|
|
}
|
|
|
|
.diff-pre {
|
|
margin: 0;
|
|
padding: 10px 12px;
|
|
background: #f7f8fa;
|
|
border: 1px solid #e8e8e8;
|
|
border-radius: 6px;
|
|
min-height: 160px;
|
|
max-height: 280px;
|
|
overflow: auto;
|
|
font-family: Menlo, Consolas, monospace;
|
|
font-size: 12px;
|
|
line-height: 18px;
|
|
white-space: pre-wrap;
|
|
word-break: break-all;
|
|
}
|
|
|
|
.diff-actions :deep(.arco-btn) {
|
|
padding: 0 8px;
|
|
min-width: 32px;
|
|
}
|
|
|
|
.merged-diff {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 8px;
|
|
}
|
|
|
|
.merged-diff-body {
|
|
border: 1px solid #e8e8e8;
|
|
border-radius: 6px;
|
|
background: #fff;
|
|
max-height: 600px;
|
|
overflow: auto;
|
|
font-family: Menlo, Consolas, monospace;
|
|
font-size: 12px;
|
|
line-height: 18px;
|
|
padding: 10px 12px;
|
|
white-space: pre-wrap;
|
|
word-break: break-all;
|
|
}
|
|
|
|
.merged-seg.added {
|
|
background: #e6ffed;
|
|
}
|
|
|
|
.merged-seg.removed {
|
|
background: #ffecec;
|
|
text-decoration: line-through;
|
|
}
|
|
|
|
.merged-line {
|
|
display: flex;
|
|
gap: 8px;
|
|
padding: 4px 0;
|
|
white-space: pre-wrap;
|
|
word-break: break-all;
|
|
}
|
|
|
|
.merged-line.added {
|
|
background: #e6ffed;
|
|
}
|
|
|
|
.merged-line.removed {
|
|
background: #ffecec;
|
|
text-decoration: line-through;
|
|
}
|
|
|
|
.merged-line.change {
|
|
background: #fffbe6;
|
|
}
|
|
|
|
.merged-line.same {
|
|
color: #666;
|
|
}
|
|
|
|
.legend {
|
|
display: flex;
|
|
gap: 12px;
|
|
font-size: 12px;
|
|
}
|
|
|
|
.legend-item.add { color: #16a34a; }
|
|
.legend-item.remove { color: #dc2626; }
|
|
.legend-item.change { color: #ca8a04; }
|
|
.legend-item.same { color: #666; }
|
|
|
|
.diff-controls {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 10px;
|
|
margin-top: 20px;
|
|
}
|
|
|
|
.control-btn {
|
|
width: 32px;
|
|
height: 32px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
border: 1px solid #e8e8e8;
|
|
border-radius: 4px;
|
|
background-color: #fff;
|
|
|
|
&:hover {
|
|
border-color: var(--app-primary-color);
|
|
color: var(--app-primary-color);
|
|
}
|
|
}
|
|
</style>
|