feat(aihr): complete company message R1-R3
This commit is contained in:
@@ -37,6 +37,12 @@
|
||||
"navigationBarTitleText": "消息详情"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/user/direct/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "直通车"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/user/assistant/index",
|
||||
"style": {
|
||||
|
||||
@@ -18,6 +18,19 @@
|
||||
</view>
|
||||
<text class="message-time">发布时间:{{ displayTime(item.publishedAt) }}</text>
|
||||
<text class="message-content">{{ item.content }}</text>
|
||||
<view v-if="item.attachment" class="attachment-card">
|
||||
<view class="attachment-head">
|
||||
<view>
|
||||
<text class="attachment-kicker">公司文件</text>
|
||||
<text class="attachment-name">{{ item.attachment.fileName }}</text>
|
||||
</view>
|
||||
<text class="attachment-size">{{ formatFileSize(item.attachment.fileSize) }}</text>
|
||||
</view>
|
||||
<text v-if="item.attachment.summary" class="attachment-summary">{{ item.attachment.summary }}</text>
|
||||
<button class="download-button" :disabled="downloading" @click="downloadAttachment">
|
||||
{{ downloading ? '正在读取…' : '查看原文件' }}
|
||||
</button>
|
||||
</view>
|
||||
<view class="detail-actions">
|
||||
<button class="ask-message-button" @click="askAboutMessage">问这条消息</button>
|
||||
<text class="ask-message-note">将在“问”中围绕本条消息继续咨询</text>
|
||||
@@ -37,7 +50,8 @@ import {
|
||||
readBroadcastQuestionContext,
|
||||
saveBroadcastQuestionContext
|
||||
} from '@/services/broadcast';
|
||||
import { ApiError } from '@/services/api';
|
||||
import { ApiError, authenticatedFileUrl } from '@/services/api';
|
||||
import { isNativeAppRuntime } from '@/services/speech-capture';
|
||||
import { isLoggedIn, rememberLoginRedirect } from '@/services/auth';
|
||||
import { currentAccountKey } from '@/services/request-scope';
|
||||
|
||||
@@ -45,11 +59,45 @@ const id = ref(0);
|
||||
const item = ref<BroadcastMessage | null>(null);
|
||||
const loading = ref(false);
|
||||
const message = ref('');
|
||||
const downloading = ref(false);
|
||||
|
||||
const goBack = () => uni.navigateBack({ delta: 1, fail: () => uni.switchTab({ url: '/pages/user/today/index' }) });
|
||||
const displayTime = (value: string) => value ? value.replace('T', ' ').slice(0, 16) : '';
|
||||
const isBroadcastMessageUnavailable = (error: unknown) =>
|
||||
error instanceof ApiError && (error.statusCode === 404 || error.bizCode === 404);
|
||||
const formatFileSize = (bytes: number) => {
|
||||
if (!Number.isFinite(bytes) || bytes <= 0) return '0 B';
|
||||
if (bytes < 1024 * 1024) return `${Math.max(1, Math.round(bytes / 1024))} KB`;
|
||||
return `${(bytes / 1024 / 1024).toFixed(1)} MB`;
|
||||
};
|
||||
|
||||
const downloadAttachment = async () => {
|
||||
if (!item.value?.attachment || downloading.value) return;
|
||||
downloading.value = true;
|
||||
try {
|
||||
const localUrl = await authenticatedFileUrl(item.value.attachment.downloadUrl);
|
||||
if (!isNativeAppRuntime() && typeof document !== 'undefined') {
|
||||
const link = document.createElement('a');
|
||||
link.href = localUrl;
|
||||
link.download = item.value.attachment.fileName;
|
||||
link.rel = 'noreferrer';
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
link.remove();
|
||||
window.setTimeout(() => URL.revokeObjectURL(localUrl), 1000);
|
||||
} else {
|
||||
uni.openDocument({
|
||||
filePath: localUrl,
|
||||
showMenu: true,
|
||||
fail: () => uni.showToast({ title: '文件无法打开,请稍后重试', icon: 'none' })
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
uni.showToast({ title: error instanceof Error ? error.message : '文件读取失败', icon: 'none' });
|
||||
} finally {
|
||||
downloading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const askAboutMessage = () => {
|
||||
if (!item.value || !ensureLogin()) return;
|
||||
@@ -125,6 +173,14 @@ onLoad((options) => {
|
||||
.detail-tag.related { background: #fff7e8; color: #a76500; }
|
||||
.message-time { color: var(--muted); font-size: 12px; }
|
||||
.message-content { overflow-wrap: anywhere; color: var(--ink); font-size: 15px; line-height: 1.8; white-space: pre-wrap; }
|
||||
.attachment-card { display: grid; gap: 10px; margin-top: 4px; padding: 14px; border: 1px solid #ead6d8; border-radius: 14px; background: #fffafb; }
|
||||
.attachment-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; }
|
||||
.attachment-kicker { display: block; color: var(--brand); font-size: 11px; font-weight: 900; }
|
||||
.attachment-name { display: block; margin-top: 4px; overflow-wrap: anywhere; color: var(--ink); font-size: 14px; font-weight: 900; }
|
||||
.attachment-size { flex: none; color: var(--muted); font-size: 12px; }
|
||||
.attachment-summary { overflow-wrap: anywhere; color: #596273; font-size: 13px; line-height: 1.65; }
|
||||
.download-button { display: flex; min-height: 40px; align-items: center; justify-content: center; padding: 0; border: 1px solid #f0a6ac; border-radius: 12px; background: #fff; color: var(--brand); font-size: 13px; font-weight: 900; }
|
||||
.download-button::after { border: 0; }
|
||||
.detail-actions { display: grid; gap: 8px; margin-top: 8px; padding-top: 16px; border-top: 1px solid var(--employee-line, #ebedf0); }
|
||||
.ask-message-button { display: flex; width: 100%; min-height: 46px; align-items: center; justify-content: center; padding: 0; border-radius: 14px; background: var(--employee-primary, #e60012); color: #fff; font-size: 15px; font-weight: 800; line-height: 1; }
|
||||
.ask-message-note { color: var(--muted); font-size: 12px; line-height: 1.5; text-align: center; }
|
||||
|
||||
@@ -16,6 +16,14 @@
|
||||
</view>
|
||||
|
||||
<template v-else>
|
||||
<button class="direct-entry employee-card" @click="openDirect">
|
||||
<view>
|
||||
<text class="direct-kicker">直通车</text>
|
||||
<text class="direct-title">直接反馈给高管、财务、人力、审计或运营</text>
|
||||
<text class="direct-note">支持业务匿名,处理结果仅回到你的账号。</text>
|
||||
</view>
|
||||
<uni-icons type="right" size="18" color="#c91825" />
|
||||
</button>
|
||||
<view class="scope-tabs" role="tablist" aria-label="消息范围筛选">
|
||||
<button
|
||||
v-for="option in scopeOptions"
|
||||
@@ -164,6 +172,7 @@ const loadMore = async () => {
|
||||
};
|
||||
|
||||
const openMessage = (id: number) => uni.navigateTo({ url: `/pages/user/broadcast/detail?id=${id}` });
|
||||
const openDirect = () => uni.navigateTo({ url: '/pages/user/direct/index' });
|
||||
|
||||
const changeScope = (nextScope: BroadcastMessageScope) => {
|
||||
if (scope.value === nextScope || loading.value || loadingMore.value) return;
|
||||
@@ -190,6 +199,11 @@ onUnload(() => {
|
||||
<style scoped>
|
||||
.broadcast-page { max-width: var(--mobile-shell-max-width); margin: 0 auto; padding-bottom: calc(28px + env(safe-area-inset-bottom)); }
|
||||
.login-panel, .loading-card, .empty-card { display: grid; gap: 10px; margin-top: 18px; padding: 20px; }
|
||||
.direct-entry { display: flex; width: 100%; align-items: center; justify-content: space-between; gap: 14px; margin-top: 18px; padding: 16px; border: 1px solid #f4cbd0; background: linear-gradient(135deg, #fff, #fff5f6); color: var(--ink); text-align: left; }
|
||||
.direct-entry::after { border: 0; }
|
||||
.direct-kicker { display: block; color: var(--brand); font-size: 12px; font-weight: 900; }
|
||||
.direct-title { display: block; margin-top: 5px; font-size: 16px; font-weight: 900; line-height: 1.45; }
|
||||
.direct-note { display: block; margin-top: 5px; color: var(--muted); font-size: 12px; line-height: 1.5; }
|
||||
.status-message { display: block; margin-top: 16px; padding: 12px; border-radius: 12px; background: #fff0f1; color: #a9000d; }
|
||||
.empty-title { font-size: 17px; font-weight: 900; }
|
||||
.message-list { display: grid; gap: 12px; margin-top: 18px; }
|
||||
|
||||
@@ -0,0 +1,252 @@
|
||||
<template>
|
||||
<view class="page employee-page-shell direct-page">
|
||||
<view class="subpage-header">
|
||||
<button class="subpage-back-button" aria-label="返回公司消息" @click="goBack">
|
||||
<uni-icons type="back" size="20" color="#25282d" />
|
||||
</button>
|
||||
<view class="subpage-heading">
|
||||
<text class="employee-kicker">点对点反馈</text>
|
||||
<text class="page-title">直通车</text>
|
||||
<text class="page-subtitle">选择对象后直接提交,处理结果会回到“我的反馈”。</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view v-if="!loggedIn" class="panel employee-card login-panel">
|
||||
<text class="section-title">登录后使用</text>
|
||||
<text class="muted">直达反馈只接受当前在职员工提交。</text>
|
||||
<button class="primary-button" @click="goLogin">去登录</button>
|
||||
</view>
|
||||
|
||||
<template v-else>
|
||||
<view class="panel employee-card submit-card">
|
||||
<view class="section-head">
|
||||
<view>
|
||||
<text class="section-title">选择直达对象</text>
|
||||
<text class="section-note">每次只提交给一个处理部门。</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="channel-grid" role="radiogroup" aria-label="直达对象">
|
||||
<button
|
||||
v-for="channel in channels"
|
||||
:key="channel.code"
|
||||
class="channel-button"
|
||||
:class="{ active: form.channelCode === channel.code }"
|
||||
role="radio"
|
||||
:aria-checked="form.channelCode === channel.code"
|
||||
@click="chooseChannel(channel)"
|
||||
>
|
||||
<text>{{ channel.name }}</text>
|
||||
<text v-if="channel.defaultAnonymous" class="channel-hint">默认匿名</text>
|
||||
</button>
|
||||
</view>
|
||||
|
||||
<label class="field-label" for="direct-content">反馈内容</label>
|
||||
<textarea
|
||||
id="direct-content"
|
||||
v-model="form.content"
|
||||
class="feedback-input"
|
||||
maxlength="5000"
|
||||
placeholder="请说明具体情况、影响和希望得到的处理。"
|
||||
aria-label="反馈内容"
|
||||
/>
|
||||
<view class="form-foot">
|
||||
<label class="anonymous-control">
|
||||
<switch
|
||||
:checked="form.anonymous"
|
||||
color="#c91825"
|
||||
aria-label="匿名提交"
|
||||
@change="form.anonymous = Boolean($event.detail.value)"
|
||||
/>
|
||||
<view>
|
||||
<text class="anonymous-title">匿名提交</text>
|
||||
<text class="section-note">处理端不显示提交人;系统内部仍保留审计记录。</text>
|
||||
</view>
|
||||
</label>
|
||||
<text class="counter">{{ form.content.length }}/5000</text>
|
||||
</view>
|
||||
<text v-if="submitMessage" class="status-message" :class="{ error: submitFailed }" role="status" aria-live="polite">
|
||||
{{ submitMessage }}
|
||||
</text>
|
||||
<button class="primary-button submit-button" :disabled="submitting || !canSubmit" @click="submit">
|
||||
{{ submitting ? '提交中…' : '确认提交' }}
|
||||
</button>
|
||||
</view>
|
||||
|
||||
<view class="history-head">
|
||||
<view>
|
||||
<text class="section-title">我的反馈</text>
|
||||
<text class="section-note">只有你本人可以查看提交内容和正式回复。</text>
|
||||
</view>
|
||||
<button class="refresh-button" :disabled="loading" @click="loadMine(true)">刷新</button>
|
||||
</view>
|
||||
<view v-if="loading" class="panel employee-card loading-card" role="status">反馈加载中…</view>
|
||||
<view v-else-if="loadFailed" class="panel employee-card empty-card" role="alert">
|
||||
<text class="section-title">反馈加载失败</text>
|
||||
<button class="secondary-button" @click="loadMine(true)">重新加载</button>
|
||||
</view>
|
||||
<view v-else-if="!items.length" class="panel employee-card empty-card">
|
||||
<text class="section-title">还没有反馈</text>
|
||||
<text class="muted">提交后会在这里显示处理状态。</text>
|
||||
</view>
|
||||
<view v-else class="feedback-list">
|
||||
<view v-for="item in items" :key="item.id" class="panel employee-card feedback-card">
|
||||
<view class="feedback-head">
|
||||
<view class="feedback-tags">
|
||||
<text class="channel-tag">{{ item.channelName }}</text>
|
||||
<text v-if="item.anonymous" class="anonymous-tag">匿名</text>
|
||||
</view>
|
||||
<text class="status-tag" :class="{ replied: item.status === 'REPLIED' }">
|
||||
{{ item.status === 'REPLIED' ? '已回复' : '待处理' }}
|
||||
</text>
|
||||
</view>
|
||||
<text class="feedback-content">{{ item.content }}</text>
|
||||
<view v-if="item.replyContent" class="reply-box">
|
||||
<text class="reply-label">正式回复</text>
|
||||
<text class="reply-content">{{ item.replyContent }}</text>
|
||||
</view>
|
||||
<text class="feedback-time">{{ displayTime(item.createdAt) }}</text>
|
||||
</view>
|
||||
<button v-if="hasMore" class="secondary-button load-more" :disabled="loadingMore" @click="loadMine(false)">
|
||||
{{ loadingMore ? '加载中…' : '加载更多' }}
|
||||
</button>
|
||||
</view>
|
||||
</template>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, reactive, ref } from 'vue';
|
||||
import { onHide, onShow, onUnload } from '@dcloudio/uni-app';
|
||||
import {
|
||||
listDirectChannels,
|
||||
listMyDirectFeedback,
|
||||
submitDirectFeedback,
|
||||
type DirectChannel,
|
||||
type DirectChannelCode,
|
||||
type DirectFeedback
|
||||
} from '@/services/direct';
|
||||
import { isLoggedIn, rememberLoginRedirect } from '@/services/auth';
|
||||
import { resetPageScroll } from '@/services/navigation';
|
||||
import { createPageRequestScope } from '@/services/request-scope';
|
||||
|
||||
const path = '/pages/user/direct/index';
|
||||
const pageSize = 20;
|
||||
const loggedIn = ref(false);
|
||||
const channels = ref<DirectChannel[]>([]);
|
||||
const items = ref<DirectFeedback[]>([]);
|
||||
const total = ref(0);
|
||||
const pageNum = ref(1);
|
||||
const loading = ref(false);
|
||||
const loadingMore = ref(false);
|
||||
const loadFailed = ref(false);
|
||||
const submitting = ref(false);
|
||||
const submitFailed = ref(false);
|
||||
const submitMessage = ref('');
|
||||
const requests = createPageRequestScope();
|
||||
const form = reactive({
|
||||
requestId: '',
|
||||
channelCode: '' as DirectChannelCode | '',
|
||||
anonymous: false,
|
||||
content: ''
|
||||
});
|
||||
|
||||
const hasMore = computed(() => items.value.length < total.value);
|
||||
const canSubmit = computed(() => Boolean(form.channelCode) && Boolean(form.content.trim()));
|
||||
const createRequestId = () =>
|
||||
globalThis.crypto?.randomUUID?.() ?? `direct-${Date.now()}-${Math.random().toString(36).slice(2, 12)}`;
|
||||
|
||||
const goBack = () => uni.navigateBack({ delta: 1, fail: () => uni.redirectTo({ url: '/pages/user/broadcast/index' }) });
|
||||
const goLogin = () => {
|
||||
rememberLoginRedirect(path);
|
||||
uni.redirectTo({ url: '/pages/auth/login/index' });
|
||||
};
|
||||
const displayTime = (value?: string) => value ? value.replace('T', ' ').slice(0, 16) : '';
|
||||
|
||||
const chooseChannel = (channel: DirectChannel) => {
|
||||
form.channelCode = channel.code;
|
||||
form.anonymous = channel.defaultAnonymous;
|
||||
};
|
||||
|
||||
const loadChannels = async () => {
|
||||
channels.value = await listDirectChannels();
|
||||
if (!form.channelCode && channels.value.length) chooseChannel(channels.value[0]);
|
||||
};
|
||||
|
||||
const loadMine = async (reset: boolean) => {
|
||||
if (loading.value || loadingMore.value) return;
|
||||
const nextPage = reset ? 1 : pageNum.value + 1;
|
||||
const request = requests.begin('direct-mine');
|
||||
reset ? loading.value = true : loadingMore.value = true;
|
||||
if (reset) loadFailed.value = false;
|
||||
try {
|
||||
const result = await listMyDirectFeedback(nextPage, pageSize);
|
||||
if (!requests.isCurrent(request)) return;
|
||||
const rows = Array.isArray(result.rows) ? result.rows : [];
|
||||
if (reset) {
|
||||
items.value = rows;
|
||||
} else {
|
||||
const existing = new Set(items.value.map((item) => item.id));
|
||||
items.value = [...items.value, ...rows.filter((item) => !existing.has(item.id))];
|
||||
}
|
||||
total.value = Math.max(items.value.length, Number(result.total) || 0);
|
||||
pageNum.value = nextPage;
|
||||
} catch {
|
||||
if (requests.isCurrent(request) && reset) loadFailed.value = true;
|
||||
} finally {
|
||||
if (requests.isCurrent(request)) {
|
||||
loading.value = false;
|
||||
loadingMore.value = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const submit = async () => {
|
||||
if (!canSubmit.value || submitting.value || !form.channelCode) return;
|
||||
submitting.value = true;
|
||||
submitFailed.value = false;
|
||||
submitMessage.value = '';
|
||||
if (!form.requestId) form.requestId = createRequestId();
|
||||
try {
|
||||
await submitDirectFeedback({
|
||||
requestId: form.requestId,
|
||||
channelCode: form.channelCode,
|
||||
anonymous: form.anonymous,
|
||||
content: form.content.trim()
|
||||
});
|
||||
form.requestId = '';
|
||||
form.content = '';
|
||||
submitMessage.value = '反馈已提交,可在“我的反馈”查看处理结果。';
|
||||
await loadMine(true);
|
||||
} catch (error) {
|
||||
submitFailed.value = true;
|
||||
submitMessage.value = error instanceof Error ? error.message : '提交失败,请使用同一页面重试。';
|
||||
} finally {
|
||||
submitting.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
onShow(async () => {
|
||||
requests.activate();
|
||||
resetPageScroll();
|
||||
loggedIn.value = isLoggedIn();
|
||||
if (!loggedIn.value) return;
|
||||
try {
|
||||
await Promise.all([loadChannels(), loadMine(true)]);
|
||||
} catch {
|
||||
loadFailed.value = true;
|
||||
}
|
||||
});
|
||||
onHide(() => requests.invalidate());
|
||||
onUnload(() => requests.invalidate());
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.direct-page{max-width:var(--mobile-shell-max-width);margin:0 auto;padding-bottom:calc(32px + env(safe-area-inset-bottom))}
|
||||
.login-panel,.submit-card,.loading-card,.empty-card{display:grid;gap:12px;margin-top:18px;padding:18px}
|
||||
.section-head,.history-head,.feedback-head,.form-foot,.anonymous-control{display:flex;align-items:center;justify-content:space-between;gap:12px}
|
||||
.section-title{display:block;color:var(--ink);font-size:18px;font-weight:900}.section-note,.muted{display:block;margin-top:4px;color:var(--muted);font-size:12px;line-height:1.6}
|
||||
.channel-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px}.channel-button{display:grid;min-height:60px;align-content:center;gap:3px;padding:10px;border:1px solid #e6e8ec;border-radius:14px;background:#fff;color:var(--ink);font-size:14px;font-weight:900}.channel-button.active{border-color:var(--brand);background:#fff5f6;color:var(--brand)}.channel-button::after,.refresh-button::after{border:0}.channel-hint{font-size:11px;font-weight:700;opacity:.72}
|
||||
.field-label{margin-top:4px;color:var(--ink);font-size:14px;font-weight:900}.feedback-input{box-sizing:border-box;width:100%;height:150px;padding:14px;border:1px solid #dfe2e7;border-radius:14px;background:#fafafa;color:var(--ink);font-size:15px;line-height:1.6}
|
||||
.form-foot{align-items:flex-start}.anonymous-control{justify-content:flex-start;flex:1}.anonymous-title{display:block;font-size:14px;font-weight:900}.counter{color:var(--muted);font-size:12px}.submit-button{margin-top:2px}.status-message{padding:10px 12px;border-radius:10px;background:#edf8f2;color:#147a45;font-size:13px}.status-message.error{background:#fff0f1;color:#a9000d}
|
||||
.history-head{margin-top:28px}.refresh-button{padding:7px 12px;border-radius:999px;background:#fff0f1;color:var(--brand);font-size:13px;font-weight:800}.feedback-list{display:grid;gap:12px;margin-top:12px}.feedback-card{display:grid;gap:11px;padding:16px}.feedback-tags{display:flex;flex-wrap:wrap;gap:6px}.channel-tag,.anonymous-tag,.status-tag{padding:4px 8px;border-radius:999px;font-size:11px;font-weight:900}.channel-tag{background:#f2f3f5;color:#343b46}.anonymous-tag{background:#fff7e8;color:#925b00}.status-tag{background:#fff0f1;color:var(--brand)}.status-tag.replied{background:#edf8f2;color:#147a45}.feedback-content,.reply-content{white-space:pre-wrap;overflow-wrap:anywhere;color:var(--ink);font-size:15px;line-height:1.7}.reply-box{display:grid;gap:6px;padding:12px;border-left:3px solid var(--brand);border-radius:4px 10px 10px 4px;background:#faf6f6}.reply-label{color:var(--brand);font-size:12px;font-weight:900}.feedback-time{color:var(--muted);font-size:12px}.load-more{width:100%}
|
||||
</style>
|
||||
@@ -23,6 +23,17 @@ export interface BroadcastMessageSummary {
|
||||
|
||||
export interface BroadcastMessage extends BroadcastMessageSummary {
|
||||
content: string;
|
||||
attachment?: BroadcastAttachment;
|
||||
}
|
||||
|
||||
export interface BroadcastAttachment {
|
||||
id: number;
|
||||
fileName: string;
|
||||
fileSize: number;
|
||||
contentType: string;
|
||||
status: 'READY';
|
||||
summary?: string;
|
||||
downloadUrl: string;
|
||||
}
|
||||
|
||||
export interface BroadcastMessagePage {
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
import { apiRequest } from './api';
|
||||
|
||||
export type DirectChannelCode = 'PRESIDENT' | 'FINANCE' | 'HR' | 'AUDIT' | 'OPERATIONS';
|
||||
export type DirectFeedbackStatus = 'SUBMITTED' | 'REPLIED';
|
||||
|
||||
export interface DirectChannel {
|
||||
code: DirectChannelCode;
|
||||
name: string;
|
||||
defaultAnonymous: boolean;
|
||||
}
|
||||
|
||||
export interface DirectFeedback {
|
||||
id: number;
|
||||
channelCode: DirectChannelCode;
|
||||
channelName: string;
|
||||
anonymous: boolean;
|
||||
content: string;
|
||||
status: DirectFeedbackStatus;
|
||||
replyContent?: string;
|
||||
repliedAt?: string;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
export interface DirectFeedbackPage {
|
||||
total: number;
|
||||
pageNum: number;
|
||||
pageSize: number;
|
||||
rows: DirectFeedback[];
|
||||
}
|
||||
|
||||
export const listDirectChannels = () =>
|
||||
apiRequest<DirectChannel[]>({
|
||||
url: '/api/aihr/direct/channels',
|
||||
method: 'GET',
|
||||
timeout: 15000
|
||||
});
|
||||
|
||||
export const submitDirectFeedback = (data: {
|
||||
requestId: string;
|
||||
channelCode: DirectChannelCode;
|
||||
anonymous: boolean;
|
||||
content: string;
|
||||
}) =>
|
||||
apiRequest<DirectFeedback>({
|
||||
url: '/api/aihr/direct/feedback',
|
||||
method: 'POST',
|
||||
data,
|
||||
timeout: 15000
|
||||
});
|
||||
|
||||
export const listMyDirectFeedback = (pageNum = 1, pageSize = 20) =>
|
||||
apiRequest<DirectFeedbackPage>({
|
||||
url: `/api/aihr/direct/mine?pageNum=${pageNum}&pageSize=${pageSize}`,
|
||||
method: 'GET',
|
||||
timeout: 15000
|
||||
});
|
||||
Reference in New Issue
Block a user