Skip to content

Commit

Permalink
feat(websites): 完成分类站点 RestFul 风格 CURD 接口
Browse files Browse the repository at this point in the history
  • Loading branch information
baiwumm committed Jun 17, 2024
1 parent b5b846a commit 088aad3
Show file tree
Hide file tree
Showing 10 changed files with 750 additions and 3 deletions.
68 changes: 68 additions & 0 deletions src/pages/admin/_components/websites/components/DynamicTag.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
<template>
<el-space wrap>
<el-tag
v-for="tag in dynamicTags"
:key="tag"
closable
:disable-transitions="false"
@close="handleClose(tag)"
>
{{ tag }}
</el-tag>
<el-input
v-if="inputVisible"
ref="InputRef"
v-model="inputValue"
class="w-20"
size="small"
@keyup.enter="handleInputConfirm"
@blur="handleInputConfirm"
/>
<el-button v-else class="button-new-tag" size="small" @click="showInput"> + New Tag </el-button>
</el-space>
</template>

<script lang="ts" setup>
import { nextTick, ref } from 'vue'
import { ElInput } from 'element-plus'
// 定义props和emits
const props = defineProps<{
modelValue: string[]
}>()
const emits = defineEmits(['update:modelValue'])
const inputValue = ref('')
const inputVisible = ref(false)
const InputRef = ref<InstanceType<typeof ElInput>>()
const dynamicTags = ref(props.modelValue)
const handleClose = (tag: string) => {
dynamicTags.value.splice(dynamicTags.value.indexOf(tag), 1)
}
const showInput = () => {
inputVisible.value = true
nextTick(() => {
InputRef.value!.input!.focus()
})
}
const handleInputConfirm = () => {
if (inputValue.value) {
dynamicTags.value.push(inputValue.value)
// 触发更新事件通知父组件
emits('update:modelValue', dynamicTags.value)
}
inputVisible.value = false
inputValue.value = ''
}
watch(
() => props.modelValue,
(newVal) => {
dynamicTags.value = newVal // 确保 dynamicTags 总是与 modelValue 同步
},
{ immediate: true } // 添加 immediate: true 以在初始渲染时立即同步
)
</script>
165 changes: 165 additions & 0 deletions src/pages/admin/_components/websites/components/EditModal.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
<!--
* @Author: 白雾茫茫丶<baiwumm.com>
* @Date: 2024-06-05 10:47:28
* @LastEditors: 白雾茫茫丶<baiwumm.com>
* @LastEditTime: 2024-06-17 14:32:12
* @Description: 新增/编辑弹窗
-->
<template>
<el-dialog :model-value="open" width="500" @close="handleClose">
<template #header>
{{ websiteId ? `编辑站点: ` : '新增站点' }}
<el-tag v-if="name" type="primary">
{{ name }}
</el-tag>
</template>
<template #footer>
<el-button type="primary" :loading="confirmLoading" @click="handleConfirm(ruleFormRef)">
确定
</el-button>
</template>
<el-form ref="ruleFormRef" :model="form" label-width="auto" :rules="rules">
<el-form-item label="所属分类" prop="category_id">
<el-select v-model="form.category_id" clearable filterable placeholder="请选择所属分类">
<el-option
v-for="item in categoryList"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item label="网站名称" prop="name">
<el-input v-model="form.name" maxlength="12" show-word-limit type="text" />
</el-form-item>
<el-form-item label="网站链接" prop="url">
<el-input v-model="form.url" type="text" />
</el-form-item>
<el-form-item label="Logo" prop="logo">
<el-input v-model="form.logo" type="text" />
</el-form-item>
<el-form-item label="站点标签" prop="tags">
<dynamic-tag v-model="form.tags" />
</el-form-item>
<el-form-item label="分类描述" prop="desc">
<el-input v-model="form.desc" type="textarea" :rows="3" maxlength="100" show-word-limit />
</el-form-item>
<el-form-item label="排序" prop="sort">
<el-input-number v-model="form.sort" :min="1" :max="99" :style="{ width: '100%' }" />
</el-form-item>
</el-form>
</el-dialog>
</template>
<script setup lang="ts">
import type { WebsiteEdit, CategoryList } from '~/types'
import type { FormInstance, FormRules } from 'element-plus'
import DynamicTag from './DynamicTag.vue'
import { RESPONSE_STATUS_CODE } from '~/enum'

const open = ref(false) // 是否显示弹窗
const name = ref('') // 当前数据
const confirmLoading = ref(false)
const websiteId = ref()

const emit = defineEmits(['refresh'])

// 父组件传递参数
defineProps<{
categoryList: CategoryList[]
}>()

const form = reactive<WebsiteEdit>({
category_id: '',
name: '',
url: '',
logo: '',
tags: [],
desc: undefined,
sort: 1
})

const ruleFormRef = ref<FormInstance>()
// 校验 logo url
const validatorLogo = (
_: any,
value: any,
callback: (error?: string | Error | undefined) => void
) => {
if (!value) {
callback(new Error('请输入站点logo'))
} else {
const reg = /^https:\/\/.*\.(jpg|jpeg|png|gif|bmp|svg)(\?|$)/i
if (reg.test(value)) {
callback()
} else {
callback(new Error('请输入正确的url'))
}
}
}
// 校验网站 url
const validatorUrl = (
_: any,
value: any,
callback: (error?: string | Error | undefined) => void
) => {
if (!value) {
callback(new Error('请输入网站链接'))
} else {
const reg =
/^(((ht|f)tps?):\/\/)?([^!@#$%^&*?.\s-]([^!@#$%^&*?.\s]{0,63}[^!@#$%^&*?.\s])?\.)+[a-z]{2,6}\/?/
if (reg.test(value)) {
callback()
} else {
callback(new Error('请输入正确的url'))
}
}
}
// 表单规则校验
const rules = reactive<FormRules<WebsiteEdit>>({
category_id: [{ required: true, message: '请选择所属分类', trigger: 'change' }],
name: [
{ required: true, message: '请输入站点名称', trigger: 'blur' },
{ min: 1, max: 12, message: '长度1-12个字符', trigger: 'blur' }
],
url: [{ required: true, validator: validatorUrl, trigger: 'blur' }],
logo: [{ required: true, validator: validatorLogo, trigger: 'blur' }],
tags: [{ required: true, message: '请输入站点标签', trigger: 'blur' }]
})

// 暴露方法
defineExpose({ open, name, form, websiteId })

// 关闭回调
const handleClose = async () => {
open.value = false
name.value = ''
websiteId.value = undefined
ruleFormRef.value?.resetFields()
}

// 确定回调
const handleConfirm = async (formEl: FormInstance | undefined) => {
if (!formEl) return
await formEl.validate(async (valid) => {
if (valid) {
confirmLoading.value = true
await $fetch('/api/websites', {
method: websiteId.value ? 'put' : 'post',
body: Object.assign(form, { id: websiteId.value })
})
.then(({ code, msg }) => {
if (code === RESPONSE_STATUS_CODE.SUCCESS) {
ElMessage.success('操作成功')
handleClose()
emit('refresh')
} else {
ElMessage.error(msg)
}
})
.finally(() => {
confirmLoading.value = false
})
}
})
}
</script>
91 changes: 91 additions & 0 deletions src/pages/admin/_components/websites/components/TableTemplate.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
<template>
<el-table v-loading="pending" :data="dataSource" border stripe table-layou="auto">
<el-table-column type="index" label="#" width="50" align="center" />
<el-table-column
prop="name"
label="站点名称"
align="center"
show-overflow-tooltip
min-width="160"
>
<template #default="{ row }">
<el-space wrap>
<el-tag type="primary">{{ row.name }}</el-tag>
<el-link :href="row.url" target="_blank">
<Icon name="ri:share-box-fill" class="h-5 w-5" />
</el-link>
</el-space>
</template>
</el-table-column>
<el-table-column prop="logo" label="Logo" align="center" min-width="100">
<template #default="{ row }">
<NuxtImg :src="row.logo" alt="logo" class="w-10 h-10 m-auto" />
</template>
</el-table-column>
<el-table-column
prop="tags"
label="站点标签"
align="center"
show-overflow-tooltip
min-width="200"
>
<template #default="{ row }">
<el-space wrap>
<el-tag v-for="(tag, index) in row.tags" :key="index" type="info">{{ tag }}</el-tag>
</el-space>
</template>
</el-table-column>
<el-table-column
prop="desc"
label="分类描述"
align="center"
show-overflow-tooltip
min-width="100"
>
<template #default="{ row }">
{{ row.desc || '--' }}
</template>
</el-table-column>
<el-table-column
prop="desc"
label="所属分类"
align="center"
show-overflow-tooltip
min-width="100"
>
<template #default="{ row }">
<el-tag type="success">{{ row.categorys.name }}</el-tag>
</template>
</el-table-column>
<el-table-column prop="sort" label="排序" align="center" sortable min-width="100" />
<el-table-column prop="created_at" label="创建时间" align="center" width="180" sortable>
<template #default="{ row }">
<div>{{ formatDateTime(row.created_at) }}</div>
</template>
</el-table-column>
<el-table-column prop="updated_at" label="更新时间" align="center" width="180" sortable>
<template #default="{ row }">
<div>{{ formatDateTime(row.updated_at) }}</div>
</template>
</el-table-column>
<el-table-column label="操作" width="140" align="center" fixed="right">
<template #default="{ row }">
<el-button size="small" @click="emit('handleEdit', row)"> 编辑 </el-button>
<el-button size="small" type="danger" @click="emit('handleDelete', row)"> 删除 </el-button>
</template>
</el-table-column>
</el-table>
</template>
<script setup lang="ts">
import { formatDateTime } from '@/utils'
import type { CategoryList } from '~/types'
// 父组件传递参数
defineProps<{
pending: boolean // 数据加载 loading
dataSource: CategoryList[] // 表格数据
}>()
// 父组件方法
const emit = defineEmits(['handleEdit', 'handleDelete'])
</script>
Loading

0 comments on commit 088aad3

Please sign in to comment.