初步实现,会员临期提醒功能,待完善
This commit is contained in:
@@ -1,3 +1,3 @@
|
||||
NODE_ENV=development
|
||||
VITE_APP_API=http://152.136.153.72:27005/admin
|
||||
# VITE_APP_API=http://10.8.0.3/api/admin
|
||||
# VITE_APP_API=http://152.136.153.72:27005/admin
|
||||
VITE_APP_API=http://10.8.0.3:8888/admin
|
||||
|
||||
@@ -21,28 +21,3 @@ npm install
|
||||
# 启动项目
|
||||
npm run dev
|
||||
```
|
||||
|
||||
> 如网络不稳定,安装时出错或进度过慢!请移步 [cnpm](https://npmmirror.com/) 淘宝镜像进行安装。
|
||||
|
||||
启动完成后,会自动打开浏览器访问 [http://localhost:8001](http://localhost:8001),如您看到下面的页面代表`前端项目`运行成功!因为前后端分离项目,需保证`前端项目`和`后台项目`分别独立正常运行。
|
||||
|
||||
请留意下面的页面,其中`验证码`未能正常显示,控制台有`API请求`报错信息!这时需检查`后台项目`是否正常运行。
|
||||
|
||||
|
||||
|
||||
## 如何交流、反馈、参与贡献?
|
||||
- 开发文档:https://www.renren.io/guide/security
|
||||
- 官方社区:https://www.renren.io/community
|
||||
- [人人开源](https://www.renren.io):https://www.renren.io
|
||||
- 如需关注项目最新动态,请Watch、Star项目,同时也是对项目最好的支持
|
||||
- 技术讨论、二次开发等咨询、问题和建议,请移步到官方社区,我会在第一时间进行解答和回复!
|
||||
- 微信扫码并关注【人人开源】,获得项目最新动态及更新提醒<br>
|
||||
|
||||
<br>
|
||||
|
||||
## 微信交流群
|
||||
我们提供了微信交流群,扫码下面的二维码,关注【人人开源】公众号,回复【加群】,即可根据提示加入微信群!
|
||||
<br><br>
|
||||
|
||||
<br>
|
||||
<br>
|
||||
|
||||
@@ -28,7 +28,7 @@ export default defineComponent({
|
||||
<span :class="`rr-header-ctx-logo-img-wrap ${'enabled-logo-' + app.enabledLogo}`">
|
||||
<!-- 支持显示图片logo或者产品名称缩写,二选一模式,通过注释开启功能,app.enabledLogo控制正常模式下图片logo是否显示,如果有图片logo,收起状态会强制显示图片logo -->
|
||||
<!-- <img :src="props.logoUrl" class="rr-header-ctx-logo-img" :alt="props.logoName" /> -->
|
||||
<span>人人</span>
|
||||
<span>DL</span>
|
||||
<span class="rr-header-ctx-logo-line"></span>
|
||||
</span>
|
||||
<span class="rr-header-ctx-logo-text">{{ props.logoName }}</span>
|
||||
|
||||
@@ -1,437 +0,0 @@
|
||||
<template>
|
||||
<div class="tts-app">
|
||||
<el-card class="box-card">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>GPT-SoVITS 语音合成</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- API 配置 -->
|
||||
<!-- <div class="config-section">
|
||||
<h3>API 配置</h3>
|
||||
<el-form :model="apiConfig" label-width="120px">
|
||||
<el-form-item label="API 地址">
|
||||
<el-input v-model="apiConfig.baseUrl" placeholder="例如: http://127.0.0.1:9880" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div> -->
|
||||
|
||||
<!-- 角色选择 -->
|
||||
<div class="character-section">
|
||||
<h3>角色选择</h3>
|
||||
<el-form label-width="120px">
|
||||
<el-form-item label="选择角色">
|
||||
<el-select
|
||||
v-model="selectedCharacterId"
|
||||
placeholder="请选择角色"
|
||||
@change="switchModelByCharacter"
|
||||
:loading="modelSwitching.gpt || modelSwitching.sovits"
|
||||
>
|
||||
<el-option
|
||||
v-for="char in availableCharacters"
|
||||
:key="char.id"
|
||||
:label="char.name"
|
||||
:value="char.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
|
||||
<!-- 合成参数 -->
|
||||
<div class="params-section">
|
||||
<h3>合成参数</h3>
|
||||
<el-form :model="ttsParams" :rules="ttsRules" ref="ttsFormRef" label-width="150px" :disabled="isGenerating">
|
||||
<el-form-item label="待合成提示文本" prop="text">
|
||||
<el-input
|
||||
v-model="ttsParams.text"
|
||||
type="textarea"
|
||||
:rows="4"
|
||||
placeholder="请输入要合成的文本..."
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="提示文本语言" prop="prompt_lang">
|
||||
<el-select v-model="ttsParams.prompt_lang" placeholder="请选择提示文本语言">
|
||||
<el-option label="中文" value="zh" />
|
||||
<el-option label="英文" value="en" />
|
||||
<el-option label="日文" value="ja" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
|
||||
<el-form-item label="生成语音语言" prop="text_lang">
|
||||
<el-select v-model="ttsParams.text_lang" placeholder="请选择文本语言">
|
||||
<el-option label="中文" value="zh" />
|
||||
<el-option label="英文" value="en" />
|
||||
<el-option label="日文" value="ja" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<!-- 参考音频路径现在由 selectedCharacter 决定 -->
|
||||
<!-- <el-form-item label="参考音频路径" prop="ref_audio_path">
|
||||
<el-input
|
||||
v-model="ttsParams.ref_audio_path"
|
||||
readonly
|
||||
placeholder="由角色自动设置"
|
||||
:disabled="true"
|
||||
/>
|
||||
</el-form-item> -->
|
||||
<!-- 提示文本 (可选) 现在由 selectedCharacter 决定 -->
|
||||
<!-- <el-form-item label="提示文本 (可选)">
|
||||
<el-input
|
||||
v-model="ttsParams.prompt_text"
|
||||
type="textarea"
|
||||
:rows="2"
|
||||
readonly
|
||||
placeholder="由角色自动设置 (如果配置)"
|
||||
:disabled="true"
|
||||
/>
|
||||
</el-form-item> -->
|
||||
|
||||
<el-form-item label="文本分割方式">
|
||||
<el-select v-model="ttsParams.text_split_method">
|
||||
<el-option label="不切" value="cut0" />
|
||||
<el-option label="按。切" value="cut1" />
|
||||
<el-option label="按,。切" value="cut2" />
|
||||
<el-option label="按?!切" value="cut3" />
|
||||
<el-option label="按,。?!切" value="cut4" />
|
||||
<el-option label="按每行切" value="cut5" />
|
||||
<el-option label="按换行切" value="cut6" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<!-- <el-form-item label="流式传输">
|
||||
<el-switch v-model="ttsParams.streaming_mode" />
|
||||
</el-form-item> -->
|
||||
<!-- <el-form-item label="批处理大小">
|
||||
<el-input-number v-model="ttsParams.batch_size" :min="1" :max="10" />
|
||||
</el-form-item> -->
|
||||
<!-- <el-form-item label="Top-K">
|
||||
<el-input-number v-model="ttsParams.top_k" :min="1" :max="100" />
|
||||
</el-form-item> -->
|
||||
<!-- <el-form-item label="Top-P">
|
||||
<el-slider v-model="ttsParams.top_p" :min="0" :max="1" :step="0.01" />
|
||||
</el-form-item> -->
|
||||
<el-form-item label="音频随机参数">
|
||||
<el-slider v-model="ttsParams.temperature" :min="0" :max="2" :step="0.01" />
|
||||
</el-form-item>
|
||||
<el-form-item label="音频语速倍率">
|
||||
<el-slider v-model="ttsParams.speed_factor" :min="0.1" :max="3" :step="0.1" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
|
||||
<!-- 控制按钮 -->
|
||||
<div class="controls-section">
|
||||
<el-button type="primary" @click="synthesize" :loading="isGenerating">
|
||||
{{ isGenerating ? '生成中...' : '合成语音' }}
|
||||
</el-button>
|
||||
<el-button @click="stopGeneration" :disabled="!isGenerating">停止</el-button>
|
||||
<el-button @click="clearAudio">清空音频</el-button>
|
||||
</div>
|
||||
|
||||
<!-- 音频播放 -->
|
||||
<div class="audio-section" v-if="audioUrl">
|
||||
<h3>合成结果</h3>
|
||||
<audio :src="audioUrl" controls class="audio-player" />
|
||||
</div>
|
||||
|
||||
<!-- 日志 -->
|
||||
<div class="log-section">
|
||||
<h3>日志</h3>
|
||||
<pre class="log-content">{{ log }}</pre>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, computed, onUnmounted } from 'vue';
|
||||
import { ElMessage, FormInstance, FormRules } from 'element-plus';
|
||||
|
||||
// API 配置
|
||||
const apiConfig = reactive({
|
||||
baseUrl: 'http://127.0.0.1:9880', // 默认地址
|
||||
});
|
||||
|
||||
// 可用角色列表 (在这里添加你的角色)
|
||||
const availableCharacters = [
|
||||
{
|
||||
id: 'yier',
|
||||
name: '一二(yier)',
|
||||
gptPath: 'E:\\AI\\GPT-SoVITS-v4-20250529\\GPT_weights_v2\\yier-e20.ckpt',
|
||||
sovitsPath: 'E:\\AI\\GPT-SoVITS-v4-20250529\\SoVITS_weights_v2\\yier_e8_s144.pth',
|
||||
refAudioPath: 'D:\\UserData\\Desktop\\声音素材\\temp\\yier001.mp3',
|
||||
// promptText: 'This is a prompt for 一二.' // 示例:可选的提示文本
|
||||
},
|
||||
{
|
||||
id: 'bubu',
|
||||
name: '布布(bubu)',
|
||||
gptPath: 'E:\\AI\\GPT-SoVITS-v4-20250529\\GPT_weights_v2\\bubu-e20.ckpt',
|
||||
sovitsPath: 'E:\\AI\\GPT-SoVITS-v4-20250529\\SoVITS_weights_v2\\bubu_e8_s136.pth',
|
||||
refAudioPath: 'D:\\UserData\\Desktop\\声音素材\\temp\\bubu001.wav'
|
||||
},
|
||||
];
|
||||
|
||||
// 当前选中的角色 ID
|
||||
const selectedCharacterId = ref('yier'); // 默认选择一二
|
||||
|
||||
// 计算属性:根据 ID 获取当前选中的角色信息
|
||||
const currentCharacter = computed(() => {
|
||||
return availableCharacters.find(char => char.id === selectedCharacterId.value) || null;
|
||||
});
|
||||
|
||||
// TTS 参数
|
||||
const ttsParams = reactive({
|
||||
text: '欢迎来到后台配音系统,这是后台语音合成示例。',
|
||||
text_lang: 'zh',
|
||||
ref_audio_path: computed(() => currentCharacter.value?.refAudioPath || '').value, // 初始化时取默认角色的路径
|
||||
aux_ref_audio_paths: [],
|
||||
prompt_text: computed(() => currentCharacter.value?.promptText || '').value, // 初始化时取默认角色的提示文本
|
||||
prompt_lang: 'zh',
|
||||
top_k: 5,
|
||||
top_p: 1.0,
|
||||
temperature: 1.0,
|
||||
text_split_method: 'cut5',
|
||||
batch_size: 1,
|
||||
batch_threshold: 0.75,
|
||||
split_bucket: true,
|
||||
speed_factor: 1.0,
|
||||
streaming_mode: true,
|
||||
seed: -1,
|
||||
parallel_infer: true,
|
||||
repetition_penalty: 1.35,
|
||||
sample_steps: 32,
|
||||
super_sampling: false,
|
||||
media_type: 'wav',
|
||||
});
|
||||
|
||||
// 表单验证规则
|
||||
const ttsRules: FormRules = {
|
||||
text: [
|
||||
{ required: true, message: '请输入待合成的文本', trigger: 'blur' },
|
||||
],
|
||||
text_lang: [
|
||||
{ required: true, message: '请选择文本语言', trigger: 'change' },
|
||||
],
|
||||
prompt_lang: [
|
||||
{ required: true, message: '请选择提示文本语言', trigger: 'change' },
|
||||
],
|
||||
};
|
||||
|
||||
// 模型切换状态
|
||||
const modelSwitching = reactive({
|
||||
gpt: false,
|
||||
sovits: false,
|
||||
});
|
||||
|
||||
const ttsFormRef = ref<FormInstance>();
|
||||
const isGenerating = ref(false);
|
||||
const audioUrl = ref<string | null>(null); // 初始值设为 null
|
||||
const log = ref('');
|
||||
|
||||
// 切换模型辅助函数
|
||||
const switchGPTModel = async (path: string) => {
|
||||
try {
|
||||
const response = await fetch(`${apiConfig.baseUrl}/set_gpt_weights?weights_path=${encodeURIComponent(path)}`, {
|
||||
method: 'GET',
|
||||
});
|
||||
const responseText = await response.text(); // 读取响应文本
|
||||
if(response.status === 200) {
|
||||
log.value += 'GPT 模型切换成功!\n';
|
||||
ElMessage.success('GPT 模型切换成功');
|
||||
return true;
|
||||
} else {
|
||||
log.value += `GPT 模型切换失败: ${responseText}\n`;
|
||||
ElMessage.error(`GPT 模型切换失败: ${responseText}`);
|
||||
return false;
|
||||
}
|
||||
} catch (error: any) {
|
||||
log.value += `GPT 模型切换网络错误: ${error.message}\n`;
|
||||
ElMessage.error(`GPT 模型切换网络错误: ${error.message}`);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const switchSoVITSModel = async (path: string) => {
|
||||
try {
|
||||
const response = await fetch(`${apiConfig.baseUrl}/set_sovits_weights?weights_path=${encodeURIComponent(path)}`, {
|
||||
method: 'GET',
|
||||
});
|
||||
const responseText = await response.text(); // 读取响应文本
|
||||
if(response.status === 200) {
|
||||
log.value += 'SoVITS 模型切换成功!\n';
|
||||
ElMessage.success('SoVITS 模型切换成功');
|
||||
return true;
|
||||
} else {
|
||||
log.value += `SoVITS 模型切换失败: ${responseText}\n`;
|
||||
ElMessage.error(`SoVITS 模型切换失败: ${responseText}`);
|
||||
return false;
|
||||
}
|
||||
} catch (error: any) {
|
||||
log.value += `SoVITS 模型切换网络错误: ${error.message}\n`;
|
||||
ElMessage.error(`SoVITS 模型切换网络错误: ${error.message}`);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
// 根据选中的角色切换模型
|
||||
const switchModelByCharacter = async (characterId: string) => {
|
||||
const char = currentCharacter.value;
|
||||
if (!char) {
|
||||
ElMessage.error(`找不到角色 ID "${characterId}" 的配置。`);
|
||||
log.value += `错误: 找不到角色 ID "${characterId}" 的配置。\n`;
|
||||
return;
|
||||
}
|
||||
|
||||
// 更新 ttsParams 中由角色决定的字段
|
||||
ttsParams.ref_audio_path = char.refAudioPath;
|
||||
ttsParams.prompt_text = char.promptText || '';
|
||||
|
||||
log.value += `开始切换模型为角色 "${char.name}"...\n`;
|
||||
|
||||
// 1. 切换 GPT 模型
|
||||
modelSwitching.gpt = true;
|
||||
log.value += `正在切换 GPT 模型到: ${char.gptPath}\n`;
|
||||
const gptSuccess = await switchGPTModel(char.gptPath);
|
||||
modelSwitching.gpt = false;
|
||||
|
||||
// 2. 切换 SoVITS 模型
|
||||
modelSwitching.sovits = true;
|
||||
log.value += `正在切换 SoVITS 模型到: ${char.sovitsPath}\n`;
|
||||
const sovitsSuccess = await switchSoVITSModel(char.sovitsPath);
|
||||
modelSwitching.sovits = false;
|
||||
|
||||
if (gptSuccess && sovitsSuccess) {
|
||||
log.value += `角色 "${char.name}" 的模型切换完成!\n`;
|
||||
} else {
|
||||
log.value += `角色 "${char.name}" 的模型切换部分失败或全部失败。\n`;
|
||||
}
|
||||
};
|
||||
|
||||
// 合成语音
|
||||
const synthesize = async () => {
|
||||
const validate = await ttsFormRef.value?.validate().catch(() => false);
|
||||
if (!validate) {
|
||||
ElMessage.error('请检查输入参数');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!currentCharacter.value) {
|
||||
ElMessage.error('当前角色配置无效,请重新选择。');
|
||||
return;
|
||||
}
|
||||
|
||||
isGenerating.value = true;
|
||||
log.value += '开始合成语音...\n';
|
||||
clearAudio(); // 清空之前的音频
|
||||
|
||||
try {
|
||||
// 构建 POST 请求体
|
||||
const requestBody = { ...ttsParams };
|
||||
// 如果 prompt_text 为空,则从 payload 中移除
|
||||
if (!requestBody.prompt_text) {
|
||||
delete requestBody.prompt_text;
|
||||
}
|
||||
|
||||
const response = await fetch(`${apiConfig.baseUrl}/tts`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(requestBody),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json().catch(() => ({ message: `HTTP Error ${response.status}` }));
|
||||
throw new Error(errorData.message || `HTTP Error ${response.status}`);
|
||||
}
|
||||
|
||||
// 检查返回的是否为音频流
|
||||
const contentType = response.headers.get('content-type');
|
||||
if (!contentType || !contentType.includes('audio')) {
|
||||
const errorText = await response.text();
|
||||
throw new Error(`API returned non-audio content: ${errorText}`);
|
||||
}
|
||||
|
||||
const audioBlob = await response.blob();
|
||||
// 创建临时 URL 用于播放和下载
|
||||
const newUrl = URL.createObjectURL(audioBlob);
|
||||
audioUrl.value = newUrl;
|
||||
log.value += '语音合成成功!\n';
|
||||
ElMessage.success('语音合成成功');
|
||||
} catch (error: any) {
|
||||
console.error('Synthesis failed:', error);
|
||||
log.value += `语音合成失败: ${error.message}\n`;
|
||||
ElMessage.error(`语音合成失败: ${error.message}`);
|
||||
} finally {
|
||||
isGenerating.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 停止生成 (当前 fetch 无法真正中断,主要重置 UI 状态)
|
||||
const stopGeneration = () => {
|
||||
isGenerating.value = false;
|
||||
log.value += '用户已请求停止生成。\n';
|
||||
ElMessage.info('已请求停止生成');
|
||||
};
|
||||
|
||||
// 清空音频
|
||||
const clearAudio = () => {
|
||||
if (audioUrl.value) {
|
||||
URL.revokeObjectURL(audioUrl.value);
|
||||
audioUrl.value = null;
|
||||
}
|
||||
};
|
||||
|
||||
// 组件卸载时清理 URL 对象
|
||||
onUnmounted(() => {
|
||||
clearAudio();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.tts-app {
|
||||
padding: 20px;
|
||||
max-width: 1000px;
|
||||
margin: 0 auto;
|
||||
|
||||
.box-card {
|
||||
min-width: 800px;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.config-section,
|
||||
.character-section,
|
||||
.params-section,
|
||||
.controls-section,
|
||||
.audio-section,
|
||||
.log-section {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.controls-section {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.audio-player {
|
||||
width: 100%;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.log-content {
|
||||
background-color: #f5f5f5;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
padding: 10px;
|
||||
height: 150px;
|
||||
overflow-y: auto;
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,142 @@
|
||||
<template>
|
||||
<el-dialog v-model="visible" :title="!dataForm.id ? '新增' : '修改'" :close-on-click-modal="false" :close-on-press-escape="false">
|
||||
<el-form :model="dataForm" :rules="rules" ref="dataFormRef" @keyup.enter="dataFormSubmitHandle()" label-width="120px">
|
||||
<el-form-item label=" 提供商" prop="provider">
|
||||
<el-input v-model="dataForm.provider" placeholder=" 提供商:阿里/腾讯/网易/百度/"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="名称" prop="name">
|
||||
<el-input v-model="dataForm.name" placeholder="名称"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input v-model="dataForm.remark" placeholder="备注"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="续费方式" prop="renewalType">
|
||||
<el-select v-model="dataForm.renewalType" placeholder="请选择续费方式" style="width: 240px">
|
||||
<el-option v-for="item in renewalType" :key="item.dictValue" :label="item.dictLabel" :value="item.dictValue" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="续费日" prop="renewalDate">
|
||||
<el-input-number v-model="dataForm.renewalDate" :min="1" :max="29" placeholder="具体续费日期 (1-29)" style="width: 100%" />
|
||||
</el-form-item>
|
||||
<el-form-item label="到期时间" prop="expireTime">
|
||||
<el-date-picker v-model="dataForm.expireTime" type="datetime" placeholder="选择到期时间" format="YYYY-MM-DD HH:mm:ss" value-format="YYYY-MM-DD HH:mm:ss" style="width: 100%" />
|
||||
</el-form-item>
|
||||
<el-form-item label="其他信息" prop="otherInfo">
|
||||
<el-input v-model="dataForm.otherInfo" placeholder="其他信息json形式"></el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="visible = false">取消</el-button>
|
||||
<el-button type="primary" @click="dataFormSubmitHandle()">确定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref, computed } from "vue";
|
||||
import baseService from "@/service/baseService";
|
||||
import { ElMessage } from "element-plus";
|
||||
import { useAppStore } from "@/store";
|
||||
import { getDictDataList } from "@/utils/utils";
|
||||
|
||||
const store = useAppStore();
|
||||
const renewalType = getDictDataList(store.state.dicts, "renewal_type");
|
||||
|
||||
const emit = defineEmits(["refreshDataList"]);
|
||||
|
||||
const visible = ref(false);
|
||||
const dataFormRef = ref();
|
||||
|
||||
const dataForm = reactive({
|
||||
id: "",
|
||||
provider: "",
|
||||
name: "",
|
||||
remark: "",
|
||||
otherInfo: "",
|
||||
renewalType: "",
|
||||
renewalDate: null as number | null,
|
||||
expireTime: "",
|
||||
|
||||
createDate: "",
|
||||
createUser: "",
|
||||
updateDate: "",
|
||||
updateUser: ""
|
||||
});
|
||||
|
||||
const getRules = () => ({
|
||||
provider: [{ required: true, message: "必填项不能为空", trigger: "blur" }],
|
||||
name: [{ required: true, message: "必填项不能为空", trigger: "blur" }],
|
||||
remark: [{ required: true, message: "必填项不能为空", trigger: "blur" }],
|
||||
renewalType: [{ required: true, message: "必填项不能为空", trigger: "blur" }],
|
||||
renewalDate:
|
||||
dataForm.renewalType !== "1"
|
||||
? [
|
||||
{ required: true, message: "续费日不能为空", trigger: "blur" },
|
||||
{ type: "number", min: 1, max: 29, message: "日期必须在1到29之间", trigger: "blur" }
|
||||
]
|
||||
: [{ type: "number", min: 1, max: 29, message: "日期必须在1到29之间", trigger: "blur" }],
|
||||
expireTime: [{ required: true, message: "到期时间不能为空", trigger: "blur" }]
|
||||
});
|
||||
|
||||
// 使用 computed 属性,使其响应式
|
||||
const rules = computed(() => getRules());
|
||||
|
||||
const init = (id?: number) => {
|
||||
visible.value = true;
|
||||
dataForm.id = "";
|
||||
|
||||
// 重置表单数据
|
||||
if (dataFormRef.value) {
|
||||
dataFormRef.value.resetFields();
|
||||
}
|
||||
|
||||
if (id) {
|
||||
getInfo(id);
|
||||
}
|
||||
};
|
||||
|
||||
// 获取信息
|
||||
const getInfo = (id: number) => {
|
||||
baseService.get("/baitutools/dlrenewalremind/" + id).then((res) => {
|
||||
const { data } = res; // 解构出 data 对象
|
||||
|
||||
// --- 新增处理逻辑 ---
|
||||
if (data.hasOwnProperty("renewalDate")) {
|
||||
if (typeof data.renewalDate === "string") {
|
||||
const parsed = parseInt(data.renewalDate, 10);
|
||||
data.renewalDate = isNaN(parsed) ? null : parsed;
|
||||
} else if (typeof data.renewalDate === "number") {
|
||||
data.renewalDate = data.renewalDate
|
||||
} else {
|
||||
data.renewalDate = null;
|
||||
}
|
||||
}
|
||||
|
||||
// 将处理后的 data 对象合并到 dataForm
|
||||
Object.assign(dataForm, data);
|
||||
});
|
||||
};
|
||||
|
||||
// 表单提交
|
||||
const dataFormSubmitHandle = () => {
|
||||
dataFormRef.value.validate((valid: boolean) => {
|
||||
if (!valid) {
|
||||
return false;
|
||||
}
|
||||
(!dataForm.id ? baseService.post : baseService.put)("/baitutools/dlrenewalremind", dataForm).then((res) => {
|
||||
ElMessage.success({
|
||||
message: "成功",
|
||||
duration: 500,
|
||||
onClose: () => {
|
||||
visible.value = false;
|
||||
emit("refreshDataList");
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
defineExpose({
|
||||
init
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,63 @@
|
||||
<template>
|
||||
<div class="mod-baitutools__dlrenewalremind">
|
||||
<el-form :inline="true" :model="state.dataForm" @keyup.enter="state.getDataList()">
|
||||
<el-form-item>
|
||||
<el-input v-model="state.dataForm.keywordName" placeholder="名称" clearable></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="state.getDataList()">查询</el-button>
|
||||
</el-form-item>
|
||||
|
||||
|
||||
<el-form-item>
|
||||
<el-button v-if="state.hasPermission('baitutools:dlrenewalremind:save')" type="primary" @click="addOrUpdateHandle()">新增</el-button>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button v-if="state.hasPermission('baitutools:dlrenewalremind:delete')" type="danger" @click="state.deleteHandle()">删除</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-table v-loading="state.dataListLoading" :data="state.dataList" border @selection-change="state.dataListSelectionChangeHandle" style="width: 100%">
|
||||
<el-table-column type="selection" header-align="center" align="center" width="50"></el-table-column>
|
||||
<el-table-column prop="provider" label=" 提供商" header-align="center" align="center"></el-table-column>
|
||||
<el-table-column prop="name" label="名称" header-align="center" align="center"></el-table-column>
|
||||
<el-table-column prop="remark" label="备注" header-align="center" align="center"></el-table-column>
|
||||
<el-table-column prop="otherInfo" label="其他信息" header-align="center" align="center"></el-table-column>
|
||||
<el-table-column prop="renewalType" label="续费方式" header-align="center" align="center"></el-table-column>
|
||||
<el-table-column prop="renewalDate" label="具体续费日期" width="130" header-align="center" align="center"></el-table-column>
|
||||
<el-table-column prop="expireTime" label="到期时间" header-align="center" align="center"></el-table-column>
|
||||
<el-table-column label="操作" fixed="right" header-align="center" align="center" width="150">
|
||||
<template v-slot="scope">
|
||||
<el-button v-if="state.hasPermission('baitutools:dlrenewalremind:update')" type="primary" link @click="addOrUpdateHandle(scope.row.id)">修改</el-button>
|
||||
<el-button v-if="state.hasPermission('baitutools:dlrenewalremind:delete')" type="primary" link @click="state.deleteHandle(scope.row.id)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-pagination :current-page="state.page" :page-sizes="[10, 20, 50, 100]" :page-size="state.limit" :total="state.total" layout="total, sizes, prev, pager, next, jumper" @size-change="state.pageSizeChangeHandle" @current-change="state.pageCurrentChangeHandle"> </el-pagination>
|
||||
<!-- 弹窗, 新增 / 修改 -->
|
||||
<add-or-update ref="addOrUpdateRef" @refreshDataList="state.getDataList">确定</add-or-update>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import useView from "@/hooks/useView";
|
||||
import { reactive, ref, toRefs } from "vue";
|
||||
import AddOrUpdate from "./renewalremind-add-or-update.vue";
|
||||
|
||||
const view = reactive({
|
||||
deleteIsBatch: true,
|
||||
getDataListURL: "/baitutools/dlrenewalremind/page",
|
||||
getDataListIsPage: true,
|
||||
exportURL: "/baitutools/dlrenewalremind/export",
|
||||
deleteURL: "/baitutools/dlrenewalremind",
|
||||
dataForm: {
|
||||
keywordName: "",
|
||||
}
|
||||
});
|
||||
|
||||
const state = reactive({ ...useView(view), ...toRefs(view) });
|
||||
|
||||
const addOrUpdateRef = ref();
|
||||
const addOrUpdateHandle = (id?: number) => {
|
||||
addOrUpdateRef.value.init(id);
|
||||
};
|
||||
</script>
|
||||
Reference in New Issue
Block a user