feat: admin portal user page add user address manangement (#623)

This commit is contained in:
Dream Hunter
2025-04-07 19:47:44 +08:00
committed by GitHub
parent 91a859bbcf
commit 31eb6c23d1
9 changed files with 180 additions and 25 deletions

View File

@@ -4,7 +4,7 @@
</template>
<script setup>
import { ref, watch, onMounted, onBeforeUnmount, defineProps } from 'vue';
import { ref, watch, onMounted, onBeforeUnmount } from 'vue';
const props = defineProps({
htmlContent: {

View File

@@ -67,7 +67,7 @@ export const useGlobalState = createGlobalState(
const useIframeShowMail = useStorage('useIframeShowMail', false);
const preferShowTextMail = useStorage('preferShowTextMail', false);
const userJwt = useStorage('userJwt', '');
const userTab = useSessionStorage('userTab', 'account_settings');
const userTab = useSessionStorage('userTab', 'address_management');
const indexTab = useSessionStorage('indexTab', 'mailbox');
const globalTabplacement = useStorage('globalTabplacement', 'top');
const useSideMargin = useStorage('useSideMargin', true);

View File

@@ -0,0 +1,94 @@
<script setup>
import { ref, h, onMounted } from 'vue';
import { useI18n } from 'vue-i18n'
import { NBadge } from 'naive-ui'
import { api } from '../../api'
const props = defineProps({
user_id: {
type: Number,
required: true
}
});
const message = useMessage()
const { locale, t } = useI18n({
messages: {
en: {
success: 'success',
name: 'Name',
mail_count: 'Mail Count',
send_count: 'Send Count',
},
zh: {
success: '成功',
name: '名称',
mail_count: '邮件数量',
send_count: '发送数量',
}
}
});
const data = ref([])
const fetchData = async () => {
try {
const { results } = await api.fetch(
`/admin/users/bind_address/${props.user_id}`,
);
data.value = results;
} catch (error) {
console.log(error)
message.error(error.message || "error");
}
}
const columns = [
{
title: t('name'),
key: "name"
},
{
title: t('mail_count'),
key: "mail_count",
render(row) {
return h(NBadge, {
value: row.mail_count,
'show-zero': true,
max: 99,
type: "success"
})
}
},
{
title: t('send_count'),
key: "send_count",
render(row) {
return h(NBadge, {
value: row.send_count,
'show-zero': true,
max: 99,
type: "success"
})
}
}
]
onMounted(async () => {
await fetchData()
})
</script>
<template>
<div style="overflow: auto;">
<n-data-table :columns="columns" :data="data" :bordered="false" embedded />
</div>
</template>
<style scoped>
.n-data-table {
min-width: 700px;
}
</style>

View File

@@ -8,6 +8,8 @@ import { useGlobalState } from '../../store'
import { api } from '../../api'
import { hashPassword } from '../../utils';
import UserAddressManagement from './UserAddressManagement.vue'
const { loading, openSettings } = useGlobalState()
const message = useMessage()
@@ -34,6 +36,7 @@ const { t } = useI18n({
prefix: 'Prefix',
domains: 'Domains',
roleDonotExist: 'Current Role does not exist',
userAddressManagement: 'User Address Management',
},
zh: {
success: '成功',
@@ -56,6 +59,7 @@ const { t } = useI18n({
prefix: '前缀',
domains: '域名',
roleDonotExist: '当前角色不存在',
userAddressManagement: '用户地址管理',
}
}
});
@@ -75,6 +79,7 @@ const user = ref({
password: ""
})
const showChangeRole = ref(false)
const showUserAddressManagement = ref(false)
const userRoles = ref([])
const curUserRole = ref('')
const userRolesOptions = computed(() => {
@@ -239,6 +244,18 @@ const columns = [
icon: () => h(MenuFilled),
key: "action",
children: [
{
label: () => h(NButton,
{
text: true,
onClick: () => {
curUserId.value = row.id;
showUserAddressManagement.value = true;
}
},
{ default: () => t('userAddressManagement') }
),
},
{
label: () => h(NButton,
{
@@ -362,6 +379,9 @@ onMounted(async () => {
</n-button>
</template>
</n-modal>
<n-modal v-model:show="showUserAddressManagement" preset="card" :title="t('userAddressManagement')">
<UserAddressManagement :user_id="curUserId" />
</n-modal>
<n-input-group>
<n-input v-model:value="userQuery" @keydown.enter="fetchData" />
<n-button @click="fetchData" type="primary" tertiary>

View File

@@ -71,6 +71,7 @@ const { locale, t } = useI18n({
messages: {
en: {
login: 'Login',
loginAndBind: 'Login and Bind',
pleaseGetNewEmail: 'Please login or click "Get New Email" button to get a new email address',
getNewEmail: 'Create New Email',
getNewEmailTip1: 'Please input the email you want to use. only allow: ',
@@ -86,6 +87,7 @@ const { locale, t } = useI18n({
},
zh: {
login: '登录',
loginAndBind: '登录并绑定',
pleaseGetNewEmail: '请"登录"或点击 "注册新邮箱" 按钮来获取一个新的邮箱地址',
getNewEmail: '创建新邮箱',
getNewEmailTip1: '请输入你想要使用的邮箱地址, 只允许: ',
@@ -102,6 +104,13 @@ const { locale, t } = useI18n({
}
});
const loginAndBindTag = computed(() => {
if (userSettings.value.user_email) {
return t('loginAndBind')
}
return t('login')
})
const addressRegex = computed(() => {
try {
if (openSettings.value.addressRegex) {
@@ -208,7 +217,7 @@ onMounted(async () => {
<span>{{ t('bindUserInfo') }}</span>
</n-alert>
<n-tabs v-if="openSettings.fetched" v-model:value="tabValue" size="large" justify-content="space-evenly">
<n-tab-pane name="signin" :tab="t('login')">
<n-tab-pane name="signin" :tab="loginAndBindTag">
<n-form>
<n-form-item-row :label="t('credential')" required>
<n-input v-model:value="credential" type="textarea" :autosize="{ minRows: 3 }" />
@@ -217,7 +226,7 @@ onMounted(async () => {
<template #icon>
<n-icon :component="EmailOutlined" />
</template>
{{ t('login') }}
{{ loginAndBindTag }}
</n-button>
<n-button v-if="showNewAddressTab" @click="tabValue = 'register'" block secondary strong>
<template #icon>

View File

@@ -8,6 +8,8 @@ import { useGlobalState } from '../../store'
import { api } from '../../api'
import { getRouterPathWithLang } from '../../utils'
import Login from '../common/Login.vue';
const { jwt } = useGlobalState()
const message = useMessage()
const router = useRouter()
@@ -25,7 +27,9 @@ const { locale, t } = useI18n({
unbindAddressTip: 'Before unbinding, please switch to this email address and save the email address credential.',
transferAddress: 'Transfer Address',
targetUserEmail: 'Target User Email',
transferAddressTip: 'Transfer address to another user will remove the address from your account and transfer it to another user. Are you sure to transfer the address?'
transferAddressTip: 'Transfer address to another user will remove the address from your account and transfer it to another user. Are you sure to transfer the address?',
address: 'Address',
create_or_bind: 'Create or Bind',
},
zh: {
success: '成功',
@@ -38,7 +42,9 @@ const { locale, t } = useI18n({
unbindAddressTip: '解绑前请切换到此邮箱地址并保存邮箱地址凭证。',
transferAddress: '转移地址',
targetUserEmail: '目标用户邮箱',
transferAddressTip: '转移地址到其他用户将会从你的账户中移除此地址并转移给其他用户。确定要转移地址吗?'
transferAddressTip: '转移地址到其他用户将会从你的账户中移除此地址并转移给其他用户。确定要转移地址吗?',
address: '地址',
create_or_bind: '创建或绑定',
}
}
});
@@ -111,13 +117,10 @@ const transferAddress = async () => {
const fetchData = async () => {
try {
const { results, count: addressCount } = await api.fetch(
const { results } = await api.fetch(
`/user_api/bind_address`
);
data.value = results;
if (addressCount > 0) {
count.value = addressCount;
}
} catch (error) {
console.log(error)
message.error(error.message || "error");
@@ -211,20 +214,29 @@ onMounted(async () => {
</script>
<template>
<n-modal v-model:show="showTranferAddress" preset="dialog" :title="t('transferAddress')">
<span>
<p>{{ t("transferAddressTip") }}</p>
<p>{{ t('transferAddress') + ": " + currentAddress }}</p>
<n-input v-model:value="targetUserEmail" :placeholder="t('targetUserEmail')" />
</span>
<template #action>
<n-button :loading="loading" @click="transferAddress" size="small" tertiary type="error">
{{ t('transferAddress') }}
</n-button>
</template>
</n-modal>
<div style="overflow: auto;">
<n-data-table :columns="columns" :data="data" :bordered="false" embedded />
<div>
<n-modal v-model:show="showTranferAddress" preset="dialog" :title="t('transferAddress')">
<span>
<p>{{ t("transferAddressTip") }}</p>
<p>{{ t('transferAddress') + ": " + currentAddress }}</p>
<n-input v-model:value="targetUserEmail" :placeholder="t('targetUserEmail')" />
</span>
<template #action>
<n-button :loading="loading" @click="transferAddress" size="small" tertiary type="error">
{{ t('transferAddress') }}
</n-button>
</template>
</n-modal>
<n-tabs type="segment">
<n-tab-pane name="address" :tab="t('address')">
<div style="overflow: auto;">
<n-data-table :columns="columns" :data="data" :bordered="false" embedded />
</div>
</n-tab-pane>
<n-tab-pane name="create_or_bind" :tab="t('create_or_bind')">
<Login />
</n-tab-pane>
</n-tabs>
</div>
</template>