feat: 初始化项目结构并添加基础配置

添加前后端基础项目结构,包括.gitignore、package.json等配置文件
实现前端基础功能模块,包括路由、状态管理、API请求封装等
添加前端UI组件库和样式体系
配置开发环境Mock系统和构建工具链
This commit is contained in:
2026-03-18 14:03:35 +08:00
parent fc53f5620e
commit 9a387f3eec
504 changed files with 80629 additions and 0 deletions

View File

@@ -0,0 +1,86 @@
<template>
<div class="calendar-container">
<el-calendar ref="calendar">
<template #header="{ date }">
<span>
{{ date }}
{{
solar2lunar(dayjs(calendar.date).format('YYYY-MM-DD'))
.gzYear
}}
{{
solar2lunar(dayjs(calendar.date).format('YYYY-MM-DD'))
.gzMonth
}}
</span>
<el-button-group>
<el-button @click="selectDate('prev-year')">
上一年
</el-button>
<el-button @click="selectDate('prev-month')">
上一月
</el-button>
<el-button @click="selectDate('today')">今天</el-button>
<el-button @click="selectDate('next-month')">
下一月
</el-button>
<el-button @click="selectDate('next-year')">
下一年
</el-button>
</el-button-group>
</template>
<template #dateCell="{ data }">
<h3>{{ solar2lunar(data.day).cDay }}</h3>
<h5>
{{ solar2lunar(data.day).IDayCn }}
<span>
{{ solar2lunar(data.day).gzDay }}
{{ solar2lunar(data.day).astro }}
</span>
</h5>
</template>
</el-calendar>
</div>
</template>
<script>
import dayjs from 'dayjs'
import { solar2lunar } from '@/plugins/VabCalendar'
export default defineComponent({
name: 'Calendar',
setup() {
const state = reactive({
calendar: '',
})
const selectDate = (val) => {
state.calendar.selectDate(val)
}
return {
...toRefs(state),
dayjs,
selectDate,
solar2lunar,
}
},
})
</script>
<style lang="scss" scoped>
.calendar-container {
:deep() {
.el-calendar {
&-day {
h5 {
span {
float: right;
font-size: $base-font-size-small;
font-weight: normal;
}
}
}
}
}
}
</style>

View File

@@ -0,0 +1,216 @@
<template>
<div class="description-container">
<vab-card shadow="never">
<!-- <el-radio-group v-model="size">
<el-radio label="large">默认</el-radio>
<el-radio label="default">中等</el-radio>
<el-radio label="small"></el-radio>
</el-radio-group> -->
<el-descriptions
border
:column="3"
:size="size"
title="With border"
>
<template #extra>
<el-button size="small" type="primary">Operation</el-button>
</template>
<el-descriptions-item>
<template #label>Username</template>
kooriookami
</el-descriptions-item>
<el-descriptions-item>
<template #label>Telephone</template>
18100000000
</el-descriptions-item>
<el-descriptions-item>
<template #label>Place</template>
Suzhou
</el-descriptions-item>
<el-descriptions-item>
<template #label>Remarks</template>
<el-tag size="small">School</el-tag>
</el-descriptions-item>
<el-descriptions-item>
<template #label>Address</template>
No.1188, Wuzhong Avenue, Wuzhong District, Suzhou, Jiangsu
Province
</el-descriptions-item>
</el-descriptions>
<el-descriptions :column="3" :size="size" title="Without border">
<template #extra>
<el-button size="small" type="primary">Operation</el-button>
</template>
<el-descriptions-item label="Username">
kooriookami
</el-descriptions-item>
<el-descriptions-item label="Telephone">
18100000000
</el-descriptions-item>
<el-descriptions-item label="Place">
Suzhou
</el-descriptions-item>
<el-descriptions-item label="Remarks">
<el-tag size="small">School</el-tag>
</el-descriptions-item>
<el-descriptions-item label="Address">
No.1188, Wuzhong Avenue, Wuzhong District, Suzhou, Jiangsu
Province
</el-descriptions-item>
</el-descriptions>
<el-descriptions
border
:column="4"
direction="vertical"
:size="size"
title="Vertical list with border"
>
<el-descriptions-item label="Username">
kooriookami
</el-descriptions-item>
<el-descriptions-item label="Telephone">
18100000000
</el-descriptions-item>
<el-descriptions-item label="Place" :span="2">
Suzhou
</el-descriptions-item>
<el-descriptions-item label="Remarks">
<el-tag size="small">School</el-tag>
</el-descriptions-item>
<el-descriptions-item label="Address">
No.1188, Wuzhong Avenue, Wuzhong District, Suzhou, Jiangsu
Province
</el-descriptions-item>
</el-descriptions>
<el-descriptions
:column="4"
direction="vertical"
:size="size"
title="Vertical list without border"
>
<el-descriptions-item label="Username">
kooriookami
</el-descriptions-item>
<el-descriptions-item label="Telephone">
18100000000
</el-descriptions-item>
<el-descriptions-item label="Place" :span="2">
Suzhou
</el-descriptions-item>
<el-descriptions-item label="Remarks">
<el-tag size="small">School</el-tag>
</el-descriptions-item>
<el-descriptions-item label="Address">
No.1188, Wuzhong Avenue, Wuzhong District, Suzhou, Jiangsu
Province
</el-descriptions-item>
</el-descriptions>
</vab-card>
<vab-card shadow="never">
手风琴
<el-switch
v-model="accordion"
inline-prompt
@change="handleAccordion"
/>
<el-collapse v-model="activeName" :accordion="accordion">
<el-collapse-item name="1" title="Consistency">
<div>
Consistent with real life: in line with the process and
logic of real life, and comply with languages and habits
that the users are used to;
</div>
<div>
Consistent within interface: all elements should be
consistent, such as: design style, icons and texts,
position of elements, etc.
</div>
</el-collapse-item>
<el-collapse-item name="2" title="Feedback">
<div>
Operation feedback: enable the users to clearly perceive
their operations by style updates and interactive
effects;
</div>
<div>
Visual feedback: reflect current state by updating or
rearranging elements of the page.
</div>
</el-collapse-item>
<el-collapse-item name="3" title="Efficiency">
<div>
Simplify the process: keep operating process simple and
intuitive;
</div>
<div>
Definite and clear: enunciate your intentions clearly so
that the users can quickly understand and make
decisions;
</div>
<div>
Easy to identify: the interface should be
straightforward, which helps the users to identify and
frees them from memorizing and recalling.
</div>
</el-collapse-item>
<el-collapse-item name="4" title="Controllability">
<div>
Decision making: giving advices about operations is
acceptable, but do not make decisions for the users;
</div>
<div>
Controlled consequences: users should be granted the
freedom to operate, including canceling, aborting or
terminating current operation.
</div>
</el-collapse-item>
</el-collapse>
</vab-card>
</div>
</template>
<script>
export default defineComponent({
name: 'Description',
setup() {
const state = reactive({
size: 'default',
accordion: false,
activeName: ['1', '2', '3', '4'],
})
const handleAccordion = (val) => {
if (val) state.activeName = '1'
else state.activeName = ['1', '2', '3', '4']
}
return {
...toRefs(state),
handleAccordion,
}
},
})
</script>
<style lang="scss" scoped>
.description-container {
padding: 0 !important;
background: $base-color-background !important;
:deep() {
.el-descriptions {
padding-top: $base-padding !important;
}
.el-collapse {
margin-top: $base-margin !important;
}
}
}
</style>

View File

@@ -0,0 +1,111 @@
<template>
<div class="wang-editor-container">
<toolbar :editor="editorRef" style="border-bottom: 1px solid #e8e8e8" />
<editor
v-model="html"
class="wang-editor-content"
:default-config="editorConfig"
style="height: 300px"
@on-created="handleCreated"
/>
<div class="wang-editor-footer">
<el-button type="primary" @click="onSubmit">保存</el-button>
</div>
</div>
</template>
<script lang="ts">
import '@wangeditor/editor/dist/css/style.css'
import { Editor, Toolbar } from '@wangeditor/editor-for-vue'
import type { IDomEditor } from '@wangeditor/editor'
export default defineComponent({
name: 'WangEditor',
components: { Editor, Toolbar },
setup() {
const $baseMessage: any = inject('$baseMessage')
const $baseAlert: any = inject('$baseAlert')
const editorRef = shallowRef<IDomEditor | undefined>()
const html = ref(
'<h1>一级标题</h1><h2>二级标题</h2><h3>三级标题</h3><p>hello world ~~~ </p><blockquote>blockquote</blockquote><pre><code class="language-javascript">const a = 100;</code></pre><p><img src="https://gcore.jsdelivr.net/gh/zxwk1998/image/table/vab-image-1.jpg" alt="" data-href="" style=""/></p>'
)
const editorConfig = ref({
placeholder: '请输入内容...',
MENU_CONF: {
uploadImage: {
server: '', // 你的服务器地址注意当前接口格式特殊与其他vab接口不同请查看vip文档
fieldName: 'vab-file-name',
allowedFileTypes: ['image/*'],
headers: {}, // 如需传递token请写到在这里
},
},
})
const handleCreated = (editor: IDomEditor) => {
editorRef.value = editor
}
onBeforeUnmount(() => {
const editor = editorRef.value
if (editor == null) return
editor.destroy()
})
const onSubmit = () => {
$baseAlert(html.value)
$baseMessage(
'模拟保存成功',
'success',
'vab-hey-message-success'
)
}
return {
editorRef,
html,
editorConfig,
handleCreated,
onSubmit,
}
},
})
</script>
<style lang="scss">
.wang-editor-container {
padding: 0 !important;
margin: -19px -19px 19px;
overflow: hidden !important;
background: $base-color-background !important;
&.w-e-full-screen-container {
z-index: 9999 !important;
}
[classname='w-e-toolbar-init'] {
border-bottom: 1px solid #e8e8e8 !important;
}
.wang-editor-content {
width: 70%;
height: 500px !important;
padding: 0 40px;
margin: 20px auto;
background-color: #fff;
border: 0;
}
.wang-editor-footer {
width: 70%;
margin: auto;
}
@media (width <= 576px) {
.wang-editor-title,
.wang-editor-content,
.wang-editor-footer {
width: 90%;
}
}
}
</style>

View File

@@ -0,0 +1,178 @@
<template>
<div class="button-container">
<vab-card shadow="never">
<template #header><span>基础用法</span></template>
<el-button>默认按钮</el-button>
<el-button type="primary">主要按钮</el-button>
<el-button type="success">成功按钮</el-button>
<el-button type="info">信息按钮</el-button>
<el-button type="warning">警告按钮</el-button>
<el-button type="danger">危险按钮</el-button>
<el-button plain>朴素按钮</el-button>
<el-button plain type="primary">主要按钮</el-button>
<el-button plain type="success">成功按钮</el-button>
<el-button plain type="info">信息按钮</el-button>
<el-button plain type="warning">警告按钮</el-button>
<el-button plain type="danger">危险按钮</el-button>
<el-button round>圆角按钮</el-button>
<el-button round type="primary">主要按钮</el-button>
<el-button round type="success">成功按钮</el-button>
<el-button round type="info">信息按钮</el-button>
<el-button round type="warning">警告按钮</el-button>
<el-button round type="danger">危险按钮</el-button>
<el-button circle :icon="Search" />
<el-button circle :icon="Edit" type="primary" />
<el-button circle :icon="Check" type="success" />
<el-button circle :icon="Message" type="info" />
<el-button circle :icon="Star" type="warning" />
<el-button circle :icon="Delete" type="danger" />
</vab-card>
<vab-card shadow="never">
<template #header>
<span>禁用状态</span>
</template>
<el-button disabled>默认按钮</el-button>
<el-button disabled type="primary">主要按钮</el-button>
<el-button disabled type="success">成功按钮</el-button>
<el-button disabled type="info">信息按钮</el-button>
<el-button disabled type="warning">警告按钮</el-button>
<el-button disabled type="danger">危险按钮</el-button>
<el-button disabled plain>朴素按钮</el-button>
<el-button disabled plain type="primary">主要按钮</el-button>
<el-button disabled plain type="success">成功按钮</el-button>
<el-button disabled plain type="info">信息按钮</el-button>
<el-button disabled plain type="warning">警告按钮</el-button>
<el-button disabled plain type="danger">危险按钮</el-button>
</vab-card>
<vab-card shadow="never">
<template #header>
<span>文字按钮</span>
</template>
<el-button text type="primary">文字按钮</el-button>
<el-button disabled text>文字按钮</el-button>
</vab-card>
<vab-card shadow="never">
<template #header>
<span>element内置图标按钮</span>
</template>
<el-button :icon="Edit" type="primary" />
<el-button :icon="Share" type="primary" />
<el-button :icon="Delete" type="primary" />
<el-button :icon="Search" type="primary">搜索</el-button>
<el-button type="primary">
上传
<el-icon class="el-icon--right"><upload /></el-icon>
</el-button>
</vab-card>
<vab-card shadow="never">
<template #header>
<span>自定义图标按钮</span>
</template>
<el-button type="primary">
<vab-icon icon="24-hours-line" />
</el-button>
<el-button type="primary">
<vab-icon icon="4k-line" />
</el-button>
<el-button type="primary">
<vab-icon icon="a-b" />
</el-button>
<el-button type="primary">
<vab-icon icon="account-box-line" />
用户名
</el-button>
</vab-card>
<vab-card shadow="never">
<template #header>
<span>按钮组</span>
</template>
<el-button-group style="margin-right: 10px">
<el-button :icon="ArrowLeft" type="primary">上一页</el-button>
<el-button type="primary">
下一页
<el-icon class="el-icon--right"><arrow-right /></el-icon>
</el-button>
</el-button-group>
<el-button-group>
<el-button :icon="Edit" type="primary" />
<el-button :icon="Share" type="primary" />
<el-button :icon="Delete" type="primary" />
</el-button-group>
</vab-card>
<vab-card shadow="never">
<template #header>
<span>加载中</span>
</template>
<el-button :loading="true" type="primary">加载中</el-button>
</vab-card>
<vab-card shadow="never">
<template #header>
<span>不同尺寸</span>
</template>
<el-button>默认按钮</el-button>
<el-button size="small">小型按钮</el-button>
<el-button round>默认按钮</el-button>
<el-button round size="small">小型按钮</el-button>
</vab-card>
</div>
</template>
<script>
import {
ArrowLeft,
ArrowRight,
Check,
Delete,
Edit,
Message,
Search,
Share,
Star,
Upload,
} from '@element-plus/icons-vue'
export default defineComponent({
name: 'Button',
components: { ArrowRight, Upload },
setup() {
return {
Search,
Edit,
Check,
Message,
Star,
Delete,
ArrowLeft,
Share,
}
},
})
</script>
<style lang="scss" scoped>
.button-container {
padding: 0 !important;
background: $base-color-background !important;
:deep() {
.el-card__body {
padding-bottom: 10px;
.el-button {
margin: 0 10px 10px;
}
}
.el-button-group {
.el-button {
margin-right: 0;
}
}
}
}
</style>

View File

@@ -0,0 +1,80 @@
<template>
<div class="checkbox-container">
<vab-card shadow="never">
<template #header><span>基础用法</span></template>
<el-checkbox v-model="checked">备选项</el-checkbox>
</vab-card>
<vab-card shadow="never">
<template #header><span>禁用状态</span></template>
<el-checkbox v-model="checked1" disabled>备选项1</el-checkbox>
<el-checkbox v-model="checked2" disabled>备选项</el-checkbox>
</vab-card>
<vab-card shadow="never">
<template #header><span>多选框组</span></template>
<el-checkbox-group v-model="checkList">
<el-checkbox label="复选框 A" value="复选框 A" />
<el-checkbox label="复选框 B" value="复选框 B" />
<el-checkbox label="复选框 C" value="复选框 C" />
<el-checkbox disabled label="禁用" value="禁用" />
<el-checkbox disabled label="选中且禁用" value="选中且禁用" />
</el-checkbox-group>
</vab-card>
<vab-card shadow="never">
<template #header><span>可选项目数量的限制</span></template>
<el-checkbox-group v-model="checkedCities" :max="2" :min="1">
<el-checkbox v-for="city in cities" :key="city" :label="city">
{{ city }}
</el-checkbox>
</el-checkbox-group>
</vab-card>
<vab-card shadow="never">
<template #header><span>按钮样式</span></template>
<el-checkbox-group v-model="checkboxGroup1">
<el-checkbox-button
v-for="city in cities"
:key="city"
:label="city"
>
{{ city }}
</el-checkbox-button>
</el-checkbox-group>
</vab-card>
<vab-card shadow="never">
<template #header><span>带有边框</span></template>
<el-checkbox v-model="checked3" border label="备选项1" />
<el-checkbox v-model="checked4" border label="备选项2" />
</vab-card>
</div>
</template>
<script>
export default defineComponent({
name: 'Checkbox',
setup() {
const cityOptions = ['上海', '北京', '广州', '深圳']
const state = reactive({
checked: true,
checked1: false,
checked2: true,
checkList: ['选中且禁用', '复选框 A'],
checkedCities: ['上海', '北京'],
cities: cityOptions,
checkboxGroup1: ['上海'],
checked3: true,
checked4: false,
})
return {
...toRefs(state),
}
},
})
</script>
<style lang="scss" scoped>
.checkbox-container {
padding: 0 !important;
background: $base-color-background !important;
}
</style>

View File

@@ -0,0 +1,109 @@
<template>
<div>
<el-form ref="formRef" label-width="100px" :model="form" :rules="rules">
<el-form-item label="付款账户" prop="payAccount">
<el-input v-model="form.payAccount" />
</el-form-item>
<el-form-item label="收款账户" prop="gatheringAccount">
<el-input v-model="form.gatheringAccount" />
</el-form-item>
<el-form-item label="收款人姓名" prop="gatheringName">
<el-input v-model="form.gatheringName" />
</el-form-item>
<el-form-item label="转账金额" prop="price">
<el-input v-model="form.price" />
</el-form-item>
</el-form>
<div class="pay-button-group">
<el-button type="primary" @click="handleSubmit">下一步</el-button>
</div>
<el-alert :closable="false">
<h3>转账到支付宝</h3>
<p>
生活好支付宝生活好支付宝生活好支付宝生活好支付宝
</p>
<h3>转账到微信</h3>
<p>
微不可挡万众一信微不可挡万众一信微不可挡万众一信微不可挡万众一信
</p>
</el-alert>
</div>
</template>
<script>
export default defineComponent({
name: 'Step1',
emits: ['change-step'],
setup(props, { emit }) {
const state = reactive({
formRef: null,
form: {
payAccount: '****************',
gatheringAccount: '****************',
gatheringName: '***',
price: '100',
},
rules: {
payAccount: [
{
required: true,
message: '请选择付款账户',
trigger: 'blur',
},
],
gatheringAccount: [
{
required: true,
message: '请输入收款账户',
trigger: 'blur',
},
{
required: true,
message: '账户名应为邮箱格式',
trigger: 'blur',
},
],
gatheringName: [
{
required: true,
message: '请输入收款人姓名',
trigger: 'blur',
},
],
price: [
{
required: true,
message: '请输入转账金额',
trigger: 'blur',
},
{
pattern: /^(\d+)((?:\.\d+)?)$/,
message: '请输入合法金额数字',
},
],
},
})
const handleSubmit = () => {
state['formRef'].validate((valid) => {
if (valid) {
emit('change-step', 1, state.form)
}
})
}
return {
...toRefs(state),
handleSubmit,
}
},
})
</script>
<style lang="scss" scoped>
.pay-button-group {
display: block;
margin: 20px auto;
text-align: center;
}
</style>

View File

@@ -0,0 +1,91 @@
<template>
<div>
<el-form ref="formRef" label-width="100px" :model="form" :rules="rules">
<el-form-item label="付款账户">
{{ infoData.payAccount }}
</el-form-item>
<el-form-item label="收款账户">
{{ infoData.gatheringAccount }}
</el-form-item>
<el-form-item label="收款人姓名">
{{ infoData.gatheringName }}
</el-form-item>
<el-form-item label="转账金额">
<strong>{{ infoData.price }}</strong>
</el-form-item>
<el-form-item label="支付密码" prop="password">
<el-input v-model="form.password" type="password" />
</el-form-item>
</el-form>
<div class="pay-button-group">
<el-button :loading="loading" type="primary" @click="handleSubmit">
提交
</el-button>
<el-button @click="handlePrev">上一步</el-button>
</div>
</div>
</template>
<script>
export default defineComponent({
name: 'Step2',
props: {
infoData: {
type: Object,
default: () => {
return {}
},
},
},
emits: ['change-step'],
setup(props, { emit }) {
const state = reactive({
formRef: null,
form: {
password: '123456',
},
rules: {
password: [
{
required: true,
message: '请输入支付密码',
trigger: 'blur',
},
],
},
loading: false,
})
const handleSubmit = () => {
state['formRef'].validate((valid) => {
if (valid) {
state.loading = true
setTimeout(() => {
emit('change-step', 2)
state.loading = false
}, 2000)
} else {
state.loading = false
}
})
}
const handlePrev = () => {
emit('change-step', 0)
}
return {
...toRefs(state),
handleSubmit,
handlePrev,
}
},
})
</script>
<style lang="scss" scoped>
.pay-button-group {
display: block;
margin: 20px auto;
text-align: center;
}
</style>

View File

@@ -0,0 +1,114 @@
<template>
<div>
<div class="pay-top-content">
<vab-icon class="pay-success" icon="checkbox-circle-line" />
<p>支付成功</p>
</div>
<el-form
ref="formRef"
class="pay-bottom"
label-width="100px"
:model="form"
:rules="rules"
>
<el-form-item label="付款账户">
{{ infoData.payAccount }}
</el-form-item>
<el-form-item label="收款账户">
{{ infoData.gatheringAccount }}
</el-form-item>
<el-form-item label="收款人姓名">
{{ infoData.gatheringName }}
</el-form-item>
<el-form-item label="转账金额">
<strong>{{ infoData.price }}</strong>
</el-form-item>
</el-form>
<div class="pay-button-group">
<el-button type="primary" @click="handlePrev">再转一笔</el-button>
</div>
</div>
</template>
<script>
export default defineComponent({
name: 'Step3',
props: {
infoData: {
type: Object,
default: () => {
return {}
},
},
},
emits: ['change-step'],
setup(props, { emit }) {
const state = reactive({
formRef: null,
form: {
password: '123456',
},
rules: {
password: [
{
required: true,
message: '请输入支付密码',
trigger: 'blur',
},
],
},
loading: false,
})
const handleSubmit = () => {
state['formRef'].validate((valid) => {
if (valid) {
state.loading = true
setTimeout(() => {
emit('change-step', 2)
state.loading = false
}, 2000)
} else {
state.loading = false
}
})
}
const handlePrev = () => {
emit('change-step', 0)
}
return {
...toRefs(state),
handleSubmit,
handlePrev,
}
},
})
</script>
<style lang="scss" scoped>
.pay-top-content {
text-align: center;
.pay-success {
display: block;
margin: $base-margin auto 5px auto;
font-size: 40px;
color: var(--el-color-success);
}
}
.pay-bottom {
padding: $base-padding;
margin-top: $base-margin;
background: #f5f7f8;
border: 1px dashed $base-border-color;
border-radius: $base-border-radius;
}
.pay-button-group {
display: block;
margin: $base-margin auto;
text-align: center;
}
</style>

View File

@@ -0,0 +1,278 @@
<template>
<div class="comprehensive-form-container">
<el-row :gutter="20">
<el-col
:lg="{ span: 12, offset: 6 }"
:md="{ span: 20, offset: 2 }"
:sm="{ span: 20, offset: 2 }"
:xl="{ span: 12, offset: 6 }"
:xs="24"
>
<vab-query-form>
<vab-query-form-left-panel>
<el-radio-group v-model="labelPosition">
<el-radio-button label="left" value="left">
左对齐
</el-radio-button>
<el-radio-button label="right" value="right">
右对齐
</el-radio-button>
<el-radio-button label="top" value="top">
顶部对齐
</el-radio-button>
</el-radio-group>
</vab-query-form-left-panel>
</vab-query-form>
<el-form
ref="formRef"
class="demo-form"
:label-position="labelPosition"
label-width="100px"
:model="form"
:rules="rules"
>
<el-form-item label="活动名称" prop="name">
<el-input v-model="form.name" />
</el-form-item>
<el-form-item label="活动区域" prop="region">
<el-select
v-model="form.region"
placeholder="请选择活动区域"
>
<el-option label="区域一" value="shanghai" />
<el-option label="区域二" value="beijing" />
</el-select>
</el-form-item>
<el-form-item label="活动时间" prop="date">
<el-date-picker
v-model="form.date"
placeholder="选择日期时间"
type="datetime"
/>
</el-form-item>
<el-form-item label="即时配送" prop="delivery">
<el-switch v-model="form.delivery" />
</el-form-item>
<el-form-item label="活动性质" prop="type">
<el-checkbox-group v-model="form.type">
<el-checkbox
label="美食/餐厅线上活动"
name="type"
value="美食/餐厅线上活动"
/>
<el-checkbox
label="地推活动"
name="type"
value="地推活动"
/>
<el-checkbox
label="线下主题活动"
name="type"
value="线下主题活动"
/>
<el-checkbox
label="单纯品牌曝光"
name="type"
value="单纯品牌曝光"
/>
</el-checkbox-group>
</el-form-item>
<el-form-item label="特殊资源" prop="resource">
<el-radio-group v-model="form.resource">
<el-radio label="线上品牌商赞助" />
<el-radio label="线下场地免费" />
</el-radio-group>
</el-form-item>
<el-form-item label="活动形式" prop="description">
<el-input v-model="form.description" type="textarea" />
</el-form-item>
<el-form-item label="评星">
<el-rate v-model="form.rate" show-text />
</el-form-item>
<el-form-item label="行政区划">
<el-cascader
v-model="form.area"
clearable
filterable
:options="areaOptions"
:props="{ label: 'name', value: 'code' }"
/>
</el-form-item>
<el-form-item label="穿梭框">
<el-transfer
v-model="form.transfer"
:data="data"
:filter-method="filterMethod"
filter-placeholder="请输入城市拼音"
filterable
/>
</el-form-item>
<el-form-item>
<el-button
type="primary"
@click="submitForm('formRef')"
>
立即创建
</el-button>
<el-button @click="resetForm('formRef')">
重置
</el-button>
</el-form-item>
</el-form>
</el-col>
</el-row>
</div>
</template>
<script>
import { getList } from '@/api/area'
export default defineComponent({
name: 'ComprehensiveForm',
setup() {
const generateData = () => {
const data = []
const cities = ['上海', '北京', '广州']
const pinyin = ['shanghai', 'beijing', 'guangzhou']
cities.forEach((city, index) => {
data.push({
label: city,
key: index,
pinyin: pinyin[index],
})
})
return data
}
const state = reactive({
formRef: null,
labelPosition: 'right',
form: {
name: '',
region: '',
date: '',
date2: '',
delivery: false,
type: [],
resource: '',
description: '',
rate: 0,
area: [],
transfer: [],
},
areaOptions: [],
rules: {
name: [
{
required: true,
message: '请输入活动名称',
trigger: 'blur',
},
{
min: 3,
max: 5,
message: '长度在 3 到 5 个字符',
trigger: 'blur',
},
],
region: [
{
required: true,
message: '请选择活动区域',
trigger: 'change',
},
],
date: [
{
type: 'date',
required: true,
message: '请选择日期',
trigger: 'change',
},
],
type: [
{
type: 'array',
required: true,
message: '请至少选择一个活动性质',
trigger: 'change',
},
],
resource: [
{
required: true,
message: '请选择活动资源',
trigger: 'change',
},
],
description: [
{
required: true,
message: '请填写活动形式',
trigger: 'blur',
},
],
},
data: generateData(),
filterMethod(query, item) {
return item.pinyin.includes(query)
},
})
const fetchData = async () => {
const {
data: { list },
} = await getList()
state.areaOptions = list
}
const submitForm = (formName) => {
state[formName].validate((valid) => {
if (valid) {
alert('submit!')
} else {
// eslint-disable-next-line no-console
console.log('error submit!!')
}
})
}
const resetForm = (formName) => {
state[formName].resetFields()
}
onMounted(() => {
fetchData()
})
return {
...toRefs(state),
submitForm,
resetForm,
fetchData,
}
},
})
</script>
<style lang="scss" scoped>
.comprehensive-form-container {
.demo-form {
margin-top: 10px;
}
:deep() {
.el-form-item__content {
.el-rate {
display: inline-block;
font-size: 0;
line-height: 1;
vertical-align: middle;
}
.el-transfer__buttons {
padding: 10px 10px 0;
}
}
}
}
</style>

View File

@@ -0,0 +1,141 @@
<template>
<div class="date-picker-container">
<vab-card shadow="never">
<template #header>
<span>选择日</span>
</template>
<el-date-picker
v-model="value1"
placeholder="选择日期"
type="date"
/>
<el-date-picker
v-model="value2"
:disabled-date="disabledDate"
placeholder="选择日期"
:shortcuts="shortcuts"
type="date"
/>
</vab-card>
<vab-card shadow="never">
<template #header>
<span>其他日期单位</span>
</template>
<el-date-picker
v-model="value3"
format="yyyy 第 WW 周"
placeholder="选择周"
type="week"
/>
<el-date-picker
v-model="value4"
placeholder="选择月"
type="month"
/>
<el-date-picker v-model="value5" placeholder="选择年" type="year" />
<el-date-picker
v-model="value6"
placeholder="选择一个或多个日期"
type="dates"
/>
</vab-card>
<vab-card shadow="never">
<template #header>
<span>选择日期范围</span>
</template>
<el-date-picker
v-model="value7"
end-placeholder="结束日期"
range-separator=""
start-placeholder="开始日期"
type="daterange"
/>
</vab-card>
<vab-card shadow="never">
<template #header>
<span>选择月份范围</span>
</template>
<el-date-picker
v-model="value8"
end-placeholder="结束月份"
range-separator=""
start-placeholder="开始月份"
type="monthrange"
/>
</vab-card>
</div>
</template>
<script>
export default defineComponent({
name: 'DatePicker',
setup() {
const state = reactive({
disabledDate(time) {
return time.getTime() > Date.now()
},
shortcuts: [
{
text: '今天',
value: new Date(),
},
{
text: '昨天',
value: () => {
const date = new Date()
date.setTime(date.getTime() - 3600 * 1000 * 24)
return date
},
},
{
text: '一周前',
value: () => {
const date = new Date()
date.setTime(date.getTime() - 3600 * 1000 * 24 * 7)
return date
},
},
],
value1: '',
value2: '',
value3: '',
value4: '',
value5: '',
value6: '',
value7: '',
value8: '',
})
return {
...toRefs(state),
}
},
})
</script>
<style lang="scss" scoped>
.date-picker-container {
padding: 0 !important;
background: $base-color-background !important;
:deep() {
.el-range-separator {
box-sizing: content-box;
}
.el-input {
width: 180px;
margin-bottom: 10px;
&:first-child {
margin-right: 10px;
}
& + .el-input {
margin-right: 10px;
margin-left: 0;
}
}
}
}
</style>

View File

@@ -0,0 +1,65 @@
<template>
<div class="date-time-picker-container">
<vab-card shadow="never">
<template #header>
<span>日期和时间点</span>
</template>
<el-date-picker
v-model="value1"
placeholder="选择日期时间"
type="datetime"
/>
</vab-card>
<vab-card shadow="never">
<template #header>
<span>日期和时间范围</span>
</template>
<el-date-picker
v-model="value2"
end-placeholder="结束日期"
range-separator=""
start-placeholder="开始日期"
type="datetimerange"
/>
</vab-card>
<vab-card shadow="never">
<template #header>
<span>默认的起始与结束时刻</span>
</template>
<el-date-picker
v-model="value3"
:default-time="value2"
end-placeholder="结束日期"
start-placeholder="开始日期"
type="datetimerange"
/>
</vab-card>
</div>
</template>
<script>
export default defineComponent({
name: 'DateTimePicker',
setup() {
const state = reactive({
value1: '',
value2: [
new Date(2000, 10, 10, 10, 10),
new Date(2000, 10, 11, 10, 10),
],
value3: '',
})
return {
...toRefs(state),
}
},
})
</script>
<style lang="scss" scoped>
.date-time-picker-container {
padding: 0 !important;
background: $base-color-background !important;
}
</style>

View File

@@ -0,0 +1,162 @@
<template>
<div class="input-container">
<vab-card shadow="never">
<template #header>
<span>基础用法</span>
</template>
<el-input
v-model="input1"
placeholder="请输入内容"
width="“200px”"
/>
</vab-card>
<vab-card shadow="never">
<template #header>
<span>禁用状态</span>
</template>
<el-input
v-model="input2"
:disabled="true"
placeholder="请输入内容"
/>
</vab-card>
<vab-card shadow="never">
<template #header>
<span>可清空</span>
</template>
<el-input v-model="input3" clearable placeholder="请输入内容" />
</vab-card>
<vab-card shadow="never">
<template #header>
<span>密码框</span>
</template>
<el-input v-model="input4" placeholder="请输入内容" show-password />
</vab-card>
<vab-card shadow="never">
<template #header>
<span> icon 的输入框</span>
</template>
<el-input
v-model="input5"
placeholder="请输入内容"
style="float: left"
:suffix-icon="Search"
/>
<el-input
v-model="input6"
placeholder="请输入内容"
:prefix-icon="Search"
style="float: left"
/>
</vab-card>
<vab-card shadow="never">
<template #header>
<span>复合型输入框</span>
</template>
<el-input
v-model="input7"
placeholder="请输入内容"
style="width: 300px"
>
<template #prepend>Http://</template>
</el-input>
<el-input
v-model="input8"
placeholder="请输入内容"
style="width: 300px"
>
<template #append>.com</template>
</el-input>
<el-input
v-model="input9"
placeholder="请输入内容"
style="width: 350px"
>
<template #prepend>
<el-select v-model="select" placeholder="请选择">
<el-option label="选项1" :value="1" />
<el-option label="选项2" :value="2" />
<el-option label="选项3" :value="3" />
</el-select>
</template>
<template #append>
<el-button :icon="Search" />
</template>
</el-input>
</vab-card>
<vab-card shadow="never">
<template #header>
<span>textarea</span>
</template>
<el-input
v-model="textarea"
placeholder="请输入内容"
:rows="2"
type="textarea"
/>
</vab-card>
</div>
</template>
<script>
import { Search } from '@element-plus/icons-vue'
export default defineComponent({
name: 'Input',
setup() {
const state = reactive({
input1: '',
input2: '',
input3: '',
input4: '',
input5: '',
input6: '',
input7: '',
input8: '',
input9: '',
select: 1,
textarea: '',
})
return {
...toRefs(state),
Search,
}
},
})
</script>
<style lang="scss" scoped>
.input-container {
padding: 0 !important;
background: $base-color-background !important;
:deep() {
.el-input {
width: 180px;
&:first-child {
margin-right: 10px;
margin-bottom: 10px;
}
& + .el-input {
margin-right: 10px;
margin-bottom: 10px;
margin-left: 0;
}
}
.el-textarea {
width: 180px;
}
.el-select {
.el-input {
width: 90px;
margin-bottom: 0;
}
}
}
}
</style>

View File

@@ -0,0 +1,75 @@
<template>
<div class="input-number-container">
<vab-card shadow="never">
<template #header>
<span>基础用法</span>
</template>
<el-input-number
v-model="num"
label="描述文字"
:max="10"
:min="1"
/>
</vab-card>
<vab-card shadow="never">
<template #header>
<span>禁用状态</span>
</template>
<el-input-number v-model="num2" :disabled="true" />
</vab-card>
<vab-card shadow="never">
<template #header>
<span>步数</span>
</template>
<el-input-number v-model="num3" :step="2" />
</vab-card>
<vab-card shadow="never">
<template #header>
<span>精度</span>
</template>
<el-input-number
v-model="num4"
:max="10"
:precision="2"
:step="0.1"
/>
</vab-card>
<vab-card shadow="never">
<template #header>
<span>按钮位置</span>
</template>
<el-input-number
v-model="num5"
controls-position="right"
:max="10"
:min="1"
/>
</vab-card>
</div>
</template>
<script>
export default defineComponent({
name: 'InputNumber',
setup() {
const state = reactive({
num: 1,
num2: 1,
num3: 5,
num4: 1,
num5: 1,
})
return {
...toRefs(state),
}
},
})
</script>
<style lang="scss" scoped>
.inputNumber-container {
padding: 0 !important;
background: $base-color-background !important;
}
</style>

View File

@@ -0,0 +1,76 @@
<template>
<div class="link-container">
<vab-card shadow="never">
<template #header>
<span>基础用法</span>
</template>
<el-link href="https://element.eleme.io" target="_blank">
默认链接
</el-link>
<el-link type="primary">主要链接</el-link>
<el-link type="success">成功链接</el-link>
<el-link type="warning">警告链接</el-link>
<el-link type="danger">危险链接</el-link>
<el-link type="info">信息链接</el-link>
</vab-card>
<vab-card shadow="never">
<template #header>
<span>禁用状态</span>
</template>
<el-link disabled>默认链接</el-link>
<el-link disabled type="primary">主要链接</el-link>
<el-link disabled type="success">成功链接</el-link>
<el-link disabled type="warning">警告链接</el-link>
<el-link disabled type="danger">危险链接</el-link>
<el-link disabled type="info">信息链接</el-link>
</vab-card>
<vab-card shadow="never">
<template #header>
<span>下划线</span>
</template>
<el-link :underline="false">无下划线</el-link>
<el-link>有下划线</el-link>
</vab-card>
<vab-card shadow="never">
<template #header>
<span>图标</span>
</template>
<el-link :icon="Edit">编辑</el-link>
</vab-card>
</div>
</template>
<script>
import { Edit } from '@element-plus/icons-vue'
export default defineComponent({
name: 'Link',
setup() {
return {
Edit,
}
},
})
</script>
<style lang="scss" scoped>
.link-container {
padding: 0 !important;
background: $base-color-background !important;
:deep() {
.el-link {
margin-bottom: 10px;
&:first-child {
margin-right: 10px;
}
& + .el-link {
margin-right: 10px;
margin-left: 0;
}
}
}
}
</style>

View File

@@ -0,0 +1,74 @@
<template>
<div class="radio-container">
<vab-card shadow="never">
<template #header>
<span>基础用法</span>
</template>
<el-radio v-model="radio" label="1">备选项</el-radio>
<el-radio v-model="radio" label="2">备选项</el-radio>
</vab-card>
<vab-card shadow="never">
<template #header>
<span>禁用状态</span>
</template>
<el-radio v-model="radio2" disabled label="禁用">备选项</el-radio>
<el-radio v-model="radio2" disabled label="选中且禁用">
备选项
</el-radio>
</vab-card>
<vab-card shadow="never">
<template #header>
<span>单选框组</span>
</template>
<el-radio-group v-model="radio3">
<el-radio :label="3" value="3">备选项</el-radio>
<el-radio :label="6" value="6">备选项</el-radio>
<el-radio :label="9" value="9">备选项</el-radio>
</el-radio-group>
</vab-card>
<vab-card shadow="never">
<template #header>
<span>按钮样式</span>
</template>
<el-radio-group v-model="radio4">
<el-radio-button label="上海" value="上海" />
<el-radio-button label="北京" value="北京" />
<el-radio-button label="广州" value="广州" />
<el-radio-button label="深圳" value="深圳" />
</el-radio-group>
</vab-card>
<vab-card shadow="never">
<template #header>
<span>带有边框</span>
</template>
<el-radio v-model="radio5" border label="1">备选项1</el-radio>
<el-radio v-model="radio5" border label="2">备选项2</el-radio>
</vab-card>
</div>
</template>
<script>
export default defineComponent({
name: 'Radio',
setup() {
const state = reactive({
radio: '1',
radio2: '选中且禁用',
radio3: 3,
radio4: '上海',
radio5: '1',
})
return {
...toRefs(state),
}
},
})
</script>
<style lang="scss" scoped>
.radio-container {
padding: 0 !important;
background: $base-color-background !important;
}
</style>

View File

@@ -0,0 +1,52 @@
<template>
<div class="rate-container">
<vab-card shadow="never">
<template #header>
<span>基础用法</span>
</template>
<el-rate v-model="value1" />
</vab-card>
<vab-card shadow="never">
<template #header>
<span>辅助文字</span>
</template>
<el-rate v-model="value2" show-text />
</vab-card>
<vab-card shadow="never">
<template #header>
<span>只读</span>
</template>
<el-rate
v-model="value3"
disabled
score-template="{value}"
show-score
text-color="#ff9900"
/>
</vab-card>
</div>
</template>
<script>
export default defineComponent({
name: 'Rate',
setup() {
const state = reactive({
value1: null,
value2: null,
value3: 3.7,
})
return {
...toRefs(state),
}
},
})
</script>
<style lang="scss" scoped>
.rate-container {
padding: 0 !important;
background: $base-color-background !important;
}
</style>

View File

@@ -0,0 +1,232 @@
<template>
<div class="select-container">
<vab-card shadow="never">
<template #header>
<span>行政区划</span>
<el-tag class="card-header-tag" type="danger">New</el-tag>
</template>
<el-cascader
v-model="area"
clearable
filterable
:options="areaOptions"
:props="{ label: 'name', value: 'code' }"
/>
</vab-card>
<vab-card shadow="never">
<template #header>
<span>树选择</span>
<el-tag class="card-header-tag" type="danger">New</el-tag>
</template>
<el-tree-select
v-model="treeValue"
:data="treeData"
multiple
show-checkbox
/>
</vab-card>
<vab-card shadow="never">
<template #header>
<span>基础用法</span>
</template>
<el-select v-model="value1" placeholder="请选择">
<el-option
v-for="item in options1"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</vab-card>
<vab-card shadow="never">
<template #header>
<span>有禁用选项</span>
</template>
<el-select v-model="value2" placeholder="请选择">
<el-option
v-for="item in options2"
:key="item.value"
:disabled="item.disabled"
:label="item.label"
:value="item.value"
/>
</el-select>
</vab-card>
<vab-card shadow="never">
<template #header>
<span>禁用状态</span>
</template>
<el-select v-model="value1" disabled placeholder="请选择">
<el-option
v-for="item in options1"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</vab-card>
<vab-card shadow="never">
<template #header>
<span>可清空单选</span>
</template>
<el-select v-model="value1" clearable placeholder="请选择">
<el-option
v-for="item in options1"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</vab-card>
<vab-card shadow="never">
<template #header>
<span>基础多选</span>
</template>
<el-select v-model="value3" multiple placeholder="请选择">
<el-option
v-for="item in options1"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</vab-card>
<vab-card shadow="never">
<template #header>
<span>可搜索</span>
</template>
<el-select v-model="value1" filterable placeholder="请选择">
<el-option
v-for="item in options1"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</vab-card>
</div>
</template>
<script>
import { getList } from '@/api/area'
export default defineComponent({
name: 'Select',
setup() {
const state = reactive({
options1: [
{ value: '选项1', label: '黄金糕' },
{ value: '选项2', label: '双皮奶' },
{ value: '选项3', label: '蚵仔煎' },
{ value: '选项4', label: '龙须面' },
{ value: '选项5', label: '北京烤鸭' },
],
value1: '',
options2: [
{ value: '选项1', label: '黄金糕' },
{ value: '选项2', label: '双皮奶', disabled: true },
{ value: '选项3', label: '蚵仔煎' },
{ value: '选项4', label: '龙须面' },
{ value: '选项5', label: '北京烤鸭' },
],
value2: '',
value3: [],
area: [],
treeValue: '',
areaOptions: [],
treeData: [
{
value: '1',
label: 'Level one 1',
children: [
{
value: '1-1',
label: 'Level two 1-1',
children: [
{
value: '1-1-1',
label: 'Level three 1-1-1',
},
],
},
],
},
{
value: '2',
label: 'Level one 2',
children: [
{
value: '2-1',
label: 'Level two 2-1',
children: [
{
value: '2-1-1',
label: 'Level three 2-1-1',
},
],
},
{
value: '2-2',
label: 'Level two 2-2',
children: [
{
value: '2-2-1',
label: 'Level three 2-2-1',
},
],
},
],
},
{
value: '3',
label: 'Level one 3',
children: [
{
value: '3-1',
label: 'Level two 3-1',
children: [
{
value: '3-1-1',
label: 'Level three 3-1-1',
},
],
},
{
value: '3-2',
label: 'Level two 3-2',
children: [
{
value: '3-2-1',
label: 'Level three 3-2-1',
},
],
},
],
},
],
})
const fetchData = async () => {
const {
data: { list },
} = await getList()
state.areaOptions = list
}
onMounted(() => {
fetchData()
})
return {
...toRefs(state),
}
},
})
</script>
<style lang="scss" scoped>
.select-container {
padding: 0 !important;
background: $base-color-background !important;
}
</style>

View File

@@ -0,0 +1,81 @@
<template>
<div class="slider-container">
<vab-card shadow="never">
<template #header>
<span>基础用法</span>
</template>
<span class="demonstration">默认</span>
<el-slider v-model="value1" />
<span class="demonstration">自定义初始值</span>
<el-slider v-model="value2" />
<span class="demonstration">隐藏 Tooltip</span>
<el-slider v-model="value3" :show-tooltip="false" />
<span class="demonstration">格式化 Tooltip</span>
<el-slider v-model="value4" :format-tooltip="formatTooltip" />
<span class="demonstration">禁用</span>
<el-slider v-model="value5" disabled />
</vab-card>
<vab-card shadow="never">
<template #header>
<span>离散值</span>
</template>
<span class="demonstration">不显示间断点</span>
<el-slider v-model="value6" :step="10" />
<span class="demonstration">显示间断点</span>
<el-slider v-model="value6" show-stops :step="10" />
</vab-card>
<vab-card shadow="never">
<template #header>
<span>带有输入框</span>
</template>
<el-slider v-model="value7" show-input />
</vab-card>
<vab-card shadow="never">
<template #header>
<span>范围选择</span>
</template>
<el-slider v-model="value8" :max="10" range show-stops />
</vab-card>
<!-- <vab-card shadow="never">
<template #header>
<span>竖向模式</span>
</template>
<el-slider v-model="value9" height="200px" vertical />
</vab-card> -->
</div>
</template>
<script>
export default defineComponent({
name: 'Slider',
setup() {
const state = reactive({
value1: 0,
value2: 50,
value3: 36,
value4: 48,
value5: 42,
value6: 0,
value7: 0,
value8: [4, 8],
value9: 0,
})
const formatTooltip = (val) => {
return val / 100
}
return {
...toRefs(state),
formatTooltip,
}
},
})
</script>
<style lang="scss" scoped>
.slider-container {
padding: 0 !important;
background: $base-color-background !important;
}
</style>

View File

@@ -0,0 +1,114 @@
<template>
<div class="step-form-container">
<el-row :gutter="20">
<el-col
:lg="{ span: 10, offset: 7 }"
:md="{ span: 20, offset: 2 }"
:sm="{ span: 20, offset: 2 }"
:xl="{ span: 10, offset: 7 }"
:xs="24"
>
<el-steps :active="active" align-center class="steps">
<el-step description="填写转账信息" title="第1步" />
<el-step description="确认转账信息" title="第2步" />
<el-step description="完成" title="第3步" />
</el-steps>
<step1 v-if="active === 0" @change-step="handleSetStep" />
<step2
v-if="active === 1"
:info-data="form"
@change-step="handleSetStep"
/>
<step3
v-if="active === 2"
:info-data="form"
@change-step="handleSetStep"
/>
</el-col>
</el-row>
</div>
</template>
<script>
import Step1 from './components/Step1'
import Step2 from './components/Step2'
import Step3 from './components/Step3'
export default defineComponent({
name: 'StepForm',
components: { Step1, Step2, Step3 },
setup() {
const state = reactive({
active: 0,
form: {},
})
const handleSetStep = (active, form) => {
state.active = active
if (form) state.form = Object.assign(state.form, form)
}
return {
...toRefs(state),
handleSetStep,
}
},
})
</script>
<style lang="scss" scoped>
.step-form-container {
:deep() {
.el-steps {
margin: $base-margin auto $base-margin * 2 auto;
.el-step__title.is-process {
color: var(--el-color-primary);
}
.el-step__description.is-process {
color: var(--el-color-primary);
}
.el-step__head {
&.is-process {
color: var(--el-color-primary);
border-color: var(--el-color-primary);
.el-step__icon.is-text {
color: var(--el-color-primary);
background: var(--el-color-primary-light-9);
border: 1px solid;
}
.el-step__line {
height: 1px;
}
}
&.is-wait {
.el-step__icon.is-text {
border: 1px solid;
}
.el-step__line {
height: 1px;
}
}
&.is-finish {
.el-step__icon.is-text {
color: var(--el-color-white);
background: var(--el-color-primary);
}
.el-step__line {
height: 1px;
background: var(--el-color-primary);
}
}
}
}
}
}
</style>

View File

@@ -0,0 +1,56 @@
<template>
<div class="switch-container">
<vab-card shadow="never">
<template #header>
<span>基础用法</span>
</template>
<el-switch
v-model="value"
active-color="#13ce66"
inactive-color="#ff4949"
/>
</vab-card>
<vab-card shadow="never">
<template #header>
<span>文字描述</span>
</template>
<el-switch
v-model="value1"
active-text="按月付费"
inactive-text="按年付费"
/>
</vab-card>
<vab-card shadow="never">
<template #header>
<span>禁用状态</span>
</template>
<el-switch v-model="value2" disabled style="margin-right: 10px" />
<el-switch v-model="value3" disabled />
</vab-card>
</div>
</template>
<script>
export default defineComponent({
name: 'Switch',
setup() {
const state = reactive({
value: true,
value1: true,
value2: true,
value3: false,
})
return {
...toRefs(state),
}
},
})
</script>
<style lang="scss" scoped>
.switch-container {
padding: 0 !important;
background: $base-color-background !important;
}
</style>

View File

@@ -0,0 +1,102 @@
<template>
<div class="time-picker-container">
<vab-card shadow="never">
<template #header>
<span>固定时间点</span>
</template>
<el-time-select
v-model="value"
end="18:30"
placeholder="选择时间"
start="08:30"
step="00:15"
/>
</vab-card>
<vab-card shadow="never">
<template #header>
<span>固定时间范围</span>
</template>
<el-time-select
v-model="startTime"
end="18:30"
placeholder="开始时间"
start="08:30"
step="00:15"
style="margin-right: 10px"
/>
<el-time-select
v-model="endTime"
end="18:30"
:min-time="startTime"
placeholder="结束时间"
start="08:30"
step="00:15"
/>
</vab-card>
<vab-card shadow="never">
<template #header>
<span>任意时间点</span>
</template>
<el-time-picker
v-model="value1"
:disabled-hours="disabledHours"
:disabled-minutes="disabledMinutes"
:disabled-seconds="disabledSeconds"
placeholder="任意时间点"
/>
</vab-card>
</div>
</template>
<script>
export default defineComponent({
name: 'Timepicker',
setup() {
const state = reactive({
value: '',
value1: new Date(2016, 9, 10, 18, 40),
startTime: '',
endTime: '',
})
const makeRange = (start, end) => {
const result = []
for (let i = start; i <= end; i++) {
result.push(i)
}
return result
}
const disabledHours = () => {
return makeRange(0, 16).concat(makeRange(19, 23))
}
const disabledMinutes = (hour) => {
if (hour === 17) {
return makeRange(0, 29)
}
if (hour === 18) {
return makeRange(31, 59)
}
}
const disabledSeconds = (hour, minute) => {
if (hour === 18 && minute === 30) {
return makeRange(1, 59)
}
}
return {
...toRefs(state),
disabledHours,
disabledMinutes,
disabledSeconds,
}
},
})
</script>
<style lang="scss" scoped>
.time-picker-container {
padding: 0 !important;
background: $base-color-background !important;
}
</style>

View File

@@ -0,0 +1,55 @@
<template>
<div class="custom-svg-container">
<!-- 具体如何自定义查看文档文档搜索vab-icon可看到用法 -->
<el-row :gutter="15">
<el-col :lg="2" :md="3" :sm="8" :xl="2" :xs="6">
<vab-card shadow="never">
<vab-icon icon="vab" is-custom-svg />
</vab-card>
</el-col>
<el-col :lg="2" :md="3" :sm="8" :xl="2" :xs="6">
<vab-card shadow="never">
<vab-icon icon="vuejs-fill" is-custom-svg />
</vab-card>
</el-col>
</el-row>
</div>
</template>
<script lang="ts">
export default defineComponent({
name: 'CustomSvg',
setup() {
return {}
},
})
</script>
<style lang="scss" scoped>
@use 'sass:math';
.custom-svg-container {
:deep() {
.el-card__body {
position: relative;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 60px;
max-height: 60px;
padding: #{math.div($base-padding, 1.4)};
cursor: pointer;
i {
font-size: 28px;
color: var(--el-color-grey);
text-align: center;
pointer-events: none;
cursor: pointer;
transition: $base-transition;
}
}
}
}
</style>

View File

@@ -0,0 +1,230 @@
<template>
<div class="remix-icon-container">
<el-row :gutter="20">
<el-col :span="24">
<el-form inline label-width="80px" @submit.prevent>
<el-form-item label="图标名称">
<el-input v-model="queryForm.title" />
</el-form-item>
<el-form-item label-width="0">
<el-button
:icon="Search"
native-type="submit"
type="primary"
@click="queryData"
>
查询
</el-button>
<el-form-item label="文字大小">
<el-input-number
v-model="queryForm.num"
:max="40"
:min="12"
style="width: 120px; margin-right: 10px"
/>
px
</el-form-item>
<el-form-item :label-width="0">
<el-checkbox
v-model="queryForm.colorful"
label="多彩图标"
@change="queryData"
/>
</el-form-item>
</el-form-item>
</el-form>
</el-col>
<el-col v-if="emptyShow" :span="24">
<el-empty class="vab-data-empty" description="暂无数据" />
</el-col>
<el-col
v-for="(item, index) in queryIcon"
:key="index"
:lg="2"
:md="3"
:sm="8"
:xl="2"
:xs="6"
>
<vab-card @click="handleCopyIcon(item.icon, $event)">
<vab-icon
:icon="item.icon"
:style="{
color: queryForm.colorful
? item.color
: 'var(--el-color-grey)',
fontSize: queryForm.num + 'px',
}"
/>
</vab-card>
<div
class="icon-text"
@click="handleCopyText(item.icon, $event)"
>
{{ item.icon }}
</div>
</el-col>
<el-col :span="24">
<el-pagination
background
:current-page="queryForm.pageNo"
:layout="layout"
:page-size="queryForm.pageSize"
:page-sizes="[72, 144, 216, 288]"
:total="total"
@current-change="handleCurrentChange"
@size-change="handleSizeChange"
/>
</el-col>
</el-row>
</div>
</template>
<script>
import { Search } from '@element-plus/icons-vue'
import { getIconList } from '@/api/defaultIcon'
import clip from '@/utils/clipboard'
export default defineComponent({
name: 'DefaultIcon',
setup() {
const state = reactive({
queryIcon: [],
total: 0,
queryForm: {
pageNo: 1,
pageSize: 108,
title: '',
colorful: false,
num: 28,
},
layout: 'total, sizes, prev, pager, next, jumper',
emptyShow: true,
})
const fetchData = async () => {
const {
data: { list, total },
} = await getIconList(state.queryForm)
state.queryIcon = list.map((icon) => {
return { icon, color: randomHexColor() }
})
state.total = total
state.emptyShow = false
}
const handleSizeChange = (val) => {
state.queryForm.pageSize = val
fetchData()
}
const handleCurrentChange = (val) => {
state.queryForm.pageNo = val
fetchData()
}
const queryData = () => {
state.queryForm.pageNo = 1
fetchData()
}
const handleCopyText = (item, event) => {
clip(item, event)
}
const handleCopyIcon = (item, event) => {
clip(`<vab-icon icon="${item}" />`, event)
}
const randomHexColor = () => {
return _.shuffle([
'#1890FF',
'#36CBCB',
'#4ECB73',
'#FBD437',
'#F2637B',
'#975FE5',
])
}
onMounted(() => {
fetchData()
})
return {
...toRefs(state),
handleSizeChange,
handleCurrentChange,
queryData,
handleCopyText,
handleCopyIcon,
Search,
}
},
})
</script>
<style lang="scss" scoped>
@use 'sass:math';
.remix-icon-container {
:deep() {
.el-form--inline {
.el-form-item {
margin-right: 10px;
}
}
.el-card__body {
position: relative;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 60px;
max-height: 60px;
padding: #{math.div($base-padding, 1.4)};
cursor: pointer;
i {
font-size: 28px;
color: var(--el-color-grey);
text-align: center;
pointer-events: none;
cursor: pointer;
transition: $base-transition;
}
&::after {
position: absolute;
bottom: -30px;
width: 100%;
padding: 4px 0;
font-size: $base-font-size-small;
color: rgb(255 255 255);
text-align: center;
content: '点击复制';
background-color: var(--el-color-primary);
transition: $base-transition;
}
&:hover {
i {
margin-top: -#{math.div($base-margin, 1.2)};
}
&::after {
bottom: 0;
}
}
}
}
.icon-text {
height: 30px;
margin-top: -15px;
overflow: hidden;
text-overflow: ellipsis;
font-size: 12px;
line-height: 30px;
text-align: center;
white-space: nowrap;
cursor: pointer;
}
}
</style>

View File

@@ -0,0 +1,56 @@
<template>
<div class="icon-selector-container">
<el-popover
popper-class="icon-selector-popper"
trigger="hover"
:width="292"
>
<template #reference>
<el-button>
<vab-icon :icon="icon" />
图标选择器
<vab-icon icon="arrow-down-s-line" />
</el-button>
</template>
<vab-icon-selector @handle-icon="handleIcon" />
</el-popover>
</div>
</template>
<script>
import VabIconSelector from '@/plugins/VabIconSelector'
export default defineComponent({
name: 'IconSelector',
components: { VabIconSelector },
setup() {
const state = reactive({
icon: '24-hours-fill',
})
const handleIcon = (item) => {
state.icon = item
}
return {
...toRefs(state),
handleIcon,
}
},
})
</script>
<style lang="scss">
.icon-selector-popper {
.vab-card{
&:hover{
border-color: var(--el-color-primary);
background-color: var(--el-color-primary-light-9);
i{
color: var(--el-color-primary);
}
}
}
}
</style>

View File

@@ -0,0 +1,202 @@
<template>
<div class="list-container">
<el-row :gutter="20">
<vab-query-form>
<vab-query-form-top-panel :span="24">
<el-form inline :model="queryForm" @submit.prevent>
<el-form-item>
<el-input
v-model.trim="queryForm.title"
clearable
placeholder="请输入标题"
/>
</el-form-item>
<el-form-item>
<el-button
:icon="Search"
type="primary"
@click="queryData"
>
查询
</el-button>
</el-form-item>
</el-form>
</vab-query-form-top-panel>
</vab-query-form>
<el-col v-if="emptyShow" :span="24">
<el-empty class="vab-data-empty" description="暂无数据" />
</el-col>
<el-col :span="24">
<ul v-loading="listLoading">
<li
v-for="(item, index) in list"
:key="index"
class="list-item"
>
<div class="list-item-meta">
<div class="list-item-meta-avatar">
<el-image :src="item.img" />
</div>
<div class="list-item-meta-content">
<div class="list-item-meta-title">
{{ item.title }}
</div>
<div class="list-item-meta-description">
{{ item.description }}
</div>
</div>
<div class="list-item-meta-content">
<div class="list-item-meta-item">
<span>时间</span>
<p>{{ item.datetime }}</p>
</div>
</div>
<div class="list-item-meta-content">
<el-progress :percentage="item.percentage" />
</div>
</div>
</li>
</ul>
</el-col>
<el-col :span="24">
<el-pagination
background
:current-page="queryForm.pageNo"
:layout="layout"
:page-size="queryForm.pageSize"
:total="total"
@current-change="handleCurrentChange"
@size-change="handleSizeChange"
/>
</el-col>
</el-row>
</div>
</template>
<script>
import { Search } from '@element-plus/icons-vue'
import { getList } from '@/api/table'
export default defineComponent({
name: 'List',
setup() {
const state = reactive({
list: [],
total: 0,
queryForm: { pageNo: 1, pageSize: 10, title: '' },
layout: 'total, sizes, prev, pager, next, jumper',
listLoading: true,
emptyShow: true,
})
const fetchData = async () => {
state.listLoading = true
const {
data: { list, total },
} = await getList(state.queryForm)
state.list = list
state.total = total
state.listLoading = false
state.emptyShow = false
}
const handleSizeChange = (val) => {
state.queryForm.pageSize = val
fetchData()
}
const handleCurrentChange = (val) => {
state.queryForm.pageNo = val
fetchData()
}
const queryData = () => {
state.queryForm.pageNo = 1
fetchData()
}
onMounted(() => {
fetchData()
})
return {
...toRefs(state),
handleSizeChange,
handleCurrentChange,
queryData,
Search,
}
},
})
</script>
<style lang="scss" scoped>
.list-container {
ul {
padding: 0;
margin: 0;
outline: none;
list-style: none;
.list-item {
padding: $base-padding;
border-bottom: 1px solid #{$base-border-color};
&-meta {
display: flex;
flex: 1 1;
align-items: flex-start;
&-avatar {
margin-right: 16px;
:deep() {
.el-image {
width: 61px;
height: 61px;
}
}
}
&-content {
flex: 1 0;
width: 0;
color: rgb(0 0 0 / 85%);
}
&-title {
margin-top: 11px;
margin-bottom: 4px;
font-size: 14px;
color: rgb(0 0 0 / 85%);
}
&-description {
font-size: 14px;
color: rgb(0 0 0 / 45%);
}
&-item {
display: inline-block;
height: 61px;
margin-left: 40px;
font-size: 14px;
vertical-align: middle;
color: rgb(0 0 0 / 45%);
> span {
line-height: 30px;
}
> p {
margin-top: 4px;
margin-bottom: 0;
}
}
:deep() {
.el-progress {
margin-top: 21px;
}
}
}
}
}
}
</style>

View File

@@ -0,0 +1,390 @@
<template>
<div class="roles-container">
<el-alert
v-if="!loginInterception"
:closable="false"
show-icon
title="检测到您当前的登录拦截已关闭无法模拟切换角色功能请在src/config/setting.config.js中配置loginInterception为ture开启登录拦截"
type="success"
/>
<el-alert
:closable="false"
show-icon
:title="`当前路由模式为:{ authentication:${authentication} },是否开启角色权限控制功能:{ rolesControl:${rolesControl} }`"
type="success"
/>
<el-form label-position="top" label-width="140px" :model="form">
<el-form-item label="账号切换">
<el-radio-group
v-model="form.account"
@change="handleChangeRole"
>
<el-radio-button label="admin" value="admin">
admin
</el-radio-button>
<el-radio-button label="editor" value="editor">
editor
</el-radio-button>
<el-radio-button label="test" value="test">
test
</el-radio-button>
</el-radio-group>
</el-form-item>
<el-form-item label="过期Token模拟访问(令牌失效5s)">
<el-button type="primary" @click="handleRefreshToken">
点击模拟token过期访问接口无痛刷新
</el-button>
</el-form-item>
<el-form-item label="当前账号">
<el-descriptions border :column="3" direction="vertical">
<el-descriptions-item>
<template #label>角色</template>
<el-tag>{{ JSON.stringify(role) }}</el-tag>
</el-descriptions-item>
<el-descriptions-item>
<template #label>权限点</template>
<el-tag>{{ JSON.stringify(permission) }}</el-tag>
</el-descriptions-item>
<el-descriptions-item>
<template #label>token</template>
<el-tag>{{ token }}</el-tag>
</el-descriptions-item>
</el-descriptions>
</el-form-item>
<el-form-item label="按钮级角色">
<el-button v-permissions="['Admin']" type="primary">
拥有["Admin"]角色的按钮
</el-button>
<el-button
v-permissions="{ role: ['Admin'], mode: 'except' }"
type="danger"
>
未拥有["Admin"]角色的按钮
</el-button>
<el-button v-permissions="['Editor']" type="primary">
拥有["Editor"]角色的按钮
</el-button>
<el-button
v-permissions="{ role: ['Editor'], mode: 'except' }"
type="danger"
>
未拥有["Editor"]角色的按钮
</el-button>
<el-button
v-permissions="{ role: ['Admin', 'Editor'], mode: 'allOf' }"
type="primary"
>
同时拥有["Admin","Editor"]角色的按钮
</el-button>
<el-button v-permissions="['Test']" type="primary">
拥有["Test"]角色的按钮
</el-button>
</el-form-item>
<!-- 注意其中roles-代表组件name这样可以区分到具体页面 -->
<el-form-item label="按钮级权限点">
<el-button
v-permissions="{ permission: ['read:system'] }"
type="primary"
>
拥有["read:system"]权限点的按钮
</el-button>
<el-button
v-permissions="{
permission: ['read:system'],
mode: 'except',
}"
type="danger"
>
未拥有["'read:system'"]权限点的按钮
</el-button>
<el-button
v-permissions="{ permission: ['write:system'] }"
type="primary"
>
拥有["write:system"]权限点的按钮
</el-button>
<el-button
v-permissions="{
permission: ['write:system'],
mode: 'except',
}"
type="danger"
>
未拥有["write:system"]权限点的按钮
</el-button>
<el-button
v-permissions="{ permission: ['delete:system'] }"
type="primary"
>
拥有["delete:system"]权限点的按钮
</el-button>
<el-button
v-permissions="{
permission: ['delete:system'],
mode: 'except',
}"
type="danger"
>
未拥有["delete:system"]权限点的按钮
</el-button>
</el-form-item>
<el-form-item label="按钮级角色&权限点">
<el-button
v-permissions="{
role: ['Admin'],
permission: ['delete:system'],
}"
type="primary"
>
拥有["Admin"]角色或者["delete:system"]权限点的按钮
</el-button>
<el-button
v-permissions="{
role: ['Editor'],
permission: ['read:system'],
mode: 'allOf',
}"
type="primary"
>
拥有["Editor"]角色和["read:system"]权限点的按钮
</el-button>
<el-button
v-permissions="{
role: ['Admin'],
permission: ['delete:system'],
mode: 'except',
}"
type="danger"
>
未拥有["Admin"]和["delete:system"]权限点的按钮
</el-button>
</el-form-item>
<el-form-item label="路由" />
</el-form>
<el-row :gutter="20">
<el-col :span="24">
<el-table
border
:data="tableData"
default-expand-all
row-key="path"
:tree-props="{
children: 'children',
hasChildren: 'hasChildren',
}"
>
<el-table-column
align="center"
label="name"
prop="name"
show-overflow-tooltip
width="220"
/>
<el-table-column
align="center"
label="path"
prop="path"
show-overflow-tooltip
width="220"
/>
<el-table-column
align="center"
label="component"
prop="component"
show-overflow-tooltip
width="220"
/>
<el-table-column
align="center"
label="redirect"
prop="redirect"
show-overflow-tooltip
width="220"
/>
<el-table-column
align="center"
label="title"
prop="meta.title"
show-overflow-tooltip
/>
<el-table-column
align="center"
label="icon"
show-overflow-tooltip
>
<template #default="{ row }">
<span v-if="row.meta">
<vab-icon
v-if="row.meta.icon"
:icon="row.meta.icon"
/>
</span>
</template>
</el-table-column>
<el-table-column
align="center"
label="noClosable"
show-overflow-tooltip
>
<template #default="{ row }">
<span v-if="row.meta">
{{ row.meta.noClosable || false }}
</span>
</template>
</el-table-column>
<el-table-column
align="center"
label="noKeepAlive"
show-overflow-tooltip
width="100"
>
<template #default="{ row }">
<span v-if="row.meta">
<template v-if="!row.meta.noKeepAlive">
false
</template>
<template v-else>true</template>
</span>
</template>
</el-table-column>
<el-table-column
align="center"
label="badge"
show-overflow-tooltip
>
<template #default="{ row }">
<span v-if="row.meta">
{{ row.meta.badge }}
</span>
</template>
</el-table-column>
<el-table-column
align="center"
label="isCustomSvg"
show-overflow-tooltip
width="140"
>
<template #default="{ row }">
<span v-if="row.meta">
{{ row.meta.isCustomSvg || false }}
</span>
</template>
</el-table-column>
<el-table-column
align="center"
label="tabHidden"
show-overflow-tooltip
>
<template #default="{ row }">
<span v-if="row.meta">
<template v-if="!row.meta.tabHidden">
false
</template>
<template v-else>true</template>
</span>
</template>
</el-table-column>
</el-table>
</el-col>
</el-row>
</div>
</template>
<script>
import { Random } from 'mockjs'
import { useAclStore } from '@/store/modules/acl'
import { useUserStore } from '@/store/modules/user'
import {
authentication,
loginInterception,
rolesControl,
tokenTableName,
} from '@/config'
import { getList } from '@/api/router'
import { filterRoutes } from '@/utils/routes'
import { expireToken } from '@/api/refreshToken'
export default defineComponent({
name: 'Permission',
setup() {
const $baseLoading = inject('$baseLoading')
const $baseMessage = inject('$baseMessage')
const aclStore = useAclStore()
const { role, permission } = storeToRefs(aclStore)
const userStore = useUserStore()
const { username, token } = storeToRefs(userStore)
const state = reactive({
form: {
account: username.value,
},
tableData: [],
res: [],
authentication,
loginInterception,
rolesControl,
})
const fetchData = async () => {
const {
data: { list },
} = await getList()
state.tableData = filterRoutes([...list])
}
const handleChangeRole = async () => {
$baseLoading(0, '正在切换账号请稍后...')
await localStorage.setItem(
tokenTableName,
`${state.form.account}-token-${Random.guid()}-${Date.now()}`
)
await location.reload()
}
const handleRefreshToken = async () => {
const { msg } = await expireToken()
$baseMessage(
`${msg}: [${token.value}] `,
'success',
'vab-hey-message-success'
)
}
fetchData()
return {
...toRefs(state),
role,
permission,
username,
token,
fetchData,
handleChangeRole,
handleRefreshToken,
}
},
})
</script>
<style lang="scss" scoped>
:deep() {
.el-button {
margin-right: 10px;
}
.el-button + .el-button {
margin-right: 10px;
margin-left: 0;
}
.el-form-item {
margin-bottom: 0;
.el-form-item__content {
> * {
margin-bottom: 10px;
}
}
}
}
</style>

View File

@@ -0,0 +1,95 @@
<template>
<el-dialog
v-model="dialogFormVisible"
align-center
:title="title"
width="500px"
@close="close"
>
<el-form ref="formRef" label-width="80px" :model="form" :rules="rules">
<el-form-item label="标题" prop="title">
<el-input v-model.trim="form.title" />
</el-form-item>
<el-form-item label="作者" prop="author">
<el-input v-model.trim="form.author" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="close"> </el-button>
<el-button type="primary" @click="save"> </el-button>
</template>
</el-dialog>
</template>
<script>
import { doEdit } from '@/api/table'
export default defineComponent({
name: 'TableEdit',
emits: ['fetch-data'],
setup(props, { emit }) {
const $baseMessage = inject('$baseMessage')
const state = reactive({
formRef: null,
form: {
title: '',
author: '',
},
rules: {
title: [
{
required: true,
trigger: 'blur',
message: '请输入标题',
},
],
author: [
{
required: true,
trigger: 'blur',
message: '请输入作者',
},
],
},
title: '',
dialogFormVisible: false,
})
const showEdit = (row) => {
if (!row) {
state.title = '添加'
} else {
state.title = '编辑'
state.form = JSON.parse(JSON.stringify(row))
}
state.dialogFormVisible = true
}
const close = () => {
state['formRef'].resetFields()
state.form = {
title: '',
author: '',
}
state.dialogFormVisible = false
}
const save = () => {
state['formRef'].validate(async (valid) => {
if (valid) {
const { msg } = await doEdit(state.form)
$baseMessage(msg, 'success', 'vab-hey-message-success')
emit('fetch-data')
close()
}
})
}
return {
...toRefs(state),
showEdit,
close,
save,
}
},
})
</script>

View File

@@ -0,0 +1,476 @@
<template>
<div class="comprehensive-table-container auto-height-container">
<vab-query-form>
<vab-query-form-top-panel>
<el-form
inline
label-width="49px"
:model="queryForm"
@submit.prevent
>
<el-form-item label="标题">
<el-input
v-model="queryForm.title"
placeholder="请输入标题"
/>
</el-form-item>
<el-form-item label="作者">
<el-input
v-model="queryForm.author"
placeholder="请输入作者"
/>
</el-form-item>
<el-form-item v-show="!fold" label="时间">
<el-date-picker
v-model="queryForm.datetime"
placeholder="请选择日期"
style="width: 192px"
type="date"
/>
</el-form-item>
<el-form-item>
<el-button
:icon="Search"
native-type="submit"
type="primary"
@click="queryData"
>
查询
</el-button>
<el-button text type="primary" @click="handleFold">
<span v-if="fold">展开</span>
<span v-else>合并</span>
<vab-icon
class="vab-dropdown"
:class="{ 'vab-dropdown-active': fold }"
icon="arrow-up-s-line"
/>
</el-button>
</el-form-item>
</el-form>
</vab-query-form-top-panel>
<vab-query-form-left-panel :span="24">
<el-button :icon="Plus" type="primary" @click="handleAdd">
添加
</el-button>
<el-button
:icon="Delete"
type="danger"
@click="handleDelete($event)"
>
删除
</el-button>
<!-- <el-button type="primary" @click="handleMessage">
$baseMessage
</el-button>
<el-button type="primary" @click="handleAlert">
$baseAlert
</el-button>
<el-button type="primary" @click="handleConfirm">
$baseConfirm
</el-button>
<el-button type="primary" @click="handleNotify">
$baseNotify
</el-button> -->
<el-button type="primary" @click="handleDetailStayTable">
停留在本页后台打开详情页后不常用
</el-button>
<el-badge class="item" value="New">
<el-button
style="margin: 0 0 10px !important"
type="primary"
@click="handleDetail"
>
详情页支持tab多开并高亮左侧菜单
</el-button>
</el-badge>
</vab-query-form-left-panel>
</vab-query-form>
<el-table
ref="tableSortRef"
v-loading="listLoading"
border
:data="list"
@selection-change="setSelectRows"
@sort-change="tableSortChange"
>
<el-table-column
align="center"
show-overflow-tooltip
type="selection"
width="55"
/>
<el-table-column
align="center"
label="序号"
show-overflow-tooltip
width="55"
>
<template #default="{ $index }">
{{ $index + 1 }}
</template>
</el-table-column>
<el-table-column
align="center"
label="标题"
prop="title"
show-overflow-tooltip
/>
<el-table-column
align="center"
label="作者"
prop="author"
show-overflow-tooltip
/>
<el-table-column align="center" label="评级">
<template #default="{ row }">
<el-rate v-model="row.rate" disabled />
</template>
</el-table-column>
<el-table-column align="center" label="头像">
<template #default="{ row }">
<el-image
:preview-src-list="imageList"
preview-teleported
:src="row.img"
/>
</template>
</el-table-column>
<el-table-column
align="center"
label="点击量"
prop="pageViews"
show-overflow-tooltip
sortable
/>
<el-table-column
align="center"
label="开关"
prop="switch"
show-overflow-tooltip
>
<template #default="{ row }">
<el-tooltip
:content="row.switch === 0 ? '点击开启' : '点击关闭'"
:enterable="false"
placement="top"
>
<el-switch v-model="row.switch" />
</el-tooltip>
</template>
</el-table-column>
<el-table-column align="center" label="状态" show-overflow-tooltip>
<template #default="{ row }">
<el-tooltip
class="item"
:content="row.status"
effect="dark"
placement="top-start"
>
<el-tag :type="statusFilter(row.status)">
{{ row.status }}
</el-tag>
</el-tooltip>
</template>
</el-table-column>
<el-table-column
align="center"
label="时间"
prop="datetime"
show-overflow-tooltip
width="200"
/>
<el-table-column
align="center"
fixed="right"
label="操作"
show-overflow-tooltip
width="200"
>
<template #default="{ row }">
<el-button text type="primary" @click="handleDetail(row)">
详情
</el-button>
<el-button text type="primary" @click="handleEdit(row)">
编辑
</el-button>
<el-button text type="primary" @click="handleDelete(row)">
删除
</el-button>
</template>
</el-table-column>
<template #empty>
<el-empty class="vab-data-empty" description="暂无数据" />
</template>
</el-table>
<el-pagination
background
:current-page="queryForm.pageNo"
:layout="layout"
:page-size="queryForm.pageSize"
:total="total"
@current-change="handleCurrentChange"
@size-change="handleSizeChange"
/>
<edit ref="editRef" @fetch-data="fetchData" />
</div>
</template>
<script>
import { Delete, Plus, Search } from '@element-plus/icons-vue'
import { useTabsStore } from '@/store/modules/tabs'
import { useRoutesStore } from '@/store/modules/routes'
import { doDelete, getList } from '@/api/table'
import { handleMatched, handleTabs } from '@/utils/routes'
export default defineComponent({
name: 'ComprehensiveTable',
components: {
Edit: defineAsyncComponent(() => import('./components/TableEdit')),
},
setup() {
const router = useRouter()
const $baseConfirm = inject('$baseConfirm')
const $baseMessage = inject('$baseMessage')
const $baseAlert = inject('$baseAlert')
const $baseNotify = inject('$baseNotify')
const routesStore = useRoutesStore()
const { getRoutes: routes } = storeToRefs(routesStore)
const tabsStore = useTabsStore()
const { changeTabsMeta, addVisitedRoute } = tabsStore
const state = reactive({
editRef: null,
tableSortRef: null,
fold: false,
list: [],
imageList: [],
listLoading: true,
layout: 'total, sizes, prev, pager, next, jumper',
total: 0,
selectRows: '',
queryForm: {
pageNo: 1,
pageSize: 10,
title: '',
author: '',
datetime: '',
},
})
onActivated(() => {
state['tableSortRef'].doLayout()
fetchData()
})
const fetchData = async () => {
state.listLoading = true
const {
data: { list, total },
} = await getList(state.queryForm)
state.list = list
const imageList = []
list.forEach((item) => {
imageList.push(item.img)
})
state.imageList = imageList
state.total = total
state.listLoading = false
setTimeout(() => {
toggleSelection([state.list[0]])
}, 0)
}
const handleSizeChange = (val) => {
state.queryForm.pageSize = val
fetchData()
}
const handleCurrentChange = (val) => {
state.queryForm.pageNo = val
fetchData()
}
const queryData = () => {
state.queryForm.pageNo = 1
fetchData()
}
const statusFilter = (status) => {
const statusMap = {
published: 'success',
draft: 'primary',
deleted: 'danger',
}
return statusMap[status]
}
const handleFold = () => {
state.fold = !state.fold
}
const tableSortChange = () => {
const imageList = []
state.list.forEach((item) => {
imageList.push(item.img)
})
state.imageList = imageList
}
const setSelectRows = (val) => {
state.selectRows = val
}
const handleAdd = () => {
state['editRef'].showEdit()
}
const handleEdit = (row) => {
state['editRef'].showEdit(row)
}
const handleDelete = (row) => {
if (row.id) {
$baseConfirm('你确定要删除当前项吗', null, async () => {
const { msg } = await doDelete({ ids: row.id })
$baseMessage(msg, 'success', 'vab-hey-message-success')
await fetchData()
})
} else {
if (state.selectRows.length > 0) {
const ids = state.selectRows
.map((item) => item.id)
.join(',')
$baseConfirm('你确定要删除选中项吗', null, async () => {
const { msg } = await doDelete({ ids })
$baseMessage(
msg,
'success',
'vab-hey-message-success'
)
await fetchData()
})
} else {
$baseMessage(
'未选中任何行',
'error',
'vab-hey-message-error'
)
}
}
}
const handleDetailStayTable = async () => {
for (let i = 0; i < state.selectRows.length; i++) {
const matched = handleMatched(
routes.value,
'/vab/table/detail'
)
const tab = handleTabs({
...matched[matched.length - 1],
query: state.selectRows[i],
})
if (tab) {
await addVisitedRoute(tab)
await changeTabsMeta({
title: '详情页',
meta: {
title: `${tab.query.title} 详情页`,
},
})
}
}
}
const handleDetail = (row) => {
if (row.id)
router.push({
path: '/vab/table/detail',
query: {
...row,
timestamp: Date.now(), //允许同一个详情页同时打开多次否则会触发路由被缓存下次无法刷新的bug
},
})
else {
if (state.selectRows.length === 1) {
router.push({
path: '/vab/table/detail',
query: {
...state.selectRows[0],
timestamp: Date.now(), //允许同一个详情页同时打开多次否则会触发路由被缓存下次无法刷新的bug
},
})
} else {
$baseMessage(
'请选择一行进行详情页跳转',
'error',
'vab-hey-message-error'
)
}
}
}
const handleMessage = () => {
$baseMessage(
'test1',
'success',
false,
'vab-hey-message-success'
)
}
const handleAlert = () => {
$baseAlert('11')
// $baseAlert('11', '自定义标题', () => {
// /* 可以写回调; */
// })
// $baseAlert('11', null, () => {
// /* 可以写回调; */
// })
}
const handleConfirm = () => {
$baseConfirm(
'你确定要执行该操作?',
null,
() => {
/* 可以写回调; */
},
() => {
/* 可以写回调; */
}
)
}
const handleNotify = () => {
$baseNotify('测试消息提示', 'test', 'success', 'bottom-right')
}
const toggleSelection = (rows) => {
if (rows) {
rows.forEach((row) => {
state['tableSortRef'].toggleRowSelection(row)
})
} else {
state['tableSortRef'].clearSelection()
}
}
onMounted(() => {
fetchData()
})
return {
...toRefs(state),
handleSizeChange,
handleCurrentChange,
queryData,
statusFilter,
handleFold,
tableSortChange,
setSelectRows,
handleAdd,
handleEdit,
handleDelete,
handleDetailStayTable,
handleDetail,
handleMessage,
handleAlert,
handleConfirm,
handleNotify,
fetchData,
Delete,
Plus,
Search,
}
},
})
</script>

View File

@@ -0,0 +1,414 @@
<template>
<div
class="custom-table-container auto-height-container"
:class="{ 'vab-fullscreen': isFullscreen }"
>
<vab-query-form>
<vab-query-form-left-panel>
<el-form
inline
label-width="0"
:model="queryForm"
@submit.prevent
>
<el-form-item>
<el-input
v-model="queryForm.title"
placeholder="标题"
/>
</el-form-item>
<el-form-item>
<el-button
:icon="Search"
native-type="submit"
type="primary"
@click="queryData"
>
查询
</el-button>
<el-button
:icon="Plus"
type="primary"
@click="handleAdd"
>
添加
</el-button>
<el-button
:icon="Delete"
type="danger"
@click="handleDelete($event)"
>
删除
</el-button>
</el-form-item>
</el-form>
</vab-query-form-left-panel>
<vab-query-form-right-panel>
<div class="stripe-panel">
<el-checkbox v-model="stripe">斑马纹</el-checkbox>
</div>
<div class="border-panel">
<el-checkbox v-model="border">边框可拉伸列</el-checkbox>
</div>
<el-button
style="margin: 0 10px 10px 0 !important"
text
type="primary"
@click="clickFullScreen"
>
<vab-icon
:icon="
isFullscreen
? 'fullscreen-exit-fill'
: 'fullscreen-fill'
"
/>
</el-button>
<el-popover popper-class="custom-table-radio" trigger="hover">
<el-radio-group v-model="lineHeight">
<el-radio label="large" value="large"></el-radio>
<el-radio label="default" value="default"></el-radio>
<el-radio label="small" value="small"></el-radio>
</el-radio-group>
<template #reference>
<el-button
style="margin: 0 10px 10px 0 !important"
text
type="primary"
>
<vab-icon icon="line-height" />
</el-button>
</template>
</el-popover>
<el-popover
popper-class="custom-table-checkbox"
trigger="hover"
>
<template #reference>
<el-button
style="margin: 0 0 10px !important"
text
type="primary"
>
<vab-icon icon="settings-line" />
</el-button>
</template>
<el-checkbox-group v-model="checkList">
<vab-draggable
v-bind="dragOptions"
item-key="{ element }"
:list="columns"
>
<template #item="{ element }">
<div>
<vab-icon icon="drag-drop-line" />
<el-checkbox
:disabled="
element.disableCheck === true
"
:label="element.label"
:value="element.label"
>
{{ element.label }}
</el-checkbox>
</div>
</template>
</vab-draggable>
</el-checkbox-group>
</el-popover>
</vab-query-form-right-panel>
</vab-query-form>
<el-table
ref="tableSortRef"
v-loading="listLoading"
:border="border"
:data="list"
:size="lineHeight"
:stripe="stripe"
@selection-change="setSelectRows"
>
<el-table-column align="center" type="selection" width="55" />
<el-table-column
align="center"
label="序号"
show-overflow-tooltip
width="200"
>
<template #default="{ $index }">
{{ $index + 1 }}
</template>
</el-table-column>
<!-- TODO element-plus未知原因不支持拖拽后宽度重新计算暂时放弃 -->
<el-table-column
v-for="(item, index) in finallyColumns"
:key="index"
align="center"
:label="item.label"
:prop="item.prop"
:sortable="item.sortable"
width="auto"
>
<template #default="{ row }">
<span v-if="item.label === '评级'">
<el-rate v-model="row.rate" disabled />
</span>
<span v-else>{{ row[item.prop] }}</span>
</template>
</el-table-column>
<el-table-column
align="center"
label="操作"
show-overflow-tooltip
width="200"
>
<template #default="{ row }">
<el-button text type="primary" @click="handleEdit(row)">
编辑
</el-button>
<el-button text type="primary" @click="handleDelete(row)">
删除
</el-button>
</template>
</el-table-column>
<template #empty>
<!-- <el-image
class="vab-data-empty"
:src="require('@/assets/empty_images/data_empty.png')"
/> -->
<el-empty class="vab-data-empty" description="暂无数据" />
</template>
</el-table>
<el-pagination
background
:current-page="queryForm.pageNo"
:layout="layout"
:page-size="queryForm.pageSize"
:total="total"
@current-change="handleCurrentChange"
@size-change="handleSizeChange"
/>
<edit ref="editRef" @fetch-data="fetchData" />
</div>
</template>
<script>
import VabDraggable from 'vuedraggable'
import { Delete, Plus, Search, Setting } from '@element-plus/icons-vue'
import { doDelete, getList } from '@/api/table'
export default defineComponent({
name: 'CustomTable',
components: {
Edit: defineAsyncComponent(() => import('./components/TableEdit')),
VabDraggable,
},
setup() {
const $baseConfirm = inject('$baseConfirm')
const $baseMessage = inject('$baseMessage')
const state = reactive({
tableSortRef: null,
editRef: null,
border: true,
stripe: false,
lineHeight: 'small',
isFullscreen: false,
checkList: ['标题', '作者', '评级', '点击量', '时间'],
columns: [
{
label: '标题',
prop: 'title',
sortable: true,
disableCheck: true,
},
{
label: '作者',
prop: 'author',
sortable: true,
},
{
label: '评级',
prop: 'rate',
sortable: true,
},
{
label: '点击量',
prop: 'pageViews',
sortable: true,
},
{
label: '时间',
prop: 'datetime',
sortable: true,
},
],
list: [],
imageList: [],
listLoading: true,
layout: 'total, sizes, prev, pager, next, jumper',
total: 0,
selectRows: '',
queryForm: {
pageNo: 1,
pageSize: 20,
title: '',
},
})
const dragOptions = computed(() => {
return {
animation: 600,
group: 'description',
}
})
const finallyColumns = computed(() => {
return state.columns.filter((item) =>
state.checkList.includes(item.label)
)
})
const fetchData = async () => {
state.listLoading = true
const {
data: { list, total },
} = await getList(state.queryForm)
state.list = list
const imageList = []
list.forEach((item) => {
imageList.push(item.img)
})
state.total = total
state.listLoading = false
}
const handleSizeChange = (val) => {
state.queryForm.pageSize = val
fetchData()
}
const handleCurrentChange = (val) => {
state.queryForm.pageNo = val
fetchData()
}
const queryData = () => {
state.queryForm.pageNo = 1
fetchData()
}
const clickFullScreen = () => {
state.isFullscreen = !state.isFullscreen
}
const setSelectRows = (val) => {
state.selectRows = val
}
const handleAdd = () => {
state['editRef'].showEdit()
}
const handleEdit = (row) => {
state['editRef'].showEdit(row)
}
const handleDelete = (row) => {
if (row.id) {
$baseConfirm('你确定要删除当前项吗', null, async () => {
const { msg } = await doDelete({ ids: row.id })
$baseMessage(msg, 'success', 'vab-hey-message-success')
await fetchData()
})
} else {
if (state.selectRows.length > 0) {
const ids = state.selectRows
.map((item) => item.id)
.join(',')
$baseConfirm('你确定要删除选中项吗', null, async () => {
const { msg } = await doDelete({ ids })
$baseMessage(
msg,
'success',
'vab-hey-message-success'
)
await fetchData()
})
} else {
$baseMessage(
'未选中任何行',
'error',
'vab-hey-message-error'
)
}
}
}
onMounted(() => {
fetchData()
})
return {
...toRefs(state),
dragOptions,
finallyColumns,
handleSizeChange,
handleCurrentChange,
queryData,
setSelectRows,
clickFullScreen,
fetchData,
handleAdd,
handleEdit,
handleDelete,
Plus,
Delete,
Search,
Setting,
}
},
})
</script>
<style lang="scss" scoped>
@use 'sass:math';
.custom-table-container {
:deep() {
i {
cursor: pointer;
}
}
.right-panel {
.stripe-panel,
.border-panel {
margin: 0 10px #{math.div($base-margin, 2)} 10px !important;
:deep() {
.el-checkbox__label {
margin-left: 0 !important;
}
}
}
[class*='ri'] {
font-size: $base-font-size-big;
color: var(--el-color-black);
}
}
}
</style>
<style lang="scss">
html body .custom-table-checkbox {
[class*='ri'] {
vertical-align: -0.5px !important;
cursor: pointer;
}
.el-checkbox {
margin: 5px 0 5px 8px;
}
}
.custom-table-radio {
width: 240px !important;
}
</style>

View File

@@ -0,0 +1,98 @@
<template>
<div class="detail-container">
<el-page-header
:content="'【' + route.query.title + '】详情页'"
@back="goBack"
/>
<el-alert :closable="false" title="当前详情页允许多开" />
<el-form inline :model="form" @submit.prevent>
<el-form-item label="输入框缓存">
<el-input v-model="form.text" clearable style="width: 200px" />
</el-form-item>
<el-form-item label="操作">
<el-button
:icon="Refresh"
type="primary"
@click="handleRefreshMainPage"
>
刷新综合表格页面
</el-button>
</el-form-item>
</el-form>
<el-descriptions border :column="2" title="详情">
<el-descriptions-item>
<template #label>标题</template>
{{ route.query.title }}
</el-descriptions-item>
<el-descriptions-item>
<template #label>作者</template>
{{ route.query.author }}
</el-descriptions-item>
<el-descriptions-item>
<template #label>时间</template>
{{ route.query.datetime }}
</el-descriptions-item>
<el-descriptions-item>
<template #label>评级</template>
<el-rate v-model="rate" disabled />
</el-descriptions-item>
<el-descriptions-item>
<template #label>备注</template>
{{ route.query.description }}
</el-descriptions-item>
</el-descriptions>
</div>
</template>
<script lang="ts" setup>
import { Refresh } from '@element-plus/icons-vue'
import { useTabsStore } from '@/store/modules/tabs'
import { handleActivePath } from '@/utils/routes'
defineOptions({
name: 'Detail',
})
const route: any = useRoute()
const $pub = inject<any>('$pub')
const tabsStore = useTabsStore()
const { changeTabsMeta, delVisitedRoute } = tabsStore
const form = reactive<any>({ text: '' })
const rate = ref<number>(Number.parseInt(route.query.rate))
const goBack = async () => {
await delVisitedRoute(handleActivePath(route, true))
history.back()
}
const handleRefreshMainPage = () => {
$pub('reload-router-view', 'ComprehensiveTable')
}
onMounted(() => {
changeTabsMeta({
title: '详情页',
meta: {
title: `${route.query.title} 详情页`,
},
})
})
</script>
<style lang="scss" scoped>
.default-table-detail-container {
:deep() {
.el-form--inline {
.el-form-item {
margin-right: 10px;
}
}
.el-descriptions__label {
min-width: 80px !important;
text-align: right;
}
}
}
</style>

View File

@@ -0,0 +1,147 @@
<template>
<div class="dynamic-table-container">
<el-form ref="formRef" :model="form">
<el-form-item :label-width="0" prop="list">
<vab-form-table
v-model="form.list"
drag
:row-template="rowTemplate"
style="width: 100%"
>
<el-table-column align="center" label="标题" prop="title">
<template #default="{ row }">
<el-input v-model="row.title" />
</template>
</el-table-column>
<el-table-column align="center" label="作者" prop="author">
<template #default="{ row }">
<el-input v-model="row.author" />
</template>
</el-table-column>
<el-table-column align="center" label="评级" prop="rate">
<template #default="{ row }">
<el-rate v-model="row.rate" />
</template>
</el-table-column>
<el-table-column
align="center"
label="点击量"
prop="pageViews"
>
<template #default="{ row }">
<el-input-number
v-model="row.pageViews"
type="number"
/>
</template>
</el-table-column>
<el-table-column
align="center"
label="时间"
prop="datetime"
>
<template #default="{ row }">
<el-date-picker v-model="row.datetime" />
</template>
</el-table-column>
<el-table-column align="center" label="类型" prop="type">
<template #default="{ row }">
<el-select v-model="row.type" placeholder="请选择">
<el-option
v-for="item in typeDic"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</template>
</el-table-column>
<el-table-column
align="center"
label="开关"
prop="switch"
width="80"
>
<template #default="{ row }">
<el-switch v-model="row.switch" />
</template>
</el-table-column>
</vab-form-table>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="save">保存</el-button>
</el-form-item>
</el-form>
</div>
</template>
<script>
import { Delete, Plus, Search } from '@element-plus/icons-vue'
import VabFormTable from '@/plugins/VabFormTable'
import { doEdit, getList } from '@/api/table'
export default defineComponent({
name: 'DynamicTable',
components: { VabFormTable },
setup() {
const $baseMessage = inject('$baseMessage')
const state = reactive({
formRef: null,
rowTemplate: {
title: '',
author: '',
rate: 0,
pageViews: 0,
dateTime: '',
type: 1,
},
form: {
list: [],
},
typeDic: [
{
label: '热点',
value: 0,
},
{
label: '头条',
value: 1,
},
],
})
const save = () => {
state.formRef.validate(async (valid) => {
if (valid) {
const { msg } = await doEdit(state.form)
$baseMessage(msg, 'success', 'vab-hey-message-success')
} else {
return false
}
})
}
const fetchData = async () => {
const {
data: { list },
} = await getList({
pageSize: 5,
pageNo: 1,
})
state.form.list = list
}
onMounted(() => {
fetchData()
})
return {
...toRefs(state),
save,
Delete,
Plus,
Search,
}
},
})
</script>

View File

@@ -0,0 +1,176 @@
<template>
<div class="inline-edit-table-container">
<el-alert
:closable="false"
show-icon
title="三级路由【不缓存路由并且不固定表格高度】的示例"
type="success"
/>
<vab-query-form>
<vab-query-form-right-panel :span="24">
<el-form inline :model="queryForm" @submit.prevent>
<el-form-item>
<el-input
v-model="queryForm.title"
placeholder="标题"
/>
</el-form-item>
<el-form-item>
<el-button
:icon="Search"
native-type="submit"
type="primary"
@click="queryData"
>
查询
</el-button>
</el-form-item>
</el-form>
</vab-query-form-right-panel>
</vab-query-form>
<el-table v-loading="listLoading" border :data="list">
<el-table-column
align="center"
show-overflow-tooltip
type="selection"
width="55"
/>
<el-table-column
align="center"
label="序号"
show-overflow-tooltip
width="200"
>
<template #default="{ $index }">
{{ $index + 1 }}
</template>
</el-table-column>
<el-table-column
align="center"
label="标题"
min-width="300px"
show-overflow-tooltip
>
<template #default="{ row }">
<template v-if="row.edit">
<el-input v-model="row.title" style="width: 300px" />
<el-button
style="margin-left: 10px"
@click="cancelEdit(row)"
>
取消
</el-button>
</template>
<span v-else>{{ row.title }}</span>
</template>
</el-table-column>
<el-table-column
align="center"
label="作者"
prop="author"
show-overflow-tooltip
/>
<el-table-column
align="center"
label="操作"
show-overflow-tooltip
width="150"
>
<template #default="{ row }">
<el-button
v-if="row.edit"
type="primary"
@click="confirmEdit(row)"
>
保存
</el-button>
<el-button v-else @click="row.edit = !row.edit">
编辑
</el-button>
</template>
</el-table-column>
<template #empty>
<el-empty class="vab-data-empty" description="暂无数据" />
</template>
</el-table>
<el-pagination
background
:current-page="queryForm.pageNo"
:layout="layout"
:page-size="queryForm.pageSize"
:total="total"
@current-change="handleCurrentChange"
@size-change="handleSizeChange"
/>
</div>
</template>
<script>
import { Edit, Search } from '@element-plus/icons-vue'
import { getList } from '@/api/table'
export default defineComponent({
name: 'InlineEditTable',
setup() {
const state = reactive({
list: [],
listLoading: true,
layout: 'total, sizes, prev, pager, next, jumper',
total: 0,
queryForm: {
pageNo: 1,
pageSize: 20,
title: '',
},
})
const fetchData = async () => {
state.listLoading = true
const {
data: { list, total },
} = await getList(state.queryForm)
state.list = list.map((v) => {
v.edit = false
v.originalTitle = v.title
return v
})
state.total = total
state.listLoading = false
}
const handleSizeChange = (val) => {
state.queryForm.pageSize = val
fetchData()
}
const handleCurrentChange = (val) => {
state.queryForm.pageNo = val
fetchData()
}
const queryData = () => {
state.queryForm.pageNo = 1
fetchData()
}
const cancelEdit = (row) => {
row.title = row.originalTitle
row.edit = false
}
const confirmEdit = (row) => {
row.edit = false
row.originalTitle = row.title
}
onMounted(() => {
fetchData()
})
return {
...toRefs(state),
handleSizeChange,
handleCurrentChange,
queryData,
cancelEdit,
confirmEdit,
Edit,
Search,
}
},
})
</script>