feat: 新增组件

This commit is contained in:
duanshuwen
2025-12-06 23:42:48 +08:00
parent 5dc7d73120
commit 0fe4dbe2ab
8 changed files with 293 additions and 328 deletions

View File

@@ -1,135 +1,49 @@
<template>
<div class="flex items-center justify-between pt-[20px]">
<div class="text-sm text-gray-600"> {{ total }} 项数据</div>
<div class="flex items-center gap-2">
<SelectRoot v-model="selectedCount" v-model:open="open">
<SelectTrigger class="inline-flex items-center justify-between rounded-md border border-gray-200 bg-white px-3 py-1 text-sm text-gray-700 shadow-sm hover:bg-gray-50">
<span>{{ selectedCount }} /</span>
<RiArrowDownSLine />
</SelectTrigger>
<SelectPortal :to="portalTarget">
<SelectContent position="popper" :side-offset="6" class="z-50 rounded-md border border-gray-200 bg-white shadow-lg" :style="{ minWidth: 'var(--radix-select-trigger-width)' }">
<SelectViewport class="p-1">
<SelectGroup>
<SelectItem v-for="(option, index) in pageSizes" :key="index" :value="String(option)" class="flex cursor-pointer select-none items-center rounded px-2 py-1 text-sm text-gray-700 hover:bg-gray-50">
<SelectItemText>{{ option }} /</SelectItemText>
</SelectItem>
</SelectGroup>
</SelectViewport>
</SelectContent>
</SelectPortal>
</SelectRoot>
<PaginationRoot
v-model:page="currentPage"
:total="total"
:items-per-page="Number(selectedCount)"
show-edges
:sibling-count="1"
:default-page="currentPage"
>
<PaginationList
v-slot="{ items }"
class="flex items-center gap-1 text-[#171717]"
>
<PaginationPrev aria-label="上一页" class="w-9 h-9 flex items-center justify-center mr-4 border rounded bg-white text-gray-700 disabled:opacity-50">
<RiArrowLeftLine size="18" />
</PaginationPrev>
<template v-for="(page, index) in items">
<PaginationListItem
v-if="page.type === 'page'"
:key="index"
class="w-9 h-9 border rounded transition data-[selected]:bg-blue-600 data-[selected]:text-white hover:bg-white/10"
:value="page.value"
>
{{ page.value }}
</PaginationListItem>
<PaginationEllipsis
v-else
:key="page.type"
:index="index"
class="w-9 h-9 flex items-center justify-center"
>
&#8230;
</PaginationEllipsis>
</template>
<PaginationNext aria-label="下一页" class="w-9 h-9 flex items-center justify-center ml-4 border rounded bg-white text-gray-700 disabled:opacity-50 focus-within:outline focus-within:outline-1 focus-within:outline-offset-1">
<RiArrowRightLine size="18" />
</PaginationNext>
</PaginationList>
</PaginationRoot>
<div class="ml-2 flex items-center gap-2 text-sm text-gray-700">
<span>跳至</span>
<input
class="w-[55px] rounded border border-gray-200 bg-white px-2 py-1 text-center outline-none focus:border-blue-500"
type="text"
:min="1"
:max="pageTotal"
v-model.number="jumpValue"
@keydown.enter="onJump"
@blur="onJump"
/>
<span>/ {{ pageTotal }} </span>
</div>
</div>
</div>
<el-pagination @size-change="sizeChangeHandle" @current-change="currentChangeHandle" class="mt-[15px]"
:pager-count="5" :page-sizes="pageSizes" :current-page="current" background :page-size="size" :layout="layout"
:total="total">
</el-pagination>
</template>
<script setup lang="ts" name="Pagination">
import { ref, defineProps, defineEmits, watch, onMounted, computed } from 'vue'
import { RiArrowLeftLine, RiArrowRightLine, RiArrowDownSLine } from '@remixicon/vue'
import { SelectRoot, SelectTrigger, SelectPortal, SelectContent, SelectViewport, SelectGroup, SelectItem, SelectItemText, PaginationRoot, PaginationList, PaginationPrev, PaginationListItem, PaginationEllipsis, PaginationNext } from 'radix-vue'
import { PaginationProps, EmitsProps } from '@/types'
<script setup lang="ts" name="pagination">
const emit = defineEmits(['sizeChange', 'currentChange']);
const props = defineProps<PaginationProps>()
const emit = defineEmits<EmitsProps>()
const pageSizes = ref(Array.isArray(props.pageSizes) && props.pageSizes.length ? props.pageSizes : [10, 20, 50, 100, 200])
const selectedCount = ref(String(props.pageSize ?? pageSizes.value[0]))
const open = ref(false)
const portalTarget = typeof document !== 'undefined' ? document.body : undefined
watch(() => props.pageSize, (val) => selectedCount.value = String(val))
watch(selectedCount, (val) => emit('update:pageSize', val))
watch(open, (val) => {
console.log('Select open state:', val)
})
onMounted(() => {
console.log('Select initial value:', selectedCount.value)
})
const pageTotal = computed(() => {
const t = Number(props.total || 0)
const s = Number(selectedCount.value || props.pageSize || 10)
const pages = s > 0 ? Math.ceil(t / s) : 1
return Math.max(1, pages)
})
watch(pageTotal, (val) => {
console.log('Pagination total pages:', val, 'from total items:', props.total, 'pageSize:', selectedCount.value)
})
// 跳转分页
const jumpValue = ref()
const onJump = () => {
const v = Number(jumpValue.value)
if (!Number.isFinite(v)) return
const clamped = Math.max(1, Math.min(pageTotal.value, v))
jumpValue.value = clamped
currentPage.value = clamped
emit('update:currentPage', clamped)
emit('currentChange', clamped)
}
const currentPage = ref(props.currentPage ?? 1)
watch(() => props.currentPage, (val) => {
if (typeof val === 'number' && Number.isFinite(val)) currentPage.value = val
})
watch(currentPage, (p) => {
emit('update:currentPage', p)
emit('currentChange', p)
})
const props = defineProps({
current: {
type: Number,
default: 1,
},
size: {
type: Number,
default: 10,
},
total: {
type: Number,
default: 0,
},
pageSizes: {
type: Array as () => number[],
default: () => {
return [10, 20, 50, 100, 200];
},
},
layout: {
type: String,
default: 'total, sizes, prev, pager, next, jumper',
},
});
// 分页改变
const sizeChangeHandle = (val: number) => {
emit('sizeChange', val);
};
// 分页改变
const currentChangeHandle = (val: number) => {
emit('currentChange', val);
};
</script>
<style></style>
<style scoped>
:deep(.el-pagination__sizes) {
margin-left: auto;
}
</style>

View File

@@ -4,6 +4,9 @@ import { createPinia } from "pinia";
import router from "./router";
import App from "./App.vue";
import Layout from "./layout/index.vue";
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import locale from 'element-plus/es/locale/lang/zh-cn'
// 创建 Vue 应用实例
const app = createApp(App);
@@ -16,6 +19,9 @@ app.component('Layout', Layout);
// 使用 Vue Router
app.use(router);
app.use(ElementPlus, {
locale,
})
// 挂载应用到 DOM
app.mount("#app");

View File

@@ -1,28 +1,45 @@
<template>
<div class="box-border border-[1px] border-[#E5E8EE] rounded-[16px] p-[20px]">
<Pagination v-model:current-page="queryParams.pageIndex" :page-size="queryParams.pageSize" :total="total" @update:pageSize="updatePageSize" @update:currentPage="updatePageIndex" />
<div class="box-border border-[1px] border-[#E5E8EE] rounded-[16px] p-[16px]">
<el-tabs v-model="activeName" class="demo-tabs">
<el-tab-pane :label="`${item.label}(${item.total})`" :name="item.name" v-for="item in tabs"
:key="item.label" />
</el-tabs>
<RateFilterSection />
<!-- 分页 -->
<Pagination v-model:current-page="queryParams.pageIndex" :page-size="queryParams.pageSize" :total="total" />
</div>
</template>
<script setup lang="ts" name="RateContentSection">
import { ref } from 'vue'
import Pagination from '@/components/Pagination/index.vue'
import RateFilterSection from '../RateFilterSection/index.vue'
const activeName = ref('all')
const tabs = ref([
{
label: '全部评价',
name: 'all',
total: 200,
},
{
label: '待回复',
name: 'second',
total: 100,
},
{
label: '差评',
name: 'third',
total: 50,
}
])
const total = ref(200)
const queryParams = ref({
pageIndex: 1,
pageSize: 10,
pageIndex: 1,
pageSize: 10,
})
const updatePageSize = (val: string) => {
console.log("🚀 ~ updatePageSize ~ val:", val)
queryParams.value.pageSize = Number(val)
}
const updatePageIndex = (val: number) => {
console.log("🚀 ~ updatePageIndex ~ val:", val)
queryParams.value.pageIndex = val
}
</script>
<style></style>

View File

@@ -0,0 +1,73 @@
<template>
<div class="flex items-center gap-[12px]">
<el-form ref="queryRef" :inline="true" :model="queryParams" @keyup.enter="handleQuery">
<el-form-item prop="dateRange">
<el-date-picker v-model="dateRange" value-format="YYYY-MM-DD" type="daterange" range-separator="-"
start-placeholder="开始日期" end-placeholder="结束日期" style="width: 240px;" />
</el-form-item>
<el-form-item prop="channel">
<el-select v-model="queryParams.channel" placeholder="请选择渠道" style="width: 240px;">
<el-option v-for="item in channels" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleQuery">搜索</el-button>
<el-button @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
</div>
</template>
<script setup lang="ts" name="RateFilterSection">
import { ref, getCurrentInstance } from 'vue'
const { proxy }: any = getCurrentInstance()
const queryParams = ref({
pageIndex: 1,
pageSize: 10,
channel: '',
startDate: '',
endDate: '',
})
const dateRange = ref([])
const channels = [
{
label: '全部',
value: 'all',
},
{
label: '携程',
value: 'ctrip',
},
{
label: '飞猪',
value: 'flyingpig',
},
{
label: '抖音',
value: 'douyin',
},
]
const loading = ref(false)
const total = ref(0)
const queryRef = ref()
/** 查询评论列表 */
function getList() {
loading.value = true
}
/** 搜索按钮操作 */
function handleQuery() {
queryParams.value.pageIndex = 1
getList()
}
/** 重置按钮操作 */
function resetQuery() {
dateRange.value = []
queryRef.value.resetFields()
handleQuery()
}
</script>

View File

@@ -1,5 +1,5 @@
<template>
<div class="grid grid-cols-5 gap-[15px] mb-[20px]">
<div class="grid grid-cols-5 gap-[15px] mb-[20px] select-none">
<div class="bg-[#F5F7FA] box-border flex flex-col p-[16px] rounded-[12px]" v-for="item in channels" :key="item.id">
<div class="flex items-center mb-[8px]">
<img :src="item.icon" class="w-[24px] h-[24px] mr-[8px]">
@@ -11,13 +11,14 @@
{{ item.total }}
</div>
<div class="flex items-center">
<RiStarFill :color="i <= item.score ? '#FA7319' : '#CACFD8'" class="w-[20px] h-[20px] mr-[2px]" v-for="i in 5" :key="i" />
<RiStarFill :color="i <= item.score ? '#FA7319' : '#CACFD8'" class="w-[20px] h-[20px] mr-[2px]" v-for="i in 5"
:key="i" />
<span class="text-[14px] text-[#525866] leading-[20px] pl-[2px]">
{{ item.score }}
</span>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts" name="RatePanelSection">