实现系统管理后台 V1
This commit is contained in:
1 parent
e76cb80f28
commit
aa5783ba61
92 files changed
+9383
-21
No files matched your search
@@ -0,0 +1,697 @@
|
||||
<template>
|
||||
<section class="system-admin-panel">
|
||||
<div class="th-section filter-section">
|
||||
<div class="th-section-header">
|
||||
<h2 class="th-section-title">{{ t('systemAdmin.users.title') }}</h2>
|
||||
<button
|
||||
type="button"
|
||||
class="text-button"
|
||||
@click="resetFilters"
|
||||
>
|
||||
{{ t('workbench.reset') }}
|
||||
</button>
|
||||
</div>
|
||||
<div class="filter-grid">
|
||||
<label>
|
||||
<span>{{ t('systemAdmin.common.keyword') }}</span>
|
||||
<input
|
||||
v-model="filters.keyword"
|
||||
type="search"
|
||||
:placeholder="t('systemAdmin.users.keywordPlaceholder')"
|
||||
@input="resetToFirstPage"
|
||||
>
|
||||
</label>
|
||||
<label>
|
||||
<span>{{ t('systemAdmin.users.userStatus') }}</span>
|
||||
<select
|
||||
v-model="filters.user_status"
|
||||
@change="resetToFirstPage"
|
||||
>
|
||||
<option value="">{{ t('common.all') }}</option>
|
||||
<option value="ACTIVE">{{ t('systemAdmin.status.ACTIVE') }}</option>
|
||||
<option value="DISABLED">{{ t('systemAdmin.status.DISABLED') }}</option>
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section class="th-section action-section">
|
||||
<div class="th-section-header">
|
||||
<h2 class="th-section-title">{{ t('systemAdmin.users.createTitle') }}</h2>
|
||||
<p
|
||||
v-if="successMessage"
|
||||
class="form-message"
|
||||
>
|
||||
{{ successMessage }}
|
||||
</p>
|
||||
</div>
|
||||
<form
|
||||
class="form-grid"
|
||||
@submit.prevent="createUser"
|
||||
>
|
||||
<label>
|
||||
<span>{{ t('systemAdmin.users.username') }}</span>
|
||||
<input
|
||||
v-model="createForm.username"
|
||||
type="text"
|
||||
autocomplete="off"
|
||||
>
|
||||
</label>
|
||||
<label>
|
||||
<span>{{ t('systemAdmin.users.initialPassword') }}</span>
|
||||
<input
|
||||
v-model="createForm.initial_password"
|
||||
type="password"
|
||||
autocomplete="new-password"
|
||||
>
|
||||
</label>
|
||||
<label>
|
||||
<span>{{ t('systemAdmin.users.displayName') }}</span>
|
||||
<input
|
||||
v-model="createForm.display_name"
|
||||
type="text"
|
||||
>
|
||||
</label>
|
||||
<label>
|
||||
<span>{{ t('systemAdmin.users.userStatus') }}</span>
|
||||
<select v-model="createForm.user_status">
|
||||
<option value="ACTIVE">{{ t('systemAdmin.status.ACTIVE') }}</option>
|
||||
<option value="DISABLED">{{ t('systemAdmin.status.DISABLED') }}</option>
|
||||
</select>
|
||||
</label>
|
||||
<label>
|
||||
<span>{{ t('systemAdmin.users.roles') }}</span>
|
||||
<select
|
||||
v-model="createForm.role_ids"
|
||||
multiple
|
||||
>
|
||||
<option
|
||||
v-for="role in availableRoles"
|
||||
:key="role.id"
|
||||
:value="role.id"
|
||||
>
|
||||
{{ role.role_name }}
|
||||
</option>
|
||||
</select>
|
||||
</label>
|
||||
<label>
|
||||
<span>{{ t('systemAdmin.users.hotels') }}</span>
|
||||
<select
|
||||
v-model="createForm.hotel_ids"
|
||||
multiple
|
||||
>
|
||||
<option
|
||||
v-for="hotel in activeHotels"
|
||||
:key="hotel.hotel_id"
|
||||
:value="hotel.hotel_id"
|
||||
>
|
||||
{{ hotel.hotel_name }}
|
||||
</option>
|
||||
</select>
|
||||
</label>
|
||||
<label>
|
||||
<span>{{ t('systemAdmin.users.defaultHotel') }}</span>
|
||||
<select v-model="createForm.default_hotel_id">
|
||||
<option
|
||||
v-for="hotel in activeHotels"
|
||||
:key="hotel.hotel_id"
|
||||
:value="hotel.hotel_id"
|
||||
>
|
||||
{{ hotel.hotel_name }}
|
||||
</option>
|
||||
</select>
|
||||
</label>
|
||||
<button
|
||||
type="submit"
|
||||
class="primary-button"
|
||||
:disabled="saving"
|
||||
>
|
||||
{{ t('systemAdmin.common.create') }}
|
||||
</button>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
<div class="system-admin-grid">
|
||||
<section class="th-section table-section">
|
||||
<div class="th-section-header">
|
||||
<div>
|
||||
<h2 class="th-section-title">{{ t('systemAdmin.users.tableTitle') }}</h2>
|
||||
<p class="th-muted">{{ t('workbench.totalPrefix') }} {{ total }} {{ t('systemAdmin.users.totalSuffix') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="loading"
|
||||
class="empty-state"
|
||||
>
|
||||
{{ t('common.loading') }}
|
||||
</div>
|
||||
<div
|
||||
v-else-if="errorMessage"
|
||||
class="empty-state empty-state--error"
|
||||
>
|
||||
{{ errorMessage }}
|
||||
</div>
|
||||
<div
|
||||
v-else-if="!users.length"
|
||||
class="empty-state"
|
||||
>
|
||||
{{ t('common.noData') }}
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
class="table-wrap"
|
||||
>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ t('systemAdmin.users.columns.user') }}</th>
|
||||
<th>{{ t('systemAdmin.users.columns.status') }}</th>
|
||||
<th>{{ t('systemAdmin.users.columns.roles') }}</th>
|
||||
<th>{{ t('systemAdmin.users.columns.defaultHotel') }}</th>
|
||||
<th>{{ t('workbench.columns.updatedAt') }}</th>
|
||||
<th>{{ t('workbench.columns.operation') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr
|
||||
v-for="user in users"
|
||||
:key="user.id"
|
||||
>
|
||||
<td>
|
||||
<strong>{{ user.display_name || user.username }}</strong>
|
||||
<small class="th-code">{{ user.username }}</small>
|
||||
</td>
|
||||
<td><span class="status-pill">{{ statusLabel(user.user_status) }}</span></td>
|
||||
<td>{{ roleSummary(user.roles) }}</td>
|
||||
<td>{{ user.default_hotel_id || '-' }}</td>
|
||||
<td>{{ formatReservationDateTime(user.updated_at) }}</td>
|
||||
<td>
|
||||
<button
|
||||
type="button"
|
||||
class="row-action"
|
||||
@click="selectUser(user.id)"
|
||||
>
|
||||
{{ t('systemAdmin.common.viewDetail') }}
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<footer class="pagination-bar">
|
||||
<label class="page-size-control">
|
||||
<span>{{ t('common.pageSize') }}</span>
|
||||
<select
|
||||
v-model.number="filters.page_size"
|
||||
@change="resetToFirstPage"
|
||||
>
|
||||
<option :value="10">10</option>
|
||||
<option :value="20">20</option>
|
||||
<option :value="50">50</option>
|
||||
</select>
|
||||
</label>
|
||||
<div class="pagination-actions">
|
||||
<button
|
||||
type="button"
|
||||
class="secondary-button"
|
||||
:disabled="currentPage <= 1 || loading"
|
||||
@click="goToPage(currentPage - 1)"
|
||||
>
|
||||
{{ t('common.previousPage') }}
|
||||
</button>
|
||||
<span>{{ t('common.pageStatus', { current: currentPage, total: totalPages }) }}</span>
|
||||
<button
|
||||
type="button"
|
||||
class="secondary-button"
|
||||
:disabled="currentPage >= totalPages || loading"
|
||||
@click="goToPage(currentPage + 1)"
|
||||
>
|
||||
{{ t('common.nextPage') }}
|
||||
</button>
|
||||
</div>
|
||||
</footer>
|
||||
</section>
|
||||
|
||||
<aside class="th-section detail-panel">
|
||||
<div class="th-section-header">
|
||||
<h2 class="th-section-title">{{ t('systemAdmin.users.detailTitle') }}</h2>
|
||||
</div>
|
||||
<div
|
||||
v-if="detailLoading"
|
||||
class="empty-state empty-state--compact"
|
||||
>
|
||||
{{ t('common.loading') }}
|
||||
</div>
|
||||
<div
|
||||
v-else-if="selectedUser"
|
||||
class="detail-list"
|
||||
>
|
||||
<div>
|
||||
<span>{{ t('systemAdmin.users.columns.user') }}</span>
|
||||
<strong>{{ selectedUser.display_name || selectedUser.username }}</strong>
|
||||
<small class="th-code">{{ selectedUser.id }}</small>
|
||||
</div>
|
||||
<div>
|
||||
<span>{{ t('systemAdmin.users.email') }}</span>
|
||||
<strong>{{ selectedUser.email || '-' }}</strong>
|
||||
</div>
|
||||
<div>
|
||||
<span>{{ t('systemAdmin.users.phone') }}</span>
|
||||
<strong>{{ selectedUser.phone || '-' }}</strong>
|
||||
</div>
|
||||
<div>
|
||||
<span>{{ t('systemAdmin.users.hotels') }}</span>
|
||||
<strong>{{ hotelSummary(selectedUser.hotels) }}</strong>
|
||||
</div>
|
||||
<form
|
||||
class="stacked-form"
|
||||
@submit.prevent="updateSelectedUser"
|
||||
>
|
||||
<label>
|
||||
<span>{{ t('systemAdmin.users.displayName') }}</span>
|
||||
<input
|
||||
v-model="editForm.display_name"
|
||||
type="text"
|
||||
>
|
||||
</label>
|
||||
<label>
|
||||
<span>{{ t('systemAdmin.users.email') }}</span>
|
||||
<input
|
||||
v-model="editForm.email"
|
||||
type="email"
|
||||
>
|
||||
</label>
|
||||
<label>
|
||||
<span>{{ t('systemAdmin.users.phone') }}</span>
|
||||
<input
|
||||
v-model="editForm.phone"
|
||||
type="text"
|
||||
>
|
||||
</label>
|
||||
<label>
|
||||
<span>{{ t('systemAdmin.users.userStatus') }}</span>
|
||||
<select v-model="editForm.user_status">
|
||||
<option value="ACTIVE">{{ t('systemAdmin.status.ACTIVE') }}</option>
|
||||
<option value="DISABLED">{{ t('systemAdmin.status.DISABLED') }}</option>
|
||||
</select>
|
||||
</label>
|
||||
<button
|
||||
type="submit"
|
||||
class="secondary-button"
|
||||
:disabled="saving"
|
||||
>
|
||||
{{ t('systemAdmin.common.save') }}
|
||||
</button>
|
||||
</form>
|
||||
<form
|
||||
class="stacked-form"
|
||||
@submit.prevent="assignSelectedUserRoles"
|
||||
>
|
||||
<label>
|
||||
<span>{{ t('systemAdmin.users.roles') }}</span>
|
||||
<select
|
||||
v-model="editRoleIds"
|
||||
multiple
|
||||
>
|
||||
<option
|
||||
v-for="role in availableRoles"
|
||||
:key="role.id"
|
||||
:value="role.id"
|
||||
>
|
||||
{{ role.role_name }}
|
||||
</option>
|
||||
</select>
|
||||
</label>
|
||||
<button
|
||||
type="submit"
|
||||
class="secondary-button"
|
||||
:disabled="saving"
|
||||
>
|
||||
{{ t('systemAdmin.users.saveRoles') }}
|
||||
</button>
|
||||
</form>
|
||||
<form
|
||||
class="stacked-form"
|
||||
@submit.prevent="assignSelectedUserHotels"
|
||||
>
|
||||
<label>
|
||||
<span>{{ t('systemAdmin.users.hotels') }}</span>
|
||||
<select
|
||||
v-model="editHotelIds"
|
||||
multiple
|
||||
>
|
||||
<option
|
||||
v-for="hotel in activeHotels"
|
||||
:key="hotel.hotel_id"
|
||||
:value="hotel.hotel_id"
|
||||
>
|
||||
{{ hotel.hotel_name }}
|
||||
</option>
|
||||
</select>
|
||||
</label>
|
||||
<label>
|
||||
<span>{{ t('systemAdmin.users.defaultHotel') }}</span>
|
||||
<select v-model="editDefaultHotelId">
|
||||
<option
|
||||
v-for="hotel in activeHotels"
|
||||
:key="hotel.hotel_id"
|
||||
:value="hotel.hotel_id"
|
||||
>
|
||||
{{ hotel.hotel_name }}
|
||||
</option>
|
||||
</select>
|
||||
</label>
|
||||
<button
|
||||
type="submit"
|
||||
class="secondary-button"
|
||||
:disabled="saving"
|
||||
>
|
||||
{{ t('systemAdmin.users.saveHotels') }}
|
||||
</button>
|
||||
</form>
|
||||
<button
|
||||
type="button"
|
||||
class="secondary-button"
|
||||
:disabled="saving"
|
||||
@click="resetSelectedUserPassword"
|
||||
>
|
||||
{{ t('systemAdmin.users.resetPassword') }}
|
||||
</button>
|
||||
<p
|
||||
v-if="temporaryPassword"
|
||||
class="form-message"
|
||||
>
|
||||
{{ t('systemAdmin.users.temporaryPassword') }} {{ temporaryPassword }}
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
class="empty-state empty-state--compact"
|
||||
>
|
||||
{{ t('systemAdmin.common.selectRow') }}
|
||||
</div>
|
||||
</aside>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, reactive, ref, watch } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import {
|
||||
assignAdminUserHotels,
|
||||
assignAdminUserRoles,
|
||||
createAdminUser,
|
||||
fetchAdminHotels,
|
||||
fetchAdminRoles,
|
||||
fetchAdminUserDetail,
|
||||
fetchAdminUsers,
|
||||
resetAdminUserPassword,
|
||||
updateAdminUser,
|
||||
} from '@/services/systemAdminService'
|
||||
import type {
|
||||
AdminHotelResult,
|
||||
AdminRoleListItemResult,
|
||||
AdminUserCreateRequest,
|
||||
AdminUserDetailResult,
|
||||
AdminUserHotelResult,
|
||||
AdminUserListFilters,
|
||||
AdminUserListItemResult,
|
||||
AdminUserRoleResult,
|
||||
PlatformPaginationResult,
|
||||
} from '@/types/systemAdmin'
|
||||
import { formatReservationDateTime } from '@/utils/reservationFormat'
|
||||
|
||||
const { t } = useI18n()
|
||||
const loading = ref(false)
|
||||
const detailLoading = ref(false)
|
||||
const errorMessage = ref('')
|
||||
const successMessage = ref('')
|
||||
const saving = ref(false)
|
||||
const temporaryPassword = ref('')
|
||||
const users = ref<AdminUserListItemResult[]>([])
|
||||
const selectedUser = ref<AdminUserDetailResult | null>(null)
|
||||
const availableRoles = ref<AdminRoleListItemResult[]>([])
|
||||
const availableHotels = ref<AdminHotelResult[]>([])
|
||||
const activeHotels = computed(() => availableHotels.value.filter((hotel) => hotel.hotel_status === 'ACTIVE'))
|
||||
const page = ref(createPage(1, 20, 0))
|
||||
const total = computed(() => page.value.total)
|
||||
const currentPage = computed(() => page.value.page_num)
|
||||
const totalPages = computed(() => Math.max(1, Math.ceil(page.value.total / Math.max(page.value.page_size, 1))))
|
||||
const filters = reactive<AdminUserListFilters>({
|
||||
keyword: '',
|
||||
user_status: '',
|
||||
page_num: 1,
|
||||
page_size: 20,
|
||||
})
|
||||
const createForm = reactive<AdminUserCreateRequest>({
|
||||
username: '',
|
||||
initial_password: '',
|
||||
display_name: '',
|
||||
email: '',
|
||||
phone: '',
|
||||
user_status: 'ACTIVE',
|
||||
role_ids: [],
|
||||
hotel_ids: [],
|
||||
default_hotel_id: '',
|
||||
})
|
||||
const editForm = reactive({
|
||||
display_name: '',
|
||||
email: '',
|
||||
phone: '',
|
||||
user_status: 'ACTIVE',
|
||||
})
|
||||
const editRoleIds = ref<string[]>([])
|
||||
const editHotelIds = ref<string[]>([])
|
||||
const editDefaultHotelId = ref('')
|
||||
let requestSequence = 0
|
||||
let detailRequestSequence = 0
|
||||
|
||||
onMounted(() => {
|
||||
void loadUsers()
|
||||
void loadReferenceData()
|
||||
})
|
||||
|
||||
watch(filters, () => {
|
||||
void loadUsers()
|
||||
})
|
||||
|
||||
async function loadUsers(): Promise<void> {
|
||||
const currentRequest = ++requestSequence
|
||||
loading.value = true
|
||||
errorMessage.value = ''
|
||||
try {
|
||||
const result = await fetchAdminUsers({ ...filters })
|
||||
if (currentRequest !== requestSequence) {
|
||||
return
|
||||
}
|
||||
users.value = result.items
|
||||
page.value = result.page
|
||||
if (!selectedUser.value && result.items[0]) {
|
||||
void selectUser(result.items[0].id)
|
||||
}
|
||||
} catch (error) {
|
||||
if (currentRequest !== requestSequence) {
|
||||
return
|
||||
}
|
||||
users.value = []
|
||||
page.value = createPage(filters.page_num ?? 1, filters.page_size ?? 20, 0)
|
||||
errorMessage.value = error instanceof Error ? error.message : String(error)
|
||||
} finally {
|
||||
if (currentRequest === requestSequence) {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function selectUser(userId: string): Promise<void> {
|
||||
const currentRequest = ++detailRequestSequence
|
||||
detailLoading.value = true
|
||||
try {
|
||||
const result = await fetchAdminUserDetail(userId)
|
||||
if (currentRequest === detailRequestSequence) {
|
||||
selectedUser.value = result
|
||||
populateEditForms(result)
|
||||
temporaryPassword.value = ''
|
||||
}
|
||||
} finally {
|
||||
if (currentRequest === detailRequestSequence) {
|
||||
detailLoading.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function loadReferenceData(): Promise<void> {
|
||||
const [rolesResult, hotelsResult] = await Promise.all([
|
||||
fetchAdminRoles({ page_num: 1, page_size: 100 }),
|
||||
fetchAdminHotels({ page_num: 1, page_size: 100 }),
|
||||
])
|
||||
availableRoles.value = rolesResult.items
|
||||
availableHotels.value = hotelsResult.items
|
||||
if (!createForm.default_hotel_id && activeHotels.value[0]) {
|
||||
createForm.hotel_ids = [activeHotels.value[0].hotel_id]
|
||||
createForm.default_hotel_id = activeHotels.value[0].hotel_id
|
||||
}
|
||||
}
|
||||
|
||||
async function createUser(): Promise<void> {
|
||||
saving.value = true
|
||||
successMessage.value = ''
|
||||
errorMessage.value = ''
|
||||
try {
|
||||
const result = await createAdminUser({ ...createForm })
|
||||
successMessage.value = t('systemAdmin.users.created')
|
||||
resetCreateForm()
|
||||
await loadUsers()
|
||||
await selectUser(result.id)
|
||||
} catch (error) {
|
||||
setOperationError(error)
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function updateSelectedUser(): Promise<void> {
|
||||
if (!selectedUser.value) {
|
||||
return
|
||||
}
|
||||
saving.value = true
|
||||
errorMessage.value = ''
|
||||
try {
|
||||
const result = await updateAdminUser(selectedUser.value.id, { ...editForm })
|
||||
selectedUser.value = result
|
||||
populateEditForms(result)
|
||||
await loadUsers()
|
||||
} catch (error) {
|
||||
setOperationError(error)
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function assignSelectedUserRoles(): Promise<void> {
|
||||
if (!selectedUser.value) {
|
||||
return
|
||||
}
|
||||
saving.value = true
|
||||
errorMessage.value = ''
|
||||
try {
|
||||
const result = await assignAdminUserRoles(selectedUser.value.id, {
|
||||
role_ids: editRoleIds.value,
|
||||
})
|
||||
selectedUser.value = result
|
||||
populateEditForms(result)
|
||||
await loadUsers()
|
||||
} catch (error) {
|
||||
setOperationError(error)
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function assignSelectedUserHotels(): Promise<void> {
|
||||
if (!selectedUser.value) {
|
||||
return
|
||||
}
|
||||
saving.value = true
|
||||
errorMessage.value = ''
|
||||
try {
|
||||
const result = await assignAdminUserHotels(selectedUser.value.id, {
|
||||
hotel_ids: editHotelIds.value,
|
||||
default_hotel_id: editDefaultHotelId.value,
|
||||
})
|
||||
selectedUser.value = result
|
||||
populateEditForms(result)
|
||||
await loadUsers()
|
||||
} catch (error) {
|
||||
setOperationError(error)
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function resetSelectedUserPassword(): Promise<void> {
|
||||
if (!selectedUser.value) {
|
||||
return
|
||||
}
|
||||
saving.value = true
|
||||
temporaryPassword.value = ''
|
||||
errorMessage.value = ''
|
||||
try {
|
||||
const result = await resetAdminUserPassword(selectedUser.value.id)
|
||||
temporaryPassword.value = result.temporary_password
|
||||
} catch (error) {
|
||||
setOperationError(error)
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function resetFilters(): void {
|
||||
filters.keyword = ''
|
||||
filters.user_status = ''
|
||||
filters.page_num = 1
|
||||
filters.page_size = 20
|
||||
}
|
||||
|
||||
function resetCreateForm(): void {
|
||||
createForm.username = ''
|
||||
createForm.initial_password = ''
|
||||
createForm.display_name = ''
|
||||
createForm.email = ''
|
||||
createForm.phone = ''
|
||||
createForm.user_status = 'ACTIVE'
|
||||
createForm.role_ids = []
|
||||
createForm.hotel_ids = activeHotels.value[0] ? [activeHotels.value[0].hotel_id] : []
|
||||
createForm.default_hotel_id = activeHotels.value[0]?.hotel_id ?? ''
|
||||
}
|
||||
|
||||
function populateEditForms(user: AdminUserDetailResult): void {
|
||||
editForm.display_name = user.display_name || ''
|
||||
editForm.email = user.email || ''
|
||||
editForm.phone = user.phone || ''
|
||||
editForm.user_status = user.user_status
|
||||
editRoleIds.value = user.roles.map((role) => role.id)
|
||||
editHotelIds.value = user.hotels.map((hotel) => hotel.hotel_id)
|
||||
editDefaultHotelId.value = user.hotels.find((hotel) => hotel.default_hotel)?.hotel_id ?? editHotelIds.value[0] ?? ''
|
||||
}
|
||||
|
||||
function resetToFirstPage(): void {
|
||||
filters.page_num = 1
|
||||
}
|
||||
|
||||
function goToPage(pageNum: number): void {
|
||||
filters.page_num = Math.min(Math.max(pageNum, 1), totalPages.value)
|
||||
}
|
||||
|
||||
function createPage(pageNum: number, pageSize: number, total: number): PlatformPaginationResult {
|
||||
return {
|
||||
page_num: pageNum,
|
||||
page_size: pageSize,
|
||||
total,
|
||||
}
|
||||
}
|
||||
|
||||
function roleSummary(roles: AdminUserRoleResult[]): string {
|
||||
return roles.map((role) => role.role_name || role.role_code).join(', ') || '-'
|
||||
}
|
||||
|
||||
function hotelSummary(hotels: AdminUserHotelResult[]): string {
|
||||
return hotels.map((hotel) => hotel.hotel_name || hotel.hotel_id).join(', ') || '-'
|
||||
}
|
||||
|
||||
function statusLabel(status: string): string {
|
||||
return t(`systemAdmin.status.${status}`)
|
||||
}
|
||||
|
||||
function setOperationError(error: unknown): void {
|
||||
errorMessage.value = error instanceof Error ? error.message : String(error)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@import './system-admin.css';
|
||||
</style>
|
||||
Reference in new issue
Block a user