<template>
<view class="container">
<view class="header">
<text class="title">历史记录</text>
</view>
<view class="content">
<view v-if="history.length === 0" class="empty-history">
<text class="empty-text">暂无历史记录</text>
</view>
<view v-else>
<view class="history-item" v-for="(item, index) in history" :key="index" @click="viewDetail(item)">
<view class="original">
<text class="original-text">{{item.original}}</text>
</view>
<view class="language-info">
<text class="language-pair">{{languagePair(item.from, item.to)}}</text>
<text class="arrow">></text>
</view>
</view>
</view>
</view>
<view class="footer">
<navigator url="/pages/translate/translate" class="translate-btn">
<text class="icon translate-icon"></text>
<text class="text">翻译</text>
</navigator>
</view>
</view>
</template>
<script>
export default {
data() {
return {
languageCodes: {
'zh-CHS': '简体中文',
'zh-CHT': '繁体中文',
'en': '英文',
'ja': '日文',
'ko': '韩文',
'fr': '法文',
'es': '西班牙文'
},
history: []
};
},
onLoad() {
this.loadHistory();
},
methods: {
loadHistory() {
const history = uni.getStorageSync('translationHistory') || [];
this.history = history;
},
languagePair(from, to) {
return `${this.languageCodes[from]}->${this.languageCodes[to]}`;
},
viewDetail(item) {
uni.navigateTo({
url: `/pages/result/result?original=${encodeURIComponent(item.original)}&translation=${encodeURIComponent(item.translation)}&from=${item.from}&to=${item.to}`
});
}
}
};
</script>
<style scoped>
.container {
padding: 0 20rpx;
background-color: #f0f5ff;
height: 100vh;
box-sizing: border-box;
}
.header {
display: flex;
justify-content: center;
padding: 20rpx 0;
}
.title {
font-size: 36rpx;
font-weight: bold;
color: #ffffff;
background-color: #2b81f5;
padding: 10rpx 40rpx;
border-radius: 10rpx;
}
.content {
height: calc(100vh - 140rpx);
overflow-y: auto;
}
.history-item {
background-color: #ffffff;
border-radius: 15rpx;
padding: 20rpx;
margin-bottom: 20rpx;
}
.original {
margin-bottom: 10rpx;
}
.original-text {
font-size: 28rpx;
color: #333333;
}
.language-info {
display: flex;
align-items: center;
color: #666666;
font-size: 24rpx;
}
.language-pair {
margin-right: 10rpx;
}
.arrow {
margin: 0 5rpx;
}
.empty-history {
display: flex;
justify-content: center;
align-items: center;
height: 100%;
}
.empty-text {
font-size: 32rpx;
color: #999999;
}
.footer {
position: fixed;
bottom: 0;
width: 100%;
display: flex;
justify-content: space-around;
background-color: #ffffff;
padding: 15rpx 0;
border-top: 1px solid #e0e0e0;
}
.translate-btn {
display: flex;
flex-direction: column;
align-items: center;
}
.translate-icon {
display: inline-block;
width: 40rpx;
height: 40rpx;
background-color: #2b81f5;
border-radius: 50%;
margin-bottom: 5rpx;
}
</style> <template>
<view class="container">
<view class="header">
<navigator open-type="navigateBack" class="back-btn">
<text class="back-icon"></text>
</navigator>
<text class="title">翻译结果</text>
</view>
<view class="content">
<view class="language-info">
<text class="language-pair">{{languagePair}}</text>
<text class="delete-btn" @click="deleteTranslation">删除</text>
</view>
<view class="divider"></view>
<view class="original-section">
<text class="section-title">原文</text>
<text class="original-text">{{originalText}}</text>
</view>
<view class="divider"></view>
<view class="translation-section">
<text class="section-title">译文</text>
<text class="translation-text">{{translationText}}</text>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
languageCodes: {
'zh-CHS': '简体中文',
'zh-CHT': '繁体中文',
'en': '英文',
'ja': '日文',
'ko': '韩文',
'fr': '法文',
'es': '西班牙文'
},
originalText: '',
translationText: '',
from: '',
to: '',
languagePair: ''
};
},
onLoad(options) {
this.originalText = decodeURIComponent(options.original);
this.translationText = decodeURIComponent(options.translation);
this.from = options.from;
this.to = options.to;
this.languagePair = `${this.languageCodes[this.from]}->${this.languageCodes[this.to]}`;
},
methods: {
deleteTranslation() {
const history = uni.getStorageSync('translationHistory') || [];
const updatedHistory = history.filter(item =>
item.original !== this.originalText ||
item.from !== this.from ||
item.to !== this.to
);
uni.setStorageSync('translationHistory', updatedHistory);
uni.showToast({
title: '已删除',
icon: 'success'
});
uni.navigateBack();
}
}
};
</script>
<style scoped>
.container {
padding: 0 20rpx;
background-color: #f0f5ff;
height: 100vh;
box-sizing: border-box;
position: relative;
}
.header {
display: flex;
align-items: center;
padding: 20rpx 0;
}
.back-btn {
position: absolute;
left: 20rpx;
top: 20rpx;
}
.back-icon {
display: inline-block;
width: 40rpx;
height: 40rpx;
border: 2px solid #2b81f5;
border-radius: 50%;
transform: rotate(45deg);
margin-right: 20rpx;
}
.title {
font-size: 36rpx;
font-weight: bold;
color: #ffffff;
background-color: #2b81f5;
padding: 10rpx 40rpx;
border-radius: 10rpx;
margin: 0 auto;
}
.content {
background-color: #ffffff;
border-radius: 15rpx;
padding: 30rpx;
margin-bottom: 20rpx;
}
.language-info {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20rpx;
}
.language-pair {
font-size: 28rpx;
color: #333333;
}
.delete-btn {
font-size: 24rpx;
color: #ff5252;
}
.divider {
height: 1px;
background-color: #e0e0e0;
margin: 20rpx 0;
}
.section-title {
font-size: 28rpx;
color: #666666;
margin-bottom: 10rpx;
}
.original-text, .translation-text {
font-size: 32rpx;
color: #333333;
line-height: 1.5;
}
</style> <template>
<view class="container">
<view class="header">
<text class="title">智云翻译</text>
</view>
<view class="content">
<view class="language-selector">
<text class="label">源语言</text>
<picker mode="selector" :range="languages" @change="onSourceLanguageChange">
<view class="picker">{{ sourceLanguage }}</view>
</picker>
</view>
<view class="language-selector">
<text class="label">目标语言</text>
<picker mode="selector" :range="languages" @change="onTargetLanguageChange">
<view class="picker">{{ targetLanguage }}</view>
</picker>
</view>
<view class="input-area">
<textarea v-model="inputText" placeholder="请输入要翻译的文本" @input="onInput" />
</view>
<view class="button-group">
<button class="btn reset-btn" @click="reset">重置</button>
<button class="btn translate-btn" @click="translate">翻译</button>
</view>
</view>
<view class="footer">
<navigator url="/pages/history/history" class="history-btn">
<text class="icon history-icon"></text>
<text class="text">历史</text>
</navigator>
<navigator url="/pages/translate/translate" class="translate-btn">
<text class="icon translate-icon"></text>
<text class="text">翻译</text>
</navigator>
</view>
</view>
</template>
<script>
import axios from 'axios';
export default {
data() {
return {
appKey: 'YOUR_APP_KEY', // 替换为你的应用ID
appSecret: 'YOUR_APP_SECRET', // 替换为你的应用密钥
languages: ['简体中文', '繁体中文', '英文', '日文', '韩文', '法文', '西班牙文'],
languageCodes: {
'简体中文': 'zh-CHS',
'繁体中文': 'zh-CHT',
'英文': 'en',
'日文': 'ja',
'韩文': 'ko',
'法文': 'fr',
'西班牙文': 'es'
},
sourceLanguage: '英文',
targetLanguage: '简体中文',
inputText: ''
};
},
methods: {
onSourceLanguageChange(e) {
this.sourceLanguage = this.languages[e.detail.value];
},
onTargetLanguageChange(e) {
this.targetLanguage = this.languages[e.detail.value];
},
onInput(e) {
this.inputText = e.detail.value;
},
reset() {
this.inputText = '';
},
async translate() {
if (!this.inputText.trim()) {
uni.showToast({
title: '请输入要翻译的文本',
icon: 'none'
});
return;
}
const sourceCode = this.languageCodes[this.sourceLanguage];
const targetCode = this.languageCodes[this.targetLanguage];
try {
const salt = Date.now().toString();
const curtime = Math.round(Date.now() / 1000);
const signStr = this.appKey + this.truncate(this.inputText) + salt + curtime + this.appSecret;
const sign = this.sha256(signStr);
const url = 'https://openapi.youdao.com/api';
const response = await axios.post(url, null, {
params: {
q: this.inputText,
from: sourceCode,
to: targetCode,
appKey: this.appKey,
salt: salt,
sign: sign,
signType: 'v3',
curtime: curtime
}
});
if (response.data.errorCode === '0') {
this.saveTranslationHistory(this.inputText, response.data.translation[0], sourceCode, targetCode);
uni.navigateTo({
url: `/pages/result/result?original=${encodeURIComponent(this.inputText)}&translation=${encodeURIComponent(response.data.translation[0])}&from=${sourceCode}&to=${targetCode}`
});
} else {
uni.showToast({
title: `翻译失败: ${response.data.errorCode}`,
icon: 'none'
});
}
} catch (error) {
console.error('翻译失败:', error);
uni.showToast({
title: '翻译失败,请重试',
icon: 'none'
});
}
},
truncate(q) {
const len = q.length;
if (len <= 20) return q;
return q.substring(0, 10) + len + q.substring(len - 10, len);
},
sha256(str) {
return 'mock-sha256-sign';
},
saveTranslationHistory(original, translation, from, to) {
const history = uni.getStorageSync('translationHistory') || [];
history.unshift({
original,
translation,
from,
to,
timestamp: Date.now()
});
if (history.length > 50) {
history.pop();
}
uni.setStorageSync('translationHistory', history);
}
}
};
</script>
<style scoped>
.container {
padding: 20rpx;
background-color: #f0f5ff;
height: 100vh;
box-sizing: border-box;
}
.header {
display: flex;
justify-content: center;
padding: 20rpx 0;
}
.title {
font-size: 36rpx;
font-weight: bold;
color: #ffffff;
background-color: #2b81f5;
padding: 10rpx 40rpx;
border-radius: 10rpx;
}
.content {
background-color: #ffffff;
border-radius: 15rpx;
padding: 30rpx;
margin-bottom: 20rpx;
}
.language-selector {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20rpx;
}
.label {
font-size: 28rpx;
color: #666666;
}
.picker {
font-size: 28rpx;
color: #333333;
}
.input-area {
width: 100%;
height: 300rpx;
background-color: #f8f8f8;
border-radius: 10rpx;
padding: 20rpx;
margin-bottom: 20rpx;
}
textarea {
width: 100%;
height: 100%;
font-size: 28rpx;
line-height: 1.5;
}
.button-group {
display: flex;
justify-content: space-between;
}
.btn {
width: 48%;
height: 80rpx;
line-height: 80rpx;
text-align: center;
font-size: 30rpx;
color: #ffffff;
border-radius: 10rpx;
}
.reset-btn {
background-color: #f0f0f0;
color: #333333;
}
.translate-btn {
background-color: #2b81f5;
}
.footer {
position: fixed;
bottom: 0;
width: 100%;
display: flex;
justify-content: space-around;
background-color: #ffffff;
padding: 15rpx 0;
border-top: 1px solid #e0e0e0;
}
.history-btn, .translate-btn {
display: flex;
flex-direction: column;
align-items: center;
}
.history-icon, .translate-icon {
display: inline-block;
width: 40rpx;
height: 40rpx;
background-color: #2b81f5;
border-radius: 50%;
margin-bottom: 5rpx;
}
.text {
font-size: 24rpx;
color: #666666;
}
</style> <template>
<view>
<navigator url="/pages/translate/translate">
<view class="app-icon"></view>
<text class="app-title">智云翻译</text>
</navigator>
</view>
</template>
<script>
export default {
name: 'App'
};
</script>
<style>
.app-icon {
display: inline-block;
width: 100px;
height: 100px;
background-color: #2b81f5;
border-radius: 20px;
margin: 100px auto;
}
.app-title {
font-size: 24px;
text-align: center;
color: #333;
}
</style> <!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<script>
var coverSupport = 'CSS' in window && typeof CSS.supports === 'function' && (CSS.supports('top: env(a)') ||
CSS.supports('top: constant(a)'))
document.write(
'<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0' +
(coverSupport ? ', viewport-fit=cover' : '') + '" />')
</script>
<title></title>
</head>
<body>
<div id="app"><!--app-html--></div>
<script type="module" src="/main.js"></script>
</body>
</html> import App from './App'
// #ifndef VUE3
import Vue from 'vue'
import './uni.promisify.adaptor'
Vue.config.productionTip = false
App.mpType = 'app'
const app = new Vue({
...App
})
app.$mount()
// #endif
// #ifdef VUE3
import { createSSRApp } from 'vue'
export function createApp() {
const app = createSSRApp(App)
return {
app
}
}
import { createApp } from 'vue';
import App from './App.vue';
const app = createApp(App);
app.mount('#app');
// #endif {
"pages": [
{
"path": "pages/translate/translate",
"style": {
"navigationBarTitleText": "翻译"
}
},
{
"path": "pages/result/result",
"style": {
"navigationBarTitleText": "翻译结果"
}
},
{
"path": "pages/history/history",
"style": {
"navigationBarTitleText": "历史记录"
}
}
],
"window": {
"navigationBarBackgroundColor": "#ffffff",
"navigationBarTextStyle": "black",
"navigationBarTitleText": "智云翻译",
"backgroundColor": "#ffffff",
"pullRefresh": {
"enable": true
}
}
}