| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914 |
- <script setup>
- import { ref, onMounted, reactive, computed, inject } from 'vue'
- import {
- listTeamConfig,
- createTeamConfig,
- updateTeamConfig,
- deleteTeamConfig,
- getTeamConfigByName,
- uploadFile as uploadFileAPI
- } from '@/services/api'
- import { ConfigType } from '@/enums/index'
- import { useToast } from 'primevue/usetoast'
- import { useConfirm } from 'primevue/useconfirm'
- import DataTable from 'primevue/datatable'
- import Column from 'primevue/column'
- import Button from 'primevue/button'
- import InputText from 'primevue/inputtext'
- import Select from 'primevue/select'
- import Dialog from 'primevue/dialog'
- import InputNumber from 'primevue/inputnumber'
- import DatePicker from 'primevue/datepicker'
- import Textarea from 'primevue/textarea'
- import { useDateFormat } from '@vueuse/core'
- import RadioButton from 'primevue/radiobutton'
- import Slider from 'primevue/slider'
- import { useUserStore } from '@/stores/user'
- import { useTeamStore } from '@/stores/team'
- const toast = useToast()
- const confirm = useConfirm()
- const userStore = useUserStore()
- const teamStore = useTeamStore()
- // 注入权限信息
- const isAdmin = inject('isAdmin')
- const isTeam = inject('isTeam')
- const isPromoter = inject('isPromoter')
- const tableRef = ref(null)
- const query = ref({})
- const tableData = ref({
- data: [],
- meta: {
- total: 0,
- page: 0,
- size: 20
- }
- })
- const selectedConfig = ref(null)
- const configTypes = ref([])
- const dialogVisible = ref(false)
- const isEditing = ref(false)
- const uploading = ref(false)
- const configModel = reactive({
- name: '',
- type: 'string',
- value: '',
- remark: '',
- teamId: null
- })
- // 复杂类型值的临时存储
- const tempValue = ref({
- object: {},
- file: '',
- time_range: [],
- range: []
- })
- // 搜索表单
- const searchForm = ref({
- name: '',
- type: '',
- teamId: null
- })
- // 计算当前用户的团队ID
- const currentTeamId = computed(() => {
- if (isAdmin.value) {
- // 管理员可以选择团队,这里先返回null,在创建/编辑时手动指定
- return null
- } else if (isTeam.value) {
- // 队长从team表获取teamId
- return userStore.userInfo?.teamId
- } else if (isPromoter.value) {
- // 推广员从team-members表获取teamId
- return userStore.userInfo?.teamId
- }
- return null
- })
- // 计算是否有操作权限
- const canCreate = computed(() => isAdmin.value || isTeam.value)
- const canUpdate = computed(() => isAdmin.value || isTeam.value)
- const canDelete = computed(() => isAdmin.value)
- const canView = computed(() => isAdmin.value || isTeam.value || isPromoter.value)
- const fetchData = async (page = 0) => {
- try {
- const result = await listTeamConfig(
- page,
- tableData.value.meta.size,
- searchForm.value.name || undefined,
- searchForm.value.type || undefined,
- searchForm.value.teamId || currentTeamId.value
- )
- tableData.value = result || {
- data: [],
- meta: {
- total: 0,
- page: 0,
- size: 20,
- totalPages: 0
- }
- }
- } catch (error) {
- console.error('获取团队配置列表失败', error)
- toast.add({ severity: 'error', summary: '错误', detail: '获取团队配置列表失败', life: 3000 })
- tableData.value = {
- data: [],
- meta: {
- total: 0,
- page: 0,
- size: 20,
- totalPages: 0
- }
- }
- }
- }
- const fetchConfigTypes = () => {
- configTypes.value = Object.entries(ConfigType).map(([key, value]) => ({
- label: value,
- value: key
- }))
- }
- const handlePageChange = (event) => {
- fetchData(event.page)
- }
- const refreshData = () => {
- const page = tableData.value.meta?.page || 0
- fetchData(page)
- }
- const handleSearch = () => {
- tableData.value.meta.page = 0
- fetchData()
- }
- const handleRefresh = () => {
- searchForm.value = {
- name: '',
- type: '',
- teamId: null
- }
- tableData.value.meta.page = 0
- fetchData()
- }
- const formatDate = (date) => {
- if (!date) return '-'
- return useDateFormat(new Date(date), 'YYYY-MM-DD HH:mm:ss').value
- }
- const resetModel = () => {
- configModel.name = ''
- configModel.type = 'string'
- configModel.value = ''
- configModel.remark = ''
- configModel.teamId = currentTeamId.value
- tempValue.value = {
- object: {},
- file: '',
- time_range: [],
- range: [0, 0]
- }
- }
- const onEdit = async (config = null) => {
- resetModel()
- if (config) {
- isEditing.value = true
- selectedConfig.value = config
- try {
- const detail = await getTeamConfigByName(config.name, currentTeamId.value)
- // 填充表单数据
- configModel.name = detail.name
- configModel.type = detail.type
- configModel.remark = detail.remark
- configModel.teamId = detail.teamId
- // 根据类型处理值
- if (detail.type === 'object') {
- try {
- tempValue.value.object = typeof detail.value === 'string' ? JSON.parse(detail.value) : detail.value
- tempValue.value.object = JSON.stringify(tempValue.value.object, null, 2)
- } catch (e) {
- tempValue.value.object = '{}'
- }
- } else if (detail.type === 'file') {
- configModel.value = detail.value
- } else if (detail.type === 'boolean') {
- configModel.value = detail.value === true || detail.value === 'true' || detail.value === '1' ? '1' : '0'
- } else if (detail.type === 'time_range') {
- if (Array.isArray(detail.value)) {
- tempValue.value.time_range = detail.value
- } else if (typeof detail.value === 'string' && detail.value.includes(',')) {
- const timeParts = detail.value.split(',')
- if (timeParts.length === 2) {
- const today = new Date()
- const startTime = new Date(today)
- const endTime = new Date(today)
- let startParts = timeParts[0].split(':')
- let endParts = timeParts[1].split(':')
- if (startParts.length >= 2 && endParts.length >= 2) {
- startTime.setHours(parseInt(startParts[0]), parseInt(startParts[1]), 0)
- endTime.setHours(parseInt(endParts[0]), parseInt(endParts[1]), 0)
- tempValue.value.time_range = [startTime, endTime]
- } else {
- tempValue.value.time_range = []
- }
- } else {
- tempValue.value.time_range = []
- }
- } else {
- tempValue.value.time_range = []
- }
- } else if (detail.type === 'range') {
- if (Array.isArray(detail.value)) {
- tempValue.value.range = detail.value
- } else if (typeof detail.value === 'string' && detail.value.includes(',')) {
- tempValue.value.range = detail.value.split(',').map(Number)
- } else {
- tempValue.value.range = [0, 100]
- }
- } else {
- configModel.value = detail.value
- }
- } catch (error) {
- toast.add({ severity: 'error', summary: '错误', detail: '获取配置详情失败', life: 3000 })
- return
- }
- } else {
- isEditing.value = false
- }
- dialogVisible.value = true
- }
- const onCancel = () => {
- dialogVisible.value = false
- }
- const validateForm = () => {
- if (!configModel.name) {
- toast.add({ severity: 'warn', summary: '警告', detail: '配置名称不能为空', life: 3000 })
- return false
- }
- if (!configModel.type) {
- toast.add({ severity: 'warn', summary: '警告', detail: '配置类型不能为空', life: 3000 })
- return false
- }
- if (configModel.type === 'time_range') {
- if (
- !Array.isArray(tempValue.value.time_range) ||
- tempValue.value.time_range.length !== 2 ||
- !tempValue.value.time_range[0] ||
- !tempValue.value.time_range[1]
- ) {
- toast.add({ severity: 'warn', summary: '警告', detail: '请选择有效的时间范围', life: 3000 })
- return false
- }
- }
- // 管理员必须指定teamId
- if (isAdmin.value && !configModel.teamId) {
- toast.add({ severity: 'warn', summary: '警告', detail: '请选择团队', life: 3000 })
- return false
- }
- return true
- }
- const getValueForSubmit = () => {
- const type = configModel.type
- if (type === 'object') {
- try {
- const parsedObj =
- typeof tempValue.value.object === 'string' ? JSON.parse(tempValue.value.object) : tempValue.value.object
- return JSON.stringify(parsedObj)
- } catch (e) {
- console.error('JSON解析错误', e)
- return '{}'
- }
- } else if (type === 'time_range') {
- if (Array.isArray(tempValue.value.time_range) && tempValue.value.time_range.length === 2) {
- const formattedTimes = tempValue.value.time_range.map((date) => {
- if (date instanceof Date) {
- return useDateFormat(date, 'HH:mm').value + ':00'
- }
- return date
- })
- return formattedTimes.join(',')
- }
- return Array.isArray(tempValue.value.time_range) ? tempValue.value.time_range.join(',') : tempValue.value.time_range
- } else if (type === 'date') {
- if (configModel.value instanceof Date) {
- return useDateFormat(configModel.value, 'YYYY-MM-DD').value
- }
- return configModel.value
- } else if (type === 'range') {
- return Array.isArray(tempValue.value.range) ? tempValue.value.range.join(',') : tempValue.value.range
- } else if (type === 'boolean') {
- return configModel.value === '1'
- } else if (type === 'number') {
- return Number(configModel.value)
- } else if (type === 'file') {
- return configModel.value
- } else {
- return configModel.value
- }
- }
- const onSubmit = async () => {
- if (!validateForm()) return
- try {
- const configData = {
- name: configModel.name,
- type: configModel.type,
- value: getValueForSubmit(),
- remark: configModel.remark
- }
- // 管理员需要传递teamId
- if (isAdmin.value) {
- configData.teamId = configModel.teamId
- }
- if (isEditing.value) {
- await updateTeamConfig(configModel.name, configData)
- toast.add({ severity: 'success', summary: '成功', detail: '更新配置成功', life: 3000 })
- } else {
- await createTeamConfig(configData)
- toast.add({ severity: 'success', summary: '成功', detail: '创建配置成功', life: 3000 })
- }
- dialogVisible.value = false
- refreshData()
- } catch (error) {
- toast.add({
- severity: 'error',
- summary: '错误',
- detail: isEditing.value ? '更新配置失败' : '创建配置失败',
- life: 3000
- })
- }
- }
- const onDelete = async (config) => {
- confirm.require({
- message: `确定要删除配置 "${config.name}" 吗?`,
- header: '删除确认',
- icon: 'pi pi-exclamation-triangle',
- rejectLabel: '取消',
- rejectProps: {
- label: '取消',
- severity: 'secondary'
- },
- acceptLabel: '删除',
- acceptProps: {
- label: '删除',
- severity: 'danger'
- },
- accept: async () => {
- try {
- await deleteTeamConfig(config.name, currentTeamId.value)
- toast.add({ severity: 'success', summary: '成功', detail: '删除配置成功', life: 3000 })
- refreshData()
- } catch (error) {
- toast.add({ severity: 'error', summary: '错误', detail: '删除配置失败', life: 3000 })
- }
- }
- })
- }
- const formatValue = (value, type) => {
- if (value === null || value === undefined) return '-'
- switch (type) {
- case 'boolean':
- return value === true || value === 'true' || value === '1' ? '是' : '否'
- case 'object':
- try {
- const obj = typeof value === 'string' ? JSON.parse(value) : value
- return JSON.stringify(obj)
- } catch (e) {
- return String(value)
- }
- case 'time_range':
- if (typeof value === 'string') {
- return value
- } else if (Array.isArray(value)) {
- return value
- .map((time) => {
- if (time instanceof Date) {
- return useDateFormat(time, 'HH:mm').value + ':00'
- }
- return time
- })
- .join(',')
- }
- return String(value)
- case 'range':
- return Array.isArray(value) ? value.join(',') : String(value)
- default:
- return String(value)
- }
- }
- const handleTypeChange = () => {
- configModel.value = ''
- if (configModel.type === 'boolean') {
- configModel.value = '0'
- } else if (configModel.type === 'number') {
- configModel.value = 0
- } else if (configModel.type === 'range') {
- tempValue.value.range = [0, 100]
- } else if (configModel.type === 'time_range') {
- const today = new Date()
- const startTime = new Date(today)
- const endTime = new Date(today)
- startTime.setHours(8, 0, 0)
- endTime.setHours(18, 0, 0)
- tempValue.value.time_range = [startTime, endTime]
- }
- }
- const uploadFile = async () => {
- const input = document.createElement('input')
- input.type = 'file'
- input.onchange = async (e) => {
- const file = e.target.files[0]
- if (!file) return
- uploading.value = true
- try {
- const result = await uploadFileAPI(file)
- configModel.value = result.data.url
- toast.add({ severity: 'success', summary: '成功', detail: '文件上传成功', life: 3000 })
- } catch (error) {
- console.error('文件上传失败', error)
- toast.add({ severity: 'error', summary: '错误', detail: '文件上传失败: ' + (error.message || error), life: 3000 })
- } finally {
- uploading.value = false
- }
- }
- input.click()
- }
- // 计算团队选项(仅管理员需要)
- const teamOptions = computed(() => {
- if (!isAdmin.value) return []
- return [
- { label: '全部团队', value: null },
- ...teamStore.teams.map((team) => ({
- label: team.name,
- value: team.id
- }))
- ]
- })
- // 获取团队名称
- const getTeamName = (teamId) => {
- if (!teamId) return '-'
- const team = teamStore.teams.find((t) => t.id === teamId)
- return team ? team.name : '-'
- }
- onMounted(() => {
- fetchData()
- fetchConfigTypes()
- })
- </script>
- <template>
- <div class="rounded-lg p-4 bg-[var(--p-content-background)]">
- <!-- 权限检查 -->
- <div v-if="!canView" class="text-center py-8">
- <p class="text-gray-500">您没有权限访问团队配置</p>
- </div>
- <!-- 主要内容 -->
- <div v-else>
- <DataTable
- ref="tableRef"
- :value="tableData.data"
- :paginator="true"
- paginatorTemplate="CurrentPageReport FirstPageLink PrevPageLink PageLinks NextPageLink LastPageLink RowsPerPageDropdown JumpToPageInput"
- currentPageReportTemplate="{totalRecords} 条记录 "
- :rows="Number(tableData.meta.size)"
- :rowsPerPageOptions="[10, 20, 50, 100]"
- :totalRecords="tableData.meta.total"
- @page="handlePageChange"
- lazy
- scrollable
- stripedRows
- showGridlines
- >
- <template #header>
- <div class="search-toolbar">
- <div class="toolbar-left">
- <Button icon="pi pi-refresh" @click="refreshData" size="small" label="刷新" />
- <Button v-if="canCreate" icon="pi pi-plus" @click="onEdit()" label="添加" size="small" />
- </div>
- <div class="toolbar-right">
- <div class="search-group">
- <InputText
- v-model="searchForm.name"
- placeholder="配置名称"
- size="small"
- class="search-field"
- @keyup.enter="handleSearch"
- />
- <Select
- v-model="searchForm.type"
- :options="configTypes"
- optionLabel="label"
- optionValue="value"
- placeholder="配置类型"
- size="small"
- class="type-field"
- clearable
- />
- <Select
- v-if="isAdmin"
- v-model="searchForm.teamId"
- :options="teamOptions"
- optionLabel="label"
- optionValue="value"
- placeholder="选择团队"
- size="small"
- class="team-field"
- clearable
- filter
- filterPlaceholder="搜索团队"
- />
- </div>
- <div class="action-group">
- <Button icon="pi pi-search" @click="handleSearch" label="搜索" size="small" severity="secondary" />
- <Button icon="pi pi-refresh" @click="handleRefresh" label="重置" size="small" />
- </div>
- </div>
- </div>
- </template>
- <Column v-if="isAdmin" field="teamId" header="所属团队" style="min-width: 120px" headerClass="font-bold">
- <template #body="slotProps">
- <span class="team-name-text font-medium">
- {{ getTeamName(slotProps.data.teamId) }}
- </span>
- </template>
- </Column>
- <Column field="name" header="配置名称" style="min-width: 150px" headerClass="font-bold"></Column>
- <Column field="remark" header="备注" style="min-width: 200px" headerClass="font-bold"></Column>
- <Column field="value" header="配置值" style="min-width: 250px" headerClass="font-bold">
- <template #body="slotProps">
- <div class="max-w-xl truncate">
- {{ formatValue(slotProps.data.value, slotProps.data.type) }}
- </div>
- </template>
- </Column>
- <Column field="type" header="配置类型" style="min-width: 100px" headerClass="font-bold">
- <template #body="slotProps">
- {{ ConfigType[slotProps.data.type] || slotProps.data.type }}
- </template>
- </Column>
- <Column header="操作" style="min-width: 120px" headerClass="font-bold" align="center">
- <template #body="slotProps">
- <div class="flex gap-2 justify-center">
- <Button v-if="canUpdate" icon="pi pi-pencil" @click="onEdit(slotProps.data)" size="small" />
- <Button
- v-if="canDelete"
- icon="pi pi-trash"
- @click="onDelete(slotProps.data)"
- size="small"
- severity="danger"
- />
- </div>
- </template>
- </Column>
- </DataTable>
- <!-- 添加/编辑配置对话框 -->
- <Dialog
- v-model:visible="dialogVisible"
- :modal="true"
- :header="isEditing ? '编辑团队配置' : '添加团队配置'"
- :style="{ width: '550px' }"
- >
- <div class="grid grid-cols-1 gap-4 p-4">
- <!-- 管理员选择团队 -->
- <div v-if="isAdmin" class="flex flex-col gap-2">
- <label class="font-medium">选择团队</label>
- <Select
- v-model="configModel.teamId"
- :options="teamOptions"
- optionLabel="label"
- optionValue="value"
- placeholder="请选择团队"
- :disabled="isEditing"
- filter
- filterPlaceholder="搜索团队"
- :showClear="true"
- />
- <small v-if="!configModel.teamId" class="p-error">请选择团队</small>
- </div>
- <div class="flex flex-col gap-2">
- <label class="font-medium">配置名称</label>
- <InputText v-model="configModel.name" placeholder="请输入配置名称" :disabled="isEditing" />
- <small v-if="!configModel.name && configModel.name !== 0" class="p-error">请输入配置名称</small>
- </div>
- <div class="flex flex-col gap-2">
- <label class="font-medium">备注</label>
- <InputText v-model="configModel.remark" placeholder="请输入配置备注" />
- <small v-if="!configModel.remark && configModel.remark !== 0" class="p-error">请输入备注</small>
- </div>
- <div class="flex flex-col gap-2">
- <label class="font-medium">配置类型</label>
- <Select
- v-model="configModel.type"
- :options="configTypes"
- optionLabel="label"
- optionValue="value"
- @change="handleTypeChange"
- :disabled="isEditing"
- placeholder="请选择配置类型"
- />
- <small v-if="!configModel.type" class="p-error">请选择配置类型</small>
- </div>
- <!-- 根据类型显示不同的输入控件 -->
- <div class="flex flex-col gap-2">
- <label class="font-medium">配置值</label>
- <!-- 文件类型 -->
- <div v-if="configModel.type === 'file'" class="flex gap-2">
- <InputText v-model="configModel.value" placeholder="请选择文件" readonly class="flex-1" />
- <Button type="button" icon="pi pi-upload" @click="uploadFile" :loading="uploading" />
- </div>
- <!-- 布尔类型 -->
- <div v-else-if="configModel.type === 'boolean'" class="flex gap-4">
- <div class="flex items-center gap-2">
- <RadioButton v-model="configModel.value" value="1" inputId="yes" />
- <label for="yes">是</label>
- </div>
- <div class="flex items-center gap-2">
- <RadioButton v-model="configModel.value" value="0" inputId="no" />
- <label for="no">否</label>
- </div>
- </div>
- <!-- 时间范围类型 -->
- <div v-else-if="configModel.type === 'time_range'" class="flex flex-col gap-2">
- <div class="grid grid-cols-2 gap-4">
- <div class="flex flex-col gap-1">
- <label class="text-sm">开始时间</label>
- <DatePicker
- v-model="tempValue.time_range[0]"
- timeOnly
- showTime
- format="HH:mm"
- hourFormat="24"
- placeholder="开始时间"
- :showIcon="true"
- :showButtonBar="true"
- />
- </div>
- <div class="flex flex-col gap-1">
- <label class="text-sm">结束时间</label>
- <DatePicker
- v-model="tempValue.time_range[1]"
- timeOnly
- showTime
- format="HH:mm"
- hourFormat="24"
- placeholder="结束时间"
- :showIcon="true"
- :showButtonBar="true"
- />
- </div>
- </div>
- <div class="flex justify-between text-sm text-gray-500 px-1 mt-1">
- <span
- >当前选择:
- {{ tempValue.time_range[0] ? useDateFormat(tempValue.time_range[0], 'HH:mm').value : '--:--' }}</span
- >
- <span>至</span>
- <span>{{
- tempValue.time_range[1] ? useDateFormat(tempValue.time_range[1], 'HH:mm').value : '--:--'
- }}</span>
- </div>
- </div>
- <!-- 范围类型 -->
- <div v-else-if="configModel.type === 'range'" class="flex flex-col gap-2">
- <Slider v-model="tempValue.range" range :max="500" :step="5" />
- <div class="flex justify-between">
- <span>{{ tempValue.range[0] }}</span>
- <span>{{ tempValue.range[1] }}</span>
- </div>
- </div>
- <!-- 数字类型 -->
- <InputNumber
- v-else-if="configModel.type === 'number'"
- v-model="configModel.value"
- placeholder="请输入数字值"
- />
- <!-- 日期类型 -->
- <DatePicker
- v-else-if="configModel.type === 'date'"
- v-model="configModel.value"
- dateFormat="yy-mm-dd"
- :showIcon="true"
- :showButtonBar="true"
- placeholder="请选择日期"
- />
- <!-- 对象类型 -->
- <Textarea
- v-else-if="configModel.type === 'object'"
- v-model="tempValue.object"
- placeholder="请输入JSON对象"
- rows="5"
- />
- <!-- 字符串类型(默认) -->
- <Textarea v-else v-model="configModel.value" placeholder="请输入值" rows="3" />
- </div>
- </div>
- <template #footer>
- <Button label="取消" icon="pi pi-times" @click="onCancel" class="p-button-text" />
- <Button label="保存" icon="pi pi-check" @click="onSubmit" />
- </template>
- </Dialog>
- </div>
- </div>
- </template>
- <style scoped>
- .team-name-text {
- color: #7c3aed;
- font-weight: 500;
- }
- /* 搜索工具栏样式 */
- .search-toolbar {
- display: flex;
- flex-wrap: wrap;
- align-items: center;
- justify-content: space-between;
- gap: 16px;
- margin-bottom: 20px;
- padding: 16px;
- background: #f8fafc;
- border-radius: 8px;
- border: 1px solid #e2e8f0;
- }
- .toolbar-left {
- display: flex;
- gap: 8px;
- align-items: center;
- }
- .toolbar-left .p-button {
- font-size: 13px;
- padding: 6px 12px;
- border-radius: 6px;
- font-weight: 500;
- }
- .toolbar-right {
- display: flex;
- gap: 12px;
- align-items: center;
- flex-wrap: wrap;
- }
- .search-group {
- display: flex;
- gap: 8px;
- align-items: center;
- }
- .search-field {
- width: 140px;
- font-size: 13px;
- padding: 6px 10px;
- border-radius: 6px;
- border: 1px solid #d1d5db;
- transition: all 0.2s ease;
- }
- .search-field:focus {
- border-color: #3b82f6;
- box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
- }
- .type-field {
- width: 140px;
- font-size: 13px;
- }
- .type-field .p-select {
- border-radius: 6px;
- }
- .team-field {
- width: 160px;
- font-size: 13px;
- }
- .team-field .p-select {
- border-radius: 6px;
- }
- .action-group {
- display: flex;
- gap: 8px;
- align-items: center;
- }
- .action-group .p-button {
- font-size: 13px;
- padding: 6px 12px;
- border-radius: 6px;
- font-weight: 500;
- }
- /* 响应式设计 */
- @media (max-width: 768px) {
- /* 移动端搜索工具栏适配 */
- .search-toolbar {
- flex-direction: column;
- gap: 12px;
- align-items: stretch;
- padding: 12px;
- }
- .toolbar-left {
- justify-content: center;
- gap: 8px;
- }
- .toolbar-left .p-button {
- flex: 1;
- max-width: 120px;
- font-size: 13px;
- padding: 8px 12px;
- }
- .toolbar-right {
- flex-direction: column;
- gap: 12px;
- align-items: stretch;
- }
- .search-group {
- flex-direction: column;
- gap: 8px;
- }
- .search-field,
- .type-field,
- .team-field {
- width: 100%;
- font-size: 14px;
- padding: 10px 12px;
- }
- .action-group {
- justify-content: center;
- gap: 8px;
- }
- .action-group .p-button {
- flex: 1;
- max-width: 140px;
- font-size: 13px;
- padding: 8px 12px;
- }
- }
- </style>
|