|
|
@@ -1,89 +1,31 @@
|
|
|
<template>
|
|
|
<div class="rounded-lg p-4 bg-[var(--p-content-background)]">
|
|
|
- <DataTable
|
|
|
- :value="tableData.content"
|
|
|
- :paginator="true"
|
|
|
+ <DataTable :value="tableData.content" :paginator="true"
|
|
|
paginatorTemplate="CurrentPageReport FirstPageLink PrevPageLink PageLinks NextPageLink LastPageLink RowsPerPageDropdown JumpToPageInput"
|
|
|
- currentPageReportTemplate="{totalRecords} 条记录 "
|
|
|
- :rows="tableData.metadata.size"
|
|
|
- :rowsPerPageOptions="[10, 20, 50, 100]"
|
|
|
- :totalRecords="tableData.metadata.total"
|
|
|
- @page="handlePageChange"
|
|
|
- lazy
|
|
|
- scrollable
|
|
|
- class="fish-table"
|
|
|
- v-model:selection="selectedFish"
|
|
|
- dataKey="id"
|
|
|
- >
|
|
|
+ currentPageReportTemplate="{totalRecords} 条记录 " :rows="tableData.metadata.size"
|
|
|
+ :rowsPerPageOptions="[10, 20, 50, 100]" :totalRecords="tableData.metadata.total" @page="handlePageChange" lazy
|
|
|
+ scrollable class="fish-table" v-model:selection="selectedFish" dataKey="id">
|
|
|
<template #header>
|
|
|
<div class="flex flex-wrap items-center gap-2">
|
|
|
<InputText v-model="searchForm.id" placeholder="ID" size="small" class="w-32" @keyup.enter="handleSearch" />
|
|
|
- <InputText
|
|
|
- v-model="searchForm.name"
|
|
|
- placeholder="用户名"
|
|
|
- size="small"
|
|
|
- class="w-32"
|
|
|
- @keyup.enter="handleSearch"
|
|
|
- />
|
|
|
- <InputText
|
|
|
- v-model="searchForm.phone"
|
|
|
- placeholder="手机号"
|
|
|
- size="small"
|
|
|
- class="w-32"
|
|
|
- @keyup.enter="handleSearch"
|
|
|
- />
|
|
|
- <InputText
|
|
|
- v-model="searchForm.ownerName"
|
|
|
- placeholder="所有者"
|
|
|
- v-if="isAdmin"
|
|
|
- size="small"
|
|
|
- class="w-32"
|
|
|
- @keyup.enter="handleSearch"
|
|
|
- />
|
|
|
- <Select
|
|
|
- v-model="searchForm.result"
|
|
|
- :options="resultOptions"
|
|
|
- optionLabel="label"
|
|
|
- optionValue="value"
|
|
|
- placeholder="操作结果"
|
|
|
- size="small"
|
|
|
- class="w-32"
|
|
|
- :clearable="true"
|
|
|
- />
|
|
|
- <DatePicker
|
|
|
- v-model="searchForm.createdAt"
|
|
|
- placeholder="中鱼时间"
|
|
|
- size="small"
|
|
|
- class="w-40"
|
|
|
- dateFormat="yy-mm-dd"
|
|
|
- showIcon
|
|
|
- />
|
|
|
- <DatePicker
|
|
|
- v-model="searchForm.loginTime"
|
|
|
- placeholder="登录时间"
|
|
|
- size="small"
|
|
|
- class="w-40"
|
|
|
- dateFormat="yy-mm-dd"
|
|
|
- showIcon
|
|
|
- />
|
|
|
+ <InputText v-model="searchForm.name" placeholder="用户名" size="small" class="w-32"
|
|
|
+ @keyup.enter="handleSearch" />
|
|
|
+ <InputText v-model="searchForm.phone" placeholder="手机号" size="small" class="w-32"
|
|
|
+ @keyup.enter="handleSearch" />
|
|
|
+ <InputText v-model="searchForm.ownerName" placeholder="所有者" v-if="isAdmin" size="small" class="w-32"
|
|
|
+ @keyup.enter="handleSearch" />
|
|
|
+ <Select v-model="searchForm.result" :options="resultOptions" optionLabel="label" optionValue="value"
|
|
|
+ placeholder="操作结果" size="small" class="w-32" :clearable="true" />
|
|
|
+ <DatePicker v-model="searchForm.createdAt" placeholder="中鱼时间" size="small" class="w-40" dateFormat="yy-mm-dd"
|
|
|
+ showIcon />
|
|
|
+ <DatePicker v-model="searchForm.loginTime" placeholder="登录时间" size="small" class="w-40" dateFormat="yy-mm-dd"
|
|
|
+ showIcon />
|
|
|
<Button icon="pi pi-search" @click="handleSearch" label="搜索" size="small" severity="secondary" />
|
|
|
<Button icon="pi pi-refresh" @click="handleRefresh" label="刷新" size="small" />
|
|
|
- <Button
|
|
|
- v-if="isAdmin"
|
|
|
- icon="pi pi-users"
|
|
|
- @click="openBatchUpdateDialog"
|
|
|
- label="批量更新所有者"
|
|
|
- size="small"
|
|
|
- severity="info"
|
|
|
- />
|
|
|
- <Button
|
|
|
- v-if="isAdmin"
|
|
|
- icon="pi pi-trash"
|
|
|
- @click="openBatchDeleteDialog"
|
|
|
- label="批量删除"
|
|
|
- size="small"
|
|
|
- severity="danger"
|
|
|
- />
|
|
|
+ <Button v-if="isAdmin" icon="pi pi-users" @click="openBatchUpdateDialog" label="批量更新所有者" size="small"
|
|
|
+ severity="info" />
|
|
|
+ <Button v-if="isAdmin" icon="pi pi-trash" @click="openBatchDeleteDialog" label="批量删除" size="small"
|
|
|
+ severity="danger" />
|
|
|
<div class="flex-1"></div>
|
|
|
</div>
|
|
|
</template>
|
|
|
@@ -91,11 +33,8 @@
|
|
|
<Column selectionMode="multiple" headerStyle="width: 3rem" frozen></Column>
|
|
|
<Column field="id" header="ID" style="width: 80px" frozen>
|
|
|
<template #body="slotProps">
|
|
|
- <span
|
|
|
- class="font-mono text-sm copyable-text"
|
|
|
- :title="slotProps.data.id"
|
|
|
- @click="copyToClipboard(slotProps.data.id)"
|
|
|
- >
|
|
|
+ <span class="font-mono text-sm copyable-text" :title="slotProps.data.id"
|
|
|
+ @click="copyToClipboard(slotProps.data.id)">
|
|
|
{{ slotProps.data.id }}
|
|
|
</span>
|
|
|
</template>
|
|
|
@@ -103,11 +42,8 @@
|
|
|
|
|
|
<Column field="name" header="用户名" style="min-width: 120px; max-width: 200px">
|
|
|
<template #body="slotProps">
|
|
|
- <span
|
|
|
- class="font-medium name-text copyable-text"
|
|
|
- :title="slotProps.data.name"
|
|
|
- @click="copyToClipboard(slotProps.data.name)"
|
|
|
- >
|
|
|
+ <span class="font-medium name-text copyable-text" :title="slotProps.data.name"
|
|
|
+ @click="copyToClipboard(slotProps.data.name)">
|
|
|
{{ slotProps.data.name }}
|
|
|
</span>
|
|
|
</template>
|
|
|
@@ -115,11 +51,8 @@
|
|
|
|
|
|
<Column field="username" header="昵称" style="min-width: 120px; max-width: 200px">
|
|
|
<template #body="slotProps">
|
|
|
- <span
|
|
|
- class="username-text copyable-text"
|
|
|
- :title="slotProps.data.username"
|
|
|
- @click="copyToClipboard(slotProps.data.username)"
|
|
|
- >
|
|
|
+ <span class="username-text copyable-text" :title="slotProps.data.username"
|
|
|
+ @click="copyToClipboard(slotProps.data.username)">
|
|
|
{{ slotProps.data.username }}
|
|
|
</span>
|
|
|
</template>
|
|
|
@@ -135,12 +68,8 @@
|
|
|
|
|
|
<Column field="password" header="二级密码" style="min-width: 140px; max-width: 200px">
|
|
|
<template #body="slotProps">
|
|
|
- <span
|
|
|
- class="password-text copyable-text"
|
|
|
- :class="{ 'has-password': slotProps.data.password }"
|
|
|
- :title="slotProps.data.password || '无'"
|
|
|
- @click="copyToClipboard(slotProps.data.password || '无')"
|
|
|
- >
|
|
|
+ <span class="password-text copyable-text" :class="{ 'has-password': slotProps.data.password }"
|
|
|
+ :title="slotProps.data.password || '无'" @click="copyToClipboard(slotProps.data.password || '无')">
|
|
|
{{ slotProps.data.password || '无' }}
|
|
|
</span>
|
|
|
</template>
|
|
|
@@ -156,11 +85,8 @@
|
|
|
|
|
|
<Column field="result" header="操作结果" style="width: 100px">
|
|
|
<template #body="slotProps">
|
|
|
- <span
|
|
|
- class="result-text"
|
|
|
- :class="getResultText(slotProps.data.result).class"
|
|
|
- :title="getResultText(slotProps.data.result).text"
|
|
|
- >
|
|
|
+ <span class="result-text" :class="getResultText(slotProps.data.result).class"
|
|
|
+ :title="getResultText(slotProps.data.result).text">
|
|
|
{{ getResultText(slotProps.data.result).text }}
|
|
|
</span>
|
|
|
</template>
|
|
|
@@ -168,11 +94,8 @@
|
|
|
|
|
|
<Column field="ownerName" header="所有者" v-if="isAdmin" style="min-width: 100px; max-width: 150px">
|
|
|
<template #body="slotProps">
|
|
|
- <span
|
|
|
- class="owner-text copyable-text"
|
|
|
- :title="slotProps.data.ownerName || '未分配'"
|
|
|
- @click="copyToClipboard(slotProps.data.ownerName || '未分配')"
|
|
|
- >
|
|
|
+ <span class="owner-text copyable-text" :title="slotProps.data.ownerName || '未分配'"
|
|
|
+ @click="copyToClipboard(slotProps.data.ownerName || '未分配')">
|
|
|
{{ slotProps.data.ownerName || '未分配' }}
|
|
|
</span>
|
|
|
</template>
|
|
|
@@ -196,82 +119,37 @@
|
|
|
|
|
|
<Column field="ip" header="IP地址" style="width: 120px">
|
|
|
<template #body="slotProps">
|
|
|
- <span
|
|
|
- class="font-mono text-sm ip-text copyable-text"
|
|
|
- :title="slotProps.data.ip"
|
|
|
- @click="copyToClipboard(slotProps.data.ip)"
|
|
|
- >
|
|
|
+ <span class="font-mono text-sm ip-text copyable-text" :title="slotProps.data.ip"
|
|
|
+ @click="copyToClipboard(slotProps.data.ip)">
|
|
|
{{ slotProps.data.ip }}
|
|
|
</span>
|
|
|
</template>
|
|
|
</Column>
|
|
|
|
|
|
- <Column
|
|
|
- header="操作"
|
|
|
- style="min-width: 220px; width: 220px"
|
|
|
- align-frozen="right"
|
|
|
- frozen
|
|
|
- :pt="{
|
|
|
- columnHeaderContent: {
|
|
|
- class: 'justify-center'
|
|
|
- }
|
|
|
- }"
|
|
|
- >
|
|
|
+ <Column header="操作" style="min-width: 300px; width: 300px" align-frozen="right" frozen :pt="{
|
|
|
+ columnHeaderContent: {
|
|
|
+ class: 'justify-center'
|
|
|
+ }
|
|
|
+ }">
|
|
|
<template #body="slotProps">
|
|
|
<div class="flex justify-center gap-1">
|
|
|
- <Button
|
|
|
- icon="pi pi-window-maximize"
|
|
|
- label="一键登录"
|
|
|
- severity="danger"
|
|
|
- size="small"
|
|
|
- text
|
|
|
- rounded
|
|
|
- class="quick-login-btn"
|
|
|
- aria-label="一键登录"
|
|
|
- @click="handleQuickLogin(slotProps.data)"
|
|
|
- />
|
|
|
- <Button
|
|
|
- icon="pi pi-file-export"
|
|
|
- label="导出好友列表"
|
|
|
- severity="warn"
|
|
|
- size="small"
|
|
|
- text
|
|
|
- rounded
|
|
|
- class="export-friends-btn"
|
|
|
- aria-label="导出好友列表"
|
|
|
- @click="handleExportFriends(slotProps.data)"
|
|
|
- />
|
|
|
- <Button
|
|
|
- icon="pi pi-pencil"
|
|
|
- severity="info"
|
|
|
- size="small"
|
|
|
- text
|
|
|
- rounded
|
|
|
- aria-label="编辑"
|
|
|
- @click="openEditDialog(slotProps.data)"
|
|
|
- />
|
|
|
- <Button
|
|
|
- icon="pi pi-trash"
|
|
|
- severity="danger"
|
|
|
- size="small"
|
|
|
- text
|
|
|
- rounded
|
|
|
- aria-label="删除"
|
|
|
- @click="confirmDelete(slotProps.data)"
|
|
|
- />
|
|
|
+ <Button icon="pi pi-window-maximize" label="一键登录" severity="danger" size="small" text rounded
|
|
|
+ class="quick-login-btn" aria-label="一键登录" @click="handleQuickLogin(slotProps.data)" />
|
|
|
+ <Button icon="pi pi-file-export" label="导出好友列表" severity="warn" size="small" text rounded
|
|
|
+ class="export-friends-btn" aria-label="导出好友列表" @click="handleExportFriends(slotProps.data)" />
|
|
|
+ <Button icon="pi pi-send" label="发送消息" severity="success" size="small" text rounded class="quick-login-btn"
|
|
|
+ aria-label="发送消息" @click="openSendMessageDialog(slotProps.data)" />
|
|
|
+ <Button icon="pi pi-pencil" severity="info" size="small" text rounded aria-label="编辑"
|
|
|
+ @click="openEditDialog(slotProps.data)" />
|
|
|
+ <Button icon="pi pi-trash" severity="danger" size="small" text rounded aria-label="删除"
|
|
|
+ @click="confirmDelete(slotProps.data)" />
|
|
|
</div>
|
|
|
</template>
|
|
|
</Column>
|
|
|
</DataTable>
|
|
|
|
|
|
<!-- 编辑弹窗 -->
|
|
|
- <Dialog
|
|
|
- v-model:visible="editDialog"
|
|
|
- :modal="true"
|
|
|
- header="编辑鱼苗信息"
|
|
|
- :style="{ width: '700px' }"
|
|
|
- position="center"
|
|
|
- >
|
|
|
+ <Dialog v-model:visible="editDialog" :modal="true" header="编辑鱼苗信息" :style="{ width: '700px' }" position="center">
|
|
|
<div class="p-fluid">
|
|
|
<div class="grid grid-cols-3 gap-4">
|
|
|
<div class="field">
|
|
|
@@ -305,88 +183,42 @@
|
|
|
<div class="grid grid-cols-2 gap-4 mt-4">
|
|
|
<div class="field">
|
|
|
<label for="edit-result" class="font-medium text-sm mb-2 block">操作结果</label>
|
|
|
- <Select
|
|
|
- id="edit-result"
|
|
|
- v-model="editForm.result"
|
|
|
- :options="resultOptions.filter((option) => option.value !== null)"
|
|
|
- optionLabel="label"
|
|
|
- optionValue="value"
|
|
|
- placeholder="选择操作结果"
|
|
|
- class="w-full"
|
|
|
- />
|
|
|
+ <Select id="edit-result" v-model="editForm.result"
|
|
|
+ :options="resultOptions.filter((option) => option.value !== null)" optionLabel="label" optionValue="value"
|
|
|
+ placeholder="选择操作结果" class="w-full" />
|
|
|
</div>
|
|
|
|
|
|
<div class="field" v-if="isAdmin">
|
|
|
<label for="edit-ownerName" class="font-medium text-sm mb-2 block">所有者</label>
|
|
|
- <Select
|
|
|
- id="edit-ownerName"
|
|
|
- v-model="editForm.ownerId"
|
|
|
- :options="ownerStore.owners"
|
|
|
- optionLabel="name"
|
|
|
- optionValue="value"
|
|
|
- placeholder="选择所有者"
|
|
|
- class="w-full"
|
|
|
- @change="handleOwnerChange"
|
|
|
- />
|
|
|
+ <Select id="edit-ownerName" v-model="editForm.ownerId" :options="ownerStore.owners" optionLabel="name"
|
|
|
+ optionValue="value" placeholder="选择所有者" class="w-full" @change="handleOwnerChange" />
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<div class="field mt-4">
|
|
|
<label for="edit-remark" class="font-medium text-sm mb-2 block">客户备注</label>
|
|
|
- <Textarea
|
|
|
- id="edit-remark"
|
|
|
- v-model="editForm.remark"
|
|
|
- rows="3"
|
|
|
- class="w-full"
|
|
|
- placeholder="请输入客户备注信息..."
|
|
|
- autoResize
|
|
|
- />
|
|
|
+ <Textarea id="edit-remark" v-model="editForm.remark" rows="3" class="w-full" placeholder="请输入客户备注信息..."
|
|
|
+ autoResize />
|
|
|
</div>
|
|
|
|
|
|
<div class="field mt-4">
|
|
|
<div class="flex items-center gap-2 mb-2">
|
|
|
<label for="edit-token" class="font-medium text-sm">Token</label>
|
|
|
- <Button
|
|
|
- icon="pi pi-copy"
|
|
|
- size="small"
|
|
|
- text
|
|
|
- rounded
|
|
|
- @click="copyToClipboard(editForm.token || '')"
|
|
|
- :disabled="!editForm.token"
|
|
|
- title="复制Token"
|
|
|
- />
|
|
|
+ <Button icon="pi pi-copy" size="small" text rounded @click="copyToClipboard(editForm.token || '')"
|
|
|
+ :disabled="!editForm.token" title="复制Token" />
|
|
|
</div>
|
|
|
- <Textarea
|
|
|
- id="edit-token"
|
|
|
- v-model="editForm.token"
|
|
|
- rows="3"
|
|
|
- class="w-full"
|
|
|
- placeholder="请输入Token信息..."
|
|
|
- autoResize
|
|
|
- />
|
|
|
+ <Textarea id="edit-token" v-model="editForm.token" rows="3" class="w-full" placeholder="请输入Token信息..."
|
|
|
+ autoResize />
|
|
|
</div>
|
|
|
|
|
|
<div class="field mt-4">
|
|
|
<div class="flex items-center gap-2 mb-2">
|
|
|
<label for="edit-session" class="font-medium text-sm">Session</label>
|
|
|
- <Button
|
|
|
- icon="pi pi-copy"
|
|
|
- size="small"
|
|
|
- text
|
|
|
- rounded
|
|
|
- @click="copyToClipboard(editForm.session || '')"
|
|
|
- :disabled="!editForm.session"
|
|
|
- title="复制Session"
|
|
|
- />
|
|
|
+ <Button icon="pi pi-copy" size="small" text rounded @click="copyToClipboard(editForm.session || '')"
|
|
|
+ :disabled="!editForm.session" title="复制Session" />
|
|
|
</div>
|
|
|
- <Textarea
|
|
|
- id="edit-session"
|
|
|
- v-model="editForm.session"
|
|
|
- rows="3"
|
|
|
- class="w-full"
|
|
|
- placeholder="请输入Session信息..."
|
|
|
- autoResize
|
|
|
- />
|
|
|
+ <Textarea id="edit-session" v-model="editForm.session" rows="3" class="w-full" placeholder="请输入Session信息..."
|
|
|
+ autoResize />
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
@@ -399,13 +231,8 @@
|
|
|
</Dialog>
|
|
|
|
|
|
<!-- 批量更新所有者弹窗 -->
|
|
|
- <Dialog
|
|
|
- v-model:visible="batchUpdateDialog"
|
|
|
- :modal="true"
|
|
|
- header="批量更新所有者"
|
|
|
- :style="{ width: '500px' }"
|
|
|
- position="center"
|
|
|
- >
|
|
|
+ <Dialog v-model:visible="batchUpdateDialog" :modal="true" header="批量更新所有者" :style="{ width: '500px' }"
|
|
|
+ position="center">
|
|
|
<div class="p-fluid">
|
|
|
<div class="mb-4">
|
|
|
<p class="text-sm text-gray-600 mb-2">已选择 {{ selectedFish.length }} 条鱼苗记录</p>
|
|
|
@@ -419,41 +246,23 @@
|
|
|
|
|
|
<div class="field">
|
|
|
<label for="batch-owner" class="font-medium text-sm mb-2 block">选择新所有者</label>
|
|
|
- <Select
|
|
|
- id="batch-owner"
|
|
|
- v-model="batchUpdateForm.ownerId"
|
|
|
- :options="ownerStore.owners"
|
|
|
- optionLabel="name"
|
|
|
- optionValue="value"
|
|
|
- placeholder="选择所有者"
|
|
|
- class="w-full"
|
|
|
- @change="handleBatchOwnerChange"
|
|
|
- />
|
|
|
+ <Select id="batch-owner" v-model="batchUpdateForm.ownerId" :options="ownerStore.owners" optionLabel="name"
|
|
|
+ optionValue="value" placeholder="选择所有者" class="w-full" @change="handleBatchOwnerChange" />
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<template #footer>
|
|
|
<div class="flex justify-end gap-3">
|
|
|
<Button label="取消" severity="secondary" @click="batchUpdateDialog = false" />
|
|
|
- <Button
|
|
|
- label="确认更新"
|
|
|
- severity="success"
|
|
|
- @click="saveBatchUpdate"
|
|
|
- :loading="batchUpdateLoading"
|
|
|
- :disabled="!batchUpdateForm.ownerId"
|
|
|
- />
|
|
|
+ <Button label="确认更新" severity="success" @click="saveBatchUpdate" :loading="batchUpdateLoading"
|
|
|
+ :disabled="!batchUpdateForm.ownerId" />
|
|
|
</div>
|
|
|
</template>
|
|
|
</Dialog>
|
|
|
|
|
|
<!-- 批量删除确认弹窗 -->
|
|
|
- <Dialog
|
|
|
- v-model:visible="batchDeleteDialog"
|
|
|
- :modal="true"
|
|
|
- header="批量删除确认"
|
|
|
- :style="{ width: '500px' }"
|
|
|
- position="center"
|
|
|
- >
|
|
|
+ <Dialog v-model:visible="batchDeleteDialog" :modal="true" header="批量删除确认" :style="{ width: '500px' }"
|
|
|
+ position="center">
|
|
|
<div class="p-fluid">
|
|
|
<div class="mb-4">
|
|
|
<p class="text-sm text-gray-600 mb-2">确定要删除以下 {{ selectedFish.length }} 条鱼苗记录吗?</p>
|
|
|
@@ -477,6 +286,41 @@
|
|
|
</div>
|
|
|
</template>
|
|
|
</Dialog>
|
|
|
+
|
|
|
+ <!-- 发送消息弹窗 -->
|
|
|
+ <Dialog v-model:visible="sendMessageDialog" :modal="true" header="发送消息" :style="{ width: '600px' }"
|
|
|
+ position="center">
|
|
|
+ <div class="p-fluid">
|
|
|
+
|
|
|
+ <div class="field mt-4">
|
|
|
+ <label for="send-target" class="font-medium text-sm mb-2 block">目标 (Target)</label>
|
|
|
+ <InputText id="send-target" v-model="sendMessageForm.target" class="w-full" placeholder="请输入目标用户名或ID..." />
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="field mt-4">
|
|
|
+ <label for="send-message" class="font-medium text-sm mb-2 block">消息内容</label>
|
|
|
+ <Textarea id="send-message" v-model="sendMessageForm.message" rows="5" class="w-full"
|
|
|
+ placeholder="请输入要发送的消息内容..." autoResize />
|
|
|
+ </div>
|
|
|
+ <div class="field">
|
|
|
+ <label for="send-session" class="font-medium text-sm mb-2 block">Session</label>
|
|
|
+ <div class="flex items-center gap-2">
|
|
|
+ <Textarea id="send-session" v-model="sendMessageForm.session" rows="3" class="flex-1"
|
|
|
+ placeholder="请输入Session信息..." autoResize />
|
|
|
+ <Button icon="pi pi-copy" size="small" text rounded @click="copyToClipboard(sendMessageForm.session || '')"
|
|
|
+ :disabled="!sendMessageForm.session" title="复制Session" />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <template #footer>
|
|
|
+ <div class="flex justify-end gap-3">
|
|
|
+ <Button label="取消" severity="secondary" @click="sendMessageDialog = false" />
|
|
|
+ <Button label="发送" severity="success" @click="handleSendMessage" :loading="sendMessageLoading"
|
|
|
+ :disabled="!sendMessageForm.session || !sendMessageForm.target || !sendMessageForm.message" />
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </Dialog>
|
|
|
</div>
|
|
|
</template>
|
|
|
|
|
|
@@ -487,7 +331,8 @@ import {
|
|
|
updateFish,
|
|
|
exportFishFriends,
|
|
|
batchUpdateFishOwner,
|
|
|
- batchDeleteFish
|
|
|
+ batchDeleteFish,
|
|
|
+ sendTelegramMessage
|
|
|
} from '@/services/api'
|
|
|
import { useDateFormat } from '@vueuse/core'
|
|
|
import Button from 'primevue/button'
|
|
|
@@ -558,6 +403,15 @@ const batchUpdateForm = ref({
|
|
|
const batchDeleteDialog = ref(false)
|
|
|
const batchDeleteLoading = ref(false)
|
|
|
|
|
|
+// 发送消息相关
|
|
|
+const sendMessageDialog = ref(false)
|
|
|
+const sendMessageLoading = ref(false)
|
|
|
+const sendMessageForm = ref({
|
|
|
+ session: null,
|
|
|
+ target: null,
|
|
|
+ message: null
|
|
|
+})
|
|
|
+
|
|
|
// 搜索表单
|
|
|
const searchForm = ref({
|
|
|
id: null,
|
|
|
@@ -1061,6 +915,71 @@ const saveBatchDelete = async () => {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+// 打开发送消息弹窗
|
|
|
+const openSendMessageDialog = (fish) => {
|
|
|
+ sendMessageForm.value = {
|
|
|
+ session: fish.session || null,
|
|
|
+ target: null,
|
|
|
+ message: null
|
|
|
+ }
|
|
|
+ sendMessageDialog.value = true
|
|
|
+}
|
|
|
+
|
|
|
+// 发送消息
|
|
|
+const handleSendMessage = async () => {
|
|
|
+ if (!sendMessageForm.value.session || !sendMessageForm.value.target || !sendMessageForm.value.message) {
|
|
|
+ toast.add({
|
|
|
+ severity: 'warn',
|
|
|
+ summary: '警告',
|
|
|
+ detail: '请填写完整的信息',
|
|
|
+ life: 3000
|
|
|
+ })
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ sendMessageLoading.value = true
|
|
|
+ try {
|
|
|
+ await sendTelegramMessage(
|
|
|
+ sendMessageForm.value.session,
|
|
|
+ sendMessageForm.value.target,
|
|
|
+ sendMessageForm.value.message
|
|
|
+ )
|
|
|
+ toast.add({
|
|
|
+ severity: 'success',
|
|
|
+ summary: '成功',
|
|
|
+ detail: '消息发送成功',
|
|
|
+ life: 3000
|
|
|
+ })
|
|
|
+ sendMessageDialog.value = false
|
|
|
+ // 清空表单
|
|
|
+ sendMessageForm.value = {
|
|
|
+ session: null,
|
|
|
+ target: null,
|
|
|
+ message: null
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('发送消息失败:', error)
|
|
|
+ let errorMessage = '发送消息失败'
|
|
|
+ if (error.message?.includes('ERR_CONNECTION_REFUSED') || error.code === 'ERR_NETWORK') {
|
|
|
+ errorMessage = '无法连接到服务器,请检查后端服务是否运行'
|
|
|
+ } else if (error.response?.status === 401) {
|
|
|
+ errorMessage = '认证失败,请重新登录'
|
|
|
+ } else if (error.response?.status) {
|
|
|
+ errorMessage = `服务器错误: ${error.response.status}`
|
|
|
+ } else if (error.message) {
|
|
|
+ errorMessage = error.message
|
|
|
+ }
|
|
|
+ toast.add({
|
|
|
+ severity: 'error',
|
|
|
+ summary: '错误',
|
|
|
+ detail: errorMessage,
|
|
|
+ life: 5000
|
|
|
+ })
|
|
|
+ } finally {
|
|
|
+ sendMessageLoading.value = false
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
// 初始化
|
|
|
onMounted(async () => {
|
|
|
if (isAdmin.value) {
|
|
|
@@ -1071,11 +990,11 @@ onMounted(async () => {
|
|
|
</script>
|
|
|
|
|
|
<style scoped>
|
|
|
-.p-datatable-sm .p-datatable-tbody > tr > td {
|
|
|
+.p-datatable-sm .p-datatable-tbody>tr>td {
|
|
|
padding: 0.5rem;
|
|
|
}
|
|
|
|
|
|
-.p-datatable-sm .p-datatable-thead > tr > th {
|
|
|
+.p-datatable-sm .p-datatable-thead>tr>th {
|
|
|
padding: 0.5rem;
|
|
|
}
|
|
|
|