mirror of
https://github.com/dreamhunter2333/cloudflare_temp_email.git
synced 2026-09-04 23:17:34 +08:00
Unify address selection UI (#801)
* feat: unify address selection UI * docs: update changelog for address UI * feat: restore user mailbox tab
This commit is contained in:
@@ -0,0 +1,35 @@
|
|||||||
|
# Repository Guidelines
|
||||||
|
|
||||||
|
## Project Structure
|
||||||
|
- Backend: `worker/` (Workers API; entry `worker/src/worker.ts`, APIs under `worker/src/*_api/`).
|
||||||
|
- Frontend: `frontend/` (Vue 3 app; routes in `frontend/src/router/`).
|
||||||
|
- Pages middleware: `pages/functions/_middleware.js`.
|
||||||
|
- Mail parser: `mail-parser-wasm/` (Rust WASM).
|
||||||
|
- SMTP/IMAP proxy: `smtp_proxy_server/`.
|
||||||
|
- DB schema/migrations: `db/`.
|
||||||
|
- Docs: `vitepress-docs/`.
|
||||||
|
|
||||||
|
## Build & Dev Commands
|
||||||
|
Run inside each folder:
|
||||||
|
- Frontend: `pnpm dev`, `pnpm build`.
|
||||||
|
- Worker: `pnpm dev`, `pnpm lint`, `pnpm build`.
|
||||||
|
- Pages: `pnpm dev`.
|
||||||
|
- Docs: `pnpm dev` in `vitepress-docs/`.
|
||||||
|
- WASM: `wasm-pack build --release` in `mail-parser-wasm/`.
|
||||||
|
- SMTP proxy: `pip install -r smtp_proxy_server/requirements.txt` then `python smtp_proxy_server/main.py`.
|
||||||
|
|
||||||
|
## Coding Style
|
||||||
|
- Follow existing module style. `worker/` uses TypeScript + ESLint; `frontend/` uses Vue SFCs.
|
||||||
|
- Keep current naming patterns: `*_api/`, `utils/`, `models/`.
|
||||||
|
- ESM imports only (`type: module`).
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
- No formal test runner. Validate with local dev servers and key flows (login, inbox, send/receive).
|
||||||
|
|
||||||
|
## Commits & PRs
|
||||||
|
- Use Conventional Commits (`feat:`, `fix:`, `docs:`). Recent history includes PR numbers like `(#123)`.
|
||||||
|
- PRs should explain scope and add screenshots for UI changes.
|
||||||
|
|
||||||
|
## Config Tips
|
||||||
|
- Worker settings in `worker/wrangler.toml` (see template for bindings).
|
||||||
|
- Frontend uses `VITE_*` env vars. Don’t commit secrets.
|
||||||
@@ -17,6 +17,7 @@
|
|||||||
- feat: |邮件转发| 新增来源地址正则转发功能,支持按发件人地址过滤转发,完全向后兼容
|
- feat: |邮件转发| 新增来源地址正则转发功能,支持按发件人地址过滤转发,完全向后兼容
|
||||||
- feat: |地址来源| 新增地址来源追踪功能,记录地址创建来源(Web 记录 IP,Telegram 记录用户 ID,Admin 后台标记)
|
- feat: |地址来源| 新增地址来源追踪功能,记录地址创建来源(Web 记录 IP,Telegram 记录用户 ID,Admin 后台标记)
|
||||||
- feat: |邮件过滤| 移除后端 keyword 参数,改为前端过滤当前页邮件,优化查询性能
|
- feat: |邮件过滤| 移除后端 keyword 参数,改为前端过滤当前页邮件,优化查询性能
|
||||||
|
- feat: |前端| 地址切换统一为下拉组件,极简模式支持切换,主页提供地址管理入口
|
||||||
- feat: |数据库| 为 `message_id` 字段添加索引,优化邮件更新操作性能,需执行 `db/2025-12-15-message-id-index.sql` 更新数据库
|
- feat: |数据库| 为 `message_id` 字段添加索引,优化邮件更新操作性能,需执行 `db/2025-12-15-message-id-index.sql` 更新数据库
|
||||||
- feat: |Admin| 维护页面增加自定义 SQL 清理功能,支持定时任务执行自定义清理语句
|
- feat: |Admin| 维护页面增加自定义 SQL 清理功能,支持定时任务执行自定义清理语句
|
||||||
- feat: |国际化| 后端 API 错误消息全面支持中英文国际化
|
- feat: |国际化| 后端 API 错误消息全面支持中英文国际化
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
- feat: |Email Forwarding| Add source address regex forwarding, filter by sender address, fully backward compatible
|
- feat: |Email Forwarding| Add source address regex forwarding, filter by sender address, fully backward compatible
|
||||||
- feat: |Address Source| Add address source tracking feature, record address creation source (Web records IP, Telegram records user ID, Admin panel marked)
|
- feat: |Address Source| Add address source tracking feature, record address creation source (Web records IP, Telegram records user ID, Admin panel marked)
|
||||||
- feat: |Email Filtering| Remove backend keyword parameter, switch to frontend filtering of current page emails, optimize query performance
|
- feat: |Email Filtering| Remove backend keyword parameter, switch to frontend filtering of current page emails, optimize query performance
|
||||||
|
- feat: |Frontend| Unify address switching into a dropdown component, support switching in simple mode, add address management entry on the homepage
|
||||||
- feat: |Database| Add index for `message_id` field to optimize email update operations, need to execute `db/2025-12-15-message-id-index.sql` to update database
|
- feat: |Database| Add index for `message_id` field to optimize email update operations, need to execute `db/2025-12-15-message-id-index.sql` to update database
|
||||||
- feat: |Admin| Add custom SQL cleanup feature to maintenance page, support scheduled task execution of custom cleanup statements
|
- feat: |Admin| Add custom SQL cleanup feature to maintenance page, support scheduled task execution of custom cleanup statements
|
||||||
- feat: |i18n| Backend API error messages now fully support Chinese and English internationalization
|
- feat: |i18n| Backend API error messages now fully support Chinese and English internationalization
|
||||||
|
|||||||
@@ -0,0 +1,259 @@
|
|||||||
|
<script setup>
|
||||||
|
import { onMounted, ref, watch } from 'vue'
|
||||||
|
import { useLocalStorage } from '@vueuse/core'
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
|
import { useMessage } from 'naive-ui'
|
||||||
|
import useClipboard from 'vue-clipboard3'
|
||||||
|
import { Copy } from '@vicons/fa'
|
||||||
|
|
||||||
|
import { useGlobalState } from '../store'
|
||||||
|
import { api } from '../api'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
showCopy: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
type: String,
|
||||||
|
default: 'small',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const message = useMessage()
|
||||||
|
const { toClipboard } = useClipboard()
|
||||||
|
|
||||||
|
const {
|
||||||
|
jwt, settings, userJwt, isTelegram, openSettings, telegramApp
|
||||||
|
} = useGlobalState()
|
||||||
|
|
||||||
|
const { t } = useI18n({
|
||||||
|
messages: {
|
||||||
|
en: {
|
||||||
|
userAddresses: 'User Addresses',
|
||||||
|
localAddresses: 'Local Addresses',
|
||||||
|
address: 'Address',
|
||||||
|
copy: 'Copy',
|
||||||
|
copied: 'Copied',
|
||||||
|
},
|
||||||
|
zh: {
|
||||||
|
userAddresses: '用户地址',
|
||||||
|
localAddresses: '本地地址',
|
||||||
|
address: '地址',
|
||||||
|
copy: '复制',
|
||||||
|
copied: '已复制',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const addressOptions = ref([])
|
||||||
|
const addressValue = ref(null)
|
||||||
|
const addressLoading = ref(false)
|
||||||
|
const localAddressCache = useLocalStorage("LocalAddressCache", [])
|
||||||
|
const optionValueMap = new Map()
|
||||||
|
|
||||||
|
const formatAddressLabel = (address) => {
|
||||||
|
if (!address) return address;
|
||||||
|
const domain = address.split('@')[1]
|
||||||
|
const domainLabel = openSettings.value.domains.find(
|
||||||
|
d => d.value === domain
|
||||||
|
)?.label;
|
||||||
|
if (!domainLabel) return address;
|
||||||
|
return address.replace('@' + domain, `@${domainLabel}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const parseJwtAddress = (curJwt) => {
|
||||||
|
try {
|
||||||
|
const payload = JSON.parse(
|
||||||
|
decodeURIComponent(
|
||||||
|
atob(curJwt.split(".")[1]
|
||||||
|
.replace(/-/g, "+").replace(/_/g, "/")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
return payload.address;
|
||||||
|
} catch (e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getOptionValue = (key, scope, payload, address) => {
|
||||||
|
if (optionValueMap.has(key)) {
|
||||||
|
const cached = optionValueMap.get(key)
|
||||||
|
cached.scope = scope
|
||||||
|
cached.payload = payload
|
||||||
|
cached.address = address
|
||||||
|
return cached
|
||||||
|
}
|
||||||
|
const value = { key, scope, payload, address }
|
||||||
|
optionValueMap.set(key, value)
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
const buildLocalOptions = (excludeAddresses = new Set()) => {
|
||||||
|
if (typeof jwt.value === 'string' && jwt.value && !localAddressCache.value.includes(jwt.value)) {
|
||||||
|
localAddressCache.value.push(jwt.value)
|
||||||
|
}
|
||||||
|
const children = localAddressCache.value
|
||||||
|
.map((curJwt) => {
|
||||||
|
const address = parseJwtAddress(curJwt);
|
||||||
|
if (!address) return null;
|
||||||
|
if (excludeAddresses.has(address)) return null;
|
||||||
|
const label = formatAddressLabel(address);
|
||||||
|
const key = `local:${curJwt}`;
|
||||||
|
const option = { label, value: getOptionValue(key, 'local', curJwt, address), address };
|
||||||
|
if (settings.value.address && address === settings.value.address) {
|
||||||
|
addressValue.value = option.value;
|
||||||
|
}
|
||||||
|
return option;
|
||||||
|
})
|
||||||
|
.filter(Boolean);
|
||||||
|
return children;
|
||||||
|
}
|
||||||
|
|
||||||
|
const buildUserOptions = async () => {
|
||||||
|
const children = [];
|
||||||
|
try {
|
||||||
|
const { results } = await api.fetch(`/user_api/bind_address`);
|
||||||
|
for (const row of results || []) {
|
||||||
|
const address = row.address || row.name;
|
||||||
|
if (!address) continue;
|
||||||
|
const label = formatAddressLabel(address);
|
||||||
|
const key = `user:${row.id}`;
|
||||||
|
const option = { label, value: getOptionValue(key, 'user', String(row.id), address), address };
|
||||||
|
if (settings.value.address && address === settings.value.address) {
|
||||||
|
addressValue.value = option.value;
|
||||||
|
}
|
||||||
|
children.push(option);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
message.error(error.message || "error");
|
||||||
|
}
|
||||||
|
return children;
|
||||||
|
}
|
||||||
|
|
||||||
|
const buildTelegramOptions = async () => {
|
||||||
|
const children = [];
|
||||||
|
try {
|
||||||
|
const data = await api.fetch(`/telegram/get_bind_address`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({
|
||||||
|
initData: telegramApp.value.initData
|
||||||
|
})
|
||||||
|
});
|
||||||
|
for (const row of data || []) {
|
||||||
|
if (!row?.address || !row?.jwt) continue;
|
||||||
|
const label = formatAddressLabel(row.address);
|
||||||
|
const key = `tg:${row.jwt}`;
|
||||||
|
const option = { label, value: getOptionValue(key, 'tg', row.jwt, row.address), address: row.address };
|
||||||
|
if (settings.value.address && row.address === settings.value.address) {
|
||||||
|
addressValue.value = option.value;
|
||||||
|
}
|
||||||
|
children.push(option);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
message.error(error.message || "error");
|
||||||
|
}
|
||||||
|
return children;
|
||||||
|
}
|
||||||
|
|
||||||
|
const refreshAddressOptions = async () => {
|
||||||
|
addressLoading.value = true;
|
||||||
|
addressValue.value = null;
|
||||||
|
try {
|
||||||
|
if (isTelegram.value) {
|
||||||
|
const telegramChildren = await buildTelegramOptions();
|
||||||
|
addressOptions.value = telegramChildren;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const groups = [];
|
||||||
|
if (userJwt.value) {
|
||||||
|
const userChildren = await buildUserOptions();
|
||||||
|
if (userChildren.length > 0) {
|
||||||
|
groups.push({ type: 'group', label: t('userAddresses'), children: userChildren });
|
||||||
|
}
|
||||||
|
const userAddressSet = new Set(userChildren.map((item) => item.address));
|
||||||
|
const localChildren = buildLocalOptions(userAddressSet);
|
||||||
|
if (localChildren.length > 0) {
|
||||||
|
groups.push({ type: 'group', label: t('localAddresses'), children: localChildren });
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const localChildren = buildLocalOptions();
|
||||||
|
if (localChildren.length > 0) {
|
||||||
|
groups.push({ type: 'group', label: t('localAddresses'), children: localChildren });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
addressOptions.value = groups;
|
||||||
|
} finally {
|
||||||
|
addressLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const onAddressChange = async (value) => {
|
||||||
|
if (!value) return;
|
||||||
|
if (value.scope === 'local' || value.scope === 'tg') {
|
||||||
|
jwt.value = value.payload;
|
||||||
|
location.reload();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (value.scope === 'user') {
|
||||||
|
try {
|
||||||
|
const res = await api.fetch(`/user_api/bind_address_jwt/${value.payload}`);
|
||||||
|
if (!res?.jwt) {
|
||||||
|
message.error("jwt not found");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
jwt.value = res.jwt;
|
||||||
|
location.reload();
|
||||||
|
} catch (error) {
|
||||||
|
message.error(error.message || "error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const copy = async () => {
|
||||||
|
try {
|
||||||
|
await toClipboard(settings.value.address)
|
||||||
|
message.success(t('copied'));
|
||||||
|
} catch (e) {
|
||||||
|
message.error(e.message || "error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
await refreshAddressOptions();
|
||||||
|
});
|
||||||
|
|
||||||
|
watch([userJwt, isTelegram, () => settings.value.address], async () => {
|
||||||
|
await refreshAddressOptions();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<n-flex class="address-row" align="center" justify="center" :wrap="false">
|
||||||
|
<n-select v-model:value="addressValue" :options="addressOptions" :size="size" filterable
|
||||||
|
:loading="addressLoading" :placeholder="t('address')" @update:value="onAddressChange"
|
||||||
|
class="address-select" />
|
||||||
|
<slot name="actions" />
|
||||||
|
<n-button v-if="showCopy" class="address-copy" @click="copy" :size="size" tertiary type="primary">
|
||||||
|
<n-icon :component="Copy" /> {{ t('copy') }}
|
||||||
|
</n-button>
|
||||||
|
</n-flex>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.address-row {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.address-select {
|
||||||
|
min-width: 220px;
|
||||||
|
max-width: 420px;
|
||||||
|
flex: 0 1 420px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.address-copy {
|
||||||
|
margin-left: 10px;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,81 +1,51 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import useClipboard from 'vue-clipboard3'
|
import { onMounted, ref } from 'vue'
|
||||||
import { computed, onMounted, ref } from 'vue'
|
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { Copy, User, ExchangeAlt } from '@vicons/fa'
|
import { User, ExchangeAlt } from '@vicons/fa'
|
||||||
|
|
||||||
import { useGlobalState } from '../../store'
|
import { useGlobalState } from '../../store'
|
||||||
import { api } from '../../api'
|
import { api } from '../../api'
|
||||||
import Login from '../common/Login.vue'
|
import Login from '../common/Login.vue'
|
||||||
import AddressManagement from '../user/AddressManagement.vue'
|
|
||||||
import TelegramAddress from './TelegramAddress.vue'
|
import TelegramAddress from './TelegramAddress.vue'
|
||||||
import LocalAddress from './LocalAddress.vue'
|
import LocalAddress from './LocalAddress.vue'
|
||||||
|
import AddressManagement from '../user/AddressManagement.vue'
|
||||||
import { getRouterPathWithLang } from '../../utils'
|
import { getRouterPathWithLang } from '../../utils'
|
||||||
|
import AddressSelect from '../../components/AddressSelect.vue'
|
||||||
|
|
||||||
const { toClipboard } = useClipboard()
|
|
||||||
const message = useMessage()
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
const {
|
const {
|
||||||
jwt, settings, showAddressCredential, userJwt,
|
jwt, settings, showAddressCredential, userJwt,
|
||||||
isTelegram, openSettings, addressPassword
|
isTelegram, addressPassword
|
||||||
} = useGlobalState()
|
} = useGlobalState()
|
||||||
|
|
||||||
const { locale, t } = useI18n({
|
const { locale, t } = useI18n({
|
||||||
messages: {
|
messages: {
|
||||||
en: {
|
en: {
|
||||||
addressManage: 'Address Manage',
|
|
||||||
changeAddress: 'Change Address',
|
|
||||||
ok: 'OK',
|
ok: 'OK',
|
||||||
copy: 'Copy',
|
|
||||||
copied: 'Copied',
|
|
||||||
fetchAddressError: 'Mail address credential is invalid or account not exist, it may be network connection issue, please try again later.',
|
fetchAddressError: 'Mail address credential is invalid or account not exist, it may be network connection issue, please try again later.',
|
||||||
addressCredential: 'Mail Address Credential',
|
addressCredential: 'Mail Address Credential',
|
||||||
linkWithAddressCredential: 'Open to auto login email link',
|
linkWithAddressCredential: 'Open to auto login email link',
|
||||||
addressCredentialTip: 'Please copy the Mail Address Credential and you can use it to login to your email account.',
|
addressCredentialTip: 'Please copy the Mail Address Credential and you can use it to login to your email account.',
|
||||||
addressPassword: 'Address Password',
|
addressPassword: 'Address Password',
|
||||||
userLogin: 'User Login',
|
userLogin: 'User Login',
|
||||||
|
addressManage: 'Manage',
|
||||||
},
|
},
|
||||||
zh: {
|
zh: {
|
||||||
addressManage: '地址管理',
|
|
||||||
changeAddress: '更换地址',
|
|
||||||
ok: '确定',
|
ok: '确定',
|
||||||
copy: '复制',
|
|
||||||
copied: '已复制',
|
|
||||||
fetchAddressError: '邮箱地址凭证无效或邮箱地址不存在,也可能是网络连接异常,请稍后再尝试。',
|
fetchAddressError: '邮箱地址凭证无效或邮箱地址不存在,也可能是网络连接异常,请稍后再尝试。',
|
||||||
addressCredential: '邮箱地址凭证',
|
addressCredential: '邮箱地址凭证',
|
||||||
linkWithAddressCredential: '打开即可自动登录邮箱的链接',
|
linkWithAddressCredential: '打开即可自动登录邮箱的链接',
|
||||||
addressCredentialTip: '请复制邮箱地址凭证,你可以使用它登录你的邮箱。',
|
addressCredentialTip: '请复制邮箱地址凭证,你可以使用它登录你的邮箱。',
|
||||||
addressPassword: '地址密码',
|
addressPassword: '地址密码',
|
||||||
userLogin: '用户登录',
|
userLogin: '用户登录',
|
||||||
|
addressManage: '地址管理',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const showChangeAddress = ref(false)
|
const showAddressManage = ref(false)
|
||||||
const showTelegramChangeAddress = ref(false)
|
|
||||||
const showLocalAddress = ref(false)
|
|
||||||
const addressLabel = computed(() => {
|
|
||||||
if (settings.value.address) {
|
|
||||||
const domain = settings.value.address.split('@')[1]
|
|
||||||
const domainLabel = openSettings.value.domains.find(
|
|
||||||
d => d.value === domain
|
|
||||||
)?.label;
|
|
||||||
if (!domainLabel) return settings.value.address;
|
|
||||||
return settings.value.address.replace('@' + domain, `@${domainLabel}`);
|
|
||||||
}
|
|
||||||
return settings.value.address;
|
|
||||||
})
|
|
||||||
|
|
||||||
const copy = async () => {
|
|
||||||
try {
|
|
||||||
await toClipboard(settings.value.address)
|
|
||||||
message.success(t('copied'));
|
|
||||||
} catch (e) {
|
|
||||||
message.error(e.message || "error");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const getUrlWithJwt = () => {
|
const getUrlWithJwt = () => {
|
||||||
return `${window.location.origin}/?jwt=${jwt.value}`
|
return `${window.location.origin}/?jwt=${jwt.value}`
|
||||||
@@ -97,29 +67,25 @@ onMounted(async () => {
|
|||||||
</n-card>
|
</n-card>
|
||||||
<div v-else-if="settings.address">
|
<div v-else-if="settings.address">
|
||||||
<n-alert type="info" :show-icon="false" :bordered="false">
|
<n-alert type="info" :show-icon="false" :bordered="false">
|
||||||
<span>
|
<AddressSelect>
|
||||||
<b>{{ addressLabel }}</b>
|
<template #actions>
|
||||||
<n-button v-if="isTelegram" style="margin-left: 10px" @click="showTelegramChangeAddress = true"
|
<n-button class="address-manage" size="small" tertiary type="primary"
|
||||||
size="small" tertiary type="primary">
|
@click="showAddressManage = true">
|
||||||
<n-icon :component="ExchangeAlt" /> {{ t('addressManage') }}
|
<n-icon :component="ExchangeAlt" />
|
||||||
</n-button>
|
{{ t('addressManage') }}
|
||||||
<n-button v-else-if="userJwt" style="margin-left: 10px" @click="showChangeAddress = true"
|
</n-button>
|
||||||
size="small" tertiary type="primary">
|
</template>
|
||||||
<n-icon :component="ExchangeAlt" /> {{ t('changeAddress') }}
|
</AddressSelect>
|
||||||
</n-button>
|
|
||||||
<n-button v-else style="margin-left: 10px" @click="showLocalAddress = true" size="small" tertiary
|
|
||||||
type="primary">
|
|
||||||
<n-icon :component="ExchangeAlt" /> {{ t('addressManage') }}
|
|
||||||
</n-button>
|
|
||||||
<n-button style="margin-left: 10px" @click="copy" size="small" tertiary type="primary">
|
|
||||||
<n-icon :component="Copy" /> {{ t('copy') }}
|
|
||||||
</n-button>
|
|
||||||
</span>
|
|
||||||
</n-alert>
|
</n-alert>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="isTelegram">
|
<div v-else-if="isTelegram">
|
||||||
<TelegramAddress />
|
<TelegramAddress />
|
||||||
</div>
|
</div>
|
||||||
|
<div v-else-if="userJwt" class="center">
|
||||||
|
<n-card :bordered="false" embedded style="max-width: 900px; width: 100%;">
|
||||||
|
<AddressManagement />
|
||||||
|
</n-card>
|
||||||
|
</div>
|
||||||
<div v-else class="center">
|
<div v-else class="center">
|
||||||
<n-card :bordered="false" embedded style="max-width: 600px;">
|
<n-card :bordered="false" embedded style="max-width: 600px;">
|
||||||
<n-alert v-if="jwt" type="warning" :show-icon="false" :bordered="false" closable>
|
<n-alert v-if="jwt" type="warning" :show-icon="false" :bordered="false" closable>
|
||||||
@@ -135,15 +101,6 @@ onMounted(async () => {
|
|||||||
</n-button>
|
</n-button>
|
||||||
</n-card>
|
</n-card>
|
||||||
</div>
|
</div>
|
||||||
<n-modal v-model:show="showTelegramChangeAddress" preset="card" :title="t('changeAddress')">
|
|
||||||
<TelegramAddress />
|
|
||||||
</n-modal>
|
|
||||||
<n-modal v-model:show="showChangeAddress" preset="card" :title="t('changeAddress')">
|
|
||||||
<AddressManagement />
|
|
||||||
</n-modal>
|
|
||||||
<n-modal v-model:show="showLocalAddress" preset="card" :title="t('changeAddress')">
|
|
||||||
<LocalAddress />
|
|
||||||
</n-modal>
|
|
||||||
<n-modal v-model:show="showAddressCredential" preset="dialog" :title="t('addressCredential')">
|
<n-modal v-model:show="showAddressCredential" preset="dialog" :title="t('addressCredential')">
|
||||||
<span>
|
<span>
|
||||||
<p>{{ t("addressCredentialTip") }}</p>
|
<p>{{ t("addressCredentialTip") }}</p>
|
||||||
@@ -165,6 +122,11 @@ onMounted(async () => {
|
|||||||
</n-collapse>
|
</n-collapse>
|
||||||
</n-card>
|
</n-card>
|
||||||
</n-modal>
|
</n-modal>
|
||||||
|
<n-modal v-model:show="showAddressManage" preset="card" :title="t('addressManage')">
|
||||||
|
<TelegramAddress v-if="isTelegram" />
|
||||||
|
<AddressManagement v-else-if="userJwt" />
|
||||||
|
<LocalAddress v-else />
|
||||||
|
</n-modal>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -186,4 +148,8 @@ onMounted(async () => {
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
margin: 20px;
|
margin: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.address-manage {
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ const data = computed(() => {
|
|||||||
}
|
}
|
||||||
return localAddressCache.value.map((curJwt: string) => {
|
return localAddressCache.value.map((curJwt: string) => {
|
||||||
try {
|
try {
|
||||||
var payload = JSON.parse(
|
const payload = JSON.parse(
|
||||||
decodeURIComponent(
|
decodeURIComponent(
|
||||||
atob(curJwt.split(".")[1]
|
atob(curJwt.split(".")[1]
|
||||||
.replace(/-/g, "+").replace(/_/g, "/")
|
.replace(/-/g, "+").replace(/_/g, "/")
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import Login from '../common/Login.vue'
|
|||||||
import AccountSettings from './AccountSettings.vue'
|
import AccountSettings from './AccountSettings.vue'
|
||||||
import { processItem } from '../../utils/email-parser'
|
import { processItem } from '../../utils/email-parser'
|
||||||
import MailContentRenderer from '../../components/MailContentRenderer.vue'
|
import MailContentRenderer from '../../components/MailContentRenderer.vue'
|
||||||
|
import AddressSelect from '../../components/AddressSelect.vue'
|
||||||
|
|
||||||
const { jwt, settings, useSimpleIndex, showAddressCredential, openSettings, loading } = useGlobalState()
|
const { jwt, settings, useSimpleIndex, showAddressCredential, openSettings, loading } = useGlobalState()
|
||||||
const message = useMessage()
|
const message = useMessage()
|
||||||
@@ -171,7 +172,7 @@ onBeforeUnmount(() => {
|
|||||||
<div v-else>
|
<div v-else>
|
||||||
<n-card :bordered="false" embedded>
|
<n-card :bordered="false" embedded>
|
||||||
<div style="text-align: center; margin-bottom: 16px; font-size: 18px;">
|
<div style="text-align: center; margin-bottom: 16px; font-size: 18px;">
|
||||||
<n-text strong size="large">{{ settings.address }}</n-text>
|
<AddressSelect :showCopy="false" size="small" />
|
||||||
</div>
|
</div>
|
||||||
<n-flex justify="center">
|
<n-flex justify="center">
|
||||||
<n-button @click="refreshMails" :loading="loading" type="primary" tertiary size="small">
|
<n-button @click="refreshMails" :loading="loading" type="primary" tertiary size="small">
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { useI18n } from 'vue-i18n'
|
|||||||
import { api } from '../../api'
|
import { api } from '../../api'
|
||||||
import MailBox from '../../components/MailBox.vue';
|
import MailBox from '../../components/MailBox.vue';
|
||||||
|
|
||||||
|
const message = useMessage()
|
||||||
|
|
||||||
const { t } = useI18n({
|
const { t } = useI18n({
|
||||||
messages: {
|
messages: {
|
||||||
|
|||||||
Reference in New Issue
Block a user