|
|
- <template>
- <div class="word-table-container">
- <div class="table-header">
- <h3>重点单词</h3>
- <div class="header-actions">
- <a-button type="primary" @click="handleAdd">
- <template #icon><PlusOutlined /></template>
- 添加单词
- </a-button>
- <a-button
- type="primary"
- danger
- :disabled="selectedRowKeys.length === 0"
- @click="handleBatchDelete"
- >
- <template #icon><DeleteOutlined /></template>
- 批量删除
- </a-button>
- </div>
- </div>
-
- <a-table
- :columns="columns"
- :data-source="dataSource"
- :loading="loading"
- :pagination="pagination"
- :row-selection="rowSelection"
- row-key="id"
- @change="handleTableChange"
- >
- <template #bodyCell="{ column, record }">
- <template v-if="column.key === 'action'">
- <a-space>
- <a-button type="link" size="small" @click="handleEdit(record)">
- <template #icon><EditOutlined /></template>
- 编辑
- </a-button>
- <a-popconfirm
- title="确定要删除这个单词吗?"
- @confirm="handleDelete(record.id)"
- >
- <a-button type="link" size="small" danger>
- <template #icon><DeleteOutlined /></template>
- 删除
- </a-button>
- </a-popconfirm>
- </a-space>
- </template>
- <template v-else-if="column.key === 'image'">
- <div class="image-cell">
- <img
- v-if="record.image"
- :src="record.image"
- :alt="record.word"
- class="word-image"
- @error="handleImageError"
- />
- <span v-else class="no-image">无图片</span>
- </div>
- </template>
- <template v-else-if="column.key === 'soundmark'">
- <span class="phonetic-text">{{ record.soundmark }}</span>
- </template>
- </template>
- </a-table>
-
- <!-- 添加/编辑单词弹窗 -->
- <a-modal
- v-model:open="modalVisible"
- :title="modalTitle"
- :confirm-loading="confirmLoading"
- @ok="handleModalOk"
- @cancel="handleModalCancel"
- width="800px"
- >
- <a-form
- ref="formRef"
- :model="formData"
- :rules="formRules"
- :label-col="{ span: 4 }"
- :wrapper-col="{ span: 18 }"
- >
- <a-form-item label="单词" name="word">
- <a-input v-model:value="formData.word" placeholder="请输入单词" />
- </a-form-item>
-
- <a-form-item label="图片" name="image">
- <JImageUpload
- v-model:value="formData.image"
- :fileMax="1"
- listType="picture-card"
- text="上传图片"
- bizPath="course"
- :accept="['image/*']"
- />
- </a-form-item>
-
- <a-form-item label="释义" name="paraphrase">
- <a-textarea
- v-model:value="formData.paraphrase"
- placeholder="请输入释义"
- :rows="3"
- />
- </a-form-item>
-
- <a-form-item label="音标" name="soundmark">
- <a-input v-model:value="formData.soundmark" placeholder="请输入音标,如:/ˈhæpɪ/" />
- </a-form-item>
-
- <a-form-item label="知识收获" name="knowledge">
- <a-textarea
- v-model:value="formData.knowledge"
- placeholder="请输入知识收获"
- :rows="4"
- />
- </a-form-item>
- </a-form>
- </a-modal>
- </div>
- </template>
-
- <script setup lang="ts">
- import { ref, reactive, onMounted, computed, watch, nextTick } from 'vue'
- import { message } from 'ant-design-vue'
- import { PlusOutlined, EditOutlined, DeleteOutlined } from '@ant-design/icons-vue'
- import { JImageUpload } from '/@/components/Form'
- import { list, saveOrUpdate, deleteOne, batchDelete } from './coursePageWord'
-
- interface WordRecord {
- id?: string
- word: string
- image?: string
- paraphrase: string
- soundmark: string
- knowledge: string
- pageId?: string
- }
-
- interface Props {
- coursePageId?: string
- }
-
- const props = withDefaults(defineProps<Props>(), {
- coursePageId: ''
- })
-
- // 表格列定义
- const columns = [
- {
- title: '单词',
- dataIndex: 'word',
- key: 'word',
- width: 150,
- },
- {
- title: '图片',
- dataIndex: 'image',
- key: 'image',
- width: 100,
- },
- {
- title: '释义',
- dataIndex: 'paraphrase',
- key: 'paraphrase',
- ellipsis: true,
- },
- {
- title: '音标',
- dataIndex: 'soundmark',
- key: 'soundmark',
- width: 150,
- },
- {
- title: '操作',
- key: 'action',
- width: 150,
- fixed: 'right',
- },
- ]
-
- // 响应式数据
- const loading = ref(false)
- const dataSource = ref<WordRecord[]>([])
- const selectedRowKeys = ref<string[]>([])
- const modalVisible = ref(false)
- const confirmLoading = ref(false)
- const formRef = ref()
-
- // 分页配置
- const pagination = reactive({
- current: 1,
- pageSize: 10,
- total: 0,
- showSizeChanger: true,
- showQuickJumper: true,
- showTotal: (total: number) => `共 ${total} 条记录`,
- })
-
- // 表单数据
- const formData = reactive<WordRecord>({
- word: '',
- image: '',
- paraphrase: '',
- soundmark: '',
- knowledge: '',
- })
-
- // 表单验证规则
- const formRules = {
- word: [
- { required: true, message: '请输入单词', trigger: 'blur' },
- { max: 50, message: '单词长度不能超过50个字符', trigger: 'blur' },
- ],
- paraphrase: [
- { required: true, message: '请输入释义', trigger: 'blur' },
- { max: 500, message: '释义长度不能超过500个字符', trigger: 'blur' },
- ],
- soundmark: [
- { max: 100, message: '音标长度不能超过100个字符', trigger: 'blur' },
- ],
- knowledge: [
- { max: 1000, message: '知识收获长度不能超过1000个字符', trigger: 'blur' },
- ],
- }
-
- // 行选择配置
- const rowSelection = computed(() => ({
- selectedRowKeys: selectedRowKeys.value,
- onChange: (keys: string[]) => {
- selectedRowKeys.value = keys
- },
- }))
-
- // 弹窗标题
- const modalTitle = computed(() => {
- return formData.id ? '编辑单词' : '添加单词'
- })
-
- // 加载数据
- const loadData = async () => {
- try {
- loading.value = true
- const params = {
- pageNo: pagination.current,
- pageSize: pagination.pageSize,
- pageId: props.coursePageId,
- }
-
- const result = await list(params)
-
- dataSource.value = result.records || []
- pagination.total = result.total || 0
- } catch (error) {
- } finally {
- loading.value = false
- }
- }
-
- // 表格变化处理
- const handleTableChange = (pag: any) => {
- pagination.current = pag.current
- pagination.pageSize = pag.pageSize
- loadData()
- }
-
- // 添加单词
- const handleAdd = async () => {
- resetForm()
- modalVisible.value = true
- // 等待DOM更新后再次重置表单,确保表单完全清空
- await nextTick()
- formRef.value?.resetFields()
- }
-
- // 编辑单词
- const handleEdit = (record: WordRecord) => {
- Object.assign(formData, record)
- modalVisible.value = true
- }
-
- // 删除单词
- const handleDelete = async (id: string) => {
- try {
- await deleteOne({ id }, () => {
- message.success('删除成功')
- loadData()
- })
- } catch (error) {
- console.error('删除失败:', error)
- }
- }
-
- // 批量删除
- const handleBatchDelete = async () => {
- if (selectedRowKeys.value.length === 0) {
- message.warning('请选择要删除的记录')
- return
- }
-
- try {
- await batchDelete({ ids: selectedRowKeys.value.join(',') }, () => {
- message.success('批量删除成功')
- selectedRowKeys.value = []
- loadData()
- })
- } catch (error) {
- console.error('批量删除失败:', error)
- }
- }
-
- // 弹窗确认
- const handleModalOk = async () => {
- try {
- await formRef.value.validate()
- confirmLoading.value = true
-
- const params = {
- ...formData,
- pageId: props.coursePageId,
- }
-
- await saveOrUpdate(params, !!formData.id)
-
- message.success(formData.id ? '更新成功' : '添加成功')
- modalVisible.value = false
- loadData()
- } catch (error) {
- console.error('保存失败:', error)
- if (error.errorFields) {
- // 表单验证失败
- return
- }
- } finally {
- confirmLoading.value = false
- }
- }
-
- // 弹窗取消
- const handleModalCancel = () => {
- modalVisible.value = false
- resetForm()
- }
-
- // 重置表单
- const resetForm = () => {
- // 重置表单数据
- Object.assign(formData, {
- id: undefined,
- word: '',
- image: '',
- paraphrase: '',
- soundmark: '',
- knowledge: '',
- })
- // 重置表单验证状态
- if (formRef.value) {
- formRef.value.resetFields()
- formRef.value.clearValidate()
- }
- }
-
- // 组件挂载时加载数据
- onMounted(() => {
- if (props.coursePageId) {
- loadData()
- }
- })
-
- // 监听coursePageId变化,重新加载数据
- watch(() => props.coursePageId, (newId) => {
- if (newId) {
- loadData()
- } else {
- // 如果coursePageId为空,清空数据
- dataSource.value = []
- pagination.total = 0
- }
- }, { immediate: true })
-
- // 图片加载错误处理
- const handleImageError = (event: Event) => {
- const img = event.target as HTMLImageElement
- img.style.display = 'none'
- }
-
- // 暴露刷新方法给父组件
- defineExpose({
- refresh: loadData,
- })
- </script>
-
- <style scoped>
- .word-table-container {
- margin-top: 20px;
- background: #fff;
- border-radius: 6px;
- padding: 16px;
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
- }
-
- .table-header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-bottom: 16px;
- }
-
- .table-header h3 {
- margin: 0;
- color: #1890ff;
- font-size: 16px;
- font-weight: 600;
- }
-
- .header-actions {
- display: flex;
- gap: 8px;
- }
-
- .phonetic-text {
- font-family: 'Times New Roman', serif;
- color: #666;
- font-style: italic;
- }
-
- .image-cell {
- display: flex;
- align-items: center;
- justify-content: center;
- }
-
- .word-image {
- width: 60px;
- height: 60px;
- object-fit: cover;
- border-radius: 4px;
- border: 1px solid #d9d9d9;
- }
-
- .no-image {
- color: #999;
- font-size: 12px;
- }
-
- :deep(.ant-table-tbody > tr > td) {
- padding: 12px 16px;
- }
-
- :deep(.ant-table-thead > tr > th) {
- background: #fafafa;
- font-weight: 600;
- }
-
- :deep(.ant-modal-body) {
- padding: 24px;
- }
-
- :deep(.ant-form-item-label > label) {
- font-weight: 500;
- }
- </style>
|