671 lines
22 KiB
Vue
671 lines
22 KiB
Vue
<template>
|
||
<div class="sql-generator h-full flex flex-col p-4 box-border gap-4">
|
||
<div class="flex flex-col gap-4 lg:grid lg:grid-cols-2 lg:items-stretch lg:flex-1 min-h-0">
|
||
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-4 flex flex-col min-h-0">
|
||
<div class="flex items-center justify-between mb-3">
|
||
<h2 class="text-sm lg:text-base font-semibold text-gray-900 flex items-center">
|
||
<svg class="w-4 h-4 text-blue-500 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/>
|
||
</svg>
|
||
生成SQL
|
||
</h2>
|
||
<span class="text-xs text-gray-500">选择功能后粘贴内容生成SQL</span>
|
||
</div>
|
||
|
||
<div class="flex flex-col gap-3 flex-1 min-h-0">
|
||
<div class="flex-shrink-0">
|
||
<label class="block text-xs font-medium text-gray-600 mb-2">功能选择</label>
|
||
<select
|
||
v-model="selectedTool"
|
||
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm"
|
||
>
|
||
<option v-for="tool in toolOptions" :key="tool.value" :value="tool.value">
|
||
{{ tool.label }}
|
||
</option>
|
||
</select>
|
||
<p class="text-xs text-gray-500 mt-2">
|
||
{{ currentTool.description }}
|
||
</p>
|
||
</div>
|
||
|
||
<div class="flex flex-col flex-1 min-h-0">
|
||
<label class="block text-xs font-medium text-gray-600 mb-2">文本内容</label>
|
||
<textarea
|
||
v-model="inputText"
|
||
rows="8"
|
||
:placeholder="currentTool.placeholder"
|
||
class="w-full flex-1 min-h-[200px] px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm font-mono"
|
||
></textarea>
|
||
<div class="flex items-center justify-between mt-2">
|
||
<p class="text-xs text-gray-500">支持换行,分隔符:逗号 / 空格 / 制表符</p>
|
||
<div class="flex items-center space-x-2">
|
||
<button
|
||
@click="clearInput"
|
||
class="px-3 py-1.5 text-xs text-gray-600 border border-gray-300 rounded-lg hover:bg-gray-50"
|
||
type="button"
|
||
>
|
||
清空
|
||
</button>
|
||
<button
|
||
@click="generateSql"
|
||
:disabled="loading"
|
||
class="px-3 py-1.5 text-xs text-white bg-blue-600 rounded-lg hover:bg-blue-700 disabled:opacity-60"
|
||
type="button"
|
||
>
|
||
<span v-if="loading">生成中...</span>
|
||
<span v-else>生成SQL</span>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-4 flex flex-col min-h-0">
|
||
<div class="flex flex-col gap-4 h-full min-h-0">
|
||
<div v-if="showQuerySql">
|
||
<div class="flex items-center justify-between mb-2">
|
||
<h3 class="text-sm lg:text-base font-semibold text-gray-900">查询SQL</h3>
|
||
<button
|
||
@click="copyQuery"
|
||
:disabled="!querySql"
|
||
class="px-3 py-1.5 text-xs text-gray-600 border border-gray-300 rounded-lg hover:bg-gray-50 disabled:opacity-60"
|
||
type="button"
|
||
>
|
||
复制查询SQL
|
||
</button>
|
||
</div>
|
||
<textarea
|
||
v-model="querySql"
|
||
readonly
|
||
ref="queryTextarea"
|
||
class="w-full min-h-[100px] px-3 py-2 border border-gray-200 rounded-lg text-sm font-mono bg-gray-50"
|
||
></textarea>
|
||
</div>
|
||
|
||
<div v-if="showSplitOutput" class="flex flex-col flex-1 min-h-0">
|
||
<div class="flex items-center justify-between mb-2">
|
||
<h3 class="text-sm lg:text-base font-semibold text-gray-900">生成结果</h3>
|
||
<div v-if="stats.total" class="text-xs text-gray-500">
|
||
共 {{ stats.total }} 条,更新 {{ stats.update }} 条
|
||
</div>
|
||
</div>
|
||
<div class="grid grid-rows-3 gap-3 flex-1 min-h-0">
|
||
<div
|
||
v-for="section in splitOutputSections"
|
||
:key="section.key"
|
||
class="flex flex-col min-h-0 border border-gray-200 rounded-lg overflow-hidden bg-gray-50"
|
||
>
|
||
<div class="flex items-center justify-between px-3 py-2 border-b border-gray-200 bg-white">
|
||
<div class="text-sm font-semibold text-gray-900">{{ section.label }}</div>
|
||
<button
|
||
@click="copySplitOutput(section.key)"
|
||
:disabled="!splitOutputSql[section.key]"
|
||
class="px-3 py-1.5 text-xs text-gray-600 border border-gray-300 rounded-lg hover:bg-gray-50 disabled:opacity-60"
|
||
type="button"
|
||
>
|
||
复制{{ section.label }}
|
||
</button>
|
||
</div>
|
||
<textarea
|
||
v-model="splitOutputSql[section.key]"
|
||
readonly
|
||
class="w-full flex-1 min-h-[120px] px-3 py-2 border-0 text-sm font-mono bg-gray-50 resize-none focus:ring-0"
|
||
></textarea>
|
||
</div>
|
||
</div>
|
||
<div class="flex items-center justify-between mt-2">
|
||
<p class="text-xs text-gray-400">结果仅用于复制执行,请确认无误后使用</p>
|
||
</div>
|
||
<div
|
||
v-if="copyStatus.message"
|
||
class="mt-2 text-xs"
|
||
:class="copyStatus.type === 'success' ? 'text-green-600' : 'text-red-600'"
|
||
>
|
||
{{ copyStatus.message }}
|
||
</div>
|
||
</div>
|
||
|
||
<div v-else class="flex flex-col flex-1 min-h-0">
|
||
<div class="flex items-center justify-between mb-2">
|
||
<h3 class="text-sm lg:text-base font-semibold text-gray-900">生成结果</h3>
|
||
<div v-if="stats.total" class="text-xs text-gray-500">
|
||
共 {{ stats.total }} 条,更新 {{ stats.update }} 条,新增 {{ stats.insert }} 条
|
||
</div>
|
||
</div>
|
||
<textarea
|
||
v-model="outputSql"
|
||
readonly
|
||
ref="outputTextarea"
|
||
class="w-full flex-1 min-h-[200px] px-3 py-2 border border-gray-200 rounded-lg text-sm font-mono bg-gray-50"
|
||
></textarea>
|
||
<div class="flex items-center justify-between mt-2">
|
||
<p class="text-xs text-gray-400">结果仅用于复制执行,请确认无误后使用</p>
|
||
<button
|
||
@click="copyOutput"
|
||
:disabled="!outputSql"
|
||
class="px-3 py-1.5 text-xs text-gray-600 border border-gray-300 rounded-lg hover:bg-gray-50 disabled:opacity-60"
|
||
type="button"
|
||
>
|
||
复制结果
|
||
</button>
|
||
</div>
|
||
<div
|
||
v-if="copyStatus.message"
|
||
class="mt-2 text-xs"
|
||
:class="copyStatus.type === 'success' ? 'text-green-600' : 'text-red-600'"
|
||
>
|
||
{{ copyStatus.message }}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div v-if="errors.length" class="bg-red-50 border border-red-200 rounded-lg p-3 text-sm text-red-700">
|
||
<div class="font-medium mb-1">输入格式问题</div>
|
||
<ul class="list-disc list-inside space-y-0.5">
|
||
<li v-for="(error, index) in errors" :key="index">{{ error }}</li>
|
||
</ul>
|
||
</div>
|
||
|
||
<div v-if="warnings.length" class="bg-yellow-50 border border-yellow-200 rounded-lg p-3 text-sm text-yellow-800">
|
||
<div class="font-medium mb-1">发现重复 case_id</div>
|
||
<ul class="list-disc list-inside space-y-0.5">
|
||
<li v-for="(warning, index) in warnings" :key="index">{{ warning }}</li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script>
|
||
export default {
|
||
name: 'SqlGenerator',
|
||
data() {
|
||
return {
|
||
selectedTool: 'ob-external-id',
|
||
inputText: '',
|
||
outputSql: '',
|
||
splitOutputSql: {
|
||
sp: '',
|
||
ppCn: '',
|
||
ppUs: ''
|
||
},
|
||
querySql: '',
|
||
loading: false,
|
||
errors: [],
|
||
warnings: [],
|
||
copyStatus: {
|
||
message: '',
|
||
type: 'success'
|
||
},
|
||
copyStatusTimer: null,
|
||
stats: {
|
||
total: 0,
|
||
update: 0,
|
||
insert: 0
|
||
},
|
||
toolOptions: [
|
||
{
|
||
value: 'ob-external-id',
|
||
label: 'OB外部ID',
|
||
description: '每行输入 case_id 与 ob_id,系统会判断是否生成更新或插入 SQL。',
|
||
placeholder: '示例: X0X60F 17141\nC01008446046, 11894'
|
||
},
|
||
{
|
||
value: 'new-factory-return-redelivery',
|
||
label: '新区工厂退库重新出库',
|
||
description: '每行输入加工单、病例号、正确运单,只读取前三列;根据 CRM 加工单关联地址国家拆分 PP-CN / PP-US SQL。',
|
||
placeholder: '示例: M20260508006905 225KF9 SF123456789\nM20260509005949 C01005934247 UPS987654321'
|
||
}
|
||
]
|
||
}
|
||
},
|
||
computed: {
|
||
currentTool() {
|
||
return this.toolOptions.find((tool) => tool.value === this.selectedTool) || this.toolOptions[0];
|
||
},
|
||
|
||
showSplitOutput() {
|
||
return this.selectedTool === 'new-factory-return-redelivery';
|
||
},
|
||
|
||
showQuerySql() {
|
||
return !this.showSplitOutput;
|
||
},
|
||
|
||
splitOutputSections() {
|
||
return [
|
||
{ key: 'sp', label: 'SP' },
|
||
{ key: 'ppCn', label: 'PP-CN' },
|
||
{ key: 'ppUs', label: 'PP-US' }
|
||
];
|
||
}
|
||
},
|
||
methods: {
|
||
clearInput() {
|
||
this.inputText = '';
|
||
this.outputSql = '';
|
||
this.resetSplitOutputSql();
|
||
this.querySql = '';
|
||
this.errors = [];
|
||
this.warnings = [];
|
||
this.resetCopyStatus();
|
||
this.resetStats();
|
||
},
|
||
|
||
resetStats() {
|
||
this.stats = {
|
||
total: 0,
|
||
update: 0,
|
||
insert: 0
|
||
};
|
||
},
|
||
|
||
parseObExternalIdInput() {
|
||
const lines = this.inputText.split(/\r?\n/);
|
||
const errors = [];
|
||
const duplicates = new Map();
|
||
const caseMap = new Map();
|
||
|
||
lines.forEach((line, index) => {
|
||
const trimmed = line.trim();
|
||
if (!trimmed) {
|
||
return;
|
||
}
|
||
|
||
const parts = trimmed.split(/[\s,]+/).filter(Boolean);
|
||
if (parts.length !== 2) {
|
||
errors.push(`第 ${index + 1} 行格式不正确,请提供 case_id 与 ob_id 两列`);
|
||
return;
|
||
}
|
||
|
||
const [caseCode, obId] = parts;
|
||
if (!caseCode || !obId) {
|
||
errors.push(`第 ${index + 1} 行缺少 case_id 或 ob_id`);
|
||
return;
|
||
}
|
||
|
||
const lineNumber = index + 1;
|
||
if (caseMap.has(caseCode)) {
|
||
const existing = caseMap.get(caseCode);
|
||
if (!duplicates.has(caseCode)) {
|
||
duplicates.set(caseCode, {
|
||
caseCode,
|
||
lineNumbers: [existing.lineNumber]
|
||
});
|
||
}
|
||
duplicates.get(caseCode).lineNumbers.push(lineNumber);
|
||
}
|
||
|
||
caseMap.set(caseCode, {
|
||
caseCode,
|
||
obId,
|
||
lineNumber
|
||
});
|
||
});
|
||
|
||
const pairs = Array.from(caseMap.values());
|
||
|
||
if (pairs.length === 0 && errors.length === 0) {
|
||
errors.push('请输入至少一行 case_id 与 ob_id。');
|
||
}
|
||
|
||
return {
|
||
pairs,
|
||
errors,
|
||
duplicates: Array.from(duplicates.values())
|
||
};
|
||
},
|
||
|
||
async generateSql() {
|
||
this.errors = [];
|
||
this.outputSql = '';
|
||
this.resetSplitOutputSql();
|
||
this.querySql = '';
|
||
this.warnings = [];
|
||
this.resetCopyStatus();
|
||
this.resetStats();
|
||
|
||
if (this.selectedTool === 'ob-external-id') {
|
||
await this.generateObExternalIdSql();
|
||
return;
|
||
}
|
||
|
||
if (this.selectedTool === 'new-factory-return-redelivery') {
|
||
await this.generateNewFactoryReturnRedeliverySql();
|
||
return;
|
||
}
|
||
|
||
this.errors = ['未识别的功能类型,请重新选择。'];
|
||
},
|
||
|
||
async generateObExternalIdSql() {
|
||
const { pairs, errors, duplicates } = this.parseObExternalIdInput();
|
||
this.warnings = this.buildDuplicateWarnings(duplicates);
|
||
if (errors.length) {
|
||
this.errors = errors;
|
||
return;
|
||
}
|
||
|
||
this.loading = true;
|
||
|
||
try {
|
||
const caseCodes = pairs.map((pair) => pair.caseCode);
|
||
this.querySql = this.buildCaseExtrasSelect(caseCodes);
|
||
const response = await fetch('/api/sql-generator/ob-external-id/check', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').getAttribute('content')
|
||
},
|
||
body: JSON.stringify({
|
||
case_codes: caseCodes
|
||
})
|
||
});
|
||
|
||
const data = await response.json();
|
||
|
||
if (!response.ok || !data.success) {
|
||
this.errors = [data.message || '查询 case_extras 失败,请稍后重试。'];
|
||
return;
|
||
}
|
||
|
||
const existingCaseCodes = new Set(data.data.existing_case_codes || []);
|
||
const sqlLines = [];
|
||
|
||
pairs.forEach((pair) => {
|
||
const caseCode = this.escapeSqlValue(pair.caseCode);
|
||
const obId = this.escapeSqlValue(pair.obId);
|
||
if (existingCaseCodes.has(pair.caseCode)) {
|
||
sqlLines.push(
|
||
`update case_extras set value = '${obId}' where case_code = '${caseCode}' and source = 'ob' and field = 'OB Case ID';`
|
||
);
|
||
this.stats.update += 1;
|
||
} else {
|
||
sqlLines.push(
|
||
`insert into case_extras(case_code, source, field, value) VALUES ('${caseCode}', 'ob', 'OB Case ID', '${obId}');`
|
||
);
|
||
this.stats.insert += 1;
|
||
}
|
||
});
|
||
|
||
this.stats.total = sqlLines.length;
|
||
this.outputSql = sqlLines.join('\n');
|
||
} catch (error) {
|
||
this.errors = ['网络请求失败: ' + error.message];
|
||
} finally {
|
||
this.loading = false;
|
||
}
|
||
},
|
||
|
||
parseNewFactoryReturnRedeliveryInput() {
|
||
const lines = this.inputText.split(/\r?\n/);
|
||
const errors = [];
|
||
const rows = [];
|
||
|
||
lines.forEach((line, index) => {
|
||
const trimmed = line.trim();
|
||
if (!trimmed) {
|
||
return;
|
||
}
|
||
|
||
const parts = trimmed.split(/[\s,,]+/).filter(Boolean);
|
||
if (parts.length < 3) {
|
||
errors.push(`第 ${index + 1} 行格式不正确,请提供加工单、病例号、正确运单三列`);
|
||
return;
|
||
}
|
||
|
||
rows.push({
|
||
productionCode: parts[0],
|
||
caseCode: parts[1],
|
||
expressNo: parts[2],
|
||
lineNumber: index + 1
|
||
});
|
||
});
|
||
|
||
if (rows.length === 0 && errors.length === 0) {
|
||
errors.push('请输入至少一行加工单、病例号、正确运单。');
|
||
}
|
||
|
||
return {
|
||
rows,
|
||
errors
|
||
};
|
||
},
|
||
|
||
async generateNewFactoryReturnRedeliverySql() {
|
||
const { rows, errors } = this.parseNewFactoryReturnRedeliveryInput();
|
||
if (errors.length) {
|
||
this.errors = errors;
|
||
return;
|
||
}
|
||
|
||
this.loading = true;
|
||
|
||
try {
|
||
const productionCodes = [...new Set(rows.map((row) => row.productionCode))];
|
||
this.querySql = this.buildProductionCountriesSelect(productionCodes);
|
||
const response = await fetch('/api/sql-generator/production-countries/check', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').getAttribute('content')
|
||
},
|
||
body: JSON.stringify({
|
||
production_codes: productionCodes
|
||
})
|
||
});
|
||
|
||
const data = await response.json();
|
||
|
||
if (!response.ok || !data.success) {
|
||
this.errors = [data.message || '查询 CRM 加工单国家失败,请稍后重试。'];
|
||
return;
|
||
}
|
||
|
||
const productionCountries = data.data.production_countries || {};
|
||
const missingRows = rows.filter((row) => !productionCountries[row.productionCode] || productionCountries[row.productionCode].length === 0);
|
||
if (missingRows.length) {
|
||
this.errors = missingRows.map((row) => `第 ${row.lineNumber} 行加工单 ${row.productionCode} 未在 CRM 查询到地址国家`);
|
||
return;
|
||
}
|
||
|
||
const spSql = [];
|
||
const ppCnSql = [];
|
||
const ppUsSql = [];
|
||
|
||
rows.forEach((row) => {
|
||
const productionCode = this.escapeSqlValue(row.productionCode);
|
||
const expressNo = this.escapeSqlValue(row.expressNo);
|
||
const ppSql = this.isUsProductionCountry(productionCountries[row.productionCode]) ? ppUsSql : ppCnSql;
|
||
|
||
spSql.push(`update case_deliveries set express_no = '${expressNo}' where production_code = '${productionCode}';`);
|
||
ppSql.push(`update delivery_records set express_no = '${expressNo}' where production_code = '${productionCode}';`);
|
||
ppSql.push(`update receives set express_no = '${expressNo}' where production_code = '${productionCode}';`);
|
||
});
|
||
|
||
const sections = [
|
||
this.buildSqlSection('SP', spSql),
|
||
this.buildSqlSection('PP-CN', ppCnSql),
|
||
this.buildSqlSection('PP-US', ppUsSql)
|
||
].filter(Boolean);
|
||
|
||
this.stats.update = spSql.length + ppCnSql.length + ppUsSql.length;
|
||
this.stats.total = this.stats.update;
|
||
this.splitOutputSql = {
|
||
sp: spSql.join('\n'),
|
||
ppCn: ppCnSql.join('\n'),
|
||
ppUs: ppUsSql.join('\n')
|
||
};
|
||
this.outputSql = sections.join('\n\n');
|
||
} catch (error) {
|
||
this.errors = ['网络请求失败: ' + error.message];
|
||
} finally {
|
||
this.loading = false;
|
||
}
|
||
},
|
||
|
||
escapeSqlValue(value) {
|
||
return String(value).replace(/'/g, "''");
|
||
},
|
||
|
||
buildCaseExtrasSelect(caseCodes) {
|
||
if (!caseCodes.length) {
|
||
return '';
|
||
}
|
||
|
||
const inValues = caseCodes.map((caseCode) => `'${this.escapeSqlValue(caseCode)}'`).join(', ');
|
||
return `select * from case_extras where case_code in (${inValues})`;
|
||
},
|
||
|
||
buildProductionCountriesSelect(productionCodes) {
|
||
if (!productionCodes.length) {
|
||
return '';
|
||
}
|
||
|
||
const inValues = productionCodes.map((productionCode) => `'${this.escapeSqlValue(productionCode)}'`).join(', ');
|
||
return [
|
||
'select ep.name, epc.ea_case_id_c, epc.ea_businessorder_id_c, epc.ea_salesorder_id_c',
|
||
'from ea_production ep',
|
||
'join ea_production_cstm epc on ep.id = epc.id_c',
|
||
`where ep.deleted = 0 and ep.name in (${inValues})`
|
||
].join('\n');
|
||
},
|
||
|
||
isUsProductionCountry(countryCodes) {
|
||
return (countryCodes || []).some((countryCode) => ['US', 'GU', 'PR'].includes(String(countryCode).toUpperCase()));
|
||
},
|
||
|
||
buildSqlSection(title, sqlLines) {
|
||
if (!sqlLines.length) {
|
||
return '';
|
||
}
|
||
|
||
return [`-- ${title}`, ...sqlLines].join('\n');
|
||
},
|
||
|
||
buildDuplicateWarnings(duplicates) {
|
||
return duplicates.map((duplicate) => {
|
||
const lines = duplicate.lineNumbers.join('、');
|
||
return `case_id ${duplicate.caseCode} 重复出现在第 ${lines} 行,已以最后一行的 ob_id 为准。`;
|
||
});
|
||
},
|
||
|
||
resetCopyStatus() {
|
||
this.copyStatus = {
|
||
message: '',
|
||
type: 'success'
|
||
};
|
||
},
|
||
|
||
resetSplitOutputSql() {
|
||
this.splitOutputSql = {
|
||
sp: '',
|
||
ppCn: '',
|
||
ppUs: ''
|
||
};
|
||
},
|
||
|
||
setCopyStatus(message, type) {
|
||
this.copyStatus = {
|
||
message,
|
||
type
|
||
};
|
||
|
||
window.clearTimeout(this.copyStatusTimer);
|
||
this.copyStatusTimer = window.setTimeout(() => {
|
||
this.resetCopyStatus();
|
||
}, 3000);
|
||
},
|
||
|
||
execCommandCopy(text) {
|
||
const textarea = document.createElement('textarea');
|
||
textarea.value = text;
|
||
textarea.setAttribute('readonly', '');
|
||
textarea.style.position = 'absolute';
|
||
textarea.style.left = '-9999px';
|
||
document.body.appendChild(textarea);
|
||
textarea.select();
|
||
|
||
let success = false;
|
||
try {
|
||
success = document.execCommand('copy');
|
||
} catch (error) {
|
||
success = false;
|
||
}
|
||
|
||
document.body.removeChild(textarea);
|
||
return success;
|
||
},
|
||
|
||
execCommandCopyFromRef(refName) {
|
||
const target = this.$refs[refName];
|
||
if (!target) {
|
||
return false;
|
||
}
|
||
|
||
target.focus();
|
||
target.select();
|
||
target.setSelectionRange(0, target.value.length);
|
||
|
||
let success = false;
|
||
try {
|
||
success = document.execCommand('copy');
|
||
} catch (error) {
|
||
success = false;
|
||
}
|
||
|
||
target.blur();
|
||
return success;
|
||
},
|
||
|
||
async copyToClipboard(text, fallbackMessage, refName) {
|
||
if (!text) {
|
||
return;
|
||
}
|
||
|
||
this.resetCopyStatus();
|
||
|
||
if (navigator.clipboard && window.isSecureContext) {
|
||
try {
|
||
await navigator.clipboard.writeText(text);
|
||
this.setCopyStatus('已复制到剪贴板。', 'success');
|
||
return;
|
||
} catch (error) {
|
||
// fallback to execCommand
|
||
}
|
||
}
|
||
|
||
const success = refName
|
||
? this.execCommandCopyFromRef(refName)
|
||
: this.execCommandCopy(text);
|
||
if (success) {
|
||
this.setCopyStatus('已复制到剪贴板。', 'success');
|
||
return;
|
||
}
|
||
|
||
this.setCopyStatus(fallbackMessage, 'error');
|
||
},
|
||
|
||
async copyOutput() {
|
||
await this.copyToClipboard(this.outputSql, '复制失败,请手动复制结果。', 'outputTextarea');
|
||
},
|
||
|
||
async copySplitOutput(key) {
|
||
await this.copyToClipboard(this.splitOutputSql[key], '复制失败,请手动复制结果。');
|
||
},
|
||
|
||
async copyQuery() {
|
||
await this.copyToClipboard(this.querySql, '复制失败,请手动复制查询SQL。', 'queryTextarea');
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style scoped>
|
||
.sql-generator {
|
||
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||
}
|
||
</style>
|