优化
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import '@/styles/adminDensity.css'
|
||||
import { computed, onMounted, ref, watch } from 'vue'
|
||||
import { AlertTriangle, ArrowLeft, CircleCheck, Download, FileSpreadsheet, RefreshCw, Ship, Warehouse } from '@lucide/vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
@@ -11,7 +12,7 @@ import ApiErrorMeta from '@/shared/components/ApiErrorMeta.vue'
|
||||
import AdminPage from '@/shared/components/AdminPage.vue'
|
||||
import DetailPageLayout from '@/shared/components/DetailPageLayout.vue'
|
||||
import LoadErrorState from '@/shared/components/LoadErrorState.vue'
|
||||
import PageToolbar from '@/shared/components/PageToolbar.vue'
|
||||
import StatusBadge from '@/shared/components/StatusBadge.vue'
|
||||
import {
|
||||
downloadPlatformPriceBookSource,
|
||||
getPlatformPriceBook,
|
||||
@@ -384,7 +385,7 @@ async function confirmPriceBook() {
|
||||
await ElMessageBox.confirm(
|
||||
`确认后,该价格表会立即成为“${priceBookScopeLabel(detail.value.version.customerId)}”的唯一生效版本,并仅替换同一报价范围的当前生效版本。历史版本和原始文件将继续保留。`,
|
||||
'确认并生效船期价格表',
|
||||
{ type: 'warning', confirmButtonText: '确认并立即生效', cancelButtonText: '取消' },
|
||||
{ customClass: 'admin-density-confirm', type: 'warning', confirmButtonText: '确认并立即生效', cancelButtonText: '取消' },
|
||||
)
|
||||
} catch (error) {
|
||||
if (error === 'cancel' || error === 'close') return
|
||||
@@ -430,37 +431,38 @@ onMounted(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AdminPage as="div" class="price-book-detail" spacing="normal">
|
||||
<DetailPageLayout aria-label="船期价格表版本详情">
|
||||
<AdminPage as="div" class="price-book-detail admin-density" spacing="normal">
|
||||
<DetailPageLayout class="density-detail-layout surface-panel" aria-label="船期价格表版本详情">
|
||||
<template #header>
|
||||
<div class="detail-heading">
|
||||
<el-button @click="backToList"><ArrowLeft :size="16" />返回列表</el-button>
|
||||
<PageToolbar
|
||||
class="detail-toolbar"
|
||||
as="section"
|
||||
:title="detail ? `船期价格表 · V${detail.version.versionNo}` : '船期价格表详情'"
|
||||
:description="detail ? `${priceBookScopeLabel(detail.version.customerId)} · 上传于 ${formatDateTime(detail.version.createdAt)}` : '查看版本、仓库映射和渠道价格'"
|
||||
>
|
||||
<template v-if="detail" #actions>
|
||||
<el-tag :type="priceBookStatusMeta(detail.version.status).type" effect="light">{{ priceBookStatusMeta(detail.version.status).label }}</el-tag>
|
||||
<el-button :loading="downloading" @click="downloadSource"><Download :size="16" />下载原文件</el-button>
|
||||
<el-button v-if="isPending && canEdit" type="primary" :loading="publishing" :disabled="!canConfirm" @click="confirmPriceBook"><CircleCheck :size="16" />确认并立即生效</el-button>
|
||||
</template>
|
||||
</PageToolbar>
|
||||
<div class="density-detail-header">
|
||||
<div class="density-detail-heading">
|
||||
<el-button link class="density-back" @click="backToList"><ArrowLeft :size="16" />返回列表</el-button>
|
||||
<h1 class="density-detail-title">{{ detail ? '船期价格表 · V' + detail.version.versionNo : '船期价格表详情' }}</h1>
|
||||
<StatusBadge v-if="detail" :status="priceBookStatusMeta(detail.version.status).label" />
|
||||
</div>
|
||||
<div v-if="detail" class="price-book-header-actions">
|
||||
<el-button :loading="downloading" @click="downloadSource"><Download :size="16" />下载原文件</el-button>
|
||||
<el-button v-if="isPending && canEdit" type="primary" :loading="publishing" :disabled="!canConfirm" @click="confirmPriceBook"><CircleCheck :size="16" />确认并立即生效</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template v-if="detail" #summary>
|
||||
<dl class="density-detail-overview">
|
||||
<div><dt>报价范围</dt><dd>{{ priceBookScopeLabel(detail.version.customerId) }}</dd></div>
|
||||
<div><dt>上传时间</dt><dd>{{ formatDateTime(detail.version.createdAt) }}</dd></div>
|
||||
</dl>
|
||||
</template>
|
||||
|
||||
<LoadErrorState v-if="loadError" title="船期价格表详情加载失败" :message="loadError.message" :loading="loading" @retry="loadDetail">
|
||||
<template #details><ApiErrorMeta :details="loadError" /></template>
|
||||
</LoadErrorState>
|
||||
|
||||
<template v-else-if="detail">
|
||||
<section class="surface-panel version-panel">
|
||||
<section class="version-panel">
|
||||
<header class="section-heading">
|
||||
<div><h2><FileSpreadsheet :size="18" />版本信息</h2><p>{{ detail.version.originalFileName }}</p></div>
|
||||
</header>
|
||||
<dl class="detail-grid">
|
||||
<div><dt>报价范围</dt><dd>{{ priceBookScopeLabel(detail.version.customerId) }}</dd></div>
|
||||
<dl class="detail-grid density-fields">
|
||||
<div><dt>解析渠道</dt><dd>{{ detail.version.channelCount }}</dd></div>
|
||||
<div><dt>解析价格行</dt><dd>{{ detail.version.rateCount }}</dd></div>
|
||||
<div><dt>阻断错误</dt><dd :class="{ 'is-danger': detail.version.blockingErrorCount }">{{ detail.version.blockingErrorCount }}</dd></div>
|
||||
@@ -470,7 +472,7 @@ onMounted(() => {
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
<section v-if="isPending && canEdit" class="surface-panel mapping-section">
|
||||
<section v-if="isPending && canEdit" class="mapping-section">
|
||||
<header class="section-heading"><div><h2><Warehouse :size="18" />仓库映射</h2><p>来源编码必须匹配启用的平台公共仓库编码。</p></div></header>
|
||||
<el-alert
|
||||
v-if="nonMappingBlockingIssues.length"
|
||||
@@ -509,7 +511,7 @@ onMounted(() => {
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="surface-panel detail-section">
|
||||
<section class="detail-section">
|
||||
<header class="section-heading"><div><h2><AlertTriangle :size="18" />解析与校验</h2><p>阻断错误未解决时不能生效;价格行不可在页面内修改。</p></div></header>
|
||||
<el-empty v-if="!detail.issues.length" description="未发现解析问题" :image-size="72" />
|
||||
<el-table v-else :data="detail.issues" size="small" max-height="280" aria-label="价格表解析问题">
|
||||
@@ -520,12 +522,12 @@ onMounted(() => {
|
||||
</el-table>
|
||||
</section>
|
||||
|
||||
<section class="surface-panel detail-section channel-preview">
|
||||
<section class="detail-section channel-preview">
|
||||
<header class="section-heading"><div><h2><Ship :size="18" />按渠道预览</h2><p>显示上传 Excel 的解析结果,确认页不提供在线改价。</p></div></header>
|
||||
<el-empty v-if="!detail.channels.length" description="未解析出渠道" :image-size="72" />
|
||||
<template v-else>
|
||||
<el-tabs v-model="selectedChannelCode" class="channel-tabs">
|
||||
<el-tab-pane v-for="channel in detail.channels" :key="channel.channelCode" :label="channel.channelName" :name="channel.channelCode" />
|
||||
<el-tabs v-model="selectedChannelCode" class="channel-tabs density-element-tabs">
|
||||
<el-tab-pane v-for="channel in detail.channels" :key="channel.channelCode" :name="channel.channelCode"><template #label><span class="density-tab-icon" aria-hidden="true">🚢</span>{{ channel.channelName }}</template></el-tab-pane>
|
||||
</el-tabs>
|
||||
<div class="channel-meta"><span>{{ priceModelLabel(selectedChannel?.priceModel || '') }}</span><span>表页:{{ selectedChannel?.sourceSheetName || '—' }}</span></div>
|
||||
<el-table :data="selectedChannelRates" size="small" max-height="440" border stripe aria-label="渠道价格行预览">
|
||||
@@ -552,38 +554,32 @@ onMounted(() => {
|
||||
|
||||
<style scoped>
|
||||
.price-book-detail { min-width: 0; max-width: 1880px; }
|
||||
.detail-heading { display: flex; min-width: 0; align-items: flex-start; gap: 16px; }
|
||||
.detail-toolbar { min-width: 0; flex: 1; }
|
||||
.detail-toolbar :deep(.page-toolbar__actions) { align-items: center; }
|
||||
.surface-panel { padding: 16px; }
|
||||
.section-heading { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; margin-bottom: 14px; }
|
||||
.section-heading h2 { display: flex; align-items: center; gap: 8px; margin: 0; color: var(--cc-text); font-size: 15px; }
|
||||
.version-panel, .mapping-section, .detail-section { padding: var(--admin-space-5) 0; border-bottom: 1px solid var(--cc-border); }
|
||||
.detail-section:last-child { border-bottom: 0; }
|
||||
.price-book-header-actions { display: flex; align-items: center; flex-wrap: wrap; gap: var(--admin-space-3); }
|
||||
.detail-grid .detail-grid__wide { grid-column: 1 / -1; }
|
||||
.section-heading > div { min-width: 0; }
|
||||
.section-heading { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--admin-space-5); margin-bottom: var(--admin-space-5); }
|
||||
.section-heading h2 { display: flex; align-items: center; gap: var(--admin-space-3); margin: 0; color: var(--cc-text); font-size: var(--admin-font-section); }
|
||||
.section-heading h2 svg { color: var(--cc-primary); }
|
||||
.section-heading p { margin: 5px 0 0; color: var(--cc-muted); font-size: 12px; line-height: 1.55; }
|
||||
.detail-grid { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); margin: 0; border: 1px solid var(--cc-border); border-radius: 8px; overflow: hidden; }
|
||||
.detail-grid > div { min-width: 0; padding: 11px 13px; border-right: 1px solid var(--cc-border); background: var(--cc-surface-soft, #f8fafc); }
|
||||
.detail-grid > div:nth-child(5n) { border-right: 0; }
|
||||
.detail-grid__wide { grid-column: span 3; border-top: 1px solid var(--cc-border); }
|
||||
.detail-grid__wide + .detail-grid__wide { grid-column: span 2; }
|
||||
.detail-grid dt { color: var(--cc-muted); font-size: 11px; }
|
||||
.detail-grid dd { margin: 4px 0 0; overflow-wrap: anywhere; color: var(--cc-text); font-size: 14px; font-weight: 700; }
|
||||
.section-heading p { overflow-wrap: anywhere; margin: 5px 0 0; color: var(--cc-muted); font-size: var(--admin-font-caption); line-height: 1.55; }
|
||||
.is-danger { color: var(--el-color-danger) !important; }
|
||||
.mapping-section, .detail-section { display: grid; min-width: 0; gap: 14px; }
|
||||
.mapping-section, .detail-section { display: grid; min-width: 0; gap: var(--admin-space-5); }
|
||||
.mapping-section .section-heading, .detail-section .section-heading { margin-bottom: 0; }
|
||||
.mapping-groups { display: grid; gap: 14px; }
|
||||
.mapping-group { display: grid; gap: 8px; }
|
||||
.mapping-group h3 { margin: 0; color: var(--cc-text); font-size: 13px; }
|
||||
.mapping-row { display: grid; grid-template-columns: minmax(220px, .8fr) minmax(320px, 1fr); align-items: center; gap: 16px; padding: 11px 12px; border: 1px solid var(--cc-border); border-radius: 8px; background: var(--cc-surface-soft, #f8fafc); }
|
||||
.mapping-row > div { display: grid; min-width: 0; gap: 4px; }
|
||||
.mapping-row strong { overflow-wrap: anywhere; color: var(--cc-text); font-size: 13px; }
|
||||
.mapping-row span { color: var(--cc-muted); font-size: 12px; }
|
||||
.mapping-groups { display: grid; gap: var(--admin-space-5); }
|
||||
.mapping-group { display: grid; gap: var(--admin-space-3); }
|
||||
.mapping-group h3 { margin: 0; color: var(--cc-text); font-size: var(--admin-font-body); }
|
||||
.mapping-row { display: grid; grid-template-columns: minmax(220px, .8fr) minmax(320px, 1fr); align-items: center; gap: var(--admin-space-6); padding: var(--admin-space-5) var(--admin-space-5); border: 1px solid var(--cc-border); border-radius: var(--admin-radius); background: var(--cc-surface-soft, #f8fafc); }
|
||||
.mapping-row > div { display: grid; min-width: 0; gap: var(--admin-space-1); }
|
||||
.mapping-row strong { overflow-wrap: anywhere; color: var(--cc-text); font-size: var(--admin-font-body); }
|
||||
.mapping-row span { color: var(--cc-muted); font-size: var(--admin-font-caption); }
|
||||
.mapping-row :deep(.el-select) { width: 100%; }
|
||||
.mapping-load-error { display: flex; align-items: center; justify-content: space-between; gap: 14px; padding: 14px; border: 1px solid color-mix(in srgb, var(--cc-danger) 24%, var(--cc-border)); border-radius: 8px; }
|
||||
.mapping-load-error strong { color: var(--cc-danger); font-size: 13px; }
|
||||
.mapping-load-error p { margin: 4px 0; color: var(--cc-text); font-size: 12px; }
|
||||
.mapping-load-error { display: flex; align-items: center; justify-content: space-between; gap: var(--admin-space-5); padding: var(--admin-space-5); border: 1px solid color-mix(in srgb, var(--cc-danger) 24%, var(--cc-border)); border-radius: var(--admin-radius); }
|
||||
.mapping-load-error strong { color: var(--cc-danger); font-size: var(--admin-font-body); }
|
||||
.mapping-load-error p { margin: var(--admin-space-1) 0; color: var(--cc-text); font-size: var(--admin-font-caption); }
|
||||
.channel-tabs { width: 100%; min-width: 0; margin-bottom: 0; overflow: hidden; }
|
||||
.channel-preview :deep(.el-table) { width: 100%; max-width: 100%; min-width: 0; }
|
||||
.channel-meta { display: flex; flex-wrap: wrap; gap: 6px 14px; margin: -4px 0 0; color: var(--cc-muted); font-size: 12px; }
|
||||
.channel-meta { display: flex; flex-wrap: wrap; gap: var(--admin-space-2) var(--admin-space-5); margin: calc(-1 * var(--admin-space-1)) 0 0; color: var(--cc-muted); font-size: var(--admin-font-caption); }
|
||||
@media (max-width: 720px) {
|
||||
.detail-heading, .mapping-load-error { align-items: stretch; flex-direction: column; }
|
||||
.detail-toolbar { width: 100%; }
|
||||
|
||||
@@ -126,12 +126,12 @@ describe('CarrierRatesView price-book source contract', () => {
|
||||
expect(source).toContain('<DataTable')
|
||||
expect(source).toContain(':columns="columns"')
|
||||
expect(detailSource).toContain('border stripe aria-label="渠道价格行预览"')
|
||||
expect(source).toContain('<el-button size="small" plain type="primary" @click="openDetail')
|
||||
expect(source).toMatch(/size="small"\r?\n\s+plain\r?\n\s+:loading="downloadingIds\.has/)
|
||||
expect(source).toContain('<Eye :size="15" />{{ detailActionLabel')
|
||||
expect(source).toContain('<Download :size="15" />原文件')
|
||||
expect(source).toContain('<el-button data-tone="neutral" class="density-action"')
|
||||
expect(source).toMatch(/:loading="downloadingIds\.has/)
|
||||
expect(source).toContain('{{ detailActionLabel')
|
||||
expect(source).toContain('原文件</el-button>')
|
||||
expect(detailSource).toContain('<CircleCheck :size="16" />确认并立即生效')
|
||||
expect(detailSource).toContain("import PageToolbar from '@/shared/components/PageToolbar.vue'")
|
||||
expect(detailSource).toContain('class="density-detail-header"')
|
||||
expect(source).not.toContain('<el-button link type="primary" @click="openDetail')
|
||||
})
|
||||
|
||||
@@ -151,9 +151,9 @@ describe('CarrierRatesView price-book source contract', () => {
|
||||
})
|
||||
|
||||
it('keeps all row actions directly visible without a premature more menu', () => {
|
||||
expect(source).toContain('<Eye :size="15" />{{ detailActionLabel')
|
||||
expect(source).toContain('<Download :size="15" />原文件')
|
||||
expect(source).toContain('<CircleX :size="15" />作废')
|
||||
expect(source).toContain('{{ detailActionLabel')
|
||||
expect(source).toContain('原文件</el-button>')
|
||||
expect(source).toContain('作废</el-button>')
|
||||
expect(source).not.toContain('MoreHorizontal')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import '@/styles/adminDensity.css'
|
||||
import { computed, onMounted, ref, watch } from 'vue'
|
||||
import { CircleX, Download, Eye, FileSpreadsheet, FileUp } from '@lucide/vue'
|
||||
import { FileSpreadsheet, FileUp } from '@lucide/vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useAccessPreviewStore } from '@/core/access/accessPreview'
|
||||
@@ -268,6 +269,7 @@ async function voidPending(row: PlatformPriceBookVersion) {
|
||||
`作废后,版本 V${row.versionNo} 将不能再确认生效;版本记录和原始文件会继续保留。`,
|
||||
'作废待确认价格表',
|
||||
{
|
||||
customClass: 'admin-density-confirm',
|
||||
type: 'warning',
|
||||
confirmButtonText: '确认作废',
|
||||
cancelButtonText: '取消',
|
||||
@@ -315,7 +317,7 @@ onMounted(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AdminPage as="div" class="price-book-page" spacing="normal">
|
||||
<AdminPage as="div" class="price-book-page admin-density" spacing="normal">
|
||||
<ListPageLayout aria-label="承运商报价版本管理">
|
||||
<template #toolbar>
|
||||
<QueryToolbar
|
||||
@@ -357,22 +359,22 @@ onMounted(() => {
|
||||
>
|
||||
<template #toolbar-actions><el-button v-if="canEdit" type="primary" @click="openUpload"><FileUp :size="16" />上传价格表</el-button></template>
|
||||
<template #actions="{ row }">
|
||||
<div class="table-actions">
|
||||
<el-button size="small" plain type="primary" @click="openDetail(row as PlatformPriceBookVersion)"><Eye :size="15" />{{ detailActionLabel(row as PlatformPriceBookVersion) }}</el-button>
|
||||
<el-button
|
||||
<div class="table-actions density-actions">
|
||||
<el-button data-tone="neutral" class="density-action" size="small" @click="openDetail(row as PlatformPriceBookVersion)">{{ detailActionLabel(row as PlatformPriceBookVersion) }}</el-button>
|
||||
<el-button data-tone="neutral" class="density-action"
|
||||
size="small"
|
||||
plain
|
||||
|
||||
:loading="downloadingIds.has((row as PlatformPriceBookVersion).id)"
|
||||
@click="downloadSource(row as PlatformPriceBookVersion)"
|
||||
><Download :size="15" />原文件</el-button>
|
||||
<el-button
|
||||
>原文件</el-button>
|
||||
<el-button data-tone="danger" class="density-action"
|
||||
v-if="canEdit && (row as PlatformPriceBookVersion).status === 'PENDING_CONFIRMATION'"
|
||||
size="small"
|
||||
plain
|
||||
type="danger"
|
||||
|
||||
|
||||
:loading="voidingIds.has((row as PlatformPriceBookVersion).id)"
|
||||
@click="voidPending(row as PlatformPriceBookVersion)"
|
||||
><CircleX :size="15" />作废</el-button>
|
||||
>作废</el-button>
|
||||
</div>
|
||||
</template>
|
||||
<template #version="{ row }"><div class="version-cell"><strong>V{{ (row as PlatformPriceBookVersion).versionNo }}</strong></div></template>
|
||||
@@ -386,7 +388,7 @@ onMounted(() => {
|
||||
</section>
|
||||
</ListPageLayout>
|
||||
|
||||
<AppDialog
|
||||
<AppDialog class="admin-density-dialog"
|
||||
v-model="uploadOpen"
|
||||
title="上传船期价格表"
|
||||
width="min(560px, calc(100vw - 28px))"
|
||||
@@ -416,25 +418,23 @@ onMounted(() => {
|
||||
|
||||
<style scoped>
|
||||
.price-book-page { min-width: 0; }
|
||||
.table-actions { display: flex; min-height: 32px; align-items: center; gap: 8px; }
|
||||
.table-actions :deep(.el-button + .el-button) { margin-left: 0; }
|
||||
.version-cell { display: flex; min-width: 0; flex-direction: column; gap: 3px; }
|
||||
.version-cell strong { color: var(--cc-text); }
|
||||
.version-cell span, .metric-cell span { color: var(--cc-muted); font-size: 11px; }
|
||||
.metric-cell, .issue-counts { display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 8px; }
|
||||
.metric-cell strong { color: var(--cc-text); font-size: 14px; }
|
||||
.issue-counts { color: var(--cc-muted); font-size: 12px; }
|
||||
.version-cell span, .metric-cell span { color: var(--cc-muted); font-size: var(--admin-font-caption); }
|
||||
.metric-cell, .issue-counts { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--admin-space-1) var(--admin-space-3); }
|
||||
.metric-cell strong { color: var(--cc-text); font-size: var(--admin-font-body); }
|
||||
.issue-counts { color: var(--cc-muted); font-size: var(--admin-font-caption); }
|
||||
.issue-counts .is-blocking, .is-danger { color: var(--el-color-danger); font-weight: 700; }
|
||||
.upload-content { display: grid; gap: 18px; }
|
||||
.upload-scope-field { display: grid; gap: 6px; color: var(--cc-muted); font-size: 12px; }
|
||||
.upload-content { display: grid; gap: var(--admin-space-6); }
|
||||
.upload-scope-field { display: grid; gap: var(--admin-space-2); color: var(--cc-muted); font-size: var(--admin-font-caption); }
|
||||
.upload-scope-field :deep(.el-select) { width: 100%; }
|
||||
.visually-hidden-input { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; clip-path: inset(50%); }
|
||||
.upload-file-control { display: flex; align-items: center; gap: 14px; }
|
||||
.upload-file-name { display: grid; min-width: 0; gap: 3px; color: var(--cc-text); font-size: 13px; }
|
||||
.upload-file-control { display: flex; align-items: center; gap: var(--admin-space-5); }
|
||||
.upload-file-name { display: grid; min-width: 0; gap: 3px; color: var(--cc-text); font-size: var(--admin-font-body); }
|
||||
.upload-file-name strong { overflow-wrap: anywhere; }
|
||||
.upload-file-name span { color: var(--cc-muted); font-size: 12px; }
|
||||
.upload-file-name span { color: var(--cc-muted); font-size: var(--admin-font-caption); }
|
||||
.upload-file-name.is-empty strong { color: var(--cc-muted); font-weight: 400; }
|
||||
.upload-file-error { margin: -8px 0 0; color: var(--el-color-danger); font-size: 12px; }
|
||||
.upload-file-error { margin: calc(-1 * var(--admin-space-3)) 0 0; color: var(--el-color-danger); font-size: var(--admin-font-caption); }
|
||||
@media (max-width: 720px) {
|
||||
.table-actions :deep(.el-button) { min-height: 40px; }
|
||||
.upload-file-control { align-items: stretch; flex-direction: column; }
|
||||
|
||||
Reference in New Issue
Block a user