feat(工具箱): 添加“生成批量下载链接”工具

This commit is contained in:
2026-01-03 23:40:47 +08:00
parent 6cbbbe8541
commit a666679ebd
2 changed files with 484 additions and 5 deletions

View File

@@ -197,10 +197,10 @@ export const toolList = [
title: '生成批量下载链接', title: '生成批量下载链接',
iconClass: 'mdi mdi-link-variant', iconClass: 'mdi mdi-link-variant',
desc: '根据设置,生成有一定规律的用于批量下载的链接。', desc: '根据设置,生成有一定规律的用于批量下载的链接。',
createdAt: '', createdAt: '2026-01-03',
updatedAt: '', updatedAt: '2026-01-03',
version: '0', version: '1',
enabled: false, enabled: true,
}, },
{ {
id: 'generate-random-string', id: 'generate-random-string',

View File

@@ -1,9 +1,488 @@
<template> <template>
<div class="tool-detail-page"></div> <div class="tool-detail-page">
<!-- 链接模板 -->
<n-card size="small" title="链接模板">
<n-form
class="form-no-feedback"
label-align="right"
label-placement="left"
label-width="auto"
>
<n-form-item label="链接模板:">
<n-input
v-model:value="linkBase"
placeholder="使用 {n} 表示变量例如https://example.com/file_{n}.jpg"
clearable
></n-input>
</n-form-item>
</n-form>
</n-card>
<!-- 配置参数 -->
<n-card size="small" title="参数配置">
<n-radio-group v-model:value="mode">
<!-- 等差数列 -->
<div class="mode-item">
<n-flex vertical>
<n-radio value="as">等差数列</n-radio>
<n-form
class="form-no-feedback"
label-align="right"
label-placement="left"
label-width="auto"
>
<n-flex gap="small">
<n-form-item label="首项:">
<n-input-number
v-model:value="modes.as.first"
:min="0"
:step="1"
></n-input-number>
</n-form-item>
<n-form-item label="公差:">
<n-input-number
v-model:value="modes.as.diff"
:min="1"
:step="1"
></n-input-number>
</n-form-item>
<n-form-item label="项数:">
<n-input-number
v-model:value="modes.as.count"
:min="1"
:max="1000"
:step="1"
></n-input-number>
</n-form-item>
</n-flex>
<n-flex gap="small">
<n-form-item label="选项:">
<n-checkbox
v-model:checked="modes.as.zero"
>补零</n-checkbox>
</n-form-item>
<n-form-item>
<n-checkbox
v-model:checked="modes.as.reverse"
>倒序</n-checkbox>
</n-form-item>
</n-flex>
</n-form>
</n-flex>
</div>
<!-- 等比数列 -->
<div class="mode-item">
<n-flex vertical>
<n-radio value="ps">等比数列</n-radio>
<n-form
class="form-no-feedback"
label-align="right"
label-placement="left"
label-width="auto"
>
<n-flex gap="small">
<n-form-item label="首项:">
<n-input-number
v-model:value="modes.ps.first"
:min="1"
:step="1"
></n-input-number>
</n-form-item>
<n-form-item label="公比:">
<n-input-number
v-model:value="modes.ps.diff"
:min="2"
:step="1"
></n-input-number>
</n-form-item>
<n-form-item label="项数:">
<n-input-number
v-model:value="modes.ps.count"
:min="1"
:max="1000"
:step="1"
></n-input-number>
</n-form-item>
</n-flex>
<n-flex gap="small">
<n-form-item label="选项:">
<n-checkbox
v-model:checked="modes.ps.zero"
>补零</n-checkbox>
</n-form-item>
<n-form-item>
<n-checkbox
v-model:checked="modes.ps.reverse"
>倒序</n-checkbox>
</n-form-item>
</n-flex>
</n-form>
</n-flex>
</div>
<!-- 字母变化 -->
<div class="mode-item">
<n-flex vertical>
<n-radio value="lc">字母变化</n-radio>
<n-form
class="form-no-feedback"
label-align="right"
label-placement="left"
label-width="auto"
>
<n-flex gap="small" align-items="center">
<n-form-item label="从:">
<n-input
v-model:value="modes.lc.start"
:maxlength="1"
placeholder="a"
></n-input>
</n-form-item>
<n-form-item label="到:">
<n-input
v-model:value="modes.lc.end"
:maxlength="1"
placeholder="z"
></n-input>
</n-form-item>
<n-form-item>
<n-checkbox
v-model:checked="modes.lc.reverse"
>倒序</n-checkbox>
</n-form-item>
</n-flex>
</n-form>
</n-flex>
</div>
</n-radio-group>
</n-card>
<!-- 操作 -->
<n-card size="small" title="操作">
<n-flex gap="small">
<n-button
type="primary"
@click="handleGenerate"
>生成链接</n-button>
<n-button
type="primary"
:disabled="!linkResult"
@click="handleCopy"
>复制结果</n-button>
<n-button
@click="handleClear"
>清空结果</n-button>
</n-flex>
</n-card>
<!-- 生成结果 -->
<n-card size="small" title="生成结果">
<n-form
class="form-no-feedback"
label-align="right"
label-placement="left"
label-width="auto"
>
<n-form-item>
<n-input
v-model:value="linkResult"
placeholder="生成的链接将显示在这里"
type="textarea"
:readonly="true"
:rows="10"
></n-input>
</n-form-item>
</n-form>
</n-card>
</div>
</template> </template>
<script setup> <script setup>
import {
NButton, NCard, NCheckbox, NFlex,
NForm, NFormItem, NInput, NInputNumber,
NRadio, NRadioGroup,
} from 'naive-ui';
import {
reactive, ref,
} from 'vue';
import {
$message,
} from '@/assets/js/naive-ui';
import {
useClipboard,
} from '@vueuse/core';
// 链接模板
const linkBase = ref('');
// 生成结果
const linkResult = ref('');
// 正则,匹配链接变量
const linkRegExp = /\{n\}/g;
// 当前模式
const mode = ref('as');
// 模式配置
const modes = reactive({
// 等差数列Arithmetic Sequence
as: {
first: 0,
diff: 1,
count: 10,
zero: false,
reverse: false,
},
// 等比数列Proportional Sequence
ps: {
first: 1,
diff: 2,
count: 10,
zero: false,
reverse: false,
},
// 字母变化Letter Change
lc: {
start: 'a',
end: 'z',
reverse: false,
},
});
// 剪贴板功能
const clipboard = useClipboard({
legacy: true,
read: false,
});
/** 处理生成链接 */
function handleGenerate() {
if (!linkBase.value) {
$message.warning('请输入链接模板');
return;
}
switch (mode.value) {
case 'as':
linkResult.value = generateSeq('as');
break;
case 'ps':
linkResult.value = generateSeq('ps');
break;
case 'lc':
linkResult.value = generateLetter();
break;
default:
break;
}
}
/**
* @description 生成数列
* @param {string} type 类型as - 等差数列ps - 等比数列)
*/
function generateSeq(type) {
let linkBaseVal = linkBase.value;
if (!linkBaseVal) {
return '';
}
let data = modes[type];
if (!data) {
return '';
}
let nFirst = data.first;
let nCount = data.count;
let nDiff = data.diff;
let nResult = {
digits: 0, // 最大位数
numbers: [], // 生成的数值
};
let links = [];
// 等差数列公式
let expAS = (i) => {
return (nFirst + (i - 1) * nDiff);
};
// 等比数列公式
let expPS = (i) => {
return (nFirst * Math.pow(nDiff, (i - 1)));
};
// 实际使用的公式
let exp = (type === 'as' ? expAS : expPS);
// 生成数值
for (let i = 1; i <= nCount; i++) {
// 等差数列 / 等比数列
let n = exp(i);
let digits = Math.abs(n).toString().length;
(digits > nResult.digits) && (nResult.digits = digits);
nResult.numbers.push(n);
}
// 补零
if (data.zero) {
let digits = nResult.digits;
let base = Math.pow(10, digits);
let numbers = nResult.numbers;
for (let i = 0; i < numbers.length; i++) {
let n = numbers[i];
if (n >= 0) {
numbers[i] = (n / base).toFixed(digits).substr(2);
} else {
numbers[i] = '-' + (n / base).toFixed(digits).substr(3);
}
}
}
// 倒序
if (data.reverse) {
nResult.numbers.reverse();
}
// 生成链接
nResult.numbers.forEach((n) => {
links.push(linkBaseVal.replace(linkRegExp, n));
});
return links.join('\n');
}
/**
* @description 生成字母变化链接
* @returns {string} 生成结果
*/
function generateLetter() {
let linkBaseVal = linkBase.value;
if (!linkBaseVal) {
return '';
}
let data = modes.lc;
// 编码数值
let cStart = data.start.charCodeAt(0);
let cEnd = data.end.charCodeAt(0);
let chars = [];
let links = [];
// 生成字母
if (cStart >= 65 && cStart <= 122 && cEnd >= 65 && cEnd <= 122) {
if (cStart < cEnd) {
for (let i = cStart; i <= cEnd; i++) {
// 跳过符号 [ \ ] ^ _ `
if (i >= 91 && i <= 96) {
continue;
}
chars.push(String.fromCharCode(i));
}
} else if (cStart > cEnd) {
$message.warning('字母先后顺序有误。注意:大写字母需要在前。');
return '';
} else if (cStart === cEnd) {
$message.warning('仅有 1 条链接,无需生成。');
return '';
} else {
$message.error('未知错误。');
return '';
}
} else {
$message.warning('输入有误,请检查字母范围。');
return '';
}
// 倒序
if (data.reverse) {
chars.reverse();
}
// 生成链接
chars.forEach((c) => {
links.push(linkBaseVal.replace(linkRegExp, c));
});
return links.join('\n');
}
/** 处理复制结果 */
function handleCopy() {
if (clipboard.isSupported) {
return clipboard.copy(linkResult.value).then(() => {
$message.success('复制成功');
}).catch((error) => {
console.error('复制失败:');
console.error(error);
$message.error('复制失败:异常');
});
} else {
$message.error('复制失败:当前浏览器不支持该操作');
return Promise.resolve();
}
}
/** 处理清空结果 */
function handleClear() {
linkResult.value = '';
}
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
.tool-detail-page {
:deep(.n-input-number) {
width: 8em;
}
:deep(.n-input) {
max-width: 48em;
}
:deep(.n-input--textarea) {
max-width: 48em;
font-family: monospace;
}
.mode-item {
padding: 12px 0;
border-bottom: 1px solid #eee;
&:last-child {
border-bottom: none;
}
:deep(.n-form) {
margin-left: 2em;
}
}
}
</style> </style>