Initial commit
This commit is contained in:
341
promo-ui2/src/views/Authorization/Department/Department.vue
Normal file
341
promo-ui2/src/views/Authorization/Department/Department.vue
Normal file
@@ -0,0 +1,341 @@
|
||||
<script setup lang="tsx">
|
||||
import { ContentWrap } from '@/components/ContentWrap'
|
||||
import { Search } from '@/components/Search'
|
||||
import { Dialog } from '@/components/Dialog'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { ElTag } from 'element-plus'
|
||||
import { Table } from '@/components/Table'
|
||||
import {
|
||||
getDepartmentApi,
|
||||
getDepartmentTableApi,
|
||||
saveDepartmentApi,
|
||||
deleteDepartmentApi
|
||||
} from '@/api/department'
|
||||
import type { DepartmentItem } from '@/api/department/types'
|
||||
import { useTable } from '@/hooks/web/useTable'
|
||||
import { ref, unref, reactive } from 'vue'
|
||||
import Write from './components/Write.vue'
|
||||
import Detail from './components/Detail.vue'
|
||||
import { CrudSchema, useCrudSchemas } from '@/hooks/web/useCrudSchemas'
|
||||
import { BaseButton } from '@/components/Button'
|
||||
|
||||
const ids = ref<string[]>([])
|
||||
|
||||
const { tableRegister, tableState, tableMethods } = useTable({
|
||||
fetchDataApi: async () => {
|
||||
const { currentPage, pageSize } = tableState
|
||||
const res = await getDepartmentTableApi({
|
||||
pageIndex: unref(currentPage),
|
||||
pageSize: unref(pageSize),
|
||||
...unref(searchParams)
|
||||
})
|
||||
return {
|
||||
list: res.data.list,
|
||||
total: res.data.total
|
||||
}
|
||||
},
|
||||
fetchDelApi: async () => {
|
||||
const res = await deleteDepartmentApi(unref(ids))
|
||||
return !!res
|
||||
}
|
||||
})
|
||||
const { loading, dataList, total, currentPage, pageSize } = tableState
|
||||
const { getList, getElTableExpose, delList } = tableMethods
|
||||
|
||||
const searchParams = ref({})
|
||||
const setSearchParams = (params: any) => {
|
||||
searchParams.value = params
|
||||
getList()
|
||||
}
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const crudSchemas = reactive<CrudSchema[]>([
|
||||
{
|
||||
field: 'selection',
|
||||
search: {
|
||||
hidden: true
|
||||
},
|
||||
form: {
|
||||
hidden: true
|
||||
},
|
||||
detail: {
|
||||
hidden: true
|
||||
},
|
||||
table: {
|
||||
type: 'selection'
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'index',
|
||||
label: t('tableDemo.index'),
|
||||
type: 'index',
|
||||
search: {
|
||||
hidden: true
|
||||
},
|
||||
form: {
|
||||
hidden: true
|
||||
},
|
||||
detail: {
|
||||
hidden: true
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'id',
|
||||
label: t('userDemo.departmentName'),
|
||||
table: {
|
||||
slots: {
|
||||
default: (data: any) => {
|
||||
return <>{data.row.departmentName}</>
|
||||
}
|
||||
}
|
||||
},
|
||||
form: {
|
||||
component: 'TreeSelect',
|
||||
componentProps: {
|
||||
nodeKey: 'id',
|
||||
props: {
|
||||
label: 'departmentName'
|
||||
}
|
||||
},
|
||||
optionApi: async () => {
|
||||
const res = await getDepartmentApi()
|
||||
return res.data.list
|
||||
}
|
||||
},
|
||||
detail: {
|
||||
slots: {
|
||||
default: (data: any) => {
|
||||
return <>{data.departmentName}</>
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'status',
|
||||
label: t('userDemo.status'),
|
||||
search: {
|
||||
hidden: true
|
||||
},
|
||||
table: {
|
||||
slots: {
|
||||
default: (data: any) => {
|
||||
const status = data.row.status
|
||||
return (
|
||||
<>
|
||||
<ElTag type={status === 0 ? 'danger' : 'success'}>
|
||||
{status === 1 ? t('userDemo.enable') : t('userDemo.disable')}
|
||||
</ElTag>
|
||||
</>
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
form: {
|
||||
component: 'Select',
|
||||
componentProps: {
|
||||
options: [
|
||||
{
|
||||
value: 0,
|
||||
label: t('userDemo.disable')
|
||||
},
|
||||
{
|
||||
value: 1,
|
||||
label: t('userDemo.enable')
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
detail: {
|
||||
slots: {
|
||||
default: (data: any) => {
|
||||
return (
|
||||
<>
|
||||
<ElTag type={data.status === 0 ? 'danger' : 'success'}>
|
||||
{data.status === 1 ? t('userDemo.enable') : t('userDemo.disable')}
|
||||
</ElTag>
|
||||
</>
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'createTime',
|
||||
label: t('tableDemo.displayTime'),
|
||||
search: {
|
||||
hidden: true
|
||||
},
|
||||
form: {
|
||||
hidden: true
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'remark',
|
||||
label: t('userDemo.remark'),
|
||||
search: {
|
||||
hidden: true
|
||||
},
|
||||
form: {
|
||||
component: 'Input',
|
||||
componentProps: {
|
||||
type: 'textarea',
|
||||
rows: 5
|
||||
},
|
||||
colProps: {
|
||||
span: 24
|
||||
}
|
||||
},
|
||||
detail: {
|
||||
slots: {
|
||||
default: (data: any) => {
|
||||
return <>{data.remark}</>
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'action',
|
||||
width: '260px',
|
||||
label: t('tableDemo.action'),
|
||||
search: {
|
||||
hidden: true
|
||||
},
|
||||
form: {
|
||||
hidden: true
|
||||
},
|
||||
detail: {
|
||||
hidden: true
|
||||
},
|
||||
table: {
|
||||
slots: {
|
||||
default: (data: any) => {
|
||||
return (
|
||||
<>
|
||||
<BaseButton type="primary" onClick={() => action(data.row, 'edit')}>
|
||||
{t('exampleDemo.edit')}
|
||||
</BaseButton>
|
||||
<BaseButton type="success" onClick={() => action(data.row, 'detail')}>
|
||||
{t('exampleDemo.detail')}
|
||||
</BaseButton>
|
||||
<BaseButton type="danger" onClick={() => delData(data.row)}>
|
||||
{t('exampleDemo.del')}
|
||||
</BaseButton>
|
||||
</>
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
])
|
||||
|
||||
// @ts-ignore
|
||||
const { allSchemas } = useCrudSchemas(crudSchemas)
|
||||
|
||||
const dialogVisible = ref(false)
|
||||
const dialogTitle = ref('')
|
||||
|
||||
const currentRow = ref<DepartmentItem | null>(null)
|
||||
const actionType = ref('')
|
||||
|
||||
const AddAction = () => {
|
||||
dialogTitle.value = t('exampleDemo.add')
|
||||
currentRow.value = null
|
||||
dialogVisible.value = true
|
||||
actionType.value = ''
|
||||
}
|
||||
|
||||
const delLoading = ref(false)
|
||||
|
||||
const delData = async (row: DepartmentItem | null) => {
|
||||
const elTableExpose = await getElTableExpose()
|
||||
ids.value = row
|
||||
? [row.id]
|
||||
: elTableExpose?.getSelectionRows().map((v: DepartmentItem) => v.id) || []
|
||||
delLoading.value = true
|
||||
await delList(unref(ids).length).finally(() => {
|
||||
delLoading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
const action = (row: DepartmentItem, type: string) => {
|
||||
dialogTitle.value = t(type === 'edit' ? 'exampleDemo.edit' : 'exampleDemo.detail')
|
||||
actionType.value = type
|
||||
currentRow.value = row
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
const writeRef = ref<ComponentRef<typeof Write>>()
|
||||
|
||||
const saveLoading = ref(false)
|
||||
|
||||
const save = async () => {
|
||||
const write = unref(writeRef)
|
||||
const formData = await write?.submit()
|
||||
if (formData) {
|
||||
saveLoading.value = true
|
||||
const res = await saveDepartmentApi(formData)
|
||||
.catch(() => {})
|
||||
.finally(() => {
|
||||
saveLoading.value = false
|
||||
})
|
||||
if (res) {
|
||||
dialogVisible.value = false
|
||||
currentPage.value = 1
|
||||
getList()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<Search :schema="allSchemas.searchSchema" @search="setSearchParams" @reset="setSearchParams" />
|
||||
|
||||
<div class="mb-10px">
|
||||
<BaseButton type="primary" @click="AddAction">{{ t('exampleDemo.add') }}</BaseButton>
|
||||
<BaseButton :loading="delLoading" type="danger" @click="delData(null)">
|
||||
{{ t('exampleDemo.del') }}
|
||||
</BaseButton>
|
||||
</div>
|
||||
|
||||
<Table
|
||||
v-model:pageSize="pageSize"
|
||||
v-model:currentPage="currentPage"
|
||||
:columns="allSchemas.tableColumns"
|
||||
:data="dataList"
|
||||
:loading="loading"
|
||||
:pagination="{
|
||||
total: total
|
||||
}"
|
||||
@register="tableRegister"
|
||||
/>
|
||||
</ContentWrap>
|
||||
|
||||
<Dialog v-model="dialogVisible" :title="dialogTitle">
|
||||
<Write
|
||||
v-if="actionType !== 'detail'"
|
||||
ref="writeRef"
|
||||
:form-schema="allSchemas.formSchema"
|
||||
:current-row="currentRow"
|
||||
/>
|
||||
|
||||
<Detail
|
||||
v-if="actionType === 'detail'"
|
||||
:detail-schema="allSchemas.detailSchema"
|
||||
:current-row="currentRow"
|
||||
/>
|
||||
|
||||
<template #footer>
|
||||
<BaseButton
|
||||
v-if="actionType !== 'detail'"
|
||||
type="primary"
|
||||
:loading="saveLoading"
|
||||
@click="save"
|
||||
>
|
||||
{{ t('exampleDemo.save') }}
|
||||
</BaseButton>
|
||||
<BaseButton @click="dialogVisible = false">{{ t('dialogDemo.close') }}</BaseButton>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
@@ -0,0 +1,20 @@
|
||||
<script setup lang="ts">
|
||||
import { PropType } from 'vue'
|
||||
import { DepartmentItem } from '@/api/department/types'
|
||||
import { Descriptions, DescriptionsSchema } from '@/components/Descriptions'
|
||||
|
||||
defineProps({
|
||||
currentRow: {
|
||||
type: Object as PropType<Nullable<DepartmentItem>>,
|
||||
default: () => null
|
||||
},
|
||||
detailSchema: {
|
||||
type: Array as PropType<DescriptionsSchema[]>,
|
||||
default: () => []
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Descriptions :schema="detailSchema" :data="currentRow || {}" />
|
||||
</template>
|
||||
@@ -0,0 +1,59 @@
|
||||
<script setup lang="ts">
|
||||
import { Form, FormSchema } from '@/components/Form'
|
||||
import { useForm } from '@/hooks/web/useForm'
|
||||
import { PropType, reactive, watch } from 'vue'
|
||||
import { useValidator } from '@/hooks/web/useValidator'
|
||||
import { DepartmentItem } from '@/api/department/types'
|
||||
|
||||
const { required } = useValidator()
|
||||
|
||||
const props = defineProps({
|
||||
currentRow: {
|
||||
type: Object as PropType<Nullable<DepartmentItem>>,
|
||||
default: () => null
|
||||
},
|
||||
formSchema: {
|
||||
type: Array as PropType<FormSchema[]>,
|
||||
default: () => []
|
||||
}
|
||||
})
|
||||
|
||||
const rules = reactive({
|
||||
id: [required()],
|
||||
status: [required()]
|
||||
})
|
||||
|
||||
const { formRegister, formMethods } = useForm()
|
||||
const { setValues, getFormData, getElFormExpose } = formMethods
|
||||
|
||||
const submit = async () => {
|
||||
const elForm = await getElFormExpose()
|
||||
const valid = await elForm?.validate().catch((err) => {
|
||||
console.log(err)
|
||||
})
|
||||
if (valid) {
|
||||
const formData = await getFormData()
|
||||
return formData
|
||||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.currentRow,
|
||||
(currentRow) => {
|
||||
if (!currentRow) return
|
||||
setValues(currentRow)
|
||||
},
|
||||
{
|
||||
deep: true,
|
||||
immediate: true
|
||||
}
|
||||
)
|
||||
|
||||
defineExpose({
|
||||
submit
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Form :rules="rules" @register="formRegister" :schema="formSchema" />
|
||||
</template>
|
||||
212
promo-ui2/src/views/Authorization/Menu/Menu.vue
Normal file
212
promo-ui2/src/views/Authorization/Menu/Menu.vue
Normal file
@@ -0,0 +1,212 @@
|
||||
<script setup lang="tsx">
|
||||
import { reactive, ref, unref } from 'vue'
|
||||
import { getMenuListApi } from '@/api/menu'
|
||||
import { useTable } from '@/hooks/web/useTable'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { Table, TableColumn } from '@/components/Table'
|
||||
import { ElTag } from 'element-plus'
|
||||
import { Icon } from '@/components/Icon'
|
||||
import { Search } from '@/components/Search'
|
||||
import { FormSchema } from '@/components/Form'
|
||||
import { ContentWrap } from '@/components/ContentWrap'
|
||||
import Write from './components/Write.vue'
|
||||
import Detail from './components/Detail.vue'
|
||||
import { Dialog } from '@/components/Dialog'
|
||||
import { BaseButton } from '@/components/Button'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const { tableRegister, tableState, tableMethods } = useTable({
|
||||
fetchDataApi: async () => {
|
||||
const res = await getMenuListApi()
|
||||
return {
|
||||
list: res.data.list || []
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const { dataList, loading } = tableState
|
||||
const { getList } = tableMethods
|
||||
|
||||
const tableColumns = reactive<TableColumn[]>([
|
||||
{
|
||||
field: 'index',
|
||||
label: t('userDemo.index'),
|
||||
type: 'index'
|
||||
},
|
||||
{
|
||||
field: 'meta.title',
|
||||
label: t('menu.menuName'),
|
||||
slots: {
|
||||
default: (data: any) => {
|
||||
const title = data.row.meta.title
|
||||
return <>{title}</>
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'meta.icon',
|
||||
label: t('menu.icon'),
|
||||
slots: {
|
||||
default: (data: any) => {
|
||||
const icon = data.row.meta.icon
|
||||
if (icon) {
|
||||
return (
|
||||
<>
|
||||
<Icon icon={icon} />
|
||||
</>
|
||||
)
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
// {
|
||||
// field: 'meta.permission',
|
||||
// label: t('menu.permission'),
|
||||
// slots: {
|
||||
// default: (data: any) => {
|
||||
// const permission = data.row.meta.permission
|
||||
// return permission ? <>{permission.join(', ')}</> : null
|
||||
// }
|
||||
// }
|
||||
// },
|
||||
{
|
||||
field: 'component',
|
||||
label: t('menu.component'),
|
||||
slots: {
|
||||
default: (data: any) => {
|
||||
const component = data.row.component
|
||||
return <>{component === '#' ? '顶级目录' : component === '##' ? '子目录' : component}</>
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'path',
|
||||
label: t('menu.path')
|
||||
},
|
||||
{
|
||||
field: 'status',
|
||||
label: t('menu.status'),
|
||||
slots: {
|
||||
default: (data: any) => {
|
||||
return (
|
||||
<>
|
||||
<ElTag type={data.row.status === 0 ? 'danger' : 'success'}>
|
||||
{data.row.status === 1 ? t('userDemo.enable') : t('userDemo.disable')}
|
||||
</ElTag>
|
||||
</>
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'action',
|
||||
label: t('userDemo.action'),
|
||||
width: 240,
|
||||
slots: {
|
||||
default: (data: any) => {
|
||||
const row = data.row
|
||||
return (
|
||||
<>
|
||||
<BaseButton type="primary" onClick={() => action(row, 'edit')}>
|
||||
{t('exampleDemo.edit')}
|
||||
</BaseButton>
|
||||
<BaseButton type="success" onClick={() => action(row, 'detail')}>
|
||||
{t('exampleDemo.detail')}
|
||||
</BaseButton>
|
||||
<BaseButton type="danger">{t('exampleDemo.del')}</BaseButton>
|
||||
</>
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
])
|
||||
|
||||
const searchSchema = reactive<FormSchema[]>([
|
||||
{
|
||||
field: 'meta.title',
|
||||
label: t('menu.menuName'),
|
||||
component: 'Input'
|
||||
}
|
||||
])
|
||||
|
||||
const searchParams = ref({})
|
||||
const setSearchParams = (data: any) => {
|
||||
searchParams.value = data
|
||||
getList()
|
||||
}
|
||||
|
||||
const dialogVisible = ref(false)
|
||||
const dialogTitle = ref('')
|
||||
|
||||
const currentRow = ref()
|
||||
const actionType = ref('')
|
||||
|
||||
const writeRef = ref<ComponentRef<typeof Write>>()
|
||||
|
||||
const saveLoading = ref(false)
|
||||
|
||||
const action = (row: any, type: string) => {
|
||||
dialogTitle.value = t(type === 'edit' ? 'exampleDemo.edit' : 'exampleDemo.detail')
|
||||
actionType.value = type
|
||||
currentRow.value = row
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
const AddAction = () => {
|
||||
dialogTitle.value = t('exampleDemo.add')
|
||||
currentRow.value = undefined
|
||||
dialogVisible.value = true
|
||||
actionType.value = ''
|
||||
}
|
||||
|
||||
const save = async () => {
|
||||
const write = unref(writeRef)
|
||||
const formData = await write?.submit()
|
||||
console.log(formData)
|
||||
if (formData) {
|
||||
saveLoading.value = true
|
||||
setTimeout(() => {
|
||||
saveLoading.value = false
|
||||
dialogVisible.value = false
|
||||
}, 1000)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<Search :schema="searchSchema" @reset="setSearchParams" @search="setSearchParams" />
|
||||
<div class="mb-10px">
|
||||
<BaseButton type="primary" @click="AddAction">{{ t('exampleDemo.add') }}</BaseButton>
|
||||
</div>
|
||||
<Table
|
||||
:columns="tableColumns"
|
||||
default-expand-all
|
||||
node-key="id"
|
||||
:data="dataList"
|
||||
:loading="loading"
|
||||
@register="tableRegister"
|
||||
/>
|
||||
</ContentWrap>
|
||||
|
||||
<Dialog v-model="dialogVisible" :title="dialogTitle">
|
||||
<Write v-if="actionType !== 'detail'" ref="writeRef" :current-row="currentRow" />
|
||||
|
||||
<Detail v-if="actionType === 'detail'" :current-row="currentRow" />
|
||||
|
||||
<template #footer>
|
||||
<BaseButton
|
||||
v-if="actionType !== 'detail'"
|
||||
type="primary"
|
||||
:loading="saveLoading"
|
||||
@click="save"
|
||||
>
|
||||
{{ t('exampleDemo.save') }}
|
||||
</BaseButton>
|
||||
<BaseButton @click="dialogVisible = false">{{ t('dialogDemo.close') }}</BaseButton>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
@@ -0,0 +1,68 @@
|
||||
<script setup lang="ts">
|
||||
import { FormSchema, Form } from '@/components/Form'
|
||||
import { ElDrawer } from 'element-plus'
|
||||
import { reactive } from 'vue'
|
||||
import { useForm } from '@/hooks/web/useForm'
|
||||
import { useValidator } from '@/hooks/web/useValidator'
|
||||
|
||||
const modelValue = defineModel<boolean>()
|
||||
|
||||
const { required } = useValidator()
|
||||
|
||||
const formSchema = reactive<FormSchema[]>([
|
||||
{
|
||||
field: 'label',
|
||||
label: 'label',
|
||||
component: 'Input',
|
||||
colProps: {
|
||||
span: 24
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'value',
|
||||
label: 'value',
|
||||
component: 'Input',
|
||||
colProps: {
|
||||
span: 24
|
||||
}
|
||||
}
|
||||
])
|
||||
|
||||
const { formRegister, formMethods } = useForm()
|
||||
const { getFormData, getElFormExpose } = formMethods
|
||||
|
||||
const emit = defineEmits(['confirm'])
|
||||
|
||||
const rules = reactive({
|
||||
label: [required()],
|
||||
value: [required()]
|
||||
})
|
||||
|
||||
const confirm = async () => {
|
||||
const elFormExpose = await getElFormExpose()
|
||||
if (!elFormExpose) return
|
||||
const valid = await elFormExpose?.validate().catch((err) => {
|
||||
console.log(err)
|
||||
})
|
||||
if (valid) {
|
||||
const formData = await getFormData()
|
||||
formData.id = Date.now()
|
||||
emit('confirm', formData)
|
||||
modelValue.value = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ElDrawer v-model="modelValue" title="新增按钮权限">
|
||||
<template #default>
|
||||
<Form :rules="rules" @register="formRegister" :schema="formSchema" />
|
||||
</template>
|
||||
<template #footer>
|
||||
<div>
|
||||
<BaseButton @click="() => (modelValue = false)">取消</BaseButton>
|
||||
<BaseButton type="primary" @click="confirm">确认</BaseButton>
|
||||
</div>
|
||||
</template>
|
||||
</ElDrawer>
|
||||
</template>
|
||||
173
promo-ui2/src/views/Authorization/Menu/components/Detail.vue
Normal file
173
promo-ui2/src/views/Authorization/Menu/components/Detail.vue
Normal file
@@ -0,0 +1,173 @@
|
||||
<script setup lang="tsx">
|
||||
import { PropType, ref } from 'vue'
|
||||
import { Descriptions, DescriptionsSchema } from '@/components/Descriptions'
|
||||
import { Icon } from '@/components/Icon'
|
||||
import { ElTag } from 'element-plus'
|
||||
|
||||
defineProps({
|
||||
currentRow: {
|
||||
type: Object as PropType<any>,
|
||||
default: () => undefined
|
||||
}
|
||||
})
|
||||
|
||||
const renderTag = (enable?: boolean) => {
|
||||
return <ElTag type={!enable ? 'danger' : 'success'}>{enable ? '启用' : '禁用'}</ElTag>
|
||||
}
|
||||
|
||||
const detailSchema = ref<DescriptionsSchema[]>([
|
||||
{
|
||||
field: 'type',
|
||||
label: '菜单类型',
|
||||
span: 24,
|
||||
slots: {
|
||||
default: (data) => {
|
||||
const type = data.type
|
||||
return <>{type === 1 ? '菜单' : '目录'}</>
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'parentName',
|
||||
label: '父级菜单'
|
||||
},
|
||||
{
|
||||
field: 'meta.title',
|
||||
label: '菜单名称'
|
||||
},
|
||||
{
|
||||
field: 'component',
|
||||
label: '组件',
|
||||
slots: {
|
||||
default: (data) => {
|
||||
const component = data.component
|
||||
return <>{component === '#' ? '顶级目录' : component === '##' ? '子目录' : component}</>
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'name',
|
||||
label: '组件名称'
|
||||
},
|
||||
{
|
||||
field: 'meta.icon',
|
||||
label: '图标',
|
||||
slots: {
|
||||
default: (data) => {
|
||||
const icon = data.icon
|
||||
if (icon) {
|
||||
return (
|
||||
<>
|
||||
<Icon icon={icon} />
|
||||
</>
|
||||
)
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'path',
|
||||
label: '路径'
|
||||
},
|
||||
{
|
||||
field: 'meta.activeMenu',
|
||||
label: '高亮菜单'
|
||||
},
|
||||
{
|
||||
field: 'permissionList',
|
||||
label: '按钮权限',
|
||||
span: 24,
|
||||
slots: {
|
||||
default: (data: any) => (
|
||||
<>
|
||||
{data?.permissionList?.map((v) => {
|
||||
return (
|
||||
<ElTag class="mr-1" key={v.value}>
|
||||
{v.label}
|
||||
</ElTag>
|
||||
)
|
||||
})}
|
||||
</>
|
||||
)
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'menuState',
|
||||
label: '菜单状态',
|
||||
slots: {
|
||||
default: (data) => {
|
||||
return renderTag(data.menuState)
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'meta.hidden',
|
||||
label: '是否隐藏',
|
||||
slots: {
|
||||
default: (data) => {
|
||||
return renderTag(data.enableHidden)
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'meta.alwaysShow',
|
||||
label: '是否一直显示',
|
||||
slots: {
|
||||
default: (data) => {
|
||||
return renderTag(data.enableDisplay)
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'meta.noCache',
|
||||
label: '是否清除缓存',
|
||||
slots: {
|
||||
default: (data) => {
|
||||
return renderTag(data.enableCleanCache)
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'meta.breadcrumb',
|
||||
label: '是否显示面包屑',
|
||||
slots: {
|
||||
default: (data) => {
|
||||
return renderTag(data.enableShowCrumb)
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'meta.affix',
|
||||
label: '是否固定标签页',
|
||||
slots: {
|
||||
default: (data) => {
|
||||
return renderTag(data.enablePinnedTab)
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'meta.noTagsView',
|
||||
label: '是否隐藏标签页',
|
||||
slots: {
|
||||
default: (data) => {
|
||||
return renderTag(data.enableHiddenTab)
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'meta.canTo',
|
||||
label: '是否可跳转',
|
||||
slots: {
|
||||
default: (data) => {
|
||||
return renderTag(data.enableSkip)
|
||||
}
|
||||
}
|
||||
}
|
||||
])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Descriptions :schema="detailSchema" :data="currentRow || {}" />
|
||||
</template>
|
||||
409
promo-ui2/src/views/Authorization/Menu/components/Write.vue
Normal file
409
promo-ui2/src/views/Authorization/Menu/components/Write.vue
Normal file
@@ -0,0 +1,409 @@
|
||||
<script setup lang="tsx">
|
||||
import { Form, FormSchema } from '@/components/Form'
|
||||
import { useForm } from '@/hooks/web/useForm'
|
||||
import { PropType, reactive, watch, ref, unref } from 'vue'
|
||||
import { useValidator } from '@/hooks/web/useValidator'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { getMenuListApi } from '@/api/menu'
|
||||
import { ElButton, ElInput, ElPopconfirm, ElTable, ElTableColumn, ElTag } from 'element-plus'
|
||||
import AddButtonPermission from './AddButtonPermission.vue'
|
||||
import { BaseButton } from '@/components/Button'
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const { required } = useValidator()
|
||||
|
||||
const props = defineProps({
|
||||
currentRow: {
|
||||
type: Object as PropType<any>,
|
||||
default: () => null
|
||||
}
|
||||
})
|
||||
|
||||
const handleClose = async (tag: any) => {
|
||||
const formData = await getFormData()
|
||||
// 删除对应的权限
|
||||
setValues({
|
||||
permissionList: formData?.permissionList?.filter((v: any) => v.value !== tag.value)
|
||||
})
|
||||
}
|
||||
|
||||
const handleEdit = async (row: any) => {
|
||||
// 深拷贝当前行数据到编辑行
|
||||
permissionEditingRow.value = { ...row }
|
||||
}
|
||||
|
||||
const handleSave = async () => {
|
||||
const formData = await getFormData()
|
||||
const index = formData?.permissionList?.findIndex((x) => x.id === permissionEditingRow.value.id)
|
||||
if (index !== -1) {
|
||||
formData.permissionList[index] = { ...permissionEditingRow.value }
|
||||
permissionEditingRow.value = null // 重置编辑状态
|
||||
}
|
||||
}
|
||||
|
||||
const showDrawer = ref(false)
|
||||
// 存储正在编辑的行的数据
|
||||
const permissionEditingRow = ref<any>(null)
|
||||
|
||||
const formSchema = reactive<FormSchema[]>([
|
||||
{
|
||||
field: 'type',
|
||||
label: '菜单类型',
|
||||
component: 'RadioButton',
|
||||
value: 0,
|
||||
colProps: {
|
||||
span: 24
|
||||
},
|
||||
componentProps: {
|
||||
options: [
|
||||
{
|
||||
label: '目录',
|
||||
value: 0
|
||||
},
|
||||
{
|
||||
label: '菜单',
|
||||
value: 1
|
||||
}
|
||||
],
|
||||
on: {
|
||||
change: async (val: number) => {
|
||||
const formData = await getFormData()
|
||||
if (val === 1) {
|
||||
setSchema([
|
||||
{
|
||||
field: 'component',
|
||||
path: 'componentProps.disabled',
|
||||
value: false
|
||||
}
|
||||
])
|
||||
setValues({
|
||||
component: unref(cacheComponent)
|
||||
})
|
||||
} else {
|
||||
setSchema([
|
||||
{
|
||||
field: 'component',
|
||||
path: 'componentProps.disabled',
|
||||
value: true
|
||||
}
|
||||
])
|
||||
|
||||
if (formData.parentId === void 0) {
|
||||
setValues({
|
||||
component: '#'
|
||||
})
|
||||
} else {
|
||||
setValues({
|
||||
component: '##'
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'parentId',
|
||||
label: '父级菜单',
|
||||
component: 'TreeSelect',
|
||||
componentProps: {
|
||||
nodeKey: 'id',
|
||||
props: {
|
||||
label: 'title',
|
||||
value: 'id',
|
||||
children: 'children'
|
||||
},
|
||||
highlightCurrent: true,
|
||||
expandOnClickNode: false,
|
||||
checkStrictly: true,
|
||||
checkOnClickNode: true,
|
||||
clearable: true,
|
||||
on: {
|
||||
change: async (val: number) => {
|
||||
const formData = await getFormData()
|
||||
if (val && formData.type === 0) {
|
||||
setValues({
|
||||
component: '##'
|
||||
})
|
||||
} else if (!val && formData.type === 0) {
|
||||
setValues({
|
||||
component: '#'
|
||||
})
|
||||
} else if (formData.type === 1) {
|
||||
setValues({
|
||||
component: unref(cacheComponent) ?? ''
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
optionApi: async () => {
|
||||
const res = await getMenuListApi()
|
||||
return res.data.list || []
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'meta.title',
|
||||
label: t('menu.menuName'),
|
||||
component: 'Input'
|
||||
},
|
||||
{
|
||||
field: 'component',
|
||||
label: '组件',
|
||||
component: 'Input',
|
||||
value: '#',
|
||||
componentProps: {
|
||||
disabled: true,
|
||||
placeholder: '#为顶级目录,##为子目录',
|
||||
on: {
|
||||
change: (val: string) => {
|
||||
cacheComponent.value = val
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'name',
|
||||
label: t('menu.name'),
|
||||
component: 'Input'
|
||||
},
|
||||
{
|
||||
field: 'meta.icon',
|
||||
label: t('menu.icon'),
|
||||
component: 'Input'
|
||||
},
|
||||
{
|
||||
field: 'path',
|
||||
label: t('menu.path'),
|
||||
component: 'Input'
|
||||
},
|
||||
{
|
||||
field: 'meta.activeMenu',
|
||||
label: t('menu.activeMenu'),
|
||||
component: 'Input'
|
||||
},
|
||||
{
|
||||
field: 'status',
|
||||
label: t('menu.status'),
|
||||
component: 'Select',
|
||||
componentProps: {
|
||||
options: [
|
||||
{
|
||||
label: t('userDemo.disable'),
|
||||
value: 0
|
||||
},
|
||||
{
|
||||
label: t('userDemo.enable'),
|
||||
value: 1
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'permissionList',
|
||||
label: t('menu.permission'),
|
||||
component: 'CheckboxGroup',
|
||||
colProps: {
|
||||
span: 24
|
||||
},
|
||||
formItemProps: {
|
||||
slots: {
|
||||
default: (data: any) => (
|
||||
<>
|
||||
<BaseButton
|
||||
class="m-t-5px"
|
||||
type="primary"
|
||||
size="small"
|
||||
onClick={() => (showDrawer.value = true)}
|
||||
>
|
||||
添加权限
|
||||
</BaseButton>
|
||||
<ElTable data={data?.permissionList}>
|
||||
<ElTableColumn type="index" prop="id" />
|
||||
<ElTableColumn
|
||||
prop="value"
|
||||
label="Value"
|
||||
v-slots={{
|
||||
default: ({ row }: any) =>
|
||||
permissionEditingRow.value && permissionEditingRow.value.id === row.id ? (
|
||||
<ElInput v-model={permissionEditingRow.value.value} size="small" />
|
||||
) : (
|
||||
<span>{row.value}</span>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
<ElTableColumn
|
||||
prop="label"
|
||||
label="Label"
|
||||
v-slots={{
|
||||
default: ({ row }: any) =>
|
||||
permissionEditingRow.value && permissionEditingRow.value.id === row.id ? (
|
||||
<ElInput v-model={permissionEditingRow.value.label} size="small" />
|
||||
) : (
|
||||
<ElTag class="mr-1" key={row.value}>
|
||||
{row.label}
|
||||
</ElTag>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
<ElTableColumn
|
||||
label="Operations"
|
||||
width="180"
|
||||
v-slots={{
|
||||
default: ({ row }: any) =>
|
||||
permissionEditingRow.value && permissionEditingRow.value.id === row.id ? (
|
||||
<ElButton size="small" type="primary" onClick={handleSave}>
|
||||
确定
|
||||
</ElButton>
|
||||
) : (
|
||||
<>
|
||||
<ElButton size="small" type="primary" onClick={() => handleEdit(row)}>
|
||||
编辑
|
||||
</ElButton>
|
||||
<ElPopconfirm
|
||||
title="Are you sure to delete this?"
|
||||
onConfirm={() => handleClose(row)}
|
||||
>
|
||||
{{
|
||||
reference: () => (
|
||||
<ElButton size="small" type="danger">
|
||||
删除
|
||||
</ElButton>
|
||||
)
|
||||
}}
|
||||
</ElPopconfirm>
|
||||
</>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</ElTable>
|
||||
</>
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'meta.hidden',
|
||||
label: t('menu.hidden'),
|
||||
component: 'Switch'
|
||||
},
|
||||
{
|
||||
field: 'meta.alwaysShow',
|
||||
label: t('menu.alwaysShow'),
|
||||
component: 'Switch'
|
||||
},
|
||||
{
|
||||
field: 'meta.noCache',
|
||||
label: t('menu.noCache'),
|
||||
component: 'Switch'
|
||||
},
|
||||
{
|
||||
field: 'meta.breadcrumb',
|
||||
label: t('menu.breadcrumb'),
|
||||
component: 'Switch'
|
||||
},
|
||||
{
|
||||
field: 'meta.affix',
|
||||
label: t('menu.affix'),
|
||||
component: 'Switch'
|
||||
},
|
||||
{
|
||||
field: 'meta.noTagsView',
|
||||
label: t('menu.noTagsView'),
|
||||
component: 'Switch'
|
||||
},
|
||||
{
|
||||
field: 'meta.canTo',
|
||||
label: t('menu.canTo'),
|
||||
component: 'Switch'
|
||||
}
|
||||
])
|
||||
|
||||
const rules = reactive({
|
||||
component: [required()],
|
||||
path: [required()],
|
||||
'meta.title': [required()]
|
||||
})
|
||||
|
||||
const { formRegister, formMethods } = useForm()
|
||||
const { setValues, getFormData, getElFormExpose, setSchema } = formMethods
|
||||
|
||||
const submit = async () => {
|
||||
const elForm = await getElFormExpose()
|
||||
const valid = await elForm?.validate().catch((err) => {
|
||||
console.log(err)
|
||||
})
|
||||
if (valid) {
|
||||
const formData = await getFormData()
|
||||
return formData
|
||||
}
|
||||
}
|
||||
|
||||
const cacheComponent = ref('')
|
||||
|
||||
watch(
|
||||
() => props.currentRow,
|
||||
(value) => {
|
||||
if (!value) return
|
||||
const currentRow = cloneDeep(value)
|
||||
cacheComponent.value = currentRow.type === 1 ? currentRow.component : ''
|
||||
if (currentRow.parentId === 0) {
|
||||
setSchema([
|
||||
{
|
||||
field: 'component',
|
||||
path: 'componentProps.disabled',
|
||||
value: true
|
||||
}
|
||||
])
|
||||
} else {
|
||||
setSchema([
|
||||
{
|
||||
field: 'component',
|
||||
path: 'componentProps.disabled',
|
||||
value: false
|
||||
}
|
||||
])
|
||||
}
|
||||
if (currentRow.type === 1) {
|
||||
setSchema([
|
||||
{
|
||||
field: 'component',
|
||||
path: 'componentProps.disabled',
|
||||
value: false
|
||||
}
|
||||
])
|
||||
} else {
|
||||
setSchema([
|
||||
{
|
||||
field: 'component',
|
||||
path: 'componentProps.disabled',
|
||||
value: true
|
||||
}
|
||||
])
|
||||
}
|
||||
setValues(currentRow)
|
||||
},
|
||||
{
|
||||
deep: true,
|
||||
immediate: true
|
||||
}
|
||||
)
|
||||
|
||||
defineExpose({
|
||||
submit
|
||||
})
|
||||
|
||||
const confirm = async (data: any) => {
|
||||
const formData = await getFormData()
|
||||
setValues({
|
||||
permissionList: [...(formData?.permissionList || []), data]
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Form :rules="rules" @register="formRegister" :schema="formSchema" />
|
||||
<AddButtonPermission v-model="showDrawer" @confirm="confirm" />
|
||||
</template>
|
||||
173
promo-ui2/src/views/Authorization/Role/Role.vue
Normal file
173
promo-ui2/src/views/Authorization/Role/Role.vue
Normal file
@@ -0,0 +1,173 @@
|
||||
<script setup lang="tsx">
|
||||
import { reactive, ref, unref } from 'vue'
|
||||
import { getRoleListApi } from '@/api/role'
|
||||
import { useTable } from '@/hooks/web/useTable'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { Table, TableColumn } from '@/components/Table'
|
||||
import { ElTag } from 'element-plus'
|
||||
import { Search } from '@/components/Search'
|
||||
import { FormSchema } from '@/components/Form'
|
||||
import { ContentWrap } from '@/components/ContentWrap'
|
||||
import Write from './components/Write.vue'
|
||||
import Detail from './components/Detail.vue'
|
||||
import { Dialog } from '@/components/Dialog'
|
||||
import { BaseButton } from '@/components/Button'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const { tableRegister, tableState, tableMethods } = useTable({
|
||||
fetchDataApi: async () => {
|
||||
const res = await getRoleListApi()
|
||||
return {
|
||||
list: res.data.list || [],
|
||||
total: res.data.total
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const { dataList, loading, total } = tableState
|
||||
const { getList } = tableMethods
|
||||
|
||||
const tableColumns = reactive<TableColumn[]>([
|
||||
{
|
||||
field: 'index',
|
||||
label: t('userDemo.index'),
|
||||
type: 'index'
|
||||
},
|
||||
{
|
||||
field: 'roleName',
|
||||
label: t('role.roleName')
|
||||
},
|
||||
{
|
||||
field: 'status',
|
||||
label: t('menu.status'),
|
||||
slots: {
|
||||
default: (data: any) => {
|
||||
return (
|
||||
<>
|
||||
<ElTag type={data.row.status === 0 ? 'danger' : 'success'}>
|
||||
{data.row.status === 1 ? t('userDemo.enable') : t('userDemo.disable')}
|
||||
</ElTag>
|
||||
</>
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'createTime',
|
||||
label: t('tableDemo.displayTime')
|
||||
},
|
||||
{
|
||||
field: 'remark',
|
||||
label: t('userDemo.remark')
|
||||
},
|
||||
{
|
||||
field: 'action',
|
||||
label: t('userDemo.action'),
|
||||
width: 240,
|
||||
slots: {
|
||||
default: (data: any) => {
|
||||
const row = data.row
|
||||
return (
|
||||
<>
|
||||
<BaseButton type="primary" onClick={() => action(row, 'edit')}>
|
||||
{t('exampleDemo.edit')}
|
||||
</BaseButton>
|
||||
<BaseButton type="success" onClick={() => action(row, 'detail')}>
|
||||
{t('exampleDemo.detail')}
|
||||
</BaseButton>
|
||||
<BaseButton type="danger">{t('exampleDemo.del')}</BaseButton>
|
||||
</>
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
])
|
||||
|
||||
const searchSchema = reactive<FormSchema[]>([
|
||||
{
|
||||
field: 'roleName',
|
||||
label: t('role.roleName'),
|
||||
component: 'Input'
|
||||
}
|
||||
])
|
||||
|
||||
const searchParams = ref({})
|
||||
const setSearchParams = (data: any) => {
|
||||
searchParams.value = data
|
||||
getList()
|
||||
}
|
||||
|
||||
const dialogVisible = ref(false)
|
||||
const dialogTitle = ref('')
|
||||
|
||||
const currentRow = ref()
|
||||
const actionType = ref('')
|
||||
|
||||
const writeRef = ref<ComponentRef<typeof Write>>()
|
||||
|
||||
const saveLoading = ref(false)
|
||||
|
||||
const action = (row: any, type: string) => {
|
||||
dialogTitle.value = t(type === 'edit' ? 'exampleDemo.edit' : 'exampleDemo.detail')
|
||||
actionType.value = type
|
||||
currentRow.value = row
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
const AddAction = () => {
|
||||
dialogTitle.value = t('exampleDemo.add')
|
||||
currentRow.value = undefined
|
||||
dialogVisible.value = true
|
||||
actionType.value = ''
|
||||
}
|
||||
|
||||
const save = async () => {
|
||||
const write = unref(writeRef)
|
||||
const formData = await write?.submit()
|
||||
if (formData) {
|
||||
saveLoading.value = true
|
||||
setTimeout(() => {
|
||||
saveLoading.value = false
|
||||
dialogVisible.value = false
|
||||
}, 1000)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<Search :schema="searchSchema" @reset="setSearchParams" @search="setSearchParams" />
|
||||
<div class="mb-10px">
|
||||
<BaseButton type="primary" @click="AddAction">{{ t('exampleDemo.add') }}</BaseButton>
|
||||
</div>
|
||||
<Table
|
||||
:columns="tableColumns"
|
||||
default-expand-all
|
||||
node-key="id"
|
||||
:data="dataList"
|
||||
:loading="loading"
|
||||
:pagination="{
|
||||
total
|
||||
}"
|
||||
@register="tableRegister"
|
||||
/>
|
||||
</ContentWrap>
|
||||
|
||||
<Dialog v-model="dialogVisible" :title="dialogTitle">
|
||||
<Write v-if="actionType !== 'detail'" ref="writeRef" :current-row="currentRow" />
|
||||
<Detail v-else :current-row="currentRow" />
|
||||
|
||||
<template #footer>
|
||||
<BaseButton
|
||||
v-if="actionType !== 'detail'"
|
||||
type="primary"
|
||||
:loading="saveLoading"
|
||||
@click="save"
|
||||
>
|
||||
{{ t('exampleDemo.save') }}
|
||||
</BaseButton>
|
||||
<BaseButton @click="dialogVisible = false">{{ t('dialogDemo.close') }}</BaseButton>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
106
promo-ui2/src/views/Authorization/Role/components/Detail.vue
Normal file
106
promo-ui2/src/views/Authorization/Role/components/Detail.vue
Normal file
@@ -0,0 +1,106 @@
|
||||
<script setup lang="tsx">
|
||||
import { PropType, ref, unref, nextTick } from 'vue'
|
||||
import { Descriptions, DescriptionsSchema } from '@/components/Descriptions'
|
||||
import { ElTag, ElTree } from 'element-plus'
|
||||
import { findIndex } from '@/utils'
|
||||
import { getMenuListApi } from '@/api/menu'
|
||||
|
||||
defineProps({
|
||||
currentRow: {
|
||||
type: Object as PropType<any>,
|
||||
default: () => undefined
|
||||
}
|
||||
})
|
||||
|
||||
const filterPermissionName = (value: string) => {
|
||||
const index = findIndex(unref(currentTreeData)?.permissionList || [], (item) => {
|
||||
return item.value === value
|
||||
})
|
||||
return (unref(currentTreeData)?.permissionList || [])[index].label ?? ''
|
||||
}
|
||||
|
||||
const renderTag = (enable?: boolean) => {
|
||||
return <ElTag type={!enable ? 'danger' : 'success'}>{enable ? '启用' : '禁用'}</ElTag>
|
||||
}
|
||||
|
||||
const treeRef = ref<typeof ElTree>()
|
||||
|
||||
const currentTreeData = ref()
|
||||
const nodeClick = (treeData: any) => {
|
||||
currentTreeData.value = treeData
|
||||
}
|
||||
|
||||
const treeData = ref<any[]>([])
|
||||
const getMenuList = async () => {
|
||||
const res = await getMenuListApi()
|
||||
if (res) {
|
||||
treeData.value = res.data.list
|
||||
await nextTick()
|
||||
}
|
||||
}
|
||||
getMenuList()
|
||||
|
||||
const detailSchema = ref<DescriptionsSchema[]>([
|
||||
{
|
||||
field: 'roleName',
|
||||
label: '角色名称'
|
||||
},
|
||||
{
|
||||
field: 'status',
|
||||
label: '状态',
|
||||
slots: {
|
||||
default: (data: any) => {
|
||||
return renderTag(data.status)
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'remark',
|
||||
label: '备注',
|
||||
span: 24
|
||||
},
|
||||
{
|
||||
field: 'permissionList',
|
||||
label: '菜单分配',
|
||||
span: 24,
|
||||
slots: {
|
||||
default: () => {
|
||||
return (
|
||||
<>
|
||||
<div class="flex w-full">
|
||||
<div class="flex-1">
|
||||
<ElTree
|
||||
ref={treeRef}
|
||||
node-key="id"
|
||||
props={{ children: 'children', label: 'title' }}
|
||||
highlight-current
|
||||
expand-on-click-node={false}
|
||||
data={treeData.value}
|
||||
onNode-click={nodeClick}
|
||||
>
|
||||
{{
|
||||
default: (data) => {
|
||||
return <span>{data?.data?.title}</span>
|
||||
}
|
||||
}}
|
||||
</ElTree>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
{unref(currentTreeData)
|
||||
? unref(currentTreeData)?.meta?.permission?.map((v: string) => {
|
||||
return <ElTag class="ml-2 mt-2">{filterPermissionName(v)}</ElTag>
|
||||
})
|
||||
: null}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Descriptions :schema="detailSchema" :data="currentRow || {}" />
|
||||
</template>
|
||||
181
promo-ui2/src/views/Authorization/Role/components/Write.vue
Normal file
181
promo-ui2/src/views/Authorization/Role/components/Write.vue
Normal file
@@ -0,0 +1,181 @@
|
||||
<script setup lang="tsx">
|
||||
import { Form, FormSchema } from '@/components/Form'
|
||||
import { useForm } from '@/hooks/web/useForm'
|
||||
import { PropType, reactive, watch, ref, unref, nextTick } from 'vue'
|
||||
import { useValidator } from '@/hooks/web/useValidator'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { ElTree, ElCheckboxGroup, ElCheckbox } from 'element-plus'
|
||||
import { getMenuListApi } from '@/api/menu'
|
||||
import { filter, eachTree } from '@/utils/tree'
|
||||
import { findIndex } from '@/utils'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const { required } = useValidator()
|
||||
|
||||
const props = defineProps({
|
||||
currentRow: {
|
||||
type: Object as PropType<any>,
|
||||
default: () => null
|
||||
}
|
||||
})
|
||||
|
||||
const treeRef = ref<typeof ElTree>()
|
||||
|
||||
const formSchema = ref<FormSchema[]>([
|
||||
{
|
||||
field: 'roleName',
|
||||
label: t('role.roleName'),
|
||||
component: 'Input'
|
||||
},
|
||||
{
|
||||
field: 'status',
|
||||
label: t('menu.status'),
|
||||
component: 'Select',
|
||||
componentProps: {
|
||||
options: [
|
||||
{
|
||||
label: t('userDemo.disable'),
|
||||
value: 0
|
||||
},
|
||||
{
|
||||
label: t('userDemo.enable'),
|
||||
value: 1
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'menu',
|
||||
label: t('role.menu'),
|
||||
colProps: {
|
||||
span: 24
|
||||
},
|
||||
formItemProps: {
|
||||
slots: {
|
||||
default: () => {
|
||||
return (
|
||||
<>
|
||||
<div class="flex w-full">
|
||||
<div class="flex-1">
|
||||
<ElTree
|
||||
ref={treeRef}
|
||||
show-checkbox
|
||||
node-key="id"
|
||||
highlight-current
|
||||
check-strictly
|
||||
expand-on-click-node={false}
|
||||
data={treeData.value}
|
||||
onNode-click={nodeClick}
|
||||
>
|
||||
{{
|
||||
default: (data) => {
|
||||
return <span>{data.data.meta.title}</span>
|
||||
}
|
||||
}}
|
||||
</ElTree>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
{unref(currentTreeData) && unref(currentTreeData)?.permissionList ? (
|
||||
<ElCheckboxGroup v-model={unref(currentTreeData).meta.permission}>
|
||||
{unref(currentTreeData)?.permissionList.map((v: any) => {
|
||||
return <ElCheckbox label={v.value}>{v.label}</ElCheckbox>
|
||||
})}
|
||||
</ElCheckboxGroup>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
])
|
||||
|
||||
const currentTreeData = ref()
|
||||
const nodeClick = (treeData: any) => {
|
||||
currentTreeData.value = treeData
|
||||
}
|
||||
|
||||
const rules = reactive({
|
||||
roleName: [required()],
|
||||
role: [required()],
|
||||
status: [required()]
|
||||
})
|
||||
|
||||
const { formRegister, formMethods } = useForm()
|
||||
const { setValues, getFormData, getElFormExpose } = formMethods
|
||||
|
||||
const treeData = ref([])
|
||||
const getMenuList = async () => {
|
||||
const res = await getMenuListApi()
|
||||
if (res) {
|
||||
treeData.value = res.data.list
|
||||
if (!props.currentRow) return
|
||||
await nextTick()
|
||||
const checked: any[] = []
|
||||
eachTree(props.currentRow.menu, (v) => {
|
||||
checked.push({
|
||||
id: v.id,
|
||||
permission: v.meta?.permission || []
|
||||
})
|
||||
})
|
||||
eachTree(treeData.value, (v) => {
|
||||
const index = findIndex(checked, (item) => {
|
||||
return item.id === v.id
|
||||
})
|
||||
if (index > -1) {
|
||||
const meta = { ...(v.meta || {}) }
|
||||
meta.permission = checked[index].permission
|
||||
v.meta = meta
|
||||
}
|
||||
})
|
||||
for (const item of checked) {
|
||||
unref(treeRef)?.setChecked(item.id, true, false)
|
||||
}
|
||||
// unref(treeRef)?.setCheckedKeys(
|
||||
// checked.map((v) => v.id),
|
||||
// false
|
||||
// )
|
||||
}
|
||||
}
|
||||
getMenuList()
|
||||
|
||||
const submit = async () => {
|
||||
const elForm = await getElFormExpose()
|
||||
const valid = await elForm?.validate().catch((err) => {
|
||||
console.log(err)
|
||||
})
|
||||
if (valid) {
|
||||
const formData = await getFormData()
|
||||
const checkedKeys = unref(treeRef)?.getCheckedKeys() || []
|
||||
const data = filter(unref(treeData), (item: any) => {
|
||||
return checkedKeys.includes(item.id)
|
||||
})
|
||||
formData.menu = data || []
|
||||
console.log(formData)
|
||||
return formData
|
||||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.currentRow,
|
||||
(currentRow) => {
|
||||
if (!currentRow) return
|
||||
setValues(currentRow)
|
||||
},
|
||||
{
|
||||
deep: true,
|
||||
immediate: true
|
||||
}
|
||||
)
|
||||
|
||||
defineExpose({
|
||||
submit
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Form :rules="rules" @register="formRegister" :schema="formSchema" />
|
||||
</template>
|
||||
384
promo-ui2/src/views/Authorization/User/User.vue
Normal file
384
promo-ui2/src/views/Authorization/User/User.vue
Normal file
@@ -0,0 +1,384 @@
|
||||
<script setup lang="tsx">
|
||||
import { ContentWrap } from '@/components/ContentWrap'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { Table } from '@/components/Table'
|
||||
import { ref, unref, nextTick, watch, reactive } from 'vue'
|
||||
import { ElTree, ElInput, ElDivider } from 'element-plus'
|
||||
import { getDepartmentApi, getUserByIdApi, saveUserApi, deleteUserByIdApi } from '@/api/department'
|
||||
import type { DepartmentItem, DepartmentUserItem } from '@/api/department/types'
|
||||
import { useTable } from '@/hooks/web/useTable'
|
||||
import { Search } from '@/components/Search'
|
||||
import Write from './components/Write.vue'
|
||||
import Detail from './components/Detail.vue'
|
||||
import { Dialog } from '@/components/Dialog'
|
||||
import { getRoleListApi } from '@/api/role'
|
||||
import { CrudSchema, useCrudSchemas } from '@/hooks/web/useCrudSchemas'
|
||||
import { BaseButton } from '@/components/Button'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const { tableRegister, tableState, tableMethods } = useTable({
|
||||
fetchDataApi: async () => {
|
||||
const { pageSize, currentPage } = tableState
|
||||
const res = await getUserByIdApi({
|
||||
id: unref(currentNodeKey),
|
||||
pageIndex: unref(currentPage),
|
||||
pageSize: unref(pageSize),
|
||||
...unref(searchParams)
|
||||
})
|
||||
return {
|
||||
list: res.data.list || [],
|
||||
total: res.data.total || 0
|
||||
}
|
||||
},
|
||||
fetchDelApi: async () => {
|
||||
const res = await deleteUserByIdApi(unref(ids))
|
||||
return !!res
|
||||
}
|
||||
})
|
||||
const { total, loading, dataList, pageSize, currentPage } = tableState
|
||||
const { getList, getElTableExpose, delList } = tableMethods
|
||||
|
||||
const crudSchemas = reactive<CrudSchema[]>([
|
||||
{
|
||||
field: 'selection',
|
||||
search: {
|
||||
hidden: true
|
||||
},
|
||||
form: {
|
||||
hidden: true
|
||||
},
|
||||
detail: {
|
||||
hidden: true
|
||||
},
|
||||
table: {
|
||||
type: 'selection'
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'index',
|
||||
label: t('userDemo.index'),
|
||||
form: {
|
||||
hidden: true
|
||||
},
|
||||
search: {
|
||||
hidden: true
|
||||
},
|
||||
detail: {
|
||||
hidden: true
|
||||
},
|
||||
table: {
|
||||
type: 'index'
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'username',
|
||||
label: t('userDemo.username')
|
||||
},
|
||||
{
|
||||
field: 'account',
|
||||
label: t('userDemo.account')
|
||||
},
|
||||
{
|
||||
field: 'department.id',
|
||||
label: t('userDemo.department'),
|
||||
detail: {
|
||||
hidden: true
|
||||
// slots: {
|
||||
// default: (data: DepartmentUserItem) => {
|
||||
// return <>{data.department.departmentName}</>
|
||||
// }
|
||||
// }
|
||||
},
|
||||
search: {
|
||||
hidden: true
|
||||
},
|
||||
form: {
|
||||
component: 'TreeSelect',
|
||||
componentProps: {
|
||||
nodeKey: 'id',
|
||||
props: {
|
||||
label: 'departmentName'
|
||||
}
|
||||
},
|
||||
optionApi: async () => {
|
||||
const res = await getDepartmentApi()
|
||||
return res.data.list
|
||||
}
|
||||
},
|
||||
table: {
|
||||
hidden: true
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'role',
|
||||
label: t('userDemo.role'),
|
||||
search: {
|
||||
hidden: true
|
||||
},
|
||||
form: {
|
||||
component: 'Select',
|
||||
value: [],
|
||||
componentProps: {
|
||||
multiple: true,
|
||||
collapseTags: true,
|
||||
maxCollapseTags: 1
|
||||
},
|
||||
optionApi: async () => {
|
||||
const res = await getRoleListApi()
|
||||
return res.data?.list?.map((v) => ({
|
||||
label: v.roleName,
|
||||
value: v.id
|
||||
}))
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'email',
|
||||
label: t('userDemo.email'),
|
||||
form: {
|
||||
component: 'Input'
|
||||
},
|
||||
search: {
|
||||
hidden: true
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'createTime',
|
||||
label: t('userDemo.createTime'),
|
||||
form: {
|
||||
component: 'Input'
|
||||
},
|
||||
search: {
|
||||
hidden: true
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'action',
|
||||
label: t('userDemo.action'),
|
||||
form: {
|
||||
hidden: true
|
||||
},
|
||||
detail: {
|
||||
hidden: true
|
||||
},
|
||||
search: {
|
||||
hidden: true
|
||||
},
|
||||
table: {
|
||||
width: 240,
|
||||
slots: {
|
||||
default: (data: any) => {
|
||||
const row = data.row as DepartmentUserItem
|
||||
return (
|
||||
<>
|
||||
<BaseButton type="primary" onClick={() => action(row, 'edit')}>
|
||||
{t('exampleDemo.edit')}
|
||||
</BaseButton>
|
||||
<BaseButton type="success" onClick={() => action(row, 'detail')}>
|
||||
{t('exampleDemo.detail')}
|
||||
</BaseButton>
|
||||
<BaseButton type="danger" onClick={() => delData(row)}>
|
||||
{t('exampleDemo.del')}
|
||||
</BaseButton>
|
||||
</>
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
])
|
||||
|
||||
const { allSchemas } = useCrudSchemas(crudSchemas)
|
||||
|
||||
const searchParams = ref({})
|
||||
const setSearchParams = (params: any) => {
|
||||
currentPage.value = 1
|
||||
searchParams.value = params
|
||||
getList()
|
||||
}
|
||||
|
||||
const treeEl = ref<typeof ElTree>()
|
||||
|
||||
const currentNodeKey = ref('')
|
||||
const departmentList = ref<DepartmentItem[]>([])
|
||||
const fetchDepartment = async () => {
|
||||
const res = await getDepartmentApi()
|
||||
departmentList.value = res.data.list
|
||||
currentNodeKey.value =
|
||||
(res.data.list[0] && res.data.list[0]?.children && res.data.list[0].children[0].id) || ''
|
||||
await nextTick()
|
||||
unref(treeEl)?.setCurrentKey(currentNodeKey.value)
|
||||
}
|
||||
fetchDepartment()
|
||||
|
||||
const currentDepartment = ref('')
|
||||
watch(
|
||||
() => currentDepartment.value,
|
||||
(val) => {
|
||||
unref(treeEl)!.filter(val)
|
||||
}
|
||||
)
|
||||
|
||||
const currentChange = (data: DepartmentItem) => {
|
||||
// if (data.children) return
|
||||
currentNodeKey.value = data.id
|
||||
currentPage.value = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
const filterNode = (value: string, data: DepartmentItem) => {
|
||||
if (!value) return true
|
||||
return data.departmentName.includes(value)
|
||||
}
|
||||
|
||||
const dialogVisible = ref(false)
|
||||
const dialogTitle = ref('')
|
||||
|
||||
const currentRow = ref<DepartmentUserItem>()
|
||||
const actionType = ref('')
|
||||
|
||||
const AddAction = () => {
|
||||
dialogTitle.value = t('exampleDemo.add')
|
||||
currentRow.value = undefined
|
||||
dialogVisible.value = true
|
||||
actionType.value = ''
|
||||
}
|
||||
|
||||
const delLoading = ref(false)
|
||||
const ids = ref<string[]>([])
|
||||
|
||||
const delData = async (row?: DepartmentUserItem) => {
|
||||
const elTableExpose = await getElTableExpose()
|
||||
ids.value = row
|
||||
? [row.id]
|
||||
: elTableExpose?.getSelectionRows().map((v: DepartmentUserItem) => v.id) || []
|
||||
delLoading.value = true
|
||||
|
||||
await delList(unref(ids).length).finally(() => {
|
||||
delLoading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
const action = (row: DepartmentUserItem, type: string) => {
|
||||
dialogTitle.value = t(type === 'edit' ? 'exampleDemo.edit' : 'exampleDemo.detail')
|
||||
actionType.value = type
|
||||
currentRow.value = { ...row, department: unref(treeEl)?.getCurrentNode() || {} }
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
const writeRef = ref<ComponentRef<typeof Write>>()
|
||||
|
||||
const saveLoading = ref(false)
|
||||
|
||||
const save = async () => {
|
||||
const write = unref(writeRef)
|
||||
const formData = await write?.submit()
|
||||
if (formData) {
|
||||
saveLoading.value = true
|
||||
try {
|
||||
const res = await saveUserApi(formData)
|
||||
if (res) {
|
||||
currentPage.value = 1
|
||||
getList()
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
} finally {
|
||||
saveLoading.value = false
|
||||
dialogVisible.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex w-100% h-100%">
|
||||
<ContentWrap class="w-250px">
|
||||
<div class="flex justify-center items-center">
|
||||
<div class="flex-1">{{ t('userDemo.departmentList') }}</div>
|
||||
<ElInput
|
||||
v-model="currentDepartment"
|
||||
class="flex-[2]"
|
||||
:placeholder="t('userDemo.searchDepartment')"
|
||||
clearable
|
||||
/>
|
||||
</div>
|
||||
<ElDivider />
|
||||
<ElTree
|
||||
ref="treeEl"
|
||||
:data="departmentList"
|
||||
default-expand-all
|
||||
:expand-on-click-node="false"
|
||||
node-key="id"
|
||||
:current-node-key="currentNodeKey"
|
||||
:props="{
|
||||
label: 'departmentName'
|
||||
}"
|
||||
:filter-node-method="filterNode"
|
||||
@current-change="currentChange"
|
||||
>
|
||||
<template #default="{ data }">
|
||||
<div
|
||||
:title="data.departmentName"
|
||||
class="whitespace-nowrap overflow-ellipsis overflow-hidden"
|
||||
>
|
||||
{{ data.departmentName }}
|
||||
</div>
|
||||
</template>
|
||||
</ElTree>
|
||||
</ContentWrap>
|
||||
<ContentWrap class="flex-[3] ml-20px">
|
||||
<Search
|
||||
:schema="allSchemas.searchSchema"
|
||||
@reset="setSearchParams"
|
||||
@search="setSearchParams"
|
||||
/>
|
||||
|
||||
<div class="mb-10px">
|
||||
<BaseButton type="primary" @click="AddAction">{{ t('exampleDemo.add') }}</BaseButton>
|
||||
<BaseButton :loading="delLoading" type="danger" @click="delData()">
|
||||
{{ t('exampleDemo.del') }}
|
||||
</BaseButton>
|
||||
</div>
|
||||
<Table
|
||||
v-model:current-page="currentPage"
|
||||
v-model:page-size="pageSize"
|
||||
:columns="allSchemas.tableColumns"
|
||||
:data="dataList"
|
||||
:loading="loading"
|
||||
@register="tableRegister"
|
||||
:pagination="{
|
||||
total
|
||||
}"
|
||||
/>
|
||||
</ContentWrap>
|
||||
|
||||
<Dialog v-model="dialogVisible" :title="dialogTitle">
|
||||
<Write
|
||||
v-if="actionType !== 'detail'"
|
||||
ref="writeRef"
|
||||
:form-schema="allSchemas.formSchema"
|
||||
:current-row="currentRow"
|
||||
/>
|
||||
|
||||
<Detail
|
||||
v-if="actionType === 'detail'"
|
||||
:detail-schema="allSchemas.detailSchema"
|
||||
:current-row="currentRow"
|
||||
/>
|
||||
|
||||
<template #footer>
|
||||
<BaseButton
|
||||
v-if="actionType !== 'detail'"
|
||||
type="primary"
|
||||
:loading="saveLoading"
|
||||
@click="save"
|
||||
>
|
||||
{{ t('exampleDemo.save') }}
|
||||
</BaseButton>
|
||||
<BaseButton @click="dialogVisible = false">{{ t('dialogDemo.close') }}</BaseButton>
|
||||
</template>
|
||||
</Dialog>
|
||||
</div>
|
||||
</template>
|
||||
20
promo-ui2/src/views/Authorization/User/components/Detail.vue
Normal file
20
promo-ui2/src/views/Authorization/User/components/Detail.vue
Normal file
@@ -0,0 +1,20 @@
|
||||
<script setup lang="ts">
|
||||
import { PropType } from 'vue'
|
||||
import { DepartmentUserItem } from '@/api/department/types'
|
||||
import { Descriptions, DescriptionsSchema } from '@/components/Descriptions'
|
||||
|
||||
defineProps({
|
||||
currentRow: {
|
||||
type: Object as PropType<DepartmentUserItem>,
|
||||
default: () => undefined
|
||||
},
|
||||
detailSchema: {
|
||||
type: Array as PropType<DescriptionsSchema[]>,
|
||||
default: () => []
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Descriptions :schema="detailSchema" :data="currentRow || {}" />
|
||||
</template>
|
||||
60
promo-ui2/src/views/Authorization/User/components/Write.vue
Normal file
60
promo-ui2/src/views/Authorization/User/components/Write.vue
Normal file
@@ -0,0 +1,60 @@
|
||||
<script setup lang="ts">
|
||||
import { Form, FormSchema } from '@/components/Form'
|
||||
import { useForm } from '@/hooks/web/useForm'
|
||||
import { PropType, reactive, watch } from 'vue'
|
||||
import { DepartmentUserItem } from '@/api/department/types'
|
||||
import { useValidator } from '@/hooks/web/useValidator'
|
||||
|
||||
const { required } = useValidator()
|
||||
|
||||
const props = defineProps({
|
||||
currentRow: {
|
||||
type: Object as PropType<DepartmentUserItem>,
|
||||
default: () => undefined
|
||||
},
|
||||
formSchema: {
|
||||
type: Array as PropType<FormSchema[]>,
|
||||
default: () => []
|
||||
}
|
||||
})
|
||||
|
||||
const rules = reactive({
|
||||
username: [required()],
|
||||
account: [required()],
|
||||
'department.id': [required()]
|
||||
})
|
||||
|
||||
const { formRegister, formMethods } = useForm()
|
||||
const { setValues, getFormData, getElFormExpose } = formMethods
|
||||
|
||||
const submit = async () => {
|
||||
const elForm = await getElFormExpose()
|
||||
const valid = await elForm?.validate().catch((err) => {
|
||||
console.log(err)
|
||||
})
|
||||
if (valid) {
|
||||
const formData = await getFormData()
|
||||
return formData
|
||||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.currentRow,
|
||||
(currentRow) => {
|
||||
if (!currentRow) return
|
||||
setValues(currentRow)
|
||||
},
|
||||
{
|
||||
deep: true,
|
||||
immediate: true
|
||||
}
|
||||
)
|
||||
|
||||
defineExpose({
|
||||
submit
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Form :rules="rules" @register="formRegister" :schema="formSchema" />
|
||||
</template>
|
||||
53
promo-ui2/src/views/Components/Avatars.vue
Normal file
53
promo-ui2/src/views/Components/Avatars.vue
Normal file
@@ -0,0 +1,53 @@
|
||||
<script setup lang="ts">
|
||||
import { ContentWrap } from '@/components/ContentWrap'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { Avatars, AvatarItem } from '@/components/Avatars'
|
||||
import { ref } from 'vue'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const data = ref<AvatarItem[]>([
|
||||
{
|
||||
name: 'Lily',
|
||||
url: 'https://avatars.githubusercontent.com/u/3459374?v=4'
|
||||
},
|
||||
{
|
||||
name: 'Amanda',
|
||||
url: 'https://avatars.githubusercontent.com/u/3459375?v=4'
|
||||
},
|
||||
{
|
||||
name: 'Daisy',
|
||||
url: 'https://avatars.githubusercontent.com/u/3459376?v=4'
|
||||
},
|
||||
{
|
||||
name: 'Olivia',
|
||||
url: 'https://avatars.githubusercontent.com/u/3459377?v=4'
|
||||
},
|
||||
{
|
||||
name: 'Tina',
|
||||
url: 'https://avatars.githubusercontent.com/u/3459378?v=4'
|
||||
},
|
||||
{
|
||||
name: 'Kitty',
|
||||
url: 'https://avatars.githubusercontent.com/u/3459323?v=4'
|
||||
},
|
||||
{
|
||||
name: 'Helen',
|
||||
url: 'https://avatars.githubusercontent.com/u/3459324?v=4'
|
||||
},
|
||||
{
|
||||
name: 'Sophia',
|
||||
url: 'https://avatars.githubusercontent.com/u/3459325?v=4'
|
||||
},
|
||||
{
|
||||
name: 'Wendy',
|
||||
url: 'https://avatars.githubusercontent.com/u/3459326?v=4'
|
||||
}
|
||||
])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ContentWrap :title="t('router.avatars')" :message="t('avatarsDemo.title')">
|
||||
<Avatars :data="data" />
|
||||
</ContentWrap>
|
||||
</template>
|
||||
100
promo-ui2/src/views/Components/CountTo.vue
Normal file
100
promo-ui2/src/views/Components/CountTo.vue
Normal file
@@ -0,0 +1,100 @@
|
||||
<script setup lang="ts">
|
||||
import { ContentWrap } from '@/components/ContentWrap'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { CountTo } from '@/components/CountTo'
|
||||
import { ElRow, ElCol, ElInputNumber, ElInput } from 'element-plus'
|
||||
import { ref, unref } from 'vue'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const countRef = ref<ComponentRef<typeof CountTo>>()
|
||||
|
||||
const startVal = ref(0)
|
||||
|
||||
const endVal = ref(1314512)
|
||||
|
||||
const duration = ref(3000)
|
||||
|
||||
const decimals = ref(0)
|
||||
|
||||
const separator = ref(',')
|
||||
|
||||
const prefix = ref('¥ ')
|
||||
|
||||
const suffix = ref(' rmb')
|
||||
|
||||
const autoplay = ref(false)
|
||||
|
||||
const start = () => {
|
||||
unref(countRef)?.start()
|
||||
}
|
||||
|
||||
const pauseResume = () => {
|
||||
unref(countRef)?.pauseResume()
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ContentWrap :title="t('countToDemo.countTo')" :message="t('countToDemo.countToDes')">
|
||||
<div class="text-center mb-40px">
|
||||
<CountTo
|
||||
ref="countRef"
|
||||
:start-val="startVal"
|
||||
:end-val="endVal"
|
||||
:duration="duration"
|
||||
:decimals="decimals"
|
||||
:separator="separator"
|
||||
:prefix="prefix"
|
||||
:suffix="suffix"
|
||||
:autoplay="autoplay"
|
||||
class="text-30px font-bold text-[var(--el-color-primary)]"
|
||||
/>
|
||||
</div>
|
||||
<ElRow :gutter="20" justify="space-between">
|
||||
<ElCol :xl="8" :lg="8" :md="12" :sm="24" :xs="24">
|
||||
<div class="flex mb-20px items-center">
|
||||
<span class="min-w-90px text-right">{{ t('countToDemo.startVal') }}:</span>
|
||||
<ElInputNumber v-model="startVal" :min="0" />
|
||||
</div>
|
||||
</ElCol>
|
||||
<ElCol :xl="8" :lg="8" :md="12" :sm="24" :xs="24">
|
||||
<div class="flex mb-20px items-center">
|
||||
<span class="min-w-90px text-right">{{ t('countToDemo.endVal') }}:</span>
|
||||
<ElInputNumber v-model="endVal" :min="1" />
|
||||
</div>
|
||||
</ElCol>
|
||||
<ElCol :xl="8" :lg="8" :md="12" :sm="24" :xs="24">
|
||||
<div class="flex mb-20px items-center">
|
||||
<span class="min-w-90px text-right">{{ t('countToDemo.duration') }}:</span>
|
||||
<ElInputNumber v-model="duration" :min="1000" />
|
||||
</div>
|
||||
</ElCol>
|
||||
<ElCol :xl="8" :lg="8" :md="12" :sm="24" :xs="24">
|
||||
<div class="flex mb-20px items-center">
|
||||
<span class="min-w-90px text-right">{{ t('countToDemo.separator') }}:</span>
|
||||
<ElInput v-model="separator" />
|
||||
</div>
|
||||
</ElCol>
|
||||
<ElCol :xl="8" :lg="8" :md="12" :sm="24" :xs="24">
|
||||
<div class="flex mb-20px items-center">
|
||||
<span class="min-w-90px text-right">{{ t('countToDemo.prefix') }}:</span>
|
||||
<ElInput v-model="prefix" />
|
||||
</div>
|
||||
</ElCol>
|
||||
<ElCol :xl="8" :lg="8" :md="12" :sm="24" :xs="24">
|
||||
<div class="flex mb-20px items-center">
|
||||
<span class="min-w-90px text-right">{{ t('countToDemo.suffix') }}:</span>
|
||||
<ElInput v-model="suffix" />
|
||||
</div>
|
||||
</ElCol>
|
||||
<ElCol :span="24">
|
||||
<div class="text-center">
|
||||
<BaseButton type="primary" @click="start">{{ t('countToDemo.start') }}</BaseButton>
|
||||
<BaseButton @click="pauseResume">
|
||||
{{ t('countToDemo.pause') }}/{{ t('countToDemo.resume') }}
|
||||
</BaseButton>
|
||||
</div>
|
||||
</ElCol>
|
||||
</ElRow>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
192
promo-ui2/src/views/Components/Descriptions.vue
Normal file
192
promo-ui2/src/views/Components/Descriptions.vue
Normal file
@@ -0,0 +1,192 @@
|
||||
<script setup lang="tsx">
|
||||
import { Descriptions } from '@/components/Descriptions'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { reactive } from 'vue'
|
||||
import { Form } from '@/components/Form'
|
||||
import { ElFormItem, ElInput } from 'element-plus'
|
||||
import { useValidator } from '@/hooks/web/useValidator'
|
||||
import { useForm } from '@/hooks/web/useForm'
|
||||
import { DescriptionsSchema } from '@/components/Descriptions'
|
||||
|
||||
const { required } = useValidator()
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const data = reactive({
|
||||
username: 'chenkl',
|
||||
nickName: '梦似花落。',
|
||||
age: 26,
|
||||
phone: '13655971xxxx',
|
||||
email: '502431556@qq.com',
|
||||
addr: '这是一个很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长的地址',
|
||||
sex: '男',
|
||||
certy: '3505831994xxxxxxxx'
|
||||
})
|
||||
|
||||
const schema = reactive<DescriptionsSchema[]>([
|
||||
{
|
||||
field: 'username',
|
||||
label: t('descriptionsDemo.username')
|
||||
},
|
||||
{
|
||||
field: 'nickName',
|
||||
label: t('descriptionsDemo.nickName')
|
||||
},
|
||||
{
|
||||
field: 'phone',
|
||||
label: t('descriptionsDemo.phone')
|
||||
},
|
||||
{
|
||||
field: 'email',
|
||||
label: t('descriptionsDemo.email')
|
||||
},
|
||||
{
|
||||
field: 'addr',
|
||||
label: t('descriptionsDemo.addr'),
|
||||
span: 24
|
||||
}
|
||||
])
|
||||
|
||||
const schema2 = reactive<DescriptionsSchema[]>([
|
||||
{
|
||||
field: 'username',
|
||||
label: t('descriptionsDemo.username'),
|
||||
slots: {
|
||||
label: (row) => {
|
||||
return <span class="is-required--item">{row.label}</span>
|
||||
},
|
||||
default: () => {
|
||||
return (
|
||||
<ElFormItem prop="username">
|
||||
<ElInput v-model={form.username} />
|
||||
</ElFormItem>
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'nickName',
|
||||
label: t('descriptionsDemo.nickName'),
|
||||
slots: {
|
||||
label: (row) => {
|
||||
return <span class="is-required--item">{row.label}</span>
|
||||
},
|
||||
default: () => {
|
||||
return (
|
||||
<ElFormItem prop="nickName">
|
||||
<ElInput v-model={form.nickName} />
|
||||
</ElFormItem>
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'phone',
|
||||
label: t('descriptionsDemo.phone'),
|
||||
slots: {
|
||||
label: (row) => {
|
||||
return <span class="is-required--item">{row.label}</span>
|
||||
},
|
||||
default: () => {
|
||||
return (
|
||||
<ElFormItem prop="phone">
|
||||
<ElInput v-model={form.phone} />
|
||||
</ElFormItem>
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'email',
|
||||
label: t('descriptionsDemo.email'),
|
||||
slots: {
|
||||
label: (row) => {
|
||||
return <span class="is-required--item">{row.label}</span>
|
||||
},
|
||||
default: () => {
|
||||
return (
|
||||
<ElFormItem prop="email">
|
||||
<ElInput v-model={form.email} />
|
||||
</ElFormItem>
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'addr',
|
||||
label: t('descriptionsDemo.addr'),
|
||||
slots: {
|
||||
label: (row) => {
|
||||
return <span class="is-required--item">{row.label}</span>
|
||||
},
|
||||
default: () => {
|
||||
return (
|
||||
<ElFormItem prop="addr">
|
||||
<ElInput v-model={form.addr} />
|
||||
</ElFormItem>
|
||||
)
|
||||
}
|
||||
},
|
||||
span: 24
|
||||
}
|
||||
])
|
||||
|
||||
const form = reactive({
|
||||
username: '',
|
||||
nickName: '',
|
||||
phone: '',
|
||||
email: '',
|
||||
addr: ''
|
||||
})
|
||||
|
||||
const rules = reactive({
|
||||
username: [required()],
|
||||
nickName: [required()],
|
||||
phone: [required()],
|
||||
email: [required()],
|
||||
addr: [required()]
|
||||
})
|
||||
|
||||
const { formRegister, formMethods } = useForm()
|
||||
const { getElFormExpose } = formMethods
|
||||
|
||||
const formValidation = async () => {
|
||||
const elFormExpose = await getElFormExpose()
|
||||
elFormExpose?.validate((isValid) => {
|
||||
console.log(isValid)
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Descriptions
|
||||
:title="t('descriptionsDemo.descriptions')"
|
||||
:message="t('descriptionsDemo.descriptionsDes')"
|
||||
:data="data"
|
||||
:schema="schema"
|
||||
/>
|
||||
|
||||
<Form is-custom :model="form" :rules="rules" @register="formRegister">
|
||||
<Descriptions
|
||||
:title="t('descriptionsDemo.form')"
|
||||
:data="data"
|
||||
:schema="schema2"
|
||||
class="mt-20px"
|
||||
/>
|
||||
<div class="text-center mt-10px">
|
||||
<BaseButton @click="formValidation"> {{ t('formDemo.formValidation') }} </BaseButton>
|
||||
</div>
|
||||
</Form>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
:deep(.is-required--item) {
|
||||
position: relative;
|
||||
|
||||
&::before {
|
||||
margin-right: 4px;
|
||||
color: var(--el-color-danger);
|
||||
content: '*';
|
||||
}
|
||||
}
|
||||
</style>
|
||||
165
promo-ui2/src/views/Components/Dialog.vue
Normal file
165
promo-ui2/src/views/Components/Dialog.vue
Normal file
@@ -0,0 +1,165 @@
|
||||
<script setup lang="ts">
|
||||
import { ContentWrap } from '@/components/ContentWrap'
|
||||
import { Dialog } from '@/components/Dialog'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { ref, reactive } from 'vue'
|
||||
import { Form, FormSchema } from '@/components/Form'
|
||||
import { useValidator } from '@/hooks/web/useValidator'
|
||||
import { getDictOneApi } from '@/api/common'
|
||||
import { useForm } from '@/hooks/web/useForm'
|
||||
import Echart from './Echart.vue'
|
||||
import ResizeDialog from '@/components/Dialog/src/ResizeDialog.vue'
|
||||
|
||||
const { required } = useValidator()
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const dialogVisible = ref(false)
|
||||
|
||||
const dialogVisible2 = ref(false)
|
||||
|
||||
const dialogVisible3 = ref(false)
|
||||
|
||||
const dialogVisible4 = ref(false)
|
||||
|
||||
const { formRegister, formMethods } = useForm()
|
||||
const { getElFormExpose } = formMethods
|
||||
|
||||
const schema = reactive<FormSchema[]>([
|
||||
{
|
||||
field: 'field1',
|
||||
label: t('formDemo.input'),
|
||||
component: 'Input',
|
||||
formItemProps: {
|
||||
rules: [required()]
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'field2',
|
||||
label: t('formDemo.select'),
|
||||
component: 'Select',
|
||||
// componentProps: {
|
||||
// options: []
|
||||
// },
|
||||
optionApi: async () => {
|
||||
const res = await getDictOneApi()
|
||||
return res.data
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'field3',
|
||||
label: t('formDemo.radio'),
|
||||
component: 'RadioGroup',
|
||||
componentProps: {
|
||||
options: [
|
||||
{
|
||||
label: 'option-1',
|
||||
value: '1'
|
||||
},
|
||||
{
|
||||
label: 'option-2',
|
||||
value: '2'
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'field4',
|
||||
label: t('formDemo.checkbox'),
|
||||
component: 'CheckboxGroup',
|
||||
value: [],
|
||||
componentProps: {
|
||||
options: [
|
||||
{
|
||||
label: 'option-1',
|
||||
value: '1'
|
||||
},
|
||||
{
|
||||
label: 'option-2',
|
||||
value: '2'
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'field5',
|
||||
component: 'DatePicker',
|
||||
label: t('formDemo.datePicker'),
|
||||
componentProps: {
|
||||
type: 'date'
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'field6',
|
||||
component: 'TimeSelect',
|
||||
label: t('formDemo.timeSelect')
|
||||
}
|
||||
])
|
||||
|
||||
const formSubmit = async () => {
|
||||
const elFormExpose = await getElFormExpose()
|
||||
elFormExpose?.validate((valid) => {
|
||||
if (valid) {
|
||||
console.log('submit success')
|
||||
} else {
|
||||
console.log('submit fail')
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ContentWrap :title="t('dialogDemo.dialog')" :message="t('dialogDemo.dialogDes')">
|
||||
<BaseButton type="primary" @click="dialogVisible = !dialogVisible">
|
||||
{{ t('dialogDemo.open') }}
|
||||
</BaseButton>
|
||||
|
||||
<BaseButton type="primary" @click="dialogVisible2 = !dialogVisible2">
|
||||
{{ t('dialogDemo.combineWithForm') }}
|
||||
</BaseButton>
|
||||
|
||||
<Dialog v-model="dialogVisible" :title="t('dialogDemo.dialog')">
|
||||
<Echart />
|
||||
<template #footer>
|
||||
<BaseButton @click="dialogVisible = false">{{ t('dialogDemo.close') }}</BaseButton>
|
||||
</template>
|
||||
</Dialog>
|
||||
|
||||
<Dialog v-model="dialogVisible2" :title="t('dialogDemo.dialog')">
|
||||
<Form :schema="schema" @register="formRegister" />
|
||||
<template #footer>
|
||||
<BaseButton type="primary" @click="formSubmit">{{ t('dialogDemo.submit') }}</BaseButton>
|
||||
<BaseButton @click="dialogVisible2 = false">{{ t('dialogDemo.close') }}</BaseButton>
|
||||
</template>
|
||||
</Dialog>
|
||||
</ContentWrap>
|
||||
|
||||
<ContentWrap
|
||||
class="mt-10px"
|
||||
:title="t('dialogDemo.resizeDialog')"
|
||||
:message="t('dialogDemo.dialogDes')"
|
||||
>
|
||||
<BaseButton type="primary" @click="dialogVisible3 = !dialogVisible3">
|
||||
{{ t('dialogDemo.open') }}
|
||||
</BaseButton>
|
||||
|
||||
<BaseButton type="primary" @click="dialogVisible4 = !dialogVisible4">
|
||||
{{ t('dialogDemo.combineWithForm') }}
|
||||
</BaseButton>
|
||||
|
||||
<ResizeDialog v-model="dialogVisible3" :title="t('dialogDemo.dialog')">
|
||||
<Echart />
|
||||
<template #footer>
|
||||
<BaseButton @click="dialogVisible3 = false">{{ t('dialogDemo.close') }}</BaseButton>
|
||||
</template>
|
||||
</ResizeDialog>
|
||||
|
||||
<ResizeDialog v-model="dialogVisible4" :title="t('dialogDemo.dialog')">
|
||||
<Form :schema="schema" @register="formRegister" />
|
||||
<template #footer>
|
||||
<BaseButton type="primary" @click="formSubmit">{{ t('dialogDemo.submit') }}</BaseButton>
|
||||
<BaseButton @click="dialogVisible4 = false">{{ t('dialogDemo.close') }}</BaseButton>
|
||||
</template>
|
||||
</ResizeDialog>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
36
promo-ui2/src/views/Components/Echart.vue
Normal file
36
promo-ui2/src/views/Components/Echart.vue
Normal file
@@ -0,0 +1,36 @@
|
||||
<script setup lang="ts">
|
||||
import { ContentWrap } from '@/components/ContentWrap'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { pieOptions, barOptions, lineOptions, wordOptions } from '@/views/Dashboard/echarts-data'
|
||||
import { Echart } from '@/components/Echart'
|
||||
import { ElRow, ElCol, ElCard } from 'element-plus'
|
||||
|
||||
const { t } = useI18n()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ContentWrap :title="t('echartDemo.echart')" :message="t('echartDemo.echartDes')">
|
||||
<ElRow :gutter="20" justify="space-between">
|
||||
<ElCol :xl="10" :lg="10" :md="24" :sm="24" :xs="24">
|
||||
<ElCard shadow="hover" class="mb-20px">
|
||||
<Echart :options="pieOptions" :height="300" />
|
||||
</ElCard>
|
||||
</ElCol>
|
||||
<ElCol :xl="14" :lg="14" :md="24" :sm="24" :xs="24">
|
||||
<ElCard shadow="hover" class="mb-20px">
|
||||
<Echart :options="barOptions" :height="300" />
|
||||
</ElCard>
|
||||
</ElCol>
|
||||
<ElCol :span="24">
|
||||
<ElCard shadow="hover" class="mb-20px">
|
||||
<Echart :options="lineOptions" :height="350" />
|
||||
</ElCard>
|
||||
</ElCol>
|
||||
<ElCol :span="24">
|
||||
<ElCard shadow="hover" class="mb-20px">
|
||||
<Echart :options="wordOptions as any" :height="300" />
|
||||
</ElCard>
|
||||
</ElCol>
|
||||
</ElRow>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
23
promo-ui2/src/views/Components/Editor/CodeEditor.vue
Normal file
23
promo-ui2/src/views/Components/Editor/CodeEditor.vue
Normal file
@@ -0,0 +1,23 @@
|
||||
<script setup lang="tsx">
|
||||
import { CodeEditor } from '@/components/CodeEditor'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { ContentWrap } from '@/components/ContentWrap'
|
||||
import { ref } from 'vue'
|
||||
import { BaseButton } from '@/components/Button'
|
||||
import { ElDivider } from 'element-plus'
|
||||
const content = ref(
|
||||
'public class HelloWorld {\n public static void main(String[] args) {\n System.out.println("Hello, World!");\n }\n}'
|
||||
)
|
||||
const { t } = useI18n()
|
||||
|
||||
const MonacoEditRef = ref<InstanceType<typeof CodeEditor>>()
|
||||
</script>
|
||||
<template>
|
||||
<ContentWrap :title="t('richText.codeEditor')" :message="t('richText.codeEditorDes')">
|
||||
<BaseButton @click="console.log(content)">控制台打印内容</BaseButton>
|
||||
<ElDivider />
|
||||
<div class="edit-container h-60vh">
|
||||
<CodeEditor ref="MonacoEditRef" v-model="content" language="java" />
|
||||
</div>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
32
promo-ui2/src/views/Components/Editor/Editor.vue
Normal file
32
promo-ui2/src/views/Components/Editor/Editor.vue
Normal file
@@ -0,0 +1,32 @@
|
||||
<script setup lang="ts">
|
||||
import { ContentWrap } from '@/components/ContentWrap'
|
||||
import { Editor, EditorExpose } from '@/components/Editor'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { IDomEditor } from '@wangeditor/editor'
|
||||
import { ref, onMounted, unref } from 'vue'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const change = (editor: IDomEditor) => {
|
||||
console.log(editor.getHtml())
|
||||
}
|
||||
|
||||
const editorRef = ref<typeof Editor & EditorExpose>()
|
||||
|
||||
const defaultHtml = ref('')
|
||||
|
||||
onMounted(async () => {
|
||||
const editor = await unref(editorRef)?.getEditorRef()
|
||||
console.log(editor)
|
||||
})
|
||||
|
||||
setTimeout(() => {
|
||||
defaultHtml.value = '<p>hello <strong>world</strong></p>'
|
||||
}, 3000)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ContentWrap :title="t('richText.richText')" :message="t('richText.richTextDes')">
|
||||
<Editor v-model="defaultHtml" ref="editorRef" @change="change" />
|
||||
</ContentWrap>
|
||||
</template>
|
||||
36
promo-ui2/src/views/Components/Editor/JsonEditor.vue
Normal file
36
promo-ui2/src/views/Components/Editor/JsonEditor.vue
Normal file
@@ -0,0 +1,36 @@
|
||||
<script setup lang="ts">
|
||||
import { ContentWrap } from '@/components/ContentWrap'
|
||||
import { JsonEditor } from '@/components/JsonEditor'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { ref, watch } from 'vue'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const defaultData = ref({
|
||||
title: '标题',
|
||||
content: '内容'
|
||||
})
|
||||
|
||||
watch(
|
||||
() => defaultData.value,
|
||||
(val) => {
|
||||
console.log(val)
|
||||
},
|
||||
{
|
||||
deep: true
|
||||
}
|
||||
)
|
||||
|
||||
setTimeout(() => {
|
||||
defaultData.value = {
|
||||
title: '异步标题',
|
||||
content: '异步内容'
|
||||
}
|
||||
}, 4000)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ContentWrap :title="t('richText.jsonEditor')" :message="t('richText.jsonEditorDes')">
|
||||
<JsonEditor v-model="defaultData" />
|
||||
</ContentWrap>
|
||||
</template>
|
||||
1863
promo-ui2/src/views/Components/Form/DefaultForm.vue
Normal file
1863
promo-ui2/src/views/Components/Form/DefaultForm.vue
Normal file
File diff suppressed because it is too large
Load Diff
470
promo-ui2/src/views/Components/Form/UseFormDemo.vue
Normal file
470
promo-ui2/src/views/Components/Form/UseFormDemo.vue
Normal file
@@ -0,0 +1,470 @@
|
||||
<script setup lang="tsx">
|
||||
import { Form, FormSchema } from '@/components/Form'
|
||||
import { ContentWrap } from '@/components/ContentWrap'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { useForm } from '@/hooks/web/useForm'
|
||||
import { reactive, unref, ref } from 'vue'
|
||||
import { ElInput, FormItemProp, ComponentSize, ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { useValidator } from '@/hooks/web/useValidator'
|
||||
import { getDictOneApi } from '@/api/common'
|
||||
import { BaseButton } from '@/components/Button'
|
||||
|
||||
const { required } = useValidator()
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const treeSelectData = [
|
||||
{
|
||||
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 getTreeSelectData = () => {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
resolve(treeSelectData)
|
||||
}, 3000)
|
||||
})
|
||||
}
|
||||
|
||||
const schema = reactive<FormSchema[]>([
|
||||
{
|
||||
field: 'field1',
|
||||
label: t('formDemo.input'),
|
||||
component: 'Input',
|
||||
formItemProps: {
|
||||
rules: [required()]
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'field2',
|
||||
label: t('formDemo.select'),
|
||||
component: 'Select',
|
||||
componentProps: {
|
||||
options: [
|
||||
{
|
||||
label: 'option1',
|
||||
value: '1'
|
||||
},
|
||||
{
|
||||
label: 'option2',
|
||||
value: '2'
|
||||
}
|
||||
]
|
||||
},
|
||||
formItemProps: {
|
||||
rules: [required()]
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'field3',
|
||||
label: t('formDemo.radio'),
|
||||
component: 'RadioGroup',
|
||||
hidden: true,
|
||||
value: '1',
|
||||
componentProps: {
|
||||
options: [
|
||||
{
|
||||
label: 'option-1',
|
||||
value: '1'
|
||||
},
|
||||
{
|
||||
label: 'option-2',
|
||||
value: '2'
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'field4',
|
||||
label: t('formDemo.checkbox'),
|
||||
component: 'CheckboxGroup',
|
||||
value: ['1'],
|
||||
remove: true,
|
||||
componentProps: {
|
||||
options: [
|
||||
{
|
||||
label: 'option-1',
|
||||
value: '1'
|
||||
},
|
||||
{
|
||||
label: 'option-2',
|
||||
value: '2'
|
||||
},
|
||||
{
|
||||
label: 'option-3',
|
||||
value: '3'
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'field5',
|
||||
component: 'DatePicker',
|
||||
label: t('formDemo.datePicker'),
|
||||
componentProps: {
|
||||
type: 'date'
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'field6',
|
||||
component: 'TimeSelect',
|
||||
label: t('formDemo.timeSelect')
|
||||
},
|
||||
{
|
||||
field: 'field7',
|
||||
label: `${t('formDemo.treeSelect')}`,
|
||||
component: 'TreeSelect',
|
||||
// 远程加载option
|
||||
optionApi: async () => {
|
||||
const res = await getTreeSelectData()
|
||||
return res
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'field8',
|
||||
component: 'Upload',
|
||||
label: `${t('formDemo.default')}`,
|
||||
componentProps: {
|
||||
limit: 3,
|
||||
action: 'https://run.mocky.io/v3/9d059bf9-4660-45f2-925d-ce80ad6c4d15',
|
||||
// fileList: ,
|
||||
multiple: true,
|
||||
onPreview: (uploadFile) => {
|
||||
console.log(uploadFile)
|
||||
},
|
||||
onRemove: (file) => {
|
||||
console.log(file)
|
||||
},
|
||||
beforeRemove: (uploadFile) => {
|
||||
return ElMessageBox.confirm(`Cancel the transfer of ${uploadFile.name} ?`).then(
|
||||
() => true,
|
||||
() => false
|
||||
)
|
||||
},
|
||||
onExceed: (files, uploadFiles) => {
|
||||
ElMessage.warning(
|
||||
`The limit is 3, you selected ${files.length} files this time, add up to ${
|
||||
files.length + uploadFiles.length
|
||||
} totally`
|
||||
)
|
||||
},
|
||||
slots: {
|
||||
default: () => <BaseButton type="primary">Click to upload</BaseButton>,
|
||||
tip: () => <div class="el-upload__tip">jpg/png files with a size less than 500KB.</div>
|
||||
}
|
||||
}
|
||||
}
|
||||
])
|
||||
|
||||
const { formRegister, formMethods } = useForm()
|
||||
const {
|
||||
setProps,
|
||||
delSchema,
|
||||
addSchema,
|
||||
setValues,
|
||||
setSchema,
|
||||
getComponentExpose,
|
||||
getFormItemExpose,
|
||||
getElFormExpose,
|
||||
getFormData
|
||||
} = formMethods
|
||||
|
||||
const changeLabelWidth = (width: number | string) => {
|
||||
setProps({
|
||||
labelWidth: width
|
||||
})
|
||||
}
|
||||
|
||||
const changeSize = (size: ComponentSize) => {
|
||||
setProps({
|
||||
size
|
||||
})
|
||||
}
|
||||
|
||||
const changeDisabled = (bool: boolean) => {
|
||||
setProps({
|
||||
disabled: bool
|
||||
})
|
||||
}
|
||||
|
||||
const changeSchema = (del: boolean) => {
|
||||
if (del) {
|
||||
delSchema('field2')
|
||||
} else if (!del && schema[1].field !== 'field2') {
|
||||
addSchema(
|
||||
{
|
||||
field: 'field2',
|
||||
label: t('formDemo.select'),
|
||||
component: 'Select',
|
||||
componentProps: {
|
||||
options: [
|
||||
{
|
||||
label: 'option1',
|
||||
value: '1'
|
||||
},
|
||||
{
|
||||
label: 'option2',
|
||||
value: '2'
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
1
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const setValue = async (reset: boolean) => {
|
||||
const elFormExpose = await getElFormExpose()
|
||||
if (reset) {
|
||||
elFormExpose?.resetFields()
|
||||
} else {
|
||||
setValues({
|
||||
field1: 'field1',
|
||||
field2: '2',
|
||||
field3: '2',
|
||||
field4: ['1', '3'],
|
||||
field5: '2022-01-27',
|
||||
field6: '17:00',
|
||||
field8: [
|
||||
{
|
||||
name: 'element-plus-logo.svg',
|
||||
url: 'https://element-plus.org/images/element-plus-logo.svg'
|
||||
},
|
||||
{
|
||||
name: 'element-plus-logo2.svg',
|
||||
url: 'https://element-plus.org/images/element-plus-logo.svg'
|
||||
}
|
||||
]
|
||||
})
|
||||
const formData = await getFormData()
|
||||
console.log(formData)
|
||||
}
|
||||
}
|
||||
|
||||
const index = ref(7)
|
||||
|
||||
const setLabel = () => {
|
||||
setSchema([
|
||||
{
|
||||
field: 'field2',
|
||||
path: 'label',
|
||||
value: `${t('formDemo.select')} ${index.value}`
|
||||
},
|
||||
{
|
||||
field: 'field2',
|
||||
path: 'componentProps.options',
|
||||
value: [
|
||||
{
|
||||
label: 'option-1',
|
||||
value: '1'
|
||||
},
|
||||
{
|
||||
label: 'option-2',
|
||||
value: '2'
|
||||
},
|
||||
{
|
||||
label: 'option-3',
|
||||
value: '3'
|
||||
}
|
||||
]
|
||||
}
|
||||
])
|
||||
index.value++
|
||||
}
|
||||
|
||||
const addItem = () => {
|
||||
if (unref(index) % 2 === 0) {
|
||||
addSchema({
|
||||
field: `field${unref(index)}`,
|
||||
label: `${t('formDemo.input')}${unref(index)}`,
|
||||
component: 'Input'
|
||||
})
|
||||
} else {
|
||||
addSchema(
|
||||
{
|
||||
field: `field${unref(index)}`,
|
||||
label: `${t('formDemo.input')}${unref(index)}`,
|
||||
component: 'Input'
|
||||
},
|
||||
unref(index)
|
||||
)
|
||||
}
|
||||
index.value++
|
||||
}
|
||||
|
||||
const formValidation = async () => {
|
||||
const elFormExpose = await getElFormExpose()
|
||||
elFormExpose?.validate((isValid) => {
|
||||
console.log(isValid)
|
||||
})
|
||||
}
|
||||
|
||||
const verifyReset = async () => {
|
||||
const elFormExpose = await getElFormExpose()
|
||||
elFormExpose?.resetFields()
|
||||
}
|
||||
|
||||
const getDictOne = async () => {
|
||||
const res = await getDictOneApi()
|
||||
if (res) {
|
||||
setSchema([
|
||||
{
|
||||
field: 'field2',
|
||||
path: 'componentProps.options',
|
||||
value: res.data
|
||||
}
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
const inoutFocus = async () => {
|
||||
const inputEl: ComponentRef<typeof ElInput> = await getComponentExpose('field1')
|
||||
inputEl?.focus()
|
||||
}
|
||||
|
||||
const inoutValidation = async () => {
|
||||
const formItem = await getFormItemExpose('field1')
|
||||
const inputEl: ComponentRef<typeof ElInput> = await getComponentExpose('field1')
|
||||
inputEl?.focus()
|
||||
formItem?.validate('focus', (val: boolean) => {
|
||||
console.log(val)
|
||||
})
|
||||
}
|
||||
|
||||
const formValidate = (prop: FormItemProp, isValid: boolean, message: string) => {
|
||||
console.log(prop, isValid, message)
|
||||
}
|
||||
|
||||
setTimeout(async () => {
|
||||
const formData = await getFormData()
|
||||
console.log(formData)
|
||||
}, 2000)
|
||||
|
||||
const getData = async () => {
|
||||
const formData = await getFormData()
|
||||
console.log(formData)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ContentWrap :title="`UseForm ${t('formDemo.operate')}`" style="margin-bottom: 20px">
|
||||
<BaseButton @click="changeLabelWidth(150)">{{ t('formDemo.change') }} labelWidth</BaseButton>
|
||||
<BaseButton @click="changeLabelWidth('auto')"
|
||||
>{{ t('formDemo.restore') }} labelWidth</BaseButton
|
||||
>
|
||||
|
||||
<BaseButton @click="changeSize('large')">{{ t('formDemo.change') }} size</BaseButton>
|
||||
<BaseButton @click="changeSize('default')">{{ t('formDemo.restore') }} size</BaseButton>
|
||||
|
||||
<BaseButton @click="changeDisabled(true)">{{ t('formDemo.disabled') }}</BaseButton>
|
||||
<BaseButton @click="changeDisabled(false)">{{ t('formDemo.disablement') }}</BaseButton>
|
||||
|
||||
<BaseButton @click="changeSchema(true)">
|
||||
{{ t('formDemo.delete') }} {{ t('formDemo.select') }}
|
||||
</BaseButton>
|
||||
<BaseButton @click="changeSchema(false)">
|
||||
{{ t('formDemo.add') }} {{ t('formDemo.select') }}
|
||||
</BaseButton>
|
||||
|
||||
<BaseButton @click="setValue(false)">{{ t('formDemo.setValue') }}</BaseButton>
|
||||
<BaseButton @click="setValue(true)">{{ t('formDemo.resetValue') }}</BaseButton>
|
||||
|
||||
<BaseButton @click="setLabel">
|
||||
{{ t('formDemo.set') }} {{ t('formDemo.select') }} label
|
||||
</BaseButton>
|
||||
|
||||
<BaseButton @click="addItem"> {{ t('formDemo.add') }} {{ t('formDemo.subitem') }} </BaseButton>
|
||||
|
||||
<BaseButton @click="formValidation"> {{ t('formDemo.formValidation') }} </BaseButton>
|
||||
<BaseButton @click="verifyReset"> {{ t('formDemo.verifyReset') }} </BaseButton>
|
||||
|
||||
<BaseButton @click="getDictOne">
|
||||
{{ `${t('formDemo.select')} ${t('searchDemo.dynamicOptions')}` }}
|
||||
</BaseButton>
|
||||
|
||||
<BaseButton @click="inoutFocus">
|
||||
{{ `${t('formDemo.input')} ${t('formDemo.focus')}` }}
|
||||
</BaseButton>
|
||||
<BaseButton @click="inoutValidation">
|
||||
{{ `${t('formDemo.input')} ${t('formDemo.formValidation')}` }}
|
||||
</BaseButton>
|
||||
<BaseButton @click="getData"> 获取值 </BaseButton>
|
||||
</ContentWrap>
|
||||
<ContentWrap :title="`UseForm ${t('formDemo.example')}`">
|
||||
<Form :schema="schema" @register="formRegister" @validate="formValidate" />
|
||||
</ContentWrap>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.el-button {
|
||||
margin-top: 10px;
|
||||
}
|
||||
</style>
|
||||
20
promo-ui2/src/views/Components/Highlight.vue
Normal file
20
promo-ui2/src/views/Components/Highlight.vue
Normal file
@@ -0,0 +1,20 @@
|
||||
<script setup lang="ts">
|
||||
import { ContentWrap } from '@/components/ContentWrap'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { Highlight } from '@/components/Highlight'
|
||||
import { ElMessage } from 'element-plus'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const keyClick = (key: string) => {
|
||||
ElMessage.info(key)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ContentWrap :title="t('highlightDemo.highlight')">
|
||||
<Highlight :keys="[t('highlightDemo.keys1'), t('highlightDemo.keys2')]" @click="keyClick">
|
||||
{{ t('highlightDemo.message') }}
|
||||
</Highlight>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
21
promo-ui2/src/views/Components/IAgree.vue
Normal file
21
promo-ui2/src/views/Components/IAgree.vue
Normal file
@@ -0,0 +1,21 @@
|
||||
<script setup lang="ts">
|
||||
import { ContentWrap } from '@/components/ContentWrap'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { IAgree } from '@/components/IAgree'
|
||||
|
||||
const { t } = useI18n()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ContentWrap :title="t('router.iAgree')">
|
||||
<IAgree
|
||||
:link="[
|
||||
{
|
||||
text: '《隐私政策》',
|
||||
url: 'https://www.baidu.com'
|
||||
}
|
||||
]"
|
||||
text="我同意《隐私政策》"
|
||||
/>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
61
promo-ui2/src/views/Components/Icon.vue
Normal file
61
promo-ui2/src/views/Components/Icon.vue
Normal file
@@ -0,0 +1,61 @@
|
||||
<script setup lang="ts">
|
||||
import { ContentWrap } from '@/components/ContentWrap'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { Infotip } from '@/components/Infotip'
|
||||
import { useIcon } from '@/hooks/web/useIcon'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const keyClick = (key: string) => {
|
||||
if (key === t('iconDemo.accessAddress')) {
|
||||
window.open('https://iconify.design/')
|
||||
}
|
||||
}
|
||||
|
||||
const peoples = useIcon({ icon: 'svg-icon:peoples' })
|
||||
const money = useIcon({ icon: 'svg-icon:money' })
|
||||
const aim = useIcon({ icon: 'ep:aim' })
|
||||
const alarmClock = useIcon({ icon: 'ep:alarm-clock' })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Infotip
|
||||
:show-index="false"
|
||||
:title="`${t('iconDemo.recommendedUse')}${t('iconDemo.iconify')}`"
|
||||
:schema="[
|
||||
{
|
||||
label: t('iconDemo.recommendeDes'),
|
||||
keys: ['Iconify']
|
||||
},
|
||||
{
|
||||
label: t('iconDemo.accessAddress'),
|
||||
keys: [t('iconDemo.accessAddress')]
|
||||
}
|
||||
]"
|
||||
@click="keyClick"
|
||||
/>
|
||||
<ContentWrap :title="t('iconDemo.localIcon')">
|
||||
<div class="flex justify-between">
|
||||
<Icon icon="svg-icon:peoples" />
|
||||
<Icon icon="svg-icon:money" />
|
||||
<Icon icon="svg-icon:message" />
|
||||
<Icon icon="svg-icon:shopping" />
|
||||
</div>
|
||||
</ContentWrap>
|
||||
<ContentWrap :title="t('iconDemo.iconify')">
|
||||
<div class="flex justify-between">
|
||||
<Icon icon="vi-ep:aim" />
|
||||
<Icon icon="vi-ep:alarm-clock" />
|
||||
<Icon icon="vi-ep:baseball" />
|
||||
<Icon icon="vi-ep:chat-line-round" />
|
||||
</div>
|
||||
</ContentWrap>
|
||||
<ContentWrap title="useIcon">
|
||||
<div class="flex justify-between">
|
||||
<BaseButton :icon="peoples">Button</BaseButton>
|
||||
<BaseButton :icon="money">Button</BaseButton>
|
||||
<BaseButton :icon="aim">Button</BaseButton>
|
||||
<BaseButton :icon="alarmClock">Button</BaseButton>
|
||||
</div>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
16
promo-ui2/src/views/Components/IconPicker.vue
Normal file
16
promo-ui2/src/views/Components/IconPicker.vue
Normal file
@@ -0,0 +1,16 @@
|
||||
<script setup lang="ts">
|
||||
import { IconPicker } from '@/components/IconPicker'
|
||||
import { ref } from 'vue'
|
||||
import { ContentWrap } from '@/components/ContentWrap'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const currentIcon = ref('')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ContentWrap :title="t('router.iconPicker')">
|
||||
<IconPicker v-model="currentIcon" />
|
||||
</ContentWrap>
|
||||
</template>
|
||||
43
promo-ui2/src/views/Components/ImageCropping.vue
Normal file
43
promo-ui2/src/views/Components/ImageCropping.vue
Normal file
@@ -0,0 +1,43 @@
|
||||
<script setup lang="ts">
|
||||
import { ContentWrap } from '@/components/ContentWrap'
|
||||
import { ImageCropping } from '@/components/ImageCropping'
|
||||
import { ref, unref } from 'vue'
|
||||
import { ElInput, ElDivider } from 'element-plus'
|
||||
|
||||
const cropperExpose = ref<InstanceType<typeof ImageCropping>>()
|
||||
|
||||
const base64 = ref('')
|
||||
|
||||
const getBase64 = () => {
|
||||
base64.value = unref(cropperExpose)?.cropperExpose?.getCroppedCanvas()?.toDataURL() ?? ''
|
||||
}
|
||||
|
||||
const cropperExpose2 = ref<InstanceType<typeof ImageCropping>>()
|
||||
|
||||
const base642 = ref('')
|
||||
|
||||
const getBase642 = () => {
|
||||
base642.value = unref(cropperExpose)?.cropperExpose?.getCroppedCanvas()?.toDataURL() ?? ''
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ContentWrap title="图片裁剪">
|
||||
<BaseButton type="primary" class="mb-20px" @click="getBase64">裁剪</BaseButton>
|
||||
<ElInput v-model="base64" class="mb-20px" type="textarea" />
|
||||
<ImageCropping
|
||||
ref="cropperExpose"
|
||||
image-url="https://hips.hearstapps.com/hmg-prod/images/%E5%AE%8B%E6%99%BA%E5%AD%9D-1597774015.jpg?crop=0.500xw:1.00xh;0.500xw,0&resize=640:*"
|
||||
/>
|
||||
<ElDivider />
|
||||
<BaseButton type="primary" class="mb-20px" @click="getBase642">裁剪</BaseButton>
|
||||
<ElInput v-model="base642" class="mb-20px" type="textarea" />
|
||||
<ImageCropping
|
||||
ref="cropperExpose2"
|
||||
:show-actions="false"
|
||||
box-width="100%"
|
||||
:show-result="false"
|
||||
image-url="https://hips.hearstapps.com/hmg-prod/images/%E5%AE%8B%E6%99%BA%E5%AD%9D-1597774015.jpg?crop=0.500xw:1.00xh;0.500xw,0&resize=640:*"
|
||||
/>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
29
promo-ui2/src/views/Components/ImageViewer.vue
Normal file
29
promo-ui2/src/views/Components/ImageViewer.vue
Normal file
@@ -0,0 +1,29 @@
|
||||
<script setup lang="ts">
|
||||
import { ContentWrap } from '@/components/ContentWrap'
|
||||
import { createImageViewer } from '@/components/ImageViewer'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const open = () => {
|
||||
createImageViewer({
|
||||
urlList: [
|
||||
'https://images6.alphacoders.com/657/thumbbig-657194.webp',
|
||||
'https://images3.alphacoders.com/677/thumbbig-677688.webp',
|
||||
'https://images4.alphacoders.com/200/thumbbig-200966.webp',
|
||||
'https://images5.alphacoders.com/657/thumbbig-657248.webp',
|
||||
'https://images3.alphacoders.com/679/thumbbig-679917.webp',
|
||||
'https://images3.alphacoders.com/737/thumbbig-73785.webp'
|
||||
]
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ContentWrap
|
||||
:title="t('imageViewerDemo.imageViewer')"
|
||||
:message="t('imageViewerDemo.imageViewerDes')"
|
||||
>
|
||||
<BaseButton type="primary" @click="open">{{ t('imageViewerDemo.open') }}</BaseButton>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
33
promo-ui2/src/views/Components/Infotip.vue
Normal file
33
promo-ui2/src/views/Components/Infotip.vue
Normal file
@@ -0,0 +1,33 @@
|
||||
<script setup lang="ts">
|
||||
import { ContentWrap } from '@/components/ContentWrap'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { Infotip } from '@/components/Infotip'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const keyClick = (key: string) => {
|
||||
if (key === t('iconDemo.accessAddress')) {
|
||||
window.open('https://iconify.design/')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ContentWrap :title="t('infotipDemo.infotip')" :message="t('infotipDemo.infotipDes')">
|
||||
<Infotip
|
||||
:show-index="false"
|
||||
:title="`${t('iconDemo.recommendedUse')}${t('iconDemo.iconify')}`"
|
||||
:schema="[
|
||||
{
|
||||
label: t('iconDemo.recommendeDes'),
|
||||
keys: ['Iconify']
|
||||
},
|
||||
{
|
||||
label: t('iconDemo.accessAddress'),
|
||||
keys: [t('iconDemo.accessAddress')]
|
||||
}
|
||||
]"
|
||||
@click="keyClick"
|
||||
/>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
21
promo-ui2/src/views/Components/InputPassword.vue
Normal file
21
promo-ui2/src/views/Components/InputPassword.vue
Normal file
@@ -0,0 +1,21 @@
|
||||
<script setup lang="ts">
|
||||
import { ContentWrap } from '@/components/ContentWrap'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { InputPassword } from '@/components/InputPassword'
|
||||
import { ref } from 'vue'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const password = ref('')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ContentWrap
|
||||
:title="t('inputPasswordDemo.title')"
|
||||
:message="t('inputPasswordDemo.inputPasswordDes')"
|
||||
>
|
||||
<InputPassword v-model="password" class="mb-20px" />
|
||||
<InputPassword v-model="password" strength />
|
||||
<InputPassword v-model="password" strength disabled class="mt-20px" />
|
||||
</ContentWrap>
|
||||
</template>
|
||||
108
promo-ui2/src/views/Components/Qrcode.vue
Normal file
108
promo-ui2/src/views/Components/Qrcode.vue
Normal file
@@ -0,0 +1,108 @@
|
||||
<script setup lang="ts">
|
||||
import { Qrcode } from '@/components/Qrcode'
|
||||
import { ContentWrap } from '@/components/ContentWrap'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { computed, ref, unref } from 'vue'
|
||||
import { useAppStore } from '@/store/modules/app'
|
||||
import { ElRow, ElCard, ElCol, ElMessage } from 'element-plus'
|
||||
// @ts-ignore
|
||||
import logoImg from '@/assets/imgs/logo.png'
|
||||
|
||||
const appStore = useAppStore()
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const title = computed(() => appStore.getTitle)
|
||||
|
||||
const asyncTitle = ref('')
|
||||
|
||||
setTimeout(() => {
|
||||
asyncTitle.value = unref(title)
|
||||
}, 3000)
|
||||
|
||||
const codeClick = () => {
|
||||
ElMessage.info(t('qrcodeDemo.click'))
|
||||
}
|
||||
|
||||
const disabledClick = () => {
|
||||
ElMessage.info(t('qrcodeDemo.invalid'))
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ContentWrap :title="t('qrcodeDemo.qrcode')" :message="t('qrcodeDemo.qrcodeDes')">
|
||||
<ElRow :gutter="20" justify="space-between">
|
||||
<ElCol :xl="6" :lg="6" :md="12" :sm="24" :xs="24">
|
||||
<ElCard shadow="hover" class="mb-10px text-center">
|
||||
<div class="font-bold">{{ t('qrcodeDemo.basicUsage') }}</div>
|
||||
<Qrcode :text="title" />
|
||||
</ElCard>
|
||||
</ElCol>
|
||||
<ElCol :xl="6" :lg="6" :md="12" :sm="24" :xs="24">
|
||||
<ElCard shadow="hover" class="mb-10px text-center">
|
||||
<div class="font-bold">{{ t('qrcodeDemo.imgTag') }}</div>
|
||||
<Qrcode :text="title" tag="img" />
|
||||
</ElCard>
|
||||
</ElCol>
|
||||
<ElCol :xl="6" :lg="6" :md="12" :sm="24" :xs="24">
|
||||
<ElCard shadow="hover" class="mb-10px text-center">
|
||||
<div class="font-bold">{{ t('qrcodeDemo.style') }}</div>
|
||||
<Qrcode
|
||||
:text="title"
|
||||
:options="{
|
||||
color: {
|
||||
dark: '#55D187',
|
||||
light: '#2d8cf0'
|
||||
}
|
||||
}"
|
||||
/>
|
||||
</ElCard>
|
||||
</ElCol>
|
||||
<ElCol :xl="6" :lg="6" :md="12" :sm="24" :xs="24">
|
||||
<ElCard shadow="hover" class="mb-10px text-center">
|
||||
<div class="font-bold">{{ t('qrcodeDemo.click') }}</div>
|
||||
<Qrcode :text="title" @click="codeClick" />
|
||||
</ElCard>
|
||||
</ElCol>
|
||||
<ElCol :xl="6" :lg="6" :md="12" :sm="24" :xs="24">
|
||||
<ElCard shadow="hover" class="mb-10px text-center">
|
||||
<div class="font-bold">{{ t('qrcodeDemo.asynchronousContent') }}</div>
|
||||
<Qrcode :text="asyncTitle" />
|
||||
</ElCard>
|
||||
</ElCol>
|
||||
<ElCol :xl="6" :lg="6" :md="12" :sm="24" :xs="24">
|
||||
<ElCard shadow="hover" class="mb-10px text-center">
|
||||
<div class="font-bold">{{ t('qrcodeDemo.invalid') }}</div>
|
||||
<Qrcode :text="title" disabled @disabled-click="disabledClick" />
|
||||
</ElCard>
|
||||
</ElCol>
|
||||
<ElCol :xl="6" :lg="6" :md="12" :sm="24" :xs="24">
|
||||
<ElCard shadow="hover" class="mb-10px text-center">
|
||||
<div class="font-bold">{{ t('qrcodeDemo.logoConfig') }}</div>
|
||||
<Qrcode :text="title" :logo="logoImg" />
|
||||
</ElCard>
|
||||
</ElCol>
|
||||
<ElCol :xl="6" :lg="6" :md="12" :sm="24" :xs="24">
|
||||
<ElCard shadow="hover" class="mb-10px text-center">
|
||||
<div class="font-bold">{{ t('qrcodeDemo.logoStyle') }}</div>
|
||||
<Qrcode
|
||||
:text="title"
|
||||
:logo="{
|
||||
src: logoImg,
|
||||
logoSize: 0.2,
|
||||
borderSize: 0.05,
|
||||
borderRadius: 50,
|
||||
bgColor: 'blue'
|
||||
}"
|
||||
/>
|
||||
</ElCard>
|
||||
</ElCol>
|
||||
<ElCol :xl="6" :lg="6" :md="12" :sm="24" :xs="24">
|
||||
<ElCard shadow="hover" class="mb-10px text-center">
|
||||
<div class="font-bold">{{ t('qrcodeDemo.size') }}</div>
|
||||
<Qrcode :text="title" :width="100" />
|
||||
</ElCard>
|
||||
</ElCol>
|
||||
</ElRow>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
363
promo-ui2/src/views/Components/Search.vue
Normal file
363
promo-ui2/src/views/Components/Search.vue
Normal file
@@ -0,0 +1,363 @@
|
||||
<script setup lang="ts">
|
||||
import { ContentWrap } from '@/components/ContentWrap'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { Search } from '@/components/Search'
|
||||
import { reactive, ref, unref } from 'vue'
|
||||
import { getDictOneApi } from '@/api/common'
|
||||
import { FormSchema } from '@/components/Form'
|
||||
import { useSearch } from '@/hooks/web/useSearch'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const { searchRegister, searchMethods } = useSearch()
|
||||
const { setSchema, setProps, setValues, getFormData } = searchMethods
|
||||
|
||||
const treeSelectData = [
|
||||
{
|
||||
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 getTreeSelectData = () => {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
resolve(treeSelectData)
|
||||
}, 3000)
|
||||
})
|
||||
}
|
||||
|
||||
const schema = reactive<FormSchema[]>([
|
||||
{
|
||||
field: 'field1',
|
||||
label: t('formDemo.input'),
|
||||
component: 'Input'
|
||||
},
|
||||
{
|
||||
field: 'field2',
|
||||
label: t('formDemo.select'),
|
||||
component: 'Select',
|
||||
componentProps: {
|
||||
options: [
|
||||
{
|
||||
label: 'option1',
|
||||
value: '1'
|
||||
},
|
||||
{
|
||||
label: 'option2',
|
||||
value: '2'
|
||||
}
|
||||
],
|
||||
on: {
|
||||
change: (value: string) => {
|
||||
console.log(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'field3',
|
||||
label: t('formDemo.radio'),
|
||||
component: 'RadioGroup',
|
||||
componentProps: {
|
||||
options: [
|
||||
{
|
||||
label: 'option-1',
|
||||
value: '1'
|
||||
},
|
||||
{
|
||||
label: 'option-2',
|
||||
value: '2'
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'field5',
|
||||
component: 'DatePicker',
|
||||
label: t('formDemo.datePicker'),
|
||||
componentProps: {
|
||||
type: 'date'
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'field6',
|
||||
component: 'TimeSelect',
|
||||
label: t('formDemo.timeSelect')
|
||||
},
|
||||
{
|
||||
field: 'field8',
|
||||
label: t('formDemo.input'),
|
||||
component: 'Input'
|
||||
},
|
||||
{
|
||||
field: 'field9',
|
||||
label: t('formDemo.input'),
|
||||
component: 'Input'
|
||||
},
|
||||
{
|
||||
field: 'field10',
|
||||
label: t('formDemo.input'),
|
||||
component: 'Input'
|
||||
},
|
||||
{
|
||||
field: 'field11',
|
||||
label: t('formDemo.input'),
|
||||
component: 'Input'
|
||||
},
|
||||
{
|
||||
field: 'field12',
|
||||
label: t('formDemo.input'),
|
||||
component: 'Input'
|
||||
},
|
||||
{
|
||||
field: 'field13',
|
||||
label: t('formDemo.input'),
|
||||
component: 'Input'
|
||||
},
|
||||
{
|
||||
field: 'field14',
|
||||
label: t('formDemo.input'),
|
||||
component: 'Input'
|
||||
},
|
||||
{
|
||||
field: 'field15',
|
||||
label: t('formDemo.input'),
|
||||
component: 'Input'
|
||||
},
|
||||
{
|
||||
field: 'field16',
|
||||
label: t('formDemo.input'),
|
||||
component: 'Input'
|
||||
},
|
||||
{
|
||||
field: 'field17',
|
||||
label: t('formDemo.input'),
|
||||
component: 'Input'
|
||||
},
|
||||
{
|
||||
field: 'field18',
|
||||
label: t('formDemo.input'),
|
||||
component: 'Input'
|
||||
},
|
||||
{
|
||||
field: 'field19',
|
||||
label: `${t('formDemo.treeSelect')}`,
|
||||
component: 'TreeSelect',
|
||||
// 远程加载option
|
||||
optionApi: async () => {
|
||||
const res = await getTreeSelectData()
|
||||
return res
|
||||
}
|
||||
}
|
||||
])
|
||||
|
||||
const isGrid = ref(false)
|
||||
|
||||
const changeGrid = (grid: boolean) => {
|
||||
setProps({
|
||||
isCol: grid
|
||||
})
|
||||
// isGrid.value = grid
|
||||
}
|
||||
|
||||
const layout = ref('inline')
|
||||
|
||||
const changeLayout = () => {
|
||||
layout.value = unref(layout) === 'inline' ? 'bottom' : 'inline'
|
||||
}
|
||||
|
||||
const buttonPosition = ref('left')
|
||||
|
||||
const changePosition = (position: string) => {
|
||||
layout.value = 'bottom'
|
||||
buttonPosition.value = position
|
||||
}
|
||||
|
||||
const getDictOne = async () => {
|
||||
const res = await getDictOneApi()
|
||||
if (res) {
|
||||
setSchema([
|
||||
{
|
||||
field: 'field2',
|
||||
path: 'componentProps.options',
|
||||
value: res.data
|
||||
}
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
const handleSearch = async (data: any) => {
|
||||
const formData = await getFormData()
|
||||
console.log(formData)
|
||||
console.log(data)
|
||||
}
|
||||
|
||||
const delRadio = () => {
|
||||
setSchema([
|
||||
{
|
||||
field: 'field3',
|
||||
path: 'remove',
|
||||
value: true
|
||||
}
|
||||
])
|
||||
}
|
||||
|
||||
const restoreRadio = () => {
|
||||
setSchema([
|
||||
{
|
||||
field: 'field3',
|
||||
path: 'remove',
|
||||
value: false
|
||||
}
|
||||
])
|
||||
}
|
||||
|
||||
const setValue = () => {
|
||||
setValues({
|
||||
field1: 'Joy'
|
||||
})
|
||||
}
|
||||
|
||||
const searchLoading = ref(false)
|
||||
const changeSearchLoading = () => {
|
||||
searchLoading.value = true
|
||||
setTimeout(() => {
|
||||
searchLoading.value = false
|
||||
}, 2000)
|
||||
}
|
||||
|
||||
const resetLoading = ref(false)
|
||||
const changeResetLoading = () => {
|
||||
resetLoading.value = true
|
||||
setTimeout(() => {
|
||||
resetLoading.value = false
|
||||
}, 2000)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ContentWrap
|
||||
:title="`${t('searchDemo.search')} ${t('searchDemo.operate')}`"
|
||||
style="margin-bottom: 20px"
|
||||
>
|
||||
<BaseButton @click="changeGrid(true)">{{ t('searchDemo.grid') }}</BaseButton>
|
||||
<BaseButton @click="changeGrid(false)">
|
||||
{{ t('searchDemo.restore') }} {{ t('searchDemo.grid') }}
|
||||
</BaseButton>
|
||||
|
||||
<BaseButton @click="changeLayout">
|
||||
{{ t('searchDemo.button') }} {{ t('searchDemo.position') }}
|
||||
</BaseButton>
|
||||
|
||||
<BaseButton @click="changePosition('left')">
|
||||
{{ t('searchDemo.bottom') }} {{ t('searchDemo.position') }}-{{ t('searchDemo.left') }}
|
||||
</BaseButton>
|
||||
<BaseButton @click="changePosition('center')">
|
||||
{{ t('searchDemo.bottom') }} {{ t('searchDemo.position') }}-{{ t('searchDemo.center') }}
|
||||
</BaseButton>
|
||||
<BaseButton @click="changePosition('right')">
|
||||
{{ t('searchDemo.bottom') }} {{ t('searchDemo.position') }}-{{ t('searchDemo.right') }}
|
||||
</BaseButton>
|
||||
<BaseButton @click="getDictOne">
|
||||
{{ t('formDemo.select') }} {{ t('searchDemo.dynamicOptions') }}
|
||||
</BaseButton>
|
||||
<BaseButton @click="delRadio">{{ t('searchDemo.deleteRadio') }}</BaseButton>
|
||||
<BaseButton @click="restoreRadio">{{ t('searchDemo.restoreRadio') }}</BaseButton>
|
||||
<BaseButton @click="setValue">{{ t('formDemo.setValue') }}</BaseButton>
|
||||
|
||||
<BaseButton @click="changeSearchLoading">
|
||||
{{ t('searchDemo.search') }} {{ t('searchDemo.loading') }}
|
||||
</BaseButton>
|
||||
<BaseButton @click="changeResetLoading">
|
||||
{{ t('searchDemo.reset') }} {{ t('searchDemo.loading') }}
|
||||
</BaseButton>
|
||||
</ContentWrap>
|
||||
|
||||
<ContentWrap :title="t('searchDemo.search')" :message="t('searchDemo.searchDes')">
|
||||
<Search
|
||||
:schema="schema"
|
||||
:is-col="isGrid"
|
||||
:layout="layout"
|
||||
:button-position="buttonPosition"
|
||||
:search-loading="searchLoading"
|
||||
:reset-loading="resetLoading"
|
||||
show-expand
|
||||
expand-field="field6"
|
||||
@search="handleSearch"
|
||||
@reset="handleSearch"
|
||||
@register="searchRegister"
|
||||
/>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.el-button {
|
||||
margin-top: 10px;
|
||||
}
|
||||
</style>
|
||||
80
promo-ui2/src/views/Components/Table/CardTable.vue
Normal file
80
promo-ui2/src/views/Components/Table/CardTable.vue
Normal file
@@ -0,0 +1,80 @@
|
||||
<script setup lang="tsx">
|
||||
import { ContentWrap } from '@/components/ContentWrap'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { Table } from '@/components/Table'
|
||||
import { getCardTableListApi } from '@/api/table'
|
||||
import { ref } from 'vue'
|
||||
import { ElLink, ElDivider } from 'element-plus'
|
||||
|
||||
interface Params {
|
||||
pageIndex?: number
|
||||
pageSize?: number
|
||||
}
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const loading = ref(true)
|
||||
|
||||
const tableDataList = ref<any[]>([])
|
||||
|
||||
const getTableList = async (params?: Params) => {
|
||||
const res = await getCardTableListApi(
|
||||
params || {
|
||||
pageIndex: 1,
|
||||
pageSize: 10
|
||||
}
|
||||
)
|
||||
.catch(() => {})
|
||||
.finally(() => {
|
||||
loading.value = false
|
||||
})
|
||||
if (res) {
|
||||
tableDataList.value = res.data.list
|
||||
}
|
||||
}
|
||||
|
||||
getTableList()
|
||||
|
||||
const actionClick = (row?: any) => {
|
||||
console.log(row)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ContentWrap :title="t('tableDemo.cardTable')">
|
||||
<Table
|
||||
:columns="[]"
|
||||
:data="tableDataList"
|
||||
:loading="loading"
|
||||
custom-content
|
||||
:card-wrap-style="{
|
||||
width: '200px',
|
||||
marginBottom: '20px',
|
||||
marginRight: '20px'
|
||||
}"
|
||||
>
|
||||
<template #content="row">
|
||||
<div class="flex cursor-pointer">
|
||||
<div class="pr-16px">
|
||||
<img :src="row.logo" class="w-48px h-48px rounded-[50%]" alt="" />
|
||||
</div>
|
||||
<div>
|
||||
<div class="mb-12px font-700 font-size-16px">{{ row.name }}</div>
|
||||
<div class="line-clamp-3 font-size-12px">{{ row.desc }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #content-footer="item">
|
||||
<div class="flex justify-center items-center">
|
||||
<div class="flex-1 text-center" @click="() => actionClick(item)">
|
||||
<ElLink :underline="false">操作一</ElLink>
|
||||
</div>
|
||||
<ElDivider direction="vertical" />
|
||||
<div class="flex-1 text-center" @click="() => actionClick(item)">
|
||||
<ElLink :underline="false">操作二</ElLink>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</Table>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
105
promo-ui2/src/views/Components/Table/DefaultTable.vue
Normal file
105
promo-ui2/src/views/Components/Table/DefaultTable.vue
Normal file
@@ -0,0 +1,105 @@
|
||||
<script setup lang="tsx">
|
||||
import { ContentWrap } from '@/components/ContentWrap'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { Table, TableColumn } from '@/components/Table'
|
||||
import { getTableListApi } from '@/api/table'
|
||||
import { TableData } from '@/api/table/types'
|
||||
import { ref, h } from 'vue'
|
||||
import { ElTag } from 'element-plus'
|
||||
import { BaseButton } from '@/components/Button'
|
||||
|
||||
interface Params {
|
||||
pageIndex?: number
|
||||
pageSize?: number
|
||||
}
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const columns: TableColumn[] = [
|
||||
{
|
||||
field: 'title',
|
||||
label: t('tableDemo.title')
|
||||
},
|
||||
{
|
||||
field: 'author',
|
||||
label: t('tableDemo.author')
|
||||
},
|
||||
{
|
||||
field: 'display_time',
|
||||
label: t('tableDemo.displayTime'),
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
field: 'importance',
|
||||
label: t('tableDemo.importance'),
|
||||
formatter: (_: Recordable, __: TableColumn, cellValue: number) => {
|
||||
return h(
|
||||
ElTag,
|
||||
{
|
||||
type: cellValue === 1 ? 'success' : cellValue === 2 ? 'warning' : 'danger'
|
||||
},
|
||||
() =>
|
||||
cellValue === 1
|
||||
? t('tableDemo.important')
|
||||
: cellValue === 2
|
||||
? t('tableDemo.good')
|
||||
: t('tableDemo.commonly')
|
||||
)
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'pageviews',
|
||||
label: t('tableDemo.pageviews')
|
||||
},
|
||||
{
|
||||
field: 'action',
|
||||
label: t('tableDemo.action'),
|
||||
slots: {
|
||||
default: (data) => {
|
||||
return (
|
||||
<BaseButton type="primary" onClick={() => actionFn(data)}>
|
||||
{t('tableDemo.action')}
|
||||
</BaseButton>
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
const loading = ref(true)
|
||||
|
||||
const tableDataList = ref<TableData[]>([])
|
||||
|
||||
const getTableList = async (params?: Params) => {
|
||||
const res = await getTableListApi(
|
||||
params || {
|
||||
pageIndex: 1,
|
||||
pageSize: 10
|
||||
}
|
||||
)
|
||||
.catch(() => {})
|
||||
.finally(() => {
|
||||
loading.value = false
|
||||
})
|
||||
if (res) {
|
||||
tableDataList.value = res.data.list
|
||||
}
|
||||
}
|
||||
|
||||
getTableList()
|
||||
|
||||
const actionFn = (data: any) => {
|
||||
console.log(data)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ContentWrap :title="t('tableDemo.table')" :message="t('tableDemo.tableDes')">
|
||||
<Table
|
||||
:columns="columns"
|
||||
:data="tableDataList"
|
||||
:loading="loading"
|
||||
:defaultSort="{ prop: 'display_time', order: 'descending' }"
|
||||
/>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
87
promo-ui2/src/views/Components/Table/TableImagePreview.vue
Normal file
87
promo-ui2/src/views/Components/Table/TableImagePreview.vue
Normal file
@@ -0,0 +1,87 @@
|
||||
<script setup lang="tsx">
|
||||
import { ContentWrap } from '@/components/ContentWrap'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { Table, TableColumn } from '@/components/Table'
|
||||
import { getTableListApi } from '@/api/table'
|
||||
import { TableData } from '@/api/table/types'
|
||||
import { ref } from 'vue'
|
||||
import { ElTag } from 'element-plus'
|
||||
|
||||
interface Params {
|
||||
pageIndex?: number
|
||||
pageSize?: number
|
||||
}
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const columns: TableColumn[] = [
|
||||
{
|
||||
field: 'title',
|
||||
label: t('tableDemo.title')
|
||||
},
|
||||
{
|
||||
field: 'image_uri',
|
||||
label: t('tableDemo.preview')
|
||||
},
|
||||
{
|
||||
field: 'author',
|
||||
label: t('tableDemo.author')
|
||||
},
|
||||
{
|
||||
field: 'display_time',
|
||||
label: t('tableDemo.displayTime')
|
||||
},
|
||||
{
|
||||
field: 'importance',
|
||||
label: t('tableDemo.importance'),
|
||||
formatter: (_: Recordable, __: TableColumn, cellValue: number) => {
|
||||
return (
|
||||
<ElTag type={cellValue === 1 ? 'success' : cellValue === 2 ? 'warning' : 'danger'}>
|
||||
{cellValue === 1
|
||||
? t('tableDemo.important')
|
||||
: cellValue === 2
|
||||
? t('tableDemo.good')
|
||||
: t('tableDemo.commonly')}
|
||||
</ElTag>
|
||||
)
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'pageviews',
|
||||
label: t('tableDemo.pageviews')
|
||||
}
|
||||
]
|
||||
|
||||
const loading = ref(true)
|
||||
|
||||
const tableDataList = ref<TableData[]>([])
|
||||
|
||||
const getTableList = async (params?: Params) => {
|
||||
const res = await getTableListApi(
|
||||
params || {
|
||||
pageIndex: 1,
|
||||
pageSize: 10
|
||||
}
|
||||
)
|
||||
.catch(() => {})
|
||||
.finally(() => {
|
||||
loading.value = false
|
||||
})
|
||||
if (res) {
|
||||
tableDataList.value = res.data.list
|
||||
}
|
||||
}
|
||||
|
||||
getTableList()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ContentWrap :title="t('router.PicturePreview')">
|
||||
<Table
|
||||
:columns="columns"
|
||||
:data="tableDataList"
|
||||
:loading="loading"
|
||||
:image-preview="['image_uri']"
|
||||
/>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
71
promo-ui2/src/views/Components/Table/TableVideoPreview.vue
Normal file
71
promo-ui2/src/views/Components/Table/TableVideoPreview.vue
Normal file
@@ -0,0 +1,71 @@
|
||||
<script setup lang="tsx">
|
||||
import { ContentWrap } from '@/components/ContentWrap'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { Table, TableColumn } from '@/components/Table'
|
||||
import { getTableListApi } from '@/api/table'
|
||||
import { TableData } from '@/api/table/types'
|
||||
import { ref } from 'vue'
|
||||
|
||||
interface Params {
|
||||
pageIndex?: number
|
||||
pageSize?: number
|
||||
}
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const columns: TableColumn[] = [
|
||||
{
|
||||
field: 'title',
|
||||
label: t('tableDemo.title')
|
||||
},
|
||||
{
|
||||
field: 'video_uri',
|
||||
label: t('tableDemo.videoPreview')
|
||||
},
|
||||
{
|
||||
field: 'author',
|
||||
label: t('tableDemo.author')
|
||||
},
|
||||
{
|
||||
field: 'display_time',
|
||||
label: t('tableDemo.displayTime')
|
||||
},
|
||||
{
|
||||
field: 'pageviews',
|
||||
label: t('tableDemo.pageviews')
|
||||
}
|
||||
]
|
||||
|
||||
const loading = ref(true)
|
||||
|
||||
const tableDataList = ref<TableData[]>([])
|
||||
|
||||
const getTableList = async (params?: Params) => {
|
||||
const res = await getTableListApi(
|
||||
params || {
|
||||
pageIndex: 1,
|
||||
pageSize: 10
|
||||
}
|
||||
)
|
||||
.catch(() => {})
|
||||
.finally(() => {
|
||||
loading.value = false
|
||||
})
|
||||
if (res) {
|
||||
tableDataList.value = res.data.list
|
||||
}
|
||||
}
|
||||
|
||||
getTableList()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ContentWrap :title="t('router.PicturePreview')">
|
||||
<Table
|
||||
:columns="columns"
|
||||
:data="tableDataList"
|
||||
:loading="loading"
|
||||
:video-preview="['image_uri', 'video_uri']"
|
||||
/>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
117
promo-ui2/src/views/Components/Table/TreeTable.vue
Normal file
117
promo-ui2/src/views/Components/Table/TreeTable.vue
Normal file
@@ -0,0 +1,117 @@
|
||||
<script setup lang="tsx">
|
||||
import { ContentWrap } from '@/components/ContentWrap'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { Table, TableColumn } from '@/components/Table'
|
||||
import { getTreeTableListApi } from '@/api/table'
|
||||
import { reactive, unref } from 'vue'
|
||||
import { ElTag } from 'element-plus'
|
||||
import { useTable } from '@/hooks/web/useTable'
|
||||
import { BaseButton } from '@/components/Button'
|
||||
|
||||
const { tableRegister, tableState } = useTable({
|
||||
fetchDataApi: async () => {
|
||||
const { currentPage, pageSize } = tableState
|
||||
const res = await getTreeTableListApi({
|
||||
pageIndex: unref(currentPage),
|
||||
pageSize: unref(pageSize)
|
||||
})
|
||||
return {
|
||||
list: res.data.list,
|
||||
total: res.data.total
|
||||
}
|
||||
}
|
||||
})
|
||||
const { loading, dataList, total, currentPage, pageSize } = tableState
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const columns = reactive<TableColumn[]>([
|
||||
{
|
||||
field: 'selection',
|
||||
type: 'selection'
|
||||
},
|
||||
{
|
||||
field: 'index',
|
||||
label: t('tableDemo.index'),
|
||||
type: 'index'
|
||||
},
|
||||
{
|
||||
field: 'content',
|
||||
label: t('tableDemo.header'),
|
||||
children: [
|
||||
{
|
||||
field: 'title',
|
||||
label: t('tableDemo.title')
|
||||
},
|
||||
{
|
||||
field: 'author',
|
||||
label: t('tableDemo.author')
|
||||
},
|
||||
{
|
||||
field: 'display_time',
|
||||
label: t('tableDemo.displayTime')
|
||||
},
|
||||
{
|
||||
field: 'importance',
|
||||
label: t('tableDemo.importance'),
|
||||
formatter: (_: Recordable, __: TableColumn, cellValue: number) => {
|
||||
return (
|
||||
<ElTag type={cellValue === 1 ? 'success' : cellValue === 2 ? 'warning' : 'danger'}>
|
||||
{cellValue === 1
|
||||
? t('tableDemo.important')
|
||||
: cellValue === 2
|
||||
? t('tableDemo.good')
|
||||
: t('tableDemo.commonly')}
|
||||
</ElTag>
|
||||
)
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'pageviews',
|
||||
label: t('tableDemo.pageviews')
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
field: 'action',
|
||||
label: t('tableDemo.action'),
|
||||
slots: {
|
||||
default: (data) => {
|
||||
return (
|
||||
<BaseButton type="primary" onClick={() => actionFn(data)}>
|
||||
{t('tableDemo.action')}
|
||||
</BaseButton>
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
])
|
||||
|
||||
const actionFn = (data) => {
|
||||
console.log(data)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ContentWrap :title="`${t('router.treeTable')} ${t('tableDemo.example')}`">
|
||||
<Table
|
||||
v-model:pageSize="pageSize"
|
||||
v-model:currentPage="currentPage"
|
||||
:columns="columns"
|
||||
:data="dataList"
|
||||
row-key="id"
|
||||
:loading="loading"
|
||||
sortable
|
||||
:pagination="{
|
||||
total: total
|
||||
}"
|
||||
@register="tableRegister"
|
||||
/>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.el-button {
|
||||
margin-top: 10px;
|
||||
}
|
||||
</style>
|
||||
283
promo-ui2/src/views/Components/Table/UseTableDemo.vue
Normal file
283
promo-ui2/src/views/Components/Table/UseTableDemo.vue
Normal file
@@ -0,0 +1,283 @@
|
||||
<script setup lang="tsx">
|
||||
import { ContentWrap } from '@/components/ContentWrap'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { Table, TableColumn, TableSlotDefault } from '@/components/Table'
|
||||
import { getTableListApi } from '@/api/table'
|
||||
import { ref, reactive, unref, onMounted } from 'vue'
|
||||
import { ElTag } from 'element-plus'
|
||||
import { useTable } from '@/hooks/web/useTable'
|
||||
import { BaseButton } from '@/components/Button'
|
||||
|
||||
const { tableRegister, tableMethods, tableState } = useTable({
|
||||
fetchDataApi: async () => {
|
||||
const { currentPage, pageSize } = tableState
|
||||
const res = await getTableListApi({
|
||||
pageIndex: unref(currentPage),
|
||||
pageSize: unref(pageSize)
|
||||
})
|
||||
return {
|
||||
list: res.data.list,
|
||||
total: res.data.total
|
||||
}
|
||||
}
|
||||
})
|
||||
const { loading, dataList, total, currentPage, pageSize } = tableState
|
||||
const { setProps, setColumn, getElTableExpose, addColumn, delColumn, refresh } = tableMethods
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const columns = reactive<TableColumn[]>([])
|
||||
|
||||
onMounted(() => {
|
||||
setTimeout(() => {
|
||||
setProps({
|
||||
columns: [
|
||||
{
|
||||
field: 'expand',
|
||||
type: 'expand',
|
||||
slots: {
|
||||
default: (data: TableSlotDefault) => {
|
||||
const { row } = data
|
||||
return (
|
||||
<div class="ml-30px">
|
||||
<div>
|
||||
{t('tableDemo.title')}:{row.title}
|
||||
</div>
|
||||
<div>
|
||||
{t('tableDemo.author')}:{row.author}
|
||||
</div>
|
||||
<div>
|
||||
{t('tableDemo.displayTime')}:{row.display_time}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'selection',
|
||||
type: 'selection'
|
||||
},
|
||||
{
|
||||
field: 'index',
|
||||
label: t('tableDemo.index'),
|
||||
type: 'index'
|
||||
},
|
||||
{
|
||||
field: 'title',
|
||||
label: t('tableDemo.title')
|
||||
},
|
||||
{
|
||||
field: 'author',
|
||||
label: t('tableDemo.author')
|
||||
},
|
||||
{
|
||||
field: 'display_time',
|
||||
label: t('tableDemo.displayTime')
|
||||
},
|
||||
{
|
||||
field: 'importance',
|
||||
label: t('tableDemo.importance'),
|
||||
formatter: (_: Recordable, __: TableColumn, cellValue: number) => {
|
||||
return (
|
||||
<ElTag type={cellValue === 1 ? 'success' : cellValue === 2 ? 'warning' : 'danger'}>
|
||||
{cellValue === 1
|
||||
? t('tableDemo.important')
|
||||
: cellValue === 2
|
||||
? t('tableDemo.good')
|
||||
: t('tableDemo.commonly')}
|
||||
</ElTag>
|
||||
)
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'pageviews',
|
||||
label: t('tableDemo.pageviews')
|
||||
},
|
||||
{
|
||||
field: 'action',
|
||||
label: t('tableDemo.action'),
|
||||
slots: {
|
||||
default: (data) => {
|
||||
return (
|
||||
<BaseButton type="primary" onClick={() => actionFn(data)}>
|
||||
{t('tableDemo.action')}
|
||||
</BaseButton>
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
}, 2000)
|
||||
})
|
||||
|
||||
const actionFn = (data: TableSlotDefault) => {
|
||||
console.log(data)
|
||||
}
|
||||
|
||||
const canShowPagination = ref(true)
|
||||
const showPagination = (show: boolean) => {
|
||||
canShowPagination.value = show
|
||||
}
|
||||
|
||||
const reserveIndex = (custom: boolean) => {
|
||||
setProps({
|
||||
reserveIndex: custom
|
||||
})
|
||||
}
|
||||
|
||||
const showSelections = (show: boolean) => {
|
||||
setColumn([
|
||||
{
|
||||
field: 'selection',
|
||||
path: 'hidden',
|
||||
value: !show
|
||||
}
|
||||
])
|
||||
}
|
||||
|
||||
const index = ref(1)
|
||||
|
||||
const changeTitle = () => {
|
||||
setColumn([
|
||||
{
|
||||
field: 'title',
|
||||
path: 'label',
|
||||
value: `${t('tableDemo.title')}${unref(index)}`
|
||||
}
|
||||
])
|
||||
index.value++
|
||||
}
|
||||
|
||||
const showExpandedRows = (show: boolean) => {
|
||||
setColumn([
|
||||
{
|
||||
field: 'expand',
|
||||
path: 'hidden',
|
||||
value: !show
|
||||
}
|
||||
])
|
||||
}
|
||||
|
||||
const selectAllNone = async () => {
|
||||
const elTableRef = await getElTableExpose()
|
||||
elTableRef?.toggleAllSelection()
|
||||
}
|
||||
|
||||
const showAction = ref(true)
|
||||
const delOrAddAction = () => {
|
||||
if (unref(showAction)) {
|
||||
delColumn('action')
|
||||
showAction.value = false
|
||||
} else {
|
||||
addColumn({
|
||||
field: 'action',
|
||||
label: t('tableDemo.action'),
|
||||
slots: {
|
||||
default: (data) => {
|
||||
return (
|
||||
<BaseButton type="primary" onClick={() => actionFn(data)}>
|
||||
{t('tableDemo.action')}
|
||||
</BaseButton>
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
showAction.value = true
|
||||
}
|
||||
}
|
||||
|
||||
const showStripe = ref(false)
|
||||
const showOrHiddenStripe = () => {
|
||||
setProps({
|
||||
stripe: !unref(showStripe)
|
||||
})
|
||||
showStripe.value = !unref(showStripe)
|
||||
}
|
||||
|
||||
const height = ref<string | number>('auto')
|
||||
const fixedHeaderOrAuto = () => {
|
||||
if (unref(height) === 'auto') {
|
||||
setProps({
|
||||
height: 300
|
||||
})
|
||||
height.value = 300
|
||||
} else {
|
||||
setProps({
|
||||
height: 'auto'
|
||||
})
|
||||
height.value = 'auto'
|
||||
}
|
||||
}
|
||||
|
||||
const getSelections = async () => {
|
||||
const elTableRef = await getElTableExpose()
|
||||
const selections = elTableRef?.getSelectionRows()
|
||||
console.log(selections)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ContentWrap :title="`UseTable ${t('tableDemo.operate')}`" style="margin-bottom: 20px">
|
||||
<BaseButton @click="showPagination(true)">
|
||||
{{ t('tableDemo.show') }} {{ t('tableDemo.pagination') }}
|
||||
</BaseButton>
|
||||
<BaseButton @click="showPagination(false)">
|
||||
{{ t('tableDemo.hidden') }} {{ t('tableDemo.pagination') }}
|
||||
</BaseButton>
|
||||
|
||||
<BaseButton @click="reserveIndex(true)">{{ t('tableDemo.reserveIndex') }}</BaseButton>
|
||||
<BaseButton @click="reserveIndex(false)">{{ t('tableDemo.restoreIndex') }}</BaseButton>
|
||||
|
||||
<BaseButton @click="showSelections(true)">{{ t('tableDemo.showSelections') }}</BaseButton>
|
||||
<BaseButton @click="showSelections(false)">{{ t('tableDemo.hiddenSelections') }}</BaseButton>
|
||||
|
||||
<BaseButton @click="changeTitle">{{ t('tableDemo.changeTitle') }}</BaseButton>
|
||||
|
||||
<BaseButton @click="showExpandedRows(true)">{{ t('tableDemo.showExpandedRows') }}</BaseButton>
|
||||
<BaseButton @click="showExpandedRows(false)">{{
|
||||
t('tableDemo.hiddenExpandedRows')
|
||||
}}</BaseButton>
|
||||
|
||||
<BaseButton @click="selectAllNone">{{ t('tableDemo.selectAllNone') }}</BaseButton>
|
||||
|
||||
<BaseButton @click="delOrAddAction">{{ t('tableDemo.delOrAddAction') }}</BaseButton>
|
||||
|
||||
<BaseButton @click="showOrHiddenStripe">{{ t('tableDemo.showOrHiddenStripe') }}</BaseButton>
|
||||
|
||||
<BaseButton @click="fixedHeaderOrAuto">{{ t('tableDemo.fixedHeaderOrAuto') }}</BaseButton>
|
||||
|
||||
<BaseButton @click="getSelections">{{ t('tableDemo.getSelections') }}</BaseButton>
|
||||
|
||||
<!-- <BaseButton @click="showOrHiddenSortable">{{ t('tableDemo.showOrHiddenSortable') }}</BaseButton> -->
|
||||
</ContentWrap>
|
||||
<ContentWrap :title="`UseTable ${t('tableDemo.example')}`">
|
||||
<Table
|
||||
v-model:pageSize="pageSize"
|
||||
v-model:currentPage="currentPage"
|
||||
showAction
|
||||
showSummary
|
||||
default-expand-all
|
||||
:columns="columns"
|
||||
row-key="id"
|
||||
:data="dataList"
|
||||
:loading="loading"
|
||||
:pagination="
|
||||
canShowPagination
|
||||
? {
|
||||
total: total
|
||||
}
|
||||
: undefined
|
||||
"
|
||||
@register="tableRegister"
|
||||
@refresh="refresh"
|
||||
/>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.el-button {
|
||||
margin-top: 10px;
|
||||
}
|
||||
</style>
|
||||
252
promo-ui2/src/views/Components/Tree.vue
Normal file
252
promo-ui2/src/views/Components/Tree.vue
Normal file
@@ -0,0 +1,252 @@
|
||||
<script setup lang="tsx">
|
||||
import { Icon } from '@/components/Icon'
|
||||
import { Tree } from '@/components/Tree'
|
||||
import { ContentWrap } from '@/components/ContentWrap'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { ref } from 'vue'
|
||||
|
||||
const { t } = useI18n()
|
||||
const treeData = ref([
|
||||
{
|
||||
id: 1,
|
||||
name: '北京',
|
||||
children: [
|
||||
{
|
||||
id: 5,
|
||||
name: '朝阳',
|
||||
children: [
|
||||
{
|
||||
id: 17,
|
||||
name: '双塔',
|
||||
children: []
|
||||
},
|
||||
{
|
||||
id: 18,
|
||||
name: '龙城',
|
||||
children: []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
name: '丰台',
|
||||
children: [
|
||||
{
|
||||
id: 19,
|
||||
name: '新村',
|
||||
children: []
|
||||
},
|
||||
{
|
||||
id: 20,
|
||||
name: '大红门',
|
||||
children: []
|
||||
},
|
||||
{
|
||||
id: 21,
|
||||
name: '长辛店',
|
||||
children: [
|
||||
{
|
||||
id: 22,
|
||||
name: '东山坡',
|
||||
children: []
|
||||
},
|
||||
{
|
||||
id: 23,
|
||||
name: '北关',
|
||||
children: []
|
||||
},
|
||||
{
|
||||
id: 24,
|
||||
name: '光明里',
|
||||
children: []
|
||||
},
|
||||
{
|
||||
id: 25,
|
||||
name: '赵辛店',
|
||||
children: []
|
||||
},
|
||||
{
|
||||
id: 26,
|
||||
name: '西峰寺',
|
||||
children: []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 7,
|
||||
name: '海淀',
|
||||
children: []
|
||||
},
|
||||
{
|
||||
id: 8,
|
||||
name: '房山',
|
||||
children: []
|
||||
},
|
||||
{
|
||||
id: 10,
|
||||
name: '顺义',
|
||||
children: []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: '上海',
|
||||
children: [
|
||||
{
|
||||
id: 11,
|
||||
name: '黄埔',
|
||||
children: []
|
||||
},
|
||||
{
|
||||
id: 12,
|
||||
name: '徐汇',
|
||||
children: []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: '广州',
|
||||
children: [
|
||||
{
|
||||
id: 13,
|
||||
name: '荔湾',
|
||||
children: []
|
||||
},
|
||||
{
|
||||
id: 14,
|
||||
name: '白云',
|
||||
children: []
|
||||
},
|
||||
{
|
||||
id: 15,
|
||||
name: '越秀',
|
||||
children: []
|
||||
},
|
||||
{
|
||||
id: 16,
|
||||
name: '南沙',
|
||||
children: []
|
||||
}
|
||||
]
|
||||
}
|
||||
])
|
||||
|
||||
const handleNodeClick = (data: any) => {
|
||||
console.log('Node clicked:', data)
|
||||
}
|
||||
|
||||
const addOrg = (node: any) => {
|
||||
ElMessageBox.prompt('请输入分组名称', '添加子分组', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
inputPattern: /\S/,
|
||||
inputErrorMessage: '分组名称不能为空'
|
||||
}).then(({ value }) => {
|
||||
node.children.push({
|
||||
id: node.children.length + 1,
|
||||
name: value,
|
||||
children: []
|
||||
})
|
||||
ElMessage.success('添加成功')
|
||||
})
|
||||
}
|
||||
const editOrg = (node: any) => {
|
||||
ElMessageBox.prompt('请输入新的分组名称', '修改分组名称', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
inputValue: node.name,
|
||||
inputPattern: /\S/,
|
||||
inputErrorMessage: '分组名称不能为空'
|
||||
}).then(({ value }) => {
|
||||
node.name = value
|
||||
ElMessage.success('修改成功')
|
||||
})
|
||||
}
|
||||
|
||||
const deleteOrg = (node: any) => {
|
||||
ElMessageBox.confirm(`删除 [${node.name}] 分组、下级子分组 <br>是否继续?`, '提示', {
|
||||
dangerouslyUseHTMLString: true,
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
center: true
|
||||
}).then(() => {
|
||||
const id = node.id
|
||||
// 查找 treeData 中对应的节点,并删除
|
||||
const deleteNode = (data: any) => {
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
if (data[i].id === id) {
|
||||
data.splice(i, 1)
|
||||
return
|
||||
}
|
||||
if (data[i].children) {
|
||||
deleteNode(data[i].children)
|
||||
}
|
||||
}
|
||||
}
|
||||
deleteNode(treeData.value)
|
||||
ElMessage.success('删除成功')
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ContentWrap :title="t('treeDemo.treeTitle')" :message="t('qrcodeDemo.qrcodeDes')">
|
||||
<Tree
|
||||
:data="treeData"
|
||||
:tree-props="{
|
||||
highlightCurrent: true,
|
||||
nodeKey: 'id',
|
||||
props: {
|
||||
children: 'children',
|
||||
label: 'name'
|
||||
}
|
||||
}"
|
||||
width="300px"
|
||||
height="400px"
|
||||
@node-click="handleNodeClick"
|
||||
>
|
||||
<!-- 自定义右键菜单 -->
|
||||
<template #context-menu="{ node }">
|
||||
<div class="menuItem" @click="addOrg(node)">
|
||||
<Icon icon="ep:plus" style="color: #1e9fff" />
|
||||
<span>添加子分组</span>
|
||||
</div>
|
||||
<div class="menuItem" @click="editOrg(node)">
|
||||
<Icon icon="ep:edit-pen" style="color: #1e9fff" />
|
||||
修改分组名称
|
||||
</div>
|
||||
<div class="menuItem" @click="deleteOrg(node)">
|
||||
<Icon icon="ep:delete" style="color: #1e9fff" />
|
||||
删除分组及子分组
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 自定义节点显示 -->
|
||||
<!-- <template #render-node="{ node }">
|
||||
<span v-if="node.isLeaf">[FILE] {{ node.label }}</span>
|
||||
<span v-else>[FOLDER] {{ node.label }}</span>
|
||||
</template> -->
|
||||
</Tree>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
<style lang="less" scoped>
|
||||
.menuItem {
|
||||
display: flex;
|
||||
padding: 2px 10px;
|
||||
text-align: left;
|
||||
box-sizing: border-box;
|
||||
align-items: center; /* 垂直居中 */
|
||||
gap: 5px; /* 图标和文字之间的间距,可根据需要调整 */
|
||||
}
|
||||
|
||||
.menuItem:hover {
|
||||
cursor: pointer;
|
||||
background-color: #eee;
|
||||
}
|
||||
</style>
|
||||
24
promo-ui2/src/views/Components/VideoPlayer.vue
Normal file
24
promo-ui2/src/views/Components/VideoPlayer.vue
Normal file
@@ -0,0 +1,24 @@
|
||||
<script setup lang="ts">
|
||||
import { VideoPlayer, createVideoViewer } from '@/components/VideoPlayer'
|
||||
import { ContentWrap } from '@/components/ContentWrap'
|
||||
import { ElDivider } from 'element-plus'
|
||||
|
||||
const showVideo = () => {
|
||||
createVideoViewer({
|
||||
url: '//sf1-cdn-tos.huoshanstatic.com/obj/media-fe/xgplayer_doc_video/mp4/xgplayer-demo-720p.mp4',
|
||||
poster: '//lf3-static.bytednsdoc.com/obj/eden-cn/nupenuvpxnuvo/xgplayer_doc/poster.jpg'
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ContentWrap title="视频播放器">
|
||||
<VideoPlayer
|
||||
url="//sf1-cdn-tos.huoshanstatic.com/obj/media-fe/xgplayer_doc_video/mp4/xgplayer-demo-720p.mp4"
|
||||
poster="//lf3-static.bytednsdoc.com/obj/eden-cn/nupenuvpxnuvo/xgplayer_doc/poster.jpg"
|
||||
/>
|
||||
|
||||
<ElDivider />
|
||||
<BaseButton type="primary" @click="showVideo">弹窗展示</BaseButton>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
62
promo-ui2/src/views/Components/Waterfall.vue
Normal file
62
promo-ui2/src/views/Components/Waterfall.vue
Normal file
@@ -0,0 +1,62 @@
|
||||
<script setup lang="ts">
|
||||
import { Waterfall } from '@/components/Waterfall'
|
||||
import { ContentWrap } from '@/components/ContentWrap'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import Mock from 'mockjs'
|
||||
import { ref, unref } from 'vue'
|
||||
import { toAnyString } from '@/utils'
|
||||
|
||||
const data = ref<any>([])
|
||||
|
||||
const getList = () => {
|
||||
const list: any = []
|
||||
for (let i = 0; i < 20; i++) {
|
||||
// 随机 100, 500 之间的整数
|
||||
const height = Mock.Random.integer(100, 500)
|
||||
const width = Mock.Random.integer(100, 500)
|
||||
list.push(
|
||||
Mock.mock({
|
||||
width,
|
||||
height,
|
||||
id: toAnyString(),
|
||||
// http更换为https
|
||||
image_uri: Mock.Random.image(`${width}x${height}`).replace('http://', 'https://')
|
||||
})
|
||||
)
|
||||
}
|
||||
data.value = [...unref(data), ...list]
|
||||
if (unref(data).length >= 60) {
|
||||
end.value = true
|
||||
}
|
||||
}
|
||||
getList()
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const loading = ref(false)
|
||||
|
||||
const end = ref(false)
|
||||
|
||||
const loadMore = () => {
|
||||
loading.value = true
|
||||
setTimeout(() => {
|
||||
getList()
|
||||
loading.value = false
|
||||
}, 1000)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ContentWrap :title="t('router.waterfall')">
|
||||
<Waterfall
|
||||
:data="data"
|
||||
:loading="loading"
|
||||
:end="end"
|
||||
:props="{
|
||||
src: 'image_uri',
|
||||
height: 'height'
|
||||
}"
|
||||
@load-more="loadMore"
|
||||
/>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
110
promo-ui2/src/views/Dashboard/Analysis.vue
Normal file
110
promo-ui2/src/views/Dashboard/Analysis.vue
Normal file
@@ -0,0 +1,110 @@
|
||||
<script setup lang="ts">
|
||||
import PanelGroup from './components/PanelGroup.vue'
|
||||
import { ElRow, ElCol, ElCard, ElSkeleton } from 'element-plus'
|
||||
import { Echart } from '@/components/Echart'
|
||||
import { pieOptions, barOptions, lineOptions } from './echarts-data'
|
||||
import { ref, reactive, computed, watch, onMounted } from 'vue'
|
||||
import { getLast10DaysPromotionData } from '@/api/turnover'
|
||||
import { set } from 'lodash-es'
|
||||
import { EChartsOption } from 'echarts'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { useAppStore } from '@/store/modules/app'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const loading = ref(true)
|
||||
|
||||
const appStore = useAppStore()
|
||||
const isDark = computed(() => appStore.getIsDark)
|
||||
|
||||
const pieOptionsData = reactive<EChartsOption>(pieOptions) as EChartsOption
|
||||
|
||||
const barOptionsData = reactive<EChartsOption>(barOptions) as EChartsOption
|
||||
|
||||
const lineOptionsData = reactive<EChartsOption>(lineOptions) as EChartsOption
|
||||
|
||||
const getMonthlySales = async () => {
|
||||
const chartData: any = {
|
||||
date: [],
|
||||
expectedData: [],
|
||||
actualData: []
|
||||
}
|
||||
const res = await getLast10DaysPromotionData().catch(() => {})
|
||||
if (res && res.data) {
|
||||
for (const item of res.data) {
|
||||
chartData.date.push(item.date)
|
||||
chartData.expectedData.push(item.last10DaysTurnover)
|
||||
chartData.actualData.push(item.last10DaysAnchorInvite)
|
||||
}
|
||||
|
||||
set(lineOptionsData, 'xAxis.data', chartData.date)
|
||||
set(lineOptionsData, 'series', [
|
||||
{
|
||||
name: '日流水',
|
||||
smooth: true,
|
||||
type: 'line',
|
||||
data: chartData.expectedData,
|
||||
animationDuration: 2800,
|
||||
animationEasing: 'cubicInOut'
|
||||
},
|
||||
{
|
||||
name: '日邀请',
|
||||
smooth: true,
|
||||
type: 'line',
|
||||
itemStyle: {},
|
||||
data: chartData.actualData,
|
||||
animationDuration: 2800,
|
||||
animationEasing: 'quadraticOut'
|
||||
}
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新 legend.textStyle
|
||||
*/
|
||||
const updateLegendTextStyle = (options) => {
|
||||
const newTextStyle = {
|
||||
color: isDark.value ? '#ccc' : '#333'
|
||||
}
|
||||
const inactiveColor = isDark.value ? '#abacac' : '#ccc'
|
||||
set(options, 'title.textStyle', newTextStyle)
|
||||
if (options !== barOptionsData) {
|
||||
set(options, 'legend.textStyle', newTextStyle)
|
||||
set(options, 'legend.inactiveColor', inactiveColor)
|
||||
}
|
||||
options === pieOptionsData && set(options, 'series[0].emptyCircleStyle.color', inactiveColor)
|
||||
}
|
||||
|
||||
const getAllApi = async () => {
|
||||
await Promise.all([getMonthlySales()])
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
getAllApi()
|
||||
|
||||
// 监听暗黑模式变化并重新更新样式
|
||||
watch(isDark, () => {
|
||||
updateLegendTextStyle(pieOptionsData)
|
||||
updateLegendTextStyle(barOptionsData)
|
||||
updateLegendTextStyle(lineOptionsData)
|
||||
})
|
||||
onMounted(() => {
|
||||
updateLegendTextStyle(pieOptionsData)
|
||||
updateLegendTextStyle(barOptionsData)
|
||||
updateLegendTextStyle(lineOptionsData)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PanelGroup />
|
||||
<ElRow :gutter="20" justify="space-between">
|
||||
<ElCol :span="24">
|
||||
<ElCard shadow="hover" class="mb-20px">
|
||||
<ElSkeleton :loading="loading" animated :rows="4">
|
||||
<Echart :options="lineOptionsData" :height="350" />
|
||||
</ElSkeleton>
|
||||
</ElCard>
|
||||
</ElCol>
|
||||
</ElRow>
|
||||
</template>
|
||||
293
promo-ui2/src/views/Dashboard/Workplace.vue
Normal file
293
promo-ui2/src/views/Dashboard/Workplace.vue
Normal file
@@ -0,0 +1,293 @@
|
||||
<script setup lang="ts">
|
||||
import { useTimeAgo } from '@/hooks/web/useTimeAgo'
|
||||
import { ElRow, ElCol, ElSkeleton, ElCard, ElDivider, ElLink } from 'element-plus'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { ref, reactive } from 'vue'
|
||||
import { CountTo } from '@/components/CountTo'
|
||||
import { formatTime } from '@/utils'
|
||||
import { Echart } from '@/components/Echart'
|
||||
import { EChartsOption } from 'echarts'
|
||||
import { radarOption } from './echarts-data'
|
||||
import { Highlight } from '@/components/Highlight'
|
||||
import {
|
||||
getCountApi,
|
||||
getProjectApi,
|
||||
getDynamicApi,
|
||||
getTeamApi,
|
||||
getRadarApi
|
||||
} from '@/api/dashboard/workplace'
|
||||
import type { WorkplaceTotal, Project, Dynamic, Team } from '@/api/dashboard/workplace/types'
|
||||
import { set } from 'lodash-es'
|
||||
|
||||
const loading = ref(true)
|
||||
|
||||
// 获取统计数
|
||||
let totalSate = reactive<WorkplaceTotal>({
|
||||
project: 0,
|
||||
access: 0,
|
||||
todo: 0
|
||||
})
|
||||
|
||||
const getCount = async () => {
|
||||
const res = await getCountApi().catch(() => {})
|
||||
if (res) {
|
||||
totalSate = Object.assign(totalSate, res.data)
|
||||
}
|
||||
}
|
||||
|
||||
let projects = reactive<Project[]>([])
|
||||
|
||||
// 获取项目数
|
||||
const getProject = async () => {
|
||||
const res = await getProjectApi().catch(() => {})
|
||||
if (res) {
|
||||
projects = Object.assign(projects, res.data)
|
||||
}
|
||||
}
|
||||
|
||||
// 获取动态
|
||||
let dynamics = reactive<Dynamic[]>([])
|
||||
|
||||
const getDynamic = async () => {
|
||||
const res = await getDynamicApi().catch(() => {})
|
||||
if (res) {
|
||||
dynamics = Object.assign(dynamics, res.data)
|
||||
}
|
||||
}
|
||||
|
||||
// 获取团队
|
||||
let team = reactive<Team[]>([])
|
||||
|
||||
const getTeam = async () => {
|
||||
const res = await getTeamApi().catch(() => {})
|
||||
if (res) {
|
||||
team = Object.assign(team, res.data)
|
||||
}
|
||||
}
|
||||
|
||||
// 获取指数
|
||||
const radarOptionData = reactive<EChartsOption>(radarOption) as EChartsOption
|
||||
|
||||
const getRadar = async () => {
|
||||
const res = await getRadarApi().catch(() => {})
|
||||
if (res) {
|
||||
set(
|
||||
radarOptionData,
|
||||
'radar.indicator',
|
||||
res.data.map((v) => {
|
||||
return {
|
||||
name: t(v.name),
|
||||
max: v.max
|
||||
}
|
||||
})
|
||||
)
|
||||
set(radarOptionData, 'series', [
|
||||
{
|
||||
name: `xxx${t('workplace.index')}`,
|
||||
type: 'radar',
|
||||
data: [
|
||||
{
|
||||
value: res.data.map((v) => v.personal),
|
||||
name: t('workplace.personal')
|
||||
},
|
||||
{
|
||||
value: res.data.map((v) => v.team),
|
||||
name: t('workplace.team')
|
||||
}
|
||||
]
|
||||
}
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
const getAllApi = async () => {
|
||||
await Promise.all([getCount(), getProject(), getDynamic(), getTeam(), getRadar()])
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
getAllApi()
|
||||
|
||||
const { t } = useI18n()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<ElCard shadow="never">
|
||||
<ElSkeleton :loading="loading" animated>
|
||||
<ElRow :gutter="20" justify="space-between">
|
||||
<ElCol :xl="12" :lg="12" :md="12" :sm="24" :xs="24">
|
||||
<div class="flex items-center">
|
||||
<img
|
||||
src="@/assets/imgs/avatar.jpg"
|
||||
alt=""
|
||||
class="w-70px h-70px rounded-[50%] mr-20px"
|
||||
/>
|
||||
<div>
|
||||
<div class="text-20px">
|
||||
{{ t('workplace.goodMorning') }},Archer,{{ t('workplace.happyDay') }}
|
||||
</div>
|
||||
<div class="mt-10px text-14px text-gray-500">
|
||||
{{ t('workplace.toady') }},20℃ - 32℃!
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ElCol>
|
||||
<ElCol :xl="12" :lg="12" :md="12" :sm="24" :xs="24">
|
||||
<div class="flex h-70px items-center justify-end lt-sm:mt-20px">
|
||||
<div class="px-8px text-right">
|
||||
<div class="text-14px text-gray-400 mb-20px">{{ t('workplace.project') }}</div>
|
||||
<CountTo
|
||||
class="text-20px"
|
||||
:start-val="0"
|
||||
:end-val="totalSate.project"
|
||||
:duration="2600"
|
||||
/>
|
||||
</div>
|
||||
<ElDivider direction="vertical" />
|
||||
<div class="px-8px text-right">
|
||||
<div class="text-14px text-gray-400 mb-20px">{{ t('workplace.toDo') }}</div>
|
||||
<CountTo
|
||||
class="text-20px"
|
||||
:start-val="0"
|
||||
:end-val="totalSate.todo"
|
||||
:duration="2600"
|
||||
/>
|
||||
</div>
|
||||
<ElDivider direction="vertical" border-style="dashed" />
|
||||
<div class="px-8px text-right">
|
||||
<div class="text-14px text-gray-400 mb-20px">{{ t('workplace.access') }}</div>
|
||||
<CountTo
|
||||
class="text-20px"
|
||||
:start-val="0"
|
||||
:end-val="totalSate.access"
|
||||
:duration="2600"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</ElCol>
|
||||
</ElRow>
|
||||
</ElSkeleton>
|
||||
</ElCard>
|
||||
</div>
|
||||
|
||||
<ElRow class="mt-20px" :gutter="20" justify="space-between">
|
||||
<ElCol :xl="16" :lg="16" :md="24" :sm="24" :xs="24" class="mb-20px">
|
||||
<ElCard shadow="never">
|
||||
<template #header>
|
||||
<div class="flex justify-between">
|
||||
<span>{{ t('workplace.project') }}</span>
|
||||
<ElLink type="primary" :underline="false">{{ t('workplace.more') }}</ElLink>
|
||||
</div>
|
||||
</template>
|
||||
<ElSkeleton :loading="loading" animated>
|
||||
<ElRow>
|
||||
<ElCol
|
||||
v-for="(item, index) in projects"
|
||||
:key="`card-${index}`"
|
||||
:xl="8"
|
||||
:lg="8"
|
||||
:md="12"
|
||||
:sm="24"
|
||||
:xs="24"
|
||||
>
|
||||
<ElCard shadow="hover">
|
||||
<div class="flex items-center">
|
||||
<Icon :icon="item.icon" :size="25" class="mr-10px" />
|
||||
<span class="text-16px">{{ item.name }}</span>
|
||||
</div>
|
||||
<div class="mt-15px text-14px text-gray-400">{{ t(item.message) }}</div>
|
||||
<div class="mt-20px text-12px text-gray-400 flex justify-between">
|
||||
<span>{{ item.personal }}</span>
|
||||
<span>{{ formatTime(item.time, 'yyyy-MM-dd') }}</span>
|
||||
</div>
|
||||
</ElCard>
|
||||
</ElCol>
|
||||
</ElRow>
|
||||
</ElSkeleton>
|
||||
</ElCard>
|
||||
|
||||
<ElCard shadow="never" class="mt-20px">
|
||||
<template #header>
|
||||
<div class="flex justify-between">
|
||||
<span>{{ t('workplace.dynamic') }}</span>
|
||||
<ElLink type="primary" :underline="false">{{ t('workplace.more') }}</ElLink>
|
||||
</div>
|
||||
</template>
|
||||
<ElSkeleton :loading="loading" animated>
|
||||
<div v-for="(item, index) in dynamics" :key="`dynamics-${index}`">
|
||||
<div class="flex items-center">
|
||||
<img
|
||||
src="@/assets/imgs/avatar.jpg"
|
||||
alt=""
|
||||
class="w-35px h-35px rounded-[50%] mr-20px"
|
||||
/>
|
||||
<div>
|
||||
<div class="text-14px">
|
||||
<Highlight :keys="item.keys.map((v) => t(v))">
|
||||
{{ t('workplace.pushCode') }}
|
||||
</Highlight>
|
||||
</div>
|
||||
<div class="mt-15px text-12px text-gray-400">
|
||||
{{ useTimeAgo(item.time) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ElDivider />
|
||||
</div>
|
||||
</ElSkeleton>
|
||||
</ElCard>
|
||||
</ElCol>
|
||||
<ElCol :xl="8" :lg="8" :md="24" :sm="24" :xs="24" class="mb-20px">
|
||||
<ElCard shadow="never">
|
||||
<template #header>
|
||||
<span>{{ t('workplace.shortcutOperation') }}</span>
|
||||
</template>
|
||||
<ElSkeleton :loading="loading" animated>
|
||||
<ElRow>
|
||||
<ElCol
|
||||
v-for="item in 9"
|
||||
:key="`card-${item}`"
|
||||
:xl="12"
|
||||
:lg="12"
|
||||
:md="12"
|
||||
:sm="24"
|
||||
:xs="24"
|
||||
class="mb-10px"
|
||||
>
|
||||
<ElLink type="default" :underline="false">
|
||||
{{ t('workplace.operation') }}{{ item }}
|
||||
</ElLink>
|
||||
</ElCol>
|
||||
</ElRow>
|
||||
</ElSkeleton>
|
||||
</ElCard>
|
||||
|
||||
<ElCard shadow="never" class="mt-20px">
|
||||
<template #header>
|
||||
<span>xx{{ t('workplace.index') }}</span>
|
||||
</template>
|
||||
<ElSkeleton :loading="loading" animated>
|
||||
<Echart :options="radarOptionData" :height="400" />
|
||||
</ElSkeleton>
|
||||
</ElCard>
|
||||
|
||||
<ElCard shadow="never" class="mt-20px">
|
||||
<template #header>
|
||||
<span>{{ t('workplace.team') }}</span>
|
||||
</template>
|
||||
<ElSkeleton :loading="loading" animated>
|
||||
<ElRow>
|
||||
<ElCol v-for="item in team" :key="`team-${item.name}`" :span="12" class="mb-20px">
|
||||
<div class="flex items-center">
|
||||
<Icon :icon="item.icon" class="mr-10px" />
|
||||
<ElLink type="default" :underline="false">
|
||||
{{ item.name }}
|
||||
</ElLink>
|
||||
</div>
|
||||
</ElCol>
|
||||
</ElRow>
|
||||
</ElSkeleton>
|
||||
</ElCard>
|
||||
</ElCol>
|
||||
</ElRow>
|
||||
</template>
|
||||
318
promo-ui2/src/views/Dashboard/components/PanelGroup.vue
Normal file
318
promo-ui2/src/views/Dashboard/components/PanelGroup.vue
Normal file
@@ -0,0 +1,318 @@
|
||||
<script setup lang="ts">
|
||||
import { ElRow, ElCol, ElCard, ElSkeleton } from 'element-plus'
|
||||
import { CountTo } from '@/components/CountTo'
|
||||
import { useDesign } from '@/hooks/web/useDesign'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { ref, reactive } from 'vue'
|
||||
import { getRealTimeData } from '@/api/turnover'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const { getPrefixCls } = useDesign()
|
||||
|
||||
const prefixCls = getPrefixCls('panel')
|
||||
|
||||
const loading = ref(true)
|
||||
let data: any = reactive({})
|
||||
|
||||
const getCount = async () => {
|
||||
const res = await getRealTimeData()
|
||||
.catch(() => {})
|
||||
.finally(() => {
|
||||
loading.value = false
|
||||
})
|
||||
|
||||
data = Object.assign(data, res?.data || {})
|
||||
}
|
||||
|
||||
getCount()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ElRow :gutter="20" justify="space-between" :class="prefixCls">
|
||||
<ElCol :xl="6" :lg="6" :md="12" :sm="12" :xs="24">
|
||||
<ElCard shadow="hover" class="mb-20px">
|
||||
<ElSkeleton :loading="loading" animated :rows="2">
|
||||
<template #default>
|
||||
<div :class="`${prefixCls}__item flex justify-between`">
|
||||
<div>
|
||||
<div
|
||||
:class="`${prefixCls}__item--icon ${prefixCls}__item--peoples p-16px inline-block rounded-6px`"
|
||||
>
|
||||
<Icon icon="svg-icon:peoples" :size="40" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col justify-between">
|
||||
<div :class="`${prefixCls}__item--text text-16px text-gray-500 text-right`"
|
||||
>下级推广数</div
|
||||
>
|
||||
<CountTo
|
||||
class="text-20px font-700 text-right"
|
||||
:start-val="0"
|
||||
:end-val="data.anchorCount"
|
||||
:duration="2600"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</ElSkeleton>
|
||||
</ElCard>
|
||||
</ElCol>
|
||||
|
||||
<ElCol :xl="6" :lg="6" :md="12" :sm="12" :xs="24">
|
||||
<ElCard shadow="hover" class="mb-20px">
|
||||
<ElSkeleton :loading="loading" animated :rows="2">
|
||||
<template #default>
|
||||
<div :class="`${prefixCls}__item flex justify-between`">
|
||||
<div>
|
||||
<div
|
||||
:class="`${prefixCls}__item--icon ${prefixCls}__item--money p-16px inline-block rounded-6px`"
|
||||
>
|
||||
<Icon icon="svg-icon:money" :size="40" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col justify-between">
|
||||
<div :class="`${prefixCls}__item--text text-16px text-gray-500 text-right`"
|
||||
>总流水</div
|
||||
>
|
||||
<CountTo
|
||||
class="text-20px font-700 text-right"
|
||||
:start-val="0"
|
||||
:end-val="data.totalTurnover"
|
||||
:decimals="data.totalTurnover ? 2 : 0"
|
||||
:duration="2600"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</ElSkeleton>
|
||||
</ElCard>
|
||||
</ElCol>
|
||||
|
||||
<ElCol :xl="6" :lg="6" :md="12" :sm="12" :xs="24">
|
||||
<ElCard shadow="hover" class="mb-20px">
|
||||
<ElSkeleton :loading="loading" animated :rows="2">
|
||||
<template #default>
|
||||
<div :class="`${prefixCls}__item flex justify-between`">
|
||||
<div>
|
||||
<div
|
||||
:class="`${prefixCls}__item--icon ${prefixCls}__item--money p-16px inline-block rounded-6px`"
|
||||
>
|
||||
<Icon icon="svg-icon:money" :size="40" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col justify-between">
|
||||
<div :class="`${prefixCls}__item--text text-16px text-gray-500 text-right`"
|
||||
>上月流水</div
|
||||
>
|
||||
<CountTo
|
||||
class="text-20px font-700 text-right"
|
||||
:start-val="0"
|
||||
:end-val="data.lastMonthTurnover"
|
||||
:decimals="data.lastMonthTurnover ? 2 : 0"
|
||||
:duration="2600"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</ElSkeleton>
|
||||
</ElCard>
|
||||
</ElCol>
|
||||
|
||||
<ElCol :xl="6" :lg="6" :md="12" :sm="12" :xs="24">
|
||||
<ElCard shadow="hover" class="mb-20px">
|
||||
<ElSkeleton :loading="loading" animated :rows="2">
|
||||
<template #default>
|
||||
<div :class="`${prefixCls}__item flex justify-between`">
|
||||
<div>
|
||||
<div
|
||||
:class="`${prefixCls}__item--icon ${prefixCls}__item--money p-16px inline-block rounded-6px`"
|
||||
>
|
||||
<Icon icon="svg-icon:money" :size="40" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col justify-between">
|
||||
<div :class="`${prefixCls}__item--text text-16px text-gray-500 text-right`"
|
||||
>本月流水</div
|
||||
>
|
||||
<CountTo
|
||||
class="text-20px font-700 text-right"
|
||||
:start-val="0"
|
||||
:end-val="data.currentMonthTurnover"
|
||||
:decimals="data.currentMonthTurnover ? 2 : 0"
|
||||
:duration="2600"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</ElSkeleton>
|
||||
</ElCard>
|
||||
</ElCol>
|
||||
</ElRow>
|
||||
<ElRow :gutter="20" justify="space-between" :class="prefixCls">
|
||||
<ElCol :xl="6" :lg="6" :md="12" :sm="12" :xs="24">
|
||||
<ElCard shadow="hover" class="mb-20px">
|
||||
<ElSkeleton :loading="loading" animated :rows="2">
|
||||
<template #default>
|
||||
<div :class="`${prefixCls}__item flex justify-between`">
|
||||
<div>
|
||||
<div
|
||||
:class="`${prefixCls}__item--icon ${prefixCls}__item--money p-16px inline-block rounded-6px`"
|
||||
>
|
||||
<Icon icon="svg-icon:money" :size="40" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col justify-between">
|
||||
<div :class="`${prefixCls}__item--text text-16px text-gray-500 text-right`"
|
||||
>上周流水</div
|
||||
>
|
||||
<CountTo
|
||||
class="text-20px font-700 text-right"
|
||||
:start-val="0"
|
||||
:end-val="data.lastWeekTurnover"
|
||||
:decimals="data.lastWeekTurnover ? 2 : 0"
|
||||
:duration="2600"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</ElSkeleton>
|
||||
</ElCard>
|
||||
</ElCol>
|
||||
|
||||
<ElCol :xl="6" :lg="6" :md="12" :sm="12" :xs="24">
|
||||
<ElCard shadow="hover" class="mb-20px">
|
||||
<ElSkeleton :loading="loading" animated :rows="2">
|
||||
<template #default>
|
||||
<div :class="`${prefixCls}__item flex justify-between`">
|
||||
<div>
|
||||
<div
|
||||
:class="`${prefixCls}__item--icon ${prefixCls}__item--money p-16px inline-block rounded-6px`"
|
||||
>
|
||||
<Icon icon="svg-icon:money" :size="40" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col justify-between">
|
||||
<div :class="`${prefixCls}__item--text text-16px text-gray-500 text-right`"
|
||||
>本周流水</div
|
||||
>
|
||||
<CountTo
|
||||
class="text-20px font-700 text-right"
|
||||
:start-val="0"
|
||||
:end-val="data.currentWeekTurnover"
|
||||
:decimals="data.currentWeekTurnover ? 2 : 0"
|
||||
:duration="2600"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</ElSkeleton>
|
||||
</ElCard>
|
||||
</ElCol>
|
||||
|
||||
<ElCol :xl="6" :lg="6" :md="12" :sm="12" :xs="24">
|
||||
<ElCard shadow="hover" class="mb-20px">
|
||||
<ElSkeleton :loading="loading" animated :rows="2">
|
||||
<template #default>
|
||||
<div :class="`${prefixCls}__item flex justify-between`">
|
||||
<div>
|
||||
<div
|
||||
:class="`${prefixCls}__item--icon ${prefixCls}__item--money p-16px inline-block rounded-6px`"
|
||||
>
|
||||
<Icon icon="svg-icon:money" :size="40" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col justify-between">
|
||||
<div :class="`${prefixCls}__item--text text-16px text-gray-500 text-right`"
|
||||
>昨日流水</div
|
||||
>
|
||||
<CountTo
|
||||
class="text-20px font-700 text-right"
|
||||
:start-val="0"
|
||||
:end-val="data.lastDayTurnover"
|
||||
:decimals="data.lastDayTurnover ? 2 : 0"
|
||||
:duration="2600"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</ElSkeleton>
|
||||
</ElCard>
|
||||
</ElCol>
|
||||
|
||||
<ElCol :xl="6" :lg="6" :md="12" :sm="12" :xs="24">
|
||||
<ElCard shadow="hover" class="mb-20px">
|
||||
<ElSkeleton :loading="loading" animated :rows="2">
|
||||
<template #default>
|
||||
<div :class="`${prefixCls}__item flex justify-between`">
|
||||
<div>
|
||||
<div
|
||||
:class="`${prefixCls}__item--icon ${prefixCls}__item--money p-16px inline-block rounded-6px`"
|
||||
>
|
||||
<Icon icon="svg-icon:money" :size="40" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col justify-between">
|
||||
<div :class="`${prefixCls}__item--text text-16px text-gray-500 text-right`"
|
||||
>今日流水</div
|
||||
>
|
||||
<CountTo
|
||||
class="text-20px font-700 text-right"
|
||||
:start-val="0"
|
||||
:end-val="data.currentDayTurnover"
|
||||
:decimals="data.currentDayTurnover ? 2 : 0"
|
||||
:duration="2600"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</ElSkeleton>
|
||||
</ElCard>
|
||||
</ElCol>
|
||||
</ElRow>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@prefix-cls: ~'@{adminNamespace}-panel';
|
||||
|
||||
.@{prefix-cls} {
|
||||
&__item {
|
||||
&--peoples {
|
||||
color: #40c9c6;
|
||||
}
|
||||
|
||||
&--message {
|
||||
color: #36a3f7;
|
||||
}
|
||||
|
||||
&--money {
|
||||
color: #f4516c;
|
||||
}
|
||||
|
||||
&--shopping {
|
||||
color: #34bfa3;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
:deep(.@{adminNamespace}-icon) {
|
||||
color: #fff !important;
|
||||
}
|
||||
.@{prefix-cls}__item--icon {
|
||||
transition: all 0.38s ease-out;
|
||||
}
|
||||
.@{prefix-cls}__item--peoples {
|
||||
background: #40c9c6;
|
||||
}
|
||||
.@{prefix-cls}__item--message {
|
||||
background: #36a3f7;
|
||||
}
|
||||
.@{prefix-cls}__item--money {
|
||||
background: #f4516c;
|
||||
}
|
||||
.@{prefix-cls}__item--shopping {
|
||||
background: #34bfa3;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
304
promo-ui2/src/views/Dashboard/echarts-data.ts
Normal file
304
promo-ui2/src/views/Dashboard/echarts-data.ts
Normal file
@@ -0,0 +1,304 @@
|
||||
import { EChartsOption } from 'echarts'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
export const lineOptions: EChartsOption = {
|
||||
xAxis: {
|
||||
data: [
|
||||
t('analysis.january'),
|
||||
t('analysis.february'),
|
||||
t('analysis.march'),
|
||||
t('analysis.april'),
|
||||
t('analysis.may'),
|
||||
t('analysis.june'),
|
||||
t('analysis.july'),
|
||||
t('analysis.august'),
|
||||
t('analysis.september'),
|
||||
t('analysis.october'),
|
||||
t('analysis.november'),
|
||||
t('analysis.december')
|
||||
],
|
||||
boundaryGap: false,
|
||||
axisTick: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
left: 20,
|
||||
right: 20,
|
||||
bottom: 20,
|
||||
top: 30,
|
||||
containLabel: true
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'cross'
|
||||
},
|
||||
padding: [5, 10]
|
||||
},
|
||||
yAxis: {
|
||||
axisTick: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
data: ['日流水', '日邀请']
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: t('analysis.estimate'),
|
||||
smooth: true,
|
||||
type: 'line',
|
||||
data: [100, 120, 161, 134, 105, 160, 165, 114, 163, 185, 118, 123],
|
||||
animationDuration: 2800,
|
||||
animationEasing: 'cubicInOut'
|
||||
},
|
||||
{
|
||||
name: t('analysis.actual'),
|
||||
smooth: true,
|
||||
type: 'line',
|
||||
itemStyle: {},
|
||||
data: [120, 82, 91, 154, 162, 140, 145, 250, 134, 56, 99, 123],
|
||||
animationDuration: 2800,
|
||||
animationEasing: 'quadraticOut'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
export const pieOptions: EChartsOption = {
|
||||
title: {
|
||||
text: t('analysis.userAccessSource'),
|
||||
left: 'center'
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
formatter: '{a} <br/>{b} : {c} ({d}%)'
|
||||
},
|
||||
legend: {
|
||||
orient: 'vertical',
|
||||
left: 'left',
|
||||
data: [
|
||||
t('analysis.directAccess'),
|
||||
t('analysis.mailMarketing'),
|
||||
t('analysis.allianceAdvertising'),
|
||||
t('analysis.videoAdvertising'),
|
||||
t('analysis.searchEngines')
|
||||
]
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: t('analysis.userAccessSource'),
|
||||
type: 'pie',
|
||||
radius: '55%',
|
||||
center: ['50%', '60%'],
|
||||
data: [
|
||||
{ value: 335, name: t('analysis.directAccess') },
|
||||
{ value: 310, name: t('analysis.mailMarketing') },
|
||||
{ value: 234, name: t('analysis.allianceAdvertising') },
|
||||
{ value: 135, name: t('analysis.videoAdvertising') },
|
||||
{ value: 1548, name: t('analysis.searchEngines') }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
export const barOptions: EChartsOption = {
|
||||
title: {
|
||||
text: t('analysis.weeklyUserActivity'),
|
||||
left: 'center'
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'shadow'
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
left: 50,
|
||||
right: 20,
|
||||
bottom: 20
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: [
|
||||
t('analysis.monday'),
|
||||
t('analysis.tuesday'),
|
||||
t('analysis.wednesday'),
|
||||
t('analysis.thursday'),
|
||||
t('analysis.friday'),
|
||||
t('analysis.saturday'),
|
||||
t('analysis.sunday')
|
||||
],
|
||||
axisTick: {
|
||||
alignWithLabel: true
|
||||
}
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value'
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: t('analysis.activeQuantity'),
|
||||
data: [13253, 34235, 26321, 12340, 24643, 1322, 1324],
|
||||
type: 'bar'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
export const radarOption: EChartsOption = {
|
||||
legend: {
|
||||
data: [t('workplace.personal'), t('workplace.team')]
|
||||
},
|
||||
radar: {
|
||||
// shape: 'circle',
|
||||
indicator: [
|
||||
{ name: t('workplace.quote'), max: 65 },
|
||||
{ name: t('workplace.contribution'), max: 160 },
|
||||
{ name: t('workplace.hot'), max: 300 },
|
||||
{ name: t('workplace.yield'), max: 130 },
|
||||
{ name: t('workplace.follow'), max: 100 }
|
||||
]
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: `xxx${t('workplace.index')}`,
|
||||
type: 'radar',
|
||||
data: [
|
||||
{
|
||||
value: [42, 30, 20, 35, 80],
|
||||
name: t('workplace.personal')
|
||||
},
|
||||
{
|
||||
value: [50, 140, 290, 100, 90],
|
||||
name: t('workplace.team')
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
export const wordOptions = {
|
||||
series: [
|
||||
{
|
||||
type: 'wordCloud',
|
||||
gridSize: 2,
|
||||
sizeRange: [12, 50],
|
||||
rotationRange: [-90, 90],
|
||||
shape: 'pentagon',
|
||||
width: 600,
|
||||
height: 400,
|
||||
drawOutOfBound: true,
|
||||
textStyle: {
|
||||
color: function () {
|
||||
return (
|
||||
'rgb(' +
|
||||
[
|
||||
Math.round(Math.random() * 160),
|
||||
Math.round(Math.random() * 160),
|
||||
Math.round(Math.random() * 160)
|
||||
].join(',') +
|
||||
')'
|
||||
)
|
||||
}
|
||||
},
|
||||
emphasis: {
|
||||
textStyle: {
|
||||
shadowBlur: 10,
|
||||
shadowColor: '#333'
|
||||
}
|
||||
},
|
||||
data: [
|
||||
{
|
||||
name: 'Sam S Club',
|
||||
value: 10000,
|
||||
textStyle: {
|
||||
color: 'black'
|
||||
},
|
||||
emphasis: {
|
||||
textStyle: {
|
||||
color: 'red'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'Macys',
|
||||
value: 6181
|
||||
},
|
||||
{
|
||||
name: 'Amy Schumer',
|
||||
value: 4386
|
||||
},
|
||||
{
|
||||
name: 'Jurassic World',
|
||||
value: 4055
|
||||
},
|
||||
{
|
||||
name: 'Charter Communications',
|
||||
value: 2467
|
||||
},
|
||||
{
|
||||
name: 'Chick Fil A',
|
||||
value: 2244
|
||||
},
|
||||
{
|
||||
name: 'Planet Fitness',
|
||||
value: 1898
|
||||
},
|
||||
{
|
||||
name: 'Pitch Perfect',
|
||||
value: 1484
|
||||
},
|
||||
{
|
||||
name: 'Express',
|
||||
value: 1112
|
||||
},
|
||||
{
|
||||
name: 'Home',
|
||||
value: 965
|
||||
},
|
||||
{
|
||||
name: 'Johnny Depp',
|
||||
value: 847
|
||||
},
|
||||
{
|
||||
name: 'Lena Dunham',
|
||||
value: 582
|
||||
},
|
||||
{
|
||||
name: 'Lewis Hamilton',
|
||||
value: 555
|
||||
},
|
||||
{
|
||||
name: 'KXAN',
|
||||
value: 550
|
||||
},
|
||||
{
|
||||
name: 'Mary Ellen Mark',
|
||||
value: 462
|
||||
},
|
||||
{
|
||||
name: 'Farrah Abraham',
|
||||
value: 366
|
||||
},
|
||||
{
|
||||
name: 'Rita Ora',
|
||||
value: 360
|
||||
},
|
||||
{
|
||||
name: 'Serena Williams',
|
||||
value: 282
|
||||
},
|
||||
{
|
||||
name: 'NCAA baseball tournament',
|
||||
value: 273
|
||||
},
|
||||
{
|
||||
name: 'Point Break',
|
||||
value: 265
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
17
promo-ui2/src/views/Error/403.vue
Normal file
17
promo-ui2/src/views/Error/403.vue
Normal file
@@ -0,0 +1,17 @@
|
||||
<script setup lang="ts">
|
||||
import { Error } from '@/components/Error'
|
||||
import { usePermissionStore } from '@/store/modules/permission'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
const { push } = useRouter()
|
||||
|
||||
const permissionStore = usePermissionStore()
|
||||
|
||||
const errorClick = () => {
|
||||
push(permissionStore.addRouters[0]?.path as string)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Error type="403" @error-click="errorClick" />
|
||||
</template>
|
||||
17
promo-ui2/src/views/Error/404.vue
Normal file
17
promo-ui2/src/views/Error/404.vue
Normal file
@@ -0,0 +1,17 @@
|
||||
<script setup lang="ts">
|
||||
import { Error } from '@/components/Error'
|
||||
import { usePermissionStore } from '@/store/modules/permission'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
const { push } = useRouter()
|
||||
|
||||
const permissionStore = usePermissionStore()
|
||||
|
||||
const errorClick = () => {
|
||||
push(permissionStore.addRouters[0]?.path as string)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Error @error-click="errorClick" />
|
||||
</template>
|
||||
17
promo-ui2/src/views/Error/500.vue
Normal file
17
promo-ui2/src/views/Error/500.vue
Normal file
@@ -0,0 +1,17 @@
|
||||
<script setup lang="ts">
|
||||
import { Error } from '@/components/Error'
|
||||
import { usePermissionStore } from '@/store/modules/permission'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
const { push } = useRouter()
|
||||
|
||||
const permissionStore = usePermissionStore()
|
||||
|
||||
const errorClick = () => {
|
||||
push(permissionStore.addRouters[0]?.path as string)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Error type="500" @error-click="errorClick" />
|
||||
</template>
|
||||
340
promo-ui2/src/views/Example/Dialog/ExampleDialog.vue
Normal file
340
promo-ui2/src/views/Example/Dialog/ExampleDialog.vue
Normal file
@@ -0,0 +1,340 @@
|
||||
<script setup lang="tsx">
|
||||
import { ContentWrap } from '@/components/ContentWrap'
|
||||
import { Search } from '@/components/Search'
|
||||
import { Dialog } from '@/components/Dialog'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { ElTag } from 'element-plus'
|
||||
import { Table } from '@/components/Table'
|
||||
import { getTableListApi, saveTableApi, delTableListApi } from '@/api/table'
|
||||
import { useTable } from '@/hooks/web/useTable'
|
||||
import { TableData } from '@/api/table/types'
|
||||
import { ref, unref, reactive } from 'vue'
|
||||
import Write from './components/Write.vue'
|
||||
import Detail from './components/Detail.vue'
|
||||
import { CrudSchema, useCrudSchemas } from '@/hooks/web/useCrudSchemas'
|
||||
import { BaseButton } from '@/components/Button'
|
||||
|
||||
const ids = ref<string[]>([])
|
||||
|
||||
const { tableRegister, tableState, tableMethods } = useTable({
|
||||
fetchDataApi: async () => {
|
||||
const { currentPage, pageSize } = tableState
|
||||
const res = await getTableListApi({
|
||||
pageIndex: unref(currentPage),
|
||||
pageSize: unref(pageSize),
|
||||
...unref(searchParams)
|
||||
})
|
||||
return {
|
||||
list: res.data.list,
|
||||
total: res.data.total
|
||||
}
|
||||
},
|
||||
fetchDelApi: async () => {
|
||||
const res = await delTableListApi(unref(ids))
|
||||
return !!res
|
||||
}
|
||||
})
|
||||
const { loading, dataList, total, currentPage, pageSize } = tableState
|
||||
const { getList, getElTableExpose, delList } = tableMethods
|
||||
|
||||
const searchParams = ref({})
|
||||
const setSearchParams = (params: any) => {
|
||||
searchParams.value = params
|
||||
getList()
|
||||
}
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const crudSchemas = reactive<CrudSchema[]>([
|
||||
{
|
||||
field: 'selection',
|
||||
search: {
|
||||
hidden: true
|
||||
},
|
||||
form: {
|
||||
hidden: true
|
||||
},
|
||||
detail: {
|
||||
hidden: true
|
||||
},
|
||||
table: {
|
||||
type: 'selection'
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'index',
|
||||
label: t('tableDemo.index'),
|
||||
type: 'index',
|
||||
search: {
|
||||
hidden: true
|
||||
},
|
||||
form: {
|
||||
hidden: true
|
||||
},
|
||||
detail: {
|
||||
hidden: true
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'title',
|
||||
label: t('tableDemo.title'),
|
||||
search: {
|
||||
component: 'Input'
|
||||
},
|
||||
form: {
|
||||
component: 'Input',
|
||||
colProps: {
|
||||
span: 24
|
||||
}
|
||||
},
|
||||
detail: {
|
||||
span: 24
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'author',
|
||||
label: t('tableDemo.author'),
|
||||
search: {
|
||||
hidden: true
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'display_time',
|
||||
label: t('tableDemo.displayTime'),
|
||||
search: {
|
||||
hidden: true
|
||||
},
|
||||
form: {
|
||||
component: 'DatePicker',
|
||||
componentProps: {
|
||||
type: 'datetime',
|
||||
valueFormat: 'YYYY-MM-DD HH:mm:ss'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'importance',
|
||||
label: t('tableDemo.importance'),
|
||||
search: {
|
||||
hidden: true
|
||||
},
|
||||
form: {
|
||||
component: 'Select',
|
||||
componentProps: {
|
||||
style: {
|
||||
width: '100%'
|
||||
},
|
||||
options: [
|
||||
{
|
||||
label: '重要',
|
||||
value: 3
|
||||
},
|
||||
{
|
||||
label: '良好',
|
||||
value: 2
|
||||
},
|
||||
{
|
||||
label: '一般',
|
||||
value: 1
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
detail: {
|
||||
slots: {
|
||||
default: (data: any) => {
|
||||
return (
|
||||
<ElTag
|
||||
type={
|
||||
data.importance === 1 ? 'success' : data.importance === 2 ? 'warning' : 'danger'
|
||||
}
|
||||
>
|
||||
{data.importance === 1
|
||||
? t('tableDemo.important')
|
||||
: data.importance === 2
|
||||
? t('tableDemo.good')
|
||||
: t('tableDemo.commonly')}
|
||||
</ElTag>
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'pageviews',
|
||||
label: t('tableDemo.pageviews'),
|
||||
search: {
|
||||
hidden: true
|
||||
},
|
||||
form: {
|
||||
component: 'InputNumber',
|
||||
value: 0
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'content',
|
||||
label: t('exampleDemo.content'),
|
||||
search: {
|
||||
hidden: true
|
||||
},
|
||||
table: {
|
||||
show: false
|
||||
},
|
||||
form: {
|
||||
component: 'Editor',
|
||||
colProps: {
|
||||
span: 24
|
||||
}
|
||||
},
|
||||
detail: {
|
||||
span: 24,
|
||||
slots: {
|
||||
default: (data: any) => {
|
||||
return <div innerHTML={data.content}></div>
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'action',
|
||||
width: '260px',
|
||||
label: t('tableDemo.action'),
|
||||
search: {
|
||||
hidden: true
|
||||
},
|
||||
form: {
|
||||
hidden: true
|
||||
},
|
||||
detail: {
|
||||
hidden: true
|
||||
},
|
||||
table: {
|
||||
slots: {
|
||||
default: (data: any) => {
|
||||
return (
|
||||
<>
|
||||
<BaseButton type="primary" onClick={() => action(data.row, 'edit')}>
|
||||
{t('exampleDemo.edit')}
|
||||
</BaseButton>
|
||||
<BaseButton type="success" onClick={() => action(data.row, 'detail')}>
|
||||
{t('exampleDemo.detail')}
|
||||
</BaseButton>
|
||||
<BaseButton type="danger" onClick={() => delData(data.row)}>
|
||||
{t('exampleDemo.del')}
|
||||
</BaseButton>
|
||||
</>
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
])
|
||||
|
||||
// @ts-ignore
|
||||
const { allSchemas } = useCrudSchemas(crudSchemas)
|
||||
|
||||
const dialogVisible = ref(false)
|
||||
const dialogTitle = ref('')
|
||||
|
||||
const currentRow = ref<TableData | null>(null)
|
||||
const actionType = ref('')
|
||||
|
||||
const AddAction = () => {
|
||||
dialogTitle.value = t('exampleDemo.add')
|
||||
currentRow.value = null
|
||||
dialogVisible.value = true
|
||||
actionType.value = ''
|
||||
}
|
||||
|
||||
const delLoading = ref(false)
|
||||
|
||||
const delData = async (row: TableData | null) => {
|
||||
const elTableExpose = await getElTableExpose()
|
||||
ids.value = row ? [row.id] : elTableExpose?.getSelectionRows().map((v: TableData) => v.id) || []
|
||||
delLoading.value = true
|
||||
await delList(unref(ids).length).finally(() => {
|
||||
delLoading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
const action = (row: TableData, type: string) => {
|
||||
dialogTitle.value = t(type === 'edit' ? 'exampleDemo.edit' : 'exampleDemo.detail')
|
||||
actionType.value = type
|
||||
currentRow.value = row
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
const writeRef = ref<ComponentRef<typeof Write>>()
|
||||
|
||||
const saveLoading = ref(false)
|
||||
|
||||
const save = async () => {
|
||||
const write = unref(writeRef)
|
||||
const formData = await write?.submit()
|
||||
if (formData) {
|
||||
saveLoading.value = true
|
||||
const res = await saveTableApi(formData)
|
||||
.catch(() => {})
|
||||
.finally(() => {
|
||||
saveLoading.value = false
|
||||
})
|
||||
if (res) {
|
||||
dialogVisible.value = false
|
||||
currentPage.value = 1
|
||||
getList()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<Search :schema="allSchemas.searchSchema" @search="setSearchParams" @reset="setSearchParams" />
|
||||
|
||||
<div class="mb-10px">
|
||||
<BaseButton type="primary" @click="AddAction">{{ t('exampleDemo.add') }}</BaseButton>
|
||||
<BaseButton :loading="delLoading" type="danger" @click="delData(null)">
|
||||
{{ t('exampleDemo.del') }}
|
||||
</BaseButton>
|
||||
</div>
|
||||
|
||||
<Table
|
||||
v-model:pageSize="pageSize"
|
||||
v-model:currentPage="currentPage"
|
||||
:columns="allSchemas.tableColumns"
|
||||
:data="dataList"
|
||||
:loading="loading"
|
||||
:pagination="{
|
||||
total: total
|
||||
}"
|
||||
@register="tableRegister"
|
||||
/>
|
||||
</ContentWrap>
|
||||
|
||||
<Dialog v-model="dialogVisible" :title="dialogTitle">
|
||||
<Write
|
||||
v-if="actionType !== 'detail'"
|
||||
ref="writeRef"
|
||||
:form-schema="allSchemas.formSchema"
|
||||
:current-row="currentRow"
|
||||
/>
|
||||
|
||||
<Detail
|
||||
v-if="actionType === 'detail'"
|
||||
:detail-schema="allSchemas.detailSchema"
|
||||
:current-row="currentRow"
|
||||
/>
|
||||
|
||||
<template #footer>
|
||||
<BaseButton
|
||||
v-if="actionType !== 'detail'"
|
||||
type="primary"
|
||||
:loading="saveLoading"
|
||||
@click="save"
|
||||
>
|
||||
{{ t('exampleDemo.save') }}
|
||||
</BaseButton>
|
||||
<BaseButton @click="dialogVisible = false">{{ t('dialogDemo.close') }}</BaseButton>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
20
promo-ui2/src/views/Example/Dialog/components/Detail.vue
Normal file
20
promo-ui2/src/views/Example/Dialog/components/Detail.vue
Normal file
@@ -0,0 +1,20 @@
|
||||
<script setup lang="ts">
|
||||
import { PropType } from 'vue'
|
||||
import type { TableData } from '@/api/table/types'
|
||||
import { Descriptions, DescriptionsSchema } from '@/components/Descriptions'
|
||||
|
||||
defineProps({
|
||||
currentRow: {
|
||||
type: Object as PropType<Nullable<TableData>>,
|
||||
default: () => null
|
||||
},
|
||||
detailSchema: {
|
||||
type: Array as PropType<DescriptionsSchema[]>,
|
||||
default: () => []
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Descriptions :schema="detailSchema" :data="currentRow || {}" />
|
||||
</template>
|
||||
63
promo-ui2/src/views/Example/Dialog/components/Write.vue
Normal file
63
promo-ui2/src/views/Example/Dialog/components/Write.vue
Normal file
@@ -0,0 +1,63 @@
|
||||
<script setup lang="ts">
|
||||
import { Form, FormSchema } from '@/components/Form'
|
||||
import { useForm } from '@/hooks/web/useForm'
|
||||
import { PropType, reactive, watch } from 'vue'
|
||||
import { TableData } from '@/api/table/types'
|
||||
import { useValidator } from '@/hooks/web/useValidator'
|
||||
|
||||
const { required } = useValidator()
|
||||
|
||||
const props = defineProps({
|
||||
currentRow: {
|
||||
type: Object as PropType<Nullable<TableData>>,
|
||||
default: () => null
|
||||
},
|
||||
formSchema: {
|
||||
type: Array as PropType<FormSchema[]>,
|
||||
default: () => []
|
||||
}
|
||||
})
|
||||
|
||||
const rules = reactive({
|
||||
title: [required()],
|
||||
author: [required()],
|
||||
importance: [required()],
|
||||
pageviews: [required()],
|
||||
display_time: [required()],
|
||||
content: [required()]
|
||||
})
|
||||
|
||||
const { formRegister, formMethods } = useForm()
|
||||
const { setValues, getFormData, getElFormExpose } = formMethods
|
||||
|
||||
const submit = async () => {
|
||||
const elForm = await getElFormExpose()
|
||||
const valid = await elForm?.validate().catch((err) => {
|
||||
console.log(err)
|
||||
})
|
||||
if (valid) {
|
||||
const formData = await getFormData()
|
||||
return formData
|
||||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.currentRow,
|
||||
(currentRow) => {
|
||||
if (!currentRow) return
|
||||
setValues(currentRow)
|
||||
},
|
||||
{
|
||||
deep: true,
|
||||
immediate: true
|
||||
}
|
||||
)
|
||||
|
||||
defineExpose({
|
||||
submit
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Form :rules="rules" @register="formRegister" :schema="formSchema" />
|
||||
</template>
|
||||
51
promo-ui2/src/views/Example/Page/ExampleAdd.vue
Normal file
51
promo-ui2/src/views/Example/Page/ExampleAdd.vue
Normal file
@@ -0,0 +1,51 @@
|
||||
<script setup lang="ts">
|
||||
import Write from './components/Write.vue'
|
||||
import { ContentDetailWrap } from '@/components/ContentDetailWrap'
|
||||
import { ref, unref } from 'vue'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { saveTableApi } from '@/api/table'
|
||||
import { useEventBus } from '@/hooks/event/useEventBus'
|
||||
|
||||
const { emit } = useEventBus()
|
||||
|
||||
const { push, go } = useRouter()
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const writeRef = ref<ComponentRef<typeof Write>>()
|
||||
|
||||
const loading = ref(false)
|
||||
|
||||
const save = async () => {
|
||||
const write = unref(writeRef)
|
||||
const formData = await write?.submit()
|
||||
if (formData) {
|
||||
loading.value = true
|
||||
const res = await saveTableApi(formData)
|
||||
.catch(() => {})
|
||||
.finally(() => {
|
||||
loading.value = false
|
||||
})
|
||||
if (res) {
|
||||
emit('getList', 'add')
|
||||
push('/example/example-page')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ContentDetailWrap :title="t('exampleDemo.add')" @back="push('/example/example-page')">
|
||||
<Write ref="writeRef" />
|
||||
|
||||
<template #header>
|
||||
<BaseButton @click="go(-1)">
|
||||
{{ t('common.back') }}
|
||||
</BaseButton>
|
||||
<BaseButton type="primary" :loading="loading" @click="save"
|
||||
>{{ t('exampleDemo.save') }}
|
||||
</BaseButton>
|
||||
</template>
|
||||
</ContentDetailWrap>
|
||||
</template>
|
||||
37
promo-ui2/src/views/Example/Page/ExampleDetail.vue
Normal file
37
promo-ui2/src/views/Example/Page/ExampleDetail.vue
Normal file
@@ -0,0 +1,37 @@
|
||||
<script setup lang="ts">
|
||||
import Detail from './components/Detail.vue'
|
||||
import { ContentDetailWrap } from '@/components/ContentDetailWrap'
|
||||
import { ref } from 'vue'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
import { getTableDetApi } from '@/api/table'
|
||||
import { TableData } from '@/api/table/types'
|
||||
|
||||
const { push, go } = useRouter()
|
||||
|
||||
const { query } = useRoute()
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const currentRow = ref<Nullable<TableData>>(null)
|
||||
|
||||
const getTableDet = async () => {
|
||||
const res = await getTableDetApi(query.id as string)
|
||||
if (res) {
|
||||
currentRow.value = res.data
|
||||
}
|
||||
}
|
||||
|
||||
getTableDet()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ContentDetailWrap :title="t('exampleDemo.detail')" @back="push('/example/example-page')">
|
||||
<template #header>
|
||||
<BaseButton @click="go(-1)">
|
||||
{{ t('common.back') }}
|
||||
</BaseButton>
|
||||
</template>
|
||||
<Detail :current-row="currentRow" />
|
||||
</ContentDetailWrap>
|
||||
</template>
|
||||
66
promo-ui2/src/views/Example/Page/ExampleEdit.vue
Normal file
66
promo-ui2/src/views/Example/Page/ExampleEdit.vue
Normal file
@@ -0,0 +1,66 @@
|
||||
<script setup lang="ts">
|
||||
import Write from './components/Write.vue'
|
||||
import { ContentDetailWrap } from '@/components/ContentDetailWrap'
|
||||
import { ref, unref } from 'vue'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
import { saveTableApi, getTableDetApi } from '@/api/table'
|
||||
import { TableData } from '@/api/table/types'
|
||||
import { useEventBus } from '@/hooks/event/useEventBus'
|
||||
|
||||
const { emit } = useEventBus()
|
||||
|
||||
const { push, go } = useRouter()
|
||||
|
||||
const { query } = useRoute()
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const currentRow = ref<Nullable<TableData>>(null)
|
||||
|
||||
const getTableDet = async () => {
|
||||
const res = await getTableDetApi(query.id as string)
|
||||
if (res) {
|
||||
currentRow.value = res.data
|
||||
}
|
||||
}
|
||||
|
||||
getTableDet()
|
||||
|
||||
const writeRef = ref<ComponentRef<typeof Write>>()
|
||||
|
||||
const loading = ref(false)
|
||||
|
||||
const save = async () => {
|
||||
const write = unref(writeRef)
|
||||
const formData = await write?.submit()
|
||||
if (formData) {
|
||||
loading.value = true
|
||||
const res = await saveTableApi(formData)
|
||||
.catch(() => {})
|
||||
.finally(() => {
|
||||
loading.value = false
|
||||
})
|
||||
if (res) {
|
||||
emit('getList', 'editor')
|
||||
push('/example/example-page')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ContentDetailWrap :title="t('exampleDemo.edit')" @back="push('/example/example-page')">
|
||||
<Write ref="writeRef" :current-row="currentRow" />
|
||||
|
||||
<template #header>
|
||||
<BaseButton @click="go(-1)">
|
||||
{{ t('common.back') }}
|
||||
</BaseButton>
|
||||
<BaseButton type="primary" :loading="loading" @click="save">
|
||||
{{ t('exampleDemo.save') }}
|
||||
</BaseButton>
|
||||
</template>
|
||||
</ContentDetailWrap>
|
||||
</template>
|
||||
@/hooks/event/useEventBus
|
||||
297
promo-ui2/src/views/Example/Page/ExamplePage.vue
Normal file
297
promo-ui2/src/views/Example/Page/ExamplePage.vue
Normal file
@@ -0,0 +1,297 @@
|
||||
<script setup lang="tsx">
|
||||
import { ContentWrap } from '@/components/ContentWrap'
|
||||
import { Search } from '@/components/Search'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { ElTag } from 'element-plus'
|
||||
import { Table } from '@/components/Table'
|
||||
import { getTableListApi, delTableListApi } from '@/api/table'
|
||||
import { useTable } from '@/hooks/web/useTable'
|
||||
import { TableData } from '@/api/table/types'
|
||||
import { reactive, ref, unref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useEventBus } from '@/hooks/event/useEventBus'
|
||||
import { CrudSchema, useCrudSchemas } from '@/hooks/web/useCrudSchemas'
|
||||
import { BaseButton } from '@/components/Button'
|
||||
|
||||
defineOptions({
|
||||
name: 'ExamplePage'
|
||||
})
|
||||
|
||||
const { push } = useRouter()
|
||||
|
||||
const ids = ref<string[]>([])
|
||||
|
||||
const searchParams = ref({})
|
||||
const setSearchParams = (params: any) => {
|
||||
searchParams.value = params
|
||||
getList()
|
||||
}
|
||||
|
||||
const { tableRegister, tableState, tableMethods } = useTable({
|
||||
fetchDataApi: async () => {
|
||||
const { currentPage, pageSize } = tableState
|
||||
const res = await getTableListApi({
|
||||
pageIndex: unref(currentPage),
|
||||
pageSize: unref(pageSize),
|
||||
...unref(searchParams)
|
||||
})
|
||||
return {
|
||||
list: res.data.list,
|
||||
total: res.data.total
|
||||
}
|
||||
},
|
||||
fetchDelApi: async () => {
|
||||
const res = await delTableListApi(unref(ids))
|
||||
return !!res
|
||||
}
|
||||
})
|
||||
const { loading, dataList, total, currentPage, pageSize } = tableState
|
||||
const { getList, getElTableExpose, delList } = tableMethods
|
||||
|
||||
getList()
|
||||
|
||||
useEventBus({
|
||||
name: 'getList',
|
||||
callback: (type: string) => {
|
||||
if (type === 'add') {
|
||||
currentPage.value = 1
|
||||
}
|
||||
getList()
|
||||
}
|
||||
})
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const crudSchemas = reactive<CrudSchema[]>([
|
||||
{
|
||||
field: 'selection',
|
||||
search: {
|
||||
hidden: true
|
||||
},
|
||||
form: {
|
||||
hidden: true
|
||||
},
|
||||
detail: {
|
||||
hidden: true
|
||||
},
|
||||
table: {
|
||||
type: 'selection'
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'index',
|
||||
label: t('tableDemo.index'),
|
||||
type: 'index',
|
||||
search: {
|
||||
hidden: true
|
||||
},
|
||||
form: {
|
||||
hidden: true
|
||||
},
|
||||
detail: {
|
||||
hidden: true
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'title',
|
||||
label: t('tableDemo.title'),
|
||||
search: {
|
||||
component: 'Input'
|
||||
},
|
||||
form: {
|
||||
component: 'Input',
|
||||
colProps: {
|
||||
span: 24
|
||||
}
|
||||
},
|
||||
detail: {
|
||||
span: 24
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'author',
|
||||
label: t('tableDemo.author'),
|
||||
search: {
|
||||
hidden: true
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'display_time',
|
||||
label: t('tableDemo.displayTime'),
|
||||
search: {
|
||||
hidden: true
|
||||
},
|
||||
form: {
|
||||
component: 'DatePicker',
|
||||
componentProps: {
|
||||
type: 'datetime',
|
||||
valueFormat: 'YYYY-MM-DD HH:mm:ss'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'importance',
|
||||
label: t('tableDemo.importance'),
|
||||
search: {
|
||||
hidden: true
|
||||
},
|
||||
form: {
|
||||
component: 'Select',
|
||||
componentProps: {
|
||||
style: {
|
||||
width: '100%'
|
||||
},
|
||||
options: [
|
||||
{
|
||||
label: '重要',
|
||||
value: 3
|
||||
},
|
||||
{
|
||||
label: '良好',
|
||||
value: 2
|
||||
},
|
||||
{
|
||||
label: '一般',
|
||||
value: 1
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
detail: {
|
||||
slots: {
|
||||
default: (data: any) => {
|
||||
return (
|
||||
<ElTag
|
||||
type={
|
||||
data.importance === 1 ? 'success' : data.importance === 2 ? 'warning' : 'danger'
|
||||
}
|
||||
>
|
||||
{data.importance === 1
|
||||
? t('tableDemo.important')
|
||||
: data.importance === 2
|
||||
? t('tableDemo.good')
|
||||
: t('tableDemo.commonly')}
|
||||
</ElTag>
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'pageviews',
|
||||
label: t('tableDemo.pageviews'),
|
||||
search: {
|
||||
hidden: true
|
||||
},
|
||||
form: {
|
||||
component: 'InputNumber',
|
||||
value: 0
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'content',
|
||||
label: t('exampleDemo.content'),
|
||||
search: {
|
||||
hidden: true
|
||||
},
|
||||
table: {
|
||||
show: false
|
||||
},
|
||||
form: {
|
||||
component: 'Editor',
|
||||
colProps: {
|
||||
span: 24
|
||||
}
|
||||
},
|
||||
detail: {
|
||||
span: 24,
|
||||
slots: {
|
||||
default: (data: any) => {
|
||||
return <div innerHTML={data.content}></div>
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'action',
|
||||
width: '260px',
|
||||
label: t('tableDemo.action'),
|
||||
search: {
|
||||
hidden: true
|
||||
},
|
||||
form: {
|
||||
hidden: true
|
||||
},
|
||||
detail: {
|
||||
hidden: true
|
||||
},
|
||||
table: {
|
||||
slots: {
|
||||
default: (data: any) => {
|
||||
return (
|
||||
<>
|
||||
<BaseButton type="primary" onClick={() => action(data.row, 'edit')}>
|
||||
{t('exampleDemo.edit')}
|
||||
</BaseButton>
|
||||
<BaseButton type="success" onClick={() => action(data.row, 'detail')}>
|
||||
{t('exampleDemo.detail')}
|
||||
</BaseButton>
|
||||
<BaseButton type="danger" onClick={() => delData(data.row)}>
|
||||
{t('exampleDemo.del')}
|
||||
</BaseButton>
|
||||
</>
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
])
|
||||
|
||||
// @ts-ignore
|
||||
const { allSchemas } = useCrudSchemas(crudSchemas)
|
||||
|
||||
const AddAction = () => {
|
||||
push('/example/example-add')
|
||||
}
|
||||
|
||||
const delLoading = ref(false)
|
||||
|
||||
const delData = async (row: TableData | null) => {
|
||||
const elTableExpose = await getElTableExpose()
|
||||
ids.value = row ? [row.id] : elTableExpose?.getSelectionRows().map((v: TableData) => v.id) || []
|
||||
delLoading.value = true
|
||||
await delList(unref(ids).length).finally(() => {
|
||||
delLoading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
const action = (row: TableData, type: string) => {
|
||||
push(`/example/example-${type}?id=${row.id}`)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<Search :schema="allSchemas.searchSchema" @search="setSearchParams" @reset="setSearchParams" />
|
||||
|
||||
<div class="mb-10px">
|
||||
<BaseButton type="primary" @click="AddAction">{{ t('exampleDemo.add') }}</BaseButton>
|
||||
<BaseButton :loading="delLoading" type="danger" @click="delData(null)">
|
||||
{{ t('exampleDemo.del') }}
|
||||
</BaseButton>
|
||||
</div>
|
||||
|
||||
<Table
|
||||
v-model:pageSize="pageSize"
|
||||
v-model:currentPage="currentPage"
|
||||
:columns="allSchemas.tableColumns"
|
||||
:data="dataList"
|
||||
:loading="loading"
|
||||
:pagination="{
|
||||
total: total
|
||||
}"
|
||||
@register="tableRegister"
|
||||
/>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
@/hooks/event/useEventBus
|
||||
69
promo-ui2/src/views/Example/Page/components/Detail.vue
Normal file
69
promo-ui2/src/views/Example/Page/components/Detail.vue
Normal file
@@ -0,0 +1,69 @@
|
||||
<script setup lang="tsx">
|
||||
import { PropType, reactive } from 'vue'
|
||||
import type { TableData } from '@/api/table/types'
|
||||
import { Descriptions, DescriptionsSchema } from '@/components/Descriptions'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { ElTag } from 'element-plus'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
defineProps({
|
||||
currentRow: {
|
||||
type: Object as PropType<Nullable<TableData>>,
|
||||
default: () => null
|
||||
}
|
||||
})
|
||||
|
||||
const schema = reactive<DescriptionsSchema[]>([
|
||||
{
|
||||
field: 'title',
|
||||
label: t('exampleDemo.title'),
|
||||
span: 24
|
||||
},
|
||||
{
|
||||
field: 'author',
|
||||
label: t('exampleDemo.author')
|
||||
},
|
||||
{
|
||||
field: 'display_time',
|
||||
label: t('exampleDemo.displayTime')
|
||||
},
|
||||
{
|
||||
field: 'importance',
|
||||
label: t('exampleDemo.importance'),
|
||||
slots: {
|
||||
default: (data: any) => {
|
||||
return (
|
||||
<ElTag
|
||||
type={data.importance === 1 ? 'success' : data.importance === 2 ? 'warning' : 'danger'}
|
||||
>
|
||||
{data.importance === 1
|
||||
? t('tableDemo.important')
|
||||
: data.importance === 2
|
||||
? t('tableDemo.good')
|
||||
: t('tableDemo.commonly')}
|
||||
</ElTag>
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'pageviews',
|
||||
label: t('exampleDemo.pageviews')
|
||||
},
|
||||
{
|
||||
field: 'content',
|
||||
label: t('exampleDemo.content'),
|
||||
span: 24,
|
||||
slots: {
|
||||
default: (data: any) => {
|
||||
return <div innerHTML={data.content}></div>
|
||||
}
|
||||
}
|
||||
}
|
||||
])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Descriptions :schema="schema" :data="currentRow || {}" />
|
||||
</template>
|
||||
154
promo-ui2/src/views/Example/Page/components/Write.vue
Normal file
154
promo-ui2/src/views/Example/Page/components/Write.vue
Normal file
@@ -0,0 +1,154 @@
|
||||
<script setup lang="ts">
|
||||
import { Form, FormSchema } from '@/components/Form'
|
||||
import { useForm } from '@/hooks/web/useForm'
|
||||
import { PropType, reactive, watch } from 'vue'
|
||||
import { TableData } from '@/api/table/types'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { useValidator } from '@/hooks/web/useValidator'
|
||||
import { IDomEditor } from '@wangeditor/editor'
|
||||
|
||||
const { required } = useValidator()
|
||||
|
||||
const props = defineProps({
|
||||
currentRow: {
|
||||
type: Object as PropType<Nullable<TableData>>,
|
||||
default: () => null
|
||||
}
|
||||
})
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const { formRegister, formMethods } = useForm()
|
||||
const { setValues, getFormData, getElFormExpose, setSchema } = formMethods
|
||||
|
||||
const schema = reactive<FormSchema[]>([
|
||||
{
|
||||
field: 'title',
|
||||
label: t('exampleDemo.title'),
|
||||
component: 'Input',
|
||||
formItemProps: {
|
||||
rules: [required()]
|
||||
},
|
||||
colProps: {
|
||||
span: 24
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'author',
|
||||
label: t('exampleDemo.author'),
|
||||
component: 'Input',
|
||||
formItemProps: {
|
||||
rules: [required()]
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'display_time',
|
||||
label: t('exampleDemo.displayTime'),
|
||||
component: 'DatePicker',
|
||||
componentProps: {
|
||||
type: 'datetime',
|
||||
valueFormat: 'YYYY-MM-DD HH:mm:ss'
|
||||
},
|
||||
formItemProps: {
|
||||
rules: [required()]
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'importance',
|
||||
label: t('exampleDemo.importance'),
|
||||
component: 'Select',
|
||||
formItemProps: {
|
||||
rules: [required()]
|
||||
},
|
||||
componentProps: {
|
||||
options: [
|
||||
{
|
||||
label: '重要',
|
||||
value: 3
|
||||
},
|
||||
{
|
||||
label: '良好',
|
||||
value: 2
|
||||
},
|
||||
{
|
||||
label: '一般',
|
||||
value: 1
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'pageviews',
|
||||
label: t('exampleDemo.pageviews'),
|
||||
component: 'InputNumber',
|
||||
value: 0,
|
||||
formItemProps: {
|
||||
rules: [required()]
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'content',
|
||||
component: 'Editor',
|
||||
colProps: {
|
||||
span: 24
|
||||
},
|
||||
componentProps: {
|
||||
defaultHtml: '',
|
||||
// @ts-ignore
|
||||
onChange: (edit: IDomEditor) => {
|
||||
setValues({
|
||||
content: edit.getHtml()
|
||||
})
|
||||
}
|
||||
},
|
||||
label: t('exampleDemo.content')
|
||||
}
|
||||
])
|
||||
|
||||
const rules = reactive({
|
||||
title: [required()],
|
||||
author: [required()],
|
||||
importance: [required()],
|
||||
pageviews: [required()],
|
||||
display_time: [required()],
|
||||
content: [required()]
|
||||
})
|
||||
|
||||
const submit = async () => {
|
||||
const elForm = await getElFormExpose()
|
||||
const valid = await elForm?.validate().catch((err) => {
|
||||
console.log(err)
|
||||
})
|
||||
if (valid) {
|
||||
const formData = await getFormData()
|
||||
return formData
|
||||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.currentRow,
|
||||
(currentRow) => {
|
||||
if (!currentRow) return
|
||||
setValues(currentRow)
|
||||
setSchema([
|
||||
{
|
||||
field: 'content',
|
||||
path: 'componentProps.defaultHtml',
|
||||
value: currentRow.content
|
||||
}
|
||||
])
|
||||
},
|
||||
{
|
||||
deep: true,
|
||||
immediate: true
|
||||
}
|
||||
)
|
||||
|
||||
defineExpose({
|
||||
submit
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Form :rules="rules" @register="formRegister" :schema="schema" />
|
||||
</template>
|
||||
18
promo-ui2/src/views/Function/MultipleTabs.vue
Normal file
18
promo-ui2/src/views/Function/MultipleTabs.vue
Normal file
@@ -0,0 +1,18 @@
|
||||
<script setup lang="ts">
|
||||
import { ContentWrap } from '@/components/ContentWrap'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
const { push } = useRouter()
|
||||
|
||||
const openTab = (item: number) => {
|
||||
push(`/function/multiple-tabs-demo/${item}`)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<BaseButton v-for="item in 5" :key="item" type="primary" @click="openTab(item)">
|
||||
打开详情页{{ item }}
|
||||
</BaseButton>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
19
promo-ui2/src/views/Function/MultipleTabsDemo.vue
Normal file
19
promo-ui2/src/views/Function/MultipleTabsDemo.vue
Normal file
@@ -0,0 +1,19 @@
|
||||
<script setup lang="ts">
|
||||
import { ContentWrap } from '@/components/ContentWrap'
|
||||
import { ElInput } from 'element-plus'
|
||||
import { ref } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { useTagsView } from '@/hooks/web/useTagsView'
|
||||
|
||||
const { setTitle } = useTagsView()
|
||||
|
||||
const { params } = useRoute()
|
||||
|
||||
const val = ref(params.id as string)
|
||||
|
||||
setTitle(`详情页-${val.value}`)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ContentWrap> 获取参数: <ElInput v-model="val" /> </ContentWrap>
|
||||
</template>
|
||||
173
promo-ui2/src/views/Function/Request.vue
Normal file
173
promo-ui2/src/views/Function/Request.vue
Normal file
@@ -0,0 +1,173 @@
|
||||
<script lang="ts" setup>
|
||||
import { ContentWrap } from '@/components/ContentWrap'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { ElDivider } from 'element-plus'
|
||||
import { request1, request2, request3, request4, request5, expired } from '@/api/request'
|
||||
import { ref } from 'vue'
|
||||
import request from '@/axios'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const pending = ref<Set<string>>(new Set())
|
||||
|
||||
const getRequest1 = async () => {
|
||||
if (pending.value.has('/request/1')) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
pending.value.add('/request/1')
|
||||
const res = await request1()
|
||||
console.log('【res】:', res)
|
||||
} catch (error) {
|
||||
console.log('【error】:', error)
|
||||
} finally {
|
||||
pending.value.delete('/request/1')
|
||||
}
|
||||
}
|
||||
|
||||
const getRequest2 = async () => {
|
||||
if (pending.value.has('/request/2')) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
pending.value.add('/request/2')
|
||||
const res = await request2()
|
||||
console.log('【res】:', res)
|
||||
} catch (error) {
|
||||
console.log('【error】:', error)
|
||||
} finally {
|
||||
pending.value.delete('/request/2')
|
||||
}
|
||||
}
|
||||
|
||||
const getRequest3 = async () => {
|
||||
if (pending.value.has('/request/3')) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
pending.value.add('/request/3')
|
||||
const res = await request3()
|
||||
console.log('【res】:', res)
|
||||
} catch (error) {
|
||||
console.log('【error】:', error)
|
||||
} finally {
|
||||
pending.value.delete('/request/3')
|
||||
}
|
||||
}
|
||||
|
||||
const getRequest4 = async () => {
|
||||
if (pending.value.has('/request/4')) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
pending.value.add('/request/4')
|
||||
const res = await request4()
|
||||
console.log('【res】:', res)
|
||||
} catch (error) {
|
||||
console.log('【error】:', error)
|
||||
} finally {
|
||||
pending.value.delete('/request/4')
|
||||
}
|
||||
}
|
||||
|
||||
const getRequest5 = async () => {
|
||||
if (pending.value.has('/request/5')) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
pending.value.add('/request/5')
|
||||
const res = await request5()
|
||||
console.log('【res】:', res)
|
||||
} catch (error) {
|
||||
console.log('【error】:', error)
|
||||
} finally {
|
||||
pending.value.delete('/request/5')
|
||||
}
|
||||
}
|
||||
|
||||
const getAll = () => {
|
||||
getRequest1()
|
||||
getRequest2()
|
||||
getRequest3()
|
||||
getRequest4()
|
||||
getRequest5()
|
||||
}
|
||||
|
||||
const cancelAll = () => {
|
||||
request.cancelAllRequest()
|
||||
pending.value.clear()
|
||||
}
|
||||
|
||||
// set转数组
|
||||
const setToArray = (set: Set<string>) => {
|
||||
const arr: string[] = []
|
||||
set.forEach((item) => {
|
||||
arr.push(item)
|
||||
})
|
||||
return arr
|
||||
}
|
||||
|
||||
const clickRequest1 = () => {
|
||||
if (pending.value.has('/request/1')) {
|
||||
request.cancelRequest('/request/1')
|
||||
pending.value.delete('/request/1')
|
||||
return
|
||||
}
|
||||
getRequest1()
|
||||
}
|
||||
|
||||
const clickRequest2 = () => {
|
||||
if (pending.value.has('/request/2')) {
|
||||
request.cancelRequest('/request/2')
|
||||
pending.value.delete('/request/2')
|
||||
return
|
||||
}
|
||||
getRequest2()
|
||||
}
|
||||
|
||||
const clickRequest3 = () => {
|
||||
if (pending.value.has('/request/3')) {
|
||||
request.cancelRequest('/request/3')
|
||||
pending.value.delete('/request/3')
|
||||
return
|
||||
}
|
||||
getRequest3()
|
||||
}
|
||||
|
||||
const clickRequest4 = () => {
|
||||
if (pending.value.has('/request/4')) {
|
||||
request.cancelRequest('/request/4')
|
||||
pending.value.delete('/request/4')
|
||||
return
|
||||
}
|
||||
getRequest4()
|
||||
}
|
||||
|
||||
const clickRequest5 = () => {
|
||||
if (pending.value.has('/request/5')) {
|
||||
request.cancelRequest('/request/5')
|
||||
pending.value.delete('/request/5')
|
||||
return
|
||||
}
|
||||
getRequest5()
|
||||
}
|
||||
|
||||
const tokenExpired = () => {
|
||||
expired()
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ContentWrap :title="t('router.request')">
|
||||
<p>正在请求的接口:{{ setToArray(pending) }}</p>
|
||||
<BaseButton type="primary" @click="clickRequest1">请求/取消request1</BaseButton>
|
||||
<BaseButton type="primary" @click="clickRequest2">请求/取消request2</BaseButton>
|
||||
<BaseButton type="primary" @click="clickRequest3">请求/取消request3</BaseButton>
|
||||
<BaseButton type="primary" @click="clickRequest4">请求/取消request4</BaseButton>
|
||||
<BaseButton type="primary" @click="clickRequest5">请求/取消request5</BaseButton>
|
||||
<BaseButton type="primary" @click="getAll">发送五个请求</BaseButton>
|
||||
<BaseButton type="primary" @click="cancelAll">关闭所有请求</BaseButton>
|
||||
<ElDivider />
|
||||
<BaseButton type="primary" @click="tokenExpired">token过期</BaseButton>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
70
promo-ui2/src/views/Function/Test.vue
Normal file
70
promo-ui2/src/views/Function/Test.vue
Normal file
@@ -0,0 +1,70 @@
|
||||
<script setup lang="tsx">
|
||||
import { ContentWrap } from '@/components/ContentWrap'
|
||||
import { ref, unref } from 'vue'
|
||||
import { ElDivider, ElRow, ElCol } from 'element-plus'
|
||||
import { hasPermi } from '@/components/Permission'
|
||||
|
||||
const permission = ref('add')
|
||||
|
||||
setTimeout(() => {
|
||||
permission.value = 'view'
|
||||
}, 3000)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<ElDivider>组件方式判断(已经全局注册,支持动态修改)</ElDivider>
|
||||
<ElRow :gutter="20">
|
||||
<ElCol :span="8">
|
||||
新增权限:
|
||||
<Permission permission="add">
|
||||
<BaseButton type="primary"> Add </BaseButton>
|
||||
</Permission>
|
||||
</ElCol>
|
||||
<ElCol :span="8">
|
||||
删除权限:
|
||||
<Permission permission="delete">
|
||||
<BaseButton type="danger"> Delete </BaseButton>
|
||||
</Permission>
|
||||
</ElCol>
|
||||
<ElCol :span="8">
|
||||
3秒后切换查看权限:
|
||||
<Permission :permission="permission">
|
||||
<BaseButton type="primary"> View </BaseButton>
|
||||
</Permission>
|
||||
</ElCol>
|
||||
</ElRow>
|
||||
|
||||
<ElDivider>指令方式判断(已经全局注册,不支持动态修改)</ElDivider>
|
||||
<ElRow :gutter="20">
|
||||
<ElCol :span="8">
|
||||
新增权限:
|
||||
<BaseButton v-hasPermi="'add'" type="primary"> Add </BaseButton>
|
||||
</ElCol>
|
||||
<ElCol :span="8">
|
||||
删除权限:
|
||||
<BaseButton v-hasPermi="'delete'" type="danger"> Delete </BaseButton>
|
||||
</ElCol>
|
||||
<ElCol :span="8">
|
||||
3秒后切换查看权限(无法动态修改):
|
||||
<BaseButton v-hasPermi="permission" type="primary"> View </BaseButton>
|
||||
</ElCol>
|
||||
</ElRow>
|
||||
|
||||
<ElDivider>函数方式判断</ElDivider>
|
||||
<ElRow :gutter="20">
|
||||
<ElCol :span="8">
|
||||
新增权限:
|
||||
<BaseButton v-if="hasPermi('add')" type="primary"> Add </BaseButton>
|
||||
</ElCol>
|
||||
<ElCol :span="8">
|
||||
删除权限:
|
||||
<BaseButton v-if="hasPermi('delete')" type="danger"> Delete </BaseButton>
|
||||
</ElCol>
|
||||
<ElCol :span="8">
|
||||
3秒后切换查看权限:
|
||||
<BaseButton v-if="hasPermi(unref(permission))" type="primary"> View </BaseButton>
|
||||
</ElCol>
|
||||
</ElRow>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
19
promo-ui2/src/views/Guide/Guide.vue
Normal file
19
promo-ui2/src/views/Guide/Guide.vue
Normal file
@@ -0,0 +1,19 @@
|
||||
<script setup lang="ts">
|
||||
import { ContentWrap } from '@/components/ContentWrap'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { useGuide } from '@/hooks/web/useGuide'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const { drive } = useGuide()
|
||||
|
||||
const guideStart = () => {
|
||||
drive()
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ContentWrap :title="t('guideDemo.guide')" :message="t('guideDemo.message')">
|
||||
<BaseButton type="primary" @click="guideStart">{{ t('guideDemo.start') }}</BaseButton>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
20
promo-ui2/src/views/Level/Menu111.vue
Normal file
20
promo-ui2/src/views/Level/Menu111.vue
Normal file
@@ -0,0 +1,20 @@
|
||||
<script setup lang="ts">
|
||||
import { ElInput } from 'element-plus'
|
||||
import { ContentWrap } from '@/components/ContentWrap'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { ref } from 'vue'
|
||||
|
||||
defineOptions({
|
||||
name: 'Menu111'
|
||||
})
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const text = ref('')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ContentWrap :title="t('levelDemo.menu')">
|
||||
<div class="flex items-center"> Menu111: <ElInput v-model="text" class="pl-20px" /> </div>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
20
promo-ui2/src/views/Level/Menu12.vue
Normal file
20
promo-ui2/src/views/Level/Menu12.vue
Normal file
@@ -0,0 +1,20 @@
|
||||
<script setup lang="ts">
|
||||
import { ElInput } from 'element-plus'
|
||||
import { ContentWrap } from '@/components/ContentWrap'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { ref } from 'vue'
|
||||
|
||||
defineOptions({
|
||||
name: 'Menu12'
|
||||
})
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const text = ref('')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ContentWrap :title="t('levelDemo.menu')">
|
||||
<div class="flex items-center"> Menu12: <ElInput v-model="text" class="pl-20px" /> </div>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
20
promo-ui2/src/views/Level/Menu2.vue
Normal file
20
promo-ui2/src/views/Level/Menu2.vue
Normal file
@@ -0,0 +1,20 @@
|
||||
<script setup lang="ts">
|
||||
import { ElInput } from 'element-plus'
|
||||
import { ContentWrap } from '@/components/ContentWrap'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { ref } from 'vue'
|
||||
|
||||
defineOptions({
|
||||
name: 'Menu2'
|
||||
})
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const text = ref('')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ContentWrap :title="t('levelDemo.menu')">
|
||||
<div class="flex items-center"> Menu2: <ElInput v-model="text" class="pl-20px" /> </div>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
116
promo-ui2/src/views/Login/Login.vue
Normal file
116
promo-ui2/src/views/Login/Login.vue
Normal file
@@ -0,0 +1,116 @@
|
||||
<script setup lang="ts">
|
||||
import { LoginForm, RegisterForm } from './components'
|
||||
import { ThemeSwitch } from '@/components/ThemeSwitch'
|
||||
import { LocaleDropdown } from '@/components/LocaleDropdown'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { underlineToHump } from '@/utils'
|
||||
import { useAppStore } from '@/store/modules/app'
|
||||
import { useDesign } from '@/hooks/web/useDesign'
|
||||
import { ref } from 'vue'
|
||||
import { ElScrollbar } from 'element-plus'
|
||||
|
||||
const { getPrefixCls } = useDesign()
|
||||
|
||||
const prefixCls = getPrefixCls('login')
|
||||
|
||||
const appStore = useAppStore()
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const isLogin = ref(true)
|
||||
|
||||
const toRegister = () => {
|
||||
isLogin.value = false
|
||||
}
|
||||
|
||||
const toLogin = () => {
|
||||
isLogin.value = true
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
:class="prefixCls"
|
||||
class="h-[100%] relative lt-xl:bg-[var(--login-bg-color)] lt-sm:px-10px lt-xl:px-10px lt-md:px-10px"
|
||||
>
|
||||
<ElScrollbar class="h-full">
|
||||
<div class="relative flex mx-auto min-h-100vh">
|
||||
<div
|
||||
:class="`${prefixCls}__left flex-1 bg-gray-500 bg-opacity-20 relative p-30px lt-xl:hidden`"
|
||||
>
|
||||
<div class="flex items-center relative text-white">
|
||||
<img src="@/assets/imgs/logo.png" alt="" class="w-48px h-48px mr-10px" />
|
||||
<span class="text-20px font-bold">{{ underlineToHump(appStore.getTitle) }}</span>
|
||||
</div>
|
||||
<div class="flex justify-center items-center h-[calc(100%-60px)]">
|
||||
<TransitionGroup
|
||||
appear
|
||||
tag="div"
|
||||
enter-active-class="animate__animated animate__bounceInLeft"
|
||||
>
|
||||
<img src="@/assets/svgs/login-box-bg.svg" key="1" alt="" class="w-350px" />
|
||||
<div class="text-3xl text-white" key="2">{{ t('login.welcome') }}</div>
|
||||
<div class="mt-5 font-normal text-white text-14px" key="3">
|
||||
{{ t('login.message') }}
|
||||
</div>
|
||||
</TransitionGroup>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-1 p-30px lt-sm:p-10px dark:bg-[var(--login-bg-color)] relative">
|
||||
<div
|
||||
class="flex justify-between items-center text-white at-2xl:justify-end at-xl:justify-end"
|
||||
>
|
||||
<div class="flex items-center at-2xl:hidden at-xl:hidden">
|
||||
<img src="@/assets/imgs/logo.png" alt="" class="w-48px h-48px mr-10px" />
|
||||
<span class="text-20px font-bold">{{ underlineToHump(appStore.getTitle) }}</span>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end items-center space-x-10px">
|
||||
<ThemeSwitch />
|
||||
<LocaleDropdown class="lt-xl:text-white dark:text-white" />
|
||||
</div>
|
||||
</div>
|
||||
<Transition appear enter-active-class="animate__animated animate__bounceInRight">
|
||||
<div
|
||||
class="h-full flex items-center m-auto w-[100%] at-2xl:max-w-500px at-xl:max-w-500px at-md:max-w-500px at-lg:max-w-500px"
|
||||
>
|
||||
<LoginForm
|
||||
v-if="isLogin"
|
||||
class="p-20px h-auto m-auto lt-xl:rounded-3xl lt-xl:light:bg-white"
|
||||
@to-register="toRegister"
|
||||
/>
|
||||
<RegisterForm
|
||||
v-else
|
||||
class="p-20px h-auto m-auto lt-xl:rounded-3xl lt-xl:light:bg-white"
|
||||
@to-login="toLogin"
|
||||
/>
|
||||
</div>
|
||||
</Transition>
|
||||
</div>
|
||||
</div>
|
||||
</ElScrollbar>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@prefix-cls: ~'@{adminNamespace}-login';
|
||||
|
||||
.@{prefix-cls} {
|
||||
overflow: auto;
|
||||
|
||||
&__left {
|
||||
&::before {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: -1;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-image: url('@/assets/svgs/login-bg.svg');
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
content: '';
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
300
promo-ui2/src/views/Login/components/LoginForm.vue
Normal file
300
promo-ui2/src/views/Login/components/LoginForm.vue
Normal file
@@ -0,0 +1,300 @@
|
||||
<script setup lang="tsx">
|
||||
import { getAdminRoleApi, loginApi } from '@/api/login'
|
||||
import { UserType } from '@/api/login/types'
|
||||
import { getInfo, getShareDomain } from '@/api/user'
|
||||
import { BaseButton } from '@/components/Button'
|
||||
import { Form, FormSchema } from '@/components/Form'
|
||||
import { useForm } from '@/hooks/web/useForm'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { useValidator } from '@/hooks/web/useValidator'
|
||||
import { role01, role04 } from '@/router/role'
|
||||
import { useAppStore } from '@/store/modules/app'
|
||||
import { usePermissionStore } from '@/store/modules/permission'
|
||||
import { useUserStore } from '@/store/modules/user'
|
||||
import { ElCheckbox } from 'element-plus'
|
||||
import { onMounted, reactive, ref, unref, watch } from 'vue'
|
||||
import type { RouteLocationNormalizedLoaded, RouteRecordRaw } from 'vue-router'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
const { required } = useValidator()
|
||||
|
||||
const emit = defineEmits(['to-register'])
|
||||
|
||||
const appStore = useAppStore()
|
||||
|
||||
const userStore = useUserStore()
|
||||
|
||||
const permissionStore = usePermissionStore()
|
||||
|
||||
const { currentRoute, addRoute, push } = useRouter()
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const rules = {
|
||||
username: [required()],
|
||||
password: [required()]
|
||||
}
|
||||
|
||||
const schema = reactive<FormSchema[]>([
|
||||
{
|
||||
field: 'title',
|
||||
colProps: {
|
||||
span: 24
|
||||
},
|
||||
formItemProps: {
|
||||
slots: {
|
||||
default: () => {
|
||||
return <h2 class="text-2xl font-bold text-center w-[100%]">{t('login.login')}</h2>
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'username',
|
||||
label: t('login.username'),
|
||||
// value: 'admin',
|
||||
component: 'Input',
|
||||
colProps: {
|
||||
span: 24
|
||||
},
|
||||
componentProps: {
|
||||
placeholder: '请输入账号'
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'password',
|
||||
label: t('login.password'),
|
||||
// value: 'admin',
|
||||
component: 'InputPassword',
|
||||
colProps: {
|
||||
span: 24
|
||||
},
|
||||
componentProps: {
|
||||
style: {
|
||||
width: '100%'
|
||||
},
|
||||
placeholder: '请输入密码',
|
||||
// 按下enter键触发登录
|
||||
onKeydown: (_e: any) => {
|
||||
if (_e.key === 'Enter') {
|
||||
_e.stopPropagation() // 阻止事件冒泡
|
||||
signIn()
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'tool',
|
||||
colProps: {
|
||||
span: 24
|
||||
},
|
||||
formItemProps: {
|
||||
slots: {
|
||||
default: () => {
|
||||
return (
|
||||
<>
|
||||
<div class="flex justify-between items-center w-[100%]">
|
||||
<ElCheckbox v-model={remember.value} label={t('login.remember')} size="small" />
|
||||
{/* <ElLink type="primary" underline={false}>
|
||||
{t('login.forgetPassword')}
|
||||
</ElLink> */}
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'login',
|
||||
colProps: {
|
||||
span: 24
|
||||
},
|
||||
formItemProps: {
|
||||
slots: {
|
||||
default: () => {
|
||||
return (
|
||||
<>
|
||||
<div class="w-[100%]">
|
||||
<BaseButton
|
||||
loading={loading.value}
|
||||
type="primary"
|
||||
class="w-[100%]"
|
||||
onClick={signIn}
|
||||
>
|
||||
{t('login.login')}
|
||||
</BaseButton>
|
||||
</div>
|
||||
<div class="w-[100%] mt-15px">
|
||||
<BaseButton class="w-[100%]" onClick={toRegister}>
|
||||
{t('login.register')}
|
||||
</BaseButton>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
])
|
||||
|
||||
const remember = ref(userStore.getRememberMe)
|
||||
|
||||
const initLoginInfo = () => {
|
||||
const loginInfo = userStore.getLoginInfo
|
||||
if (loginInfo) {
|
||||
const { username, password } = loginInfo
|
||||
setValues({ username, password })
|
||||
}
|
||||
}
|
||||
onMounted(() => {
|
||||
initLoginInfo()
|
||||
})
|
||||
|
||||
const { formRegister, formMethods } = useForm()
|
||||
const { getFormData, getElFormExpose, setValues } = formMethods
|
||||
|
||||
const loading = ref(false)
|
||||
|
||||
const redirect = ref<string>('')
|
||||
|
||||
watch(
|
||||
() => currentRoute.value,
|
||||
(route: RouteLocationNormalizedLoaded) => {
|
||||
redirect.value = route?.query?.redirect as string
|
||||
},
|
||||
{
|
||||
immediate: true
|
||||
}
|
||||
)
|
||||
|
||||
// 登录
|
||||
const signIn = async () => {
|
||||
const formRef = await getElFormExpose()
|
||||
await formRef?.validate(async (isValid) => {
|
||||
if (isValid) {
|
||||
loading.value = true
|
||||
const formData = await getFormData<UserType>()
|
||||
|
||||
try {
|
||||
const res = (await loginApi(formData)) as any
|
||||
|
||||
if (res) {
|
||||
// 是否记住我
|
||||
if (unref(remember)) {
|
||||
userStore.setLoginInfo({
|
||||
username: formData.username,
|
||||
password: formData.password
|
||||
})
|
||||
} else {
|
||||
userStore.setLoginInfo(undefined)
|
||||
}
|
||||
userStore.setToken(res.token)
|
||||
userStore.setRememberMe(unref(remember))
|
||||
const res2 = await getInfo()
|
||||
const shareDomainRes = await getShareDomain()
|
||||
let shareLink = `${shareDomainRes.data.shareDomain}`.replace("${code}", res2.data.invitationCode);
|
||||
if (res2.data.userType === '01') {
|
||||
shareLink = `${shareDomainRes.data.zhuboShareDomain}`.replace("${code}", res2.data.invitationCode);
|
||||
} else {
|
||||
shareLink = `${shareDomainRes.data.zhaoshaoShareDomain}`.replace("${code}", res2.data.invitationCode);
|
||||
}
|
||||
|
||||
userStore.setUserInfo({
|
||||
uid: res2.data.userId,
|
||||
username: formData.username,
|
||||
password: formData.password,
|
||||
role: res2.data.userType,
|
||||
roleId: '1',
|
||||
token: res.token,
|
||||
shareLink: shareLink,
|
||||
shareDomain: res2.data.shareDomain
|
||||
})
|
||||
|
||||
// 是否使用动态路由
|
||||
if (appStore.getDynamicRouter) {
|
||||
getRole(res2.data.userType)
|
||||
} else {
|
||||
await permissionStore.generateRoutes('static').catch(() => {})
|
||||
permissionStore.getAddRouters.forEach((route) => {
|
||||
addRoute(route as RouteRecordRaw) // 动态添加可访问路由表
|
||||
})
|
||||
permissionStore.setIsAddRouters(true)
|
||||
|
||||
getInfo().then((res2) => {
|
||||
userStore.setUserInfo({
|
||||
uid: res2.data.userId,
|
||||
username: formData.username,
|
||||
password: formData.password,
|
||||
role: res2.data.userType,
|
||||
roleId: '1',
|
||||
token: res.token,
|
||||
shareLink: shareLink,
|
||||
shareDomain: res2.data.shareDomain
|
||||
})
|
||||
push({ path: redirect.value || permissionStore.addRouters[0].path })
|
||||
})
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const getRoleRouters = async (roleType: string) => {
|
||||
if (roleType === '01') {
|
||||
return {
|
||||
code: 0,
|
||||
data: role01
|
||||
}
|
||||
}
|
||||
return {
|
||||
code: 0,
|
||||
data: role04
|
||||
}
|
||||
}
|
||||
|
||||
// 获取角色信息
|
||||
const getRole = async (roleType: string) => {
|
||||
const formData = await getFormData<UserType>()
|
||||
const params = {
|
||||
roleName: formData.username
|
||||
}
|
||||
const res =
|
||||
appStore.getDynamicRouter && appStore.getServerDynamicRouter
|
||||
? await getAdminRoleApi(params)
|
||||
: await getRoleRouters(roleType)
|
||||
if (res) {
|
||||
const routers = res.data || []
|
||||
userStore.setRoleRouters(routers)
|
||||
appStore.getDynamicRouter && appStore.getServerDynamicRouter
|
||||
? await permissionStore.generateRoutes('server', routers).catch(() => {})
|
||||
: await permissionStore.generateRoutes('frontEnd', routers).catch(() => {})
|
||||
|
||||
permissionStore.getAddRouters.forEach((route) => {
|
||||
addRoute(route as RouteRecordRaw) // 动态添加可访问路由表
|
||||
})
|
||||
permissionStore.setIsAddRouters(true)
|
||||
push({ path: redirect.value || permissionStore.addRouters[0].path })
|
||||
}
|
||||
}
|
||||
|
||||
// 去注册页面
|
||||
const toRegister = () => {
|
||||
emit('to-register')
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Form
|
||||
:schema="schema"
|
||||
:rules="rules"
|
||||
label-position="top"
|
||||
hide-required-asterisk
|
||||
size="large"
|
||||
class="dark:(border-1 border-[var(--el-border-color)] border-solid)"
|
||||
@register="formRegister"
|
||||
/>
|
||||
</template>
|
||||
251
promo-ui2/src/views/Login/components/RegisterForm.vue
Normal file
251
promo-ui2/src/views/Login/components/RegisterForm.vue
Normal file
@@ -0,0 +1,251 @@
|
||||
<script setup lang="tsx">
|
||||
import { Form, FormSchema } from '@/components/Form'
|
||||
import { reactive, ref, unref } from 'vue'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { useForm } from '@/hooks/web/useForm'
|
||||
import { ElInput, ElMessage, FormRules } from 'element-plus'
|
||||
import { useValidator } from '@/hooks/web/useValidator'
|
||||
import { BaseButton } from '@/components/Button'
|
||||
import { IAgree } from '@/components/IAgree'
|
||||
import { RegisterUserType } from '@/api/login/types'
|
||||
import { registerApi, getCodeApi } from '@/api/login'
|
||||
|
||||
const emit = defineEmits(['to-login'])
|
||||
|
||||
const { formRegister, formMethods } = useForm()
|
||||
const { getElFormExpose, getFormData } = formMethods
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const { required, check } = useValidator()
|
||||
|
||||
const getCodeTime = ref(60)
|
||||
const getCodeLoading = ref(false)
|
||||
const getCode = async () => {
|
||||
const formData = await getFormData<RegisterUserType>()
|
||||
if (!formData.username) {
|
||||
ElMessage.error('手机号不能为空')
|
||||
} else {
|
||||
getCodeApi(formData.username)
|
||||
.then(() => {
|
||||
ElMessage.success('获取验证码成功')
|
||||
getCodeLoading.value = true
|
||||
const timer = setInterval(() => {
|
||||
getCodeTime.value--
|
||||
if (getCodeTime.value <= 0) {
|
||||
clearInterval(timer)
|
||||
getCodeTime.value = 60
|
||||
getCodeLoading.value = false
|
||||
}
|
||||
}, 1000)
|
||||
})
|
||||
.catch(() => {
|
||||
ElMessage.error('获取验证码失败')
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const schema = reactive<FormSchema[]>([
|
||||
{
|
||||
field: 'title',
|
||||
colProps: {
|
||||
span: 24
|
||||
},
|
||||
formItemProps: {
|
||||
slots: {
|
||||
default: () => {
|
||||
return <h2 class="text-2xl font-bold text-center w-[100%]">{t('login.register')}</h2>
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'username',
|
||||
label: t('login.username'),
|
||||
value: '',
|
||||
component: 'Input',
|
||||
colProps: {
|
||||
span: 24
|
||||
},
|
||||
componentProps: {
|
||||
placeholder: '请输入手机号'
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'password',
|
||||
label: t('login.password'),
|
||||
value: '',
|
||||
component: 'InputPassword',
|
||||
colProps: {
|
||||
span: 24
|
||||
},
|
||||
componentProps: {
|
||||
style: {
|
||||
width: '100%'
|
||||
},
|
||||
strength: true,
|
||||
placeholder: t('login.passwordPlaceholder')
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'check_password',
|
||||
label: t('login.checkPassword'),
|
||||
value: '',
|
||||
component: 'InputPassword',
|
||||
colProps: {
|
||||
span: 24
|
||||
},
|
||||
componentProps: {
|
||||
style: {
|
||||
width: '100%'
|
||||
},
|
||||
strength: true,
|
||||
placeholder: t('login.passwordPlaceholder')
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'parentInvitationCode',
|
||||
label: '上级邀请码',
|
||||
value: '',
|
||||
component: 'Input',
|
||||
colProps: {
|
||||
span: 24
|
||||
},
|
||||
componentProps: {
|
||||
placeholder: '六位数邀请码'
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'code',
|
||||
label: t('login.code'),
|
||||
colProps: {
|
||||
span: 24
|
||||
},
|
||||
formItemProps: {
|
||||
slots: {
|
||||
default: (formData) => {
|
||||
return (
|
||||
<div class="w-[100%] flex">
|
||||
<ElInput v-model={formData.code} placeholder={t('login.codePlaceholder')} />
|
||||
<BaseButton
|
||||
type="primary"
|
||||
disabled={unref(getCodeLoading)}
|
||||
class="ml-10px"
|
||||
onClick={getCode}
|
||||
>
|
||||
{t('login.getCode')}
|
||||
{unref(getCodeLoading) ? `(${unref(getCodeTime)})` : ''}
|
||||
</BaseButton>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// {
|
||||
// field: 'iAgree',
|
||||
// colProps: {
|
||||
// span: 24
|
||||
// },
|
||||
// formItemProps: {
|
||||
// slots: {
|
||||
// default: (formData: any) => {
|
||||
// return (
|
||||
// <>
|
||||
// <IAgree
|
||||
// v-model={formData.iAgree}
|
||||
// text="我同意《用户协议》"
|
||||
// link={[
|
||||
// {
|
||||
// text: '《用户协议》',
|
||||
// url: 'https://element-plus.org/'
|
||||
// }
|
||||
// ]}
|
||||
// />
|
||||
// </>
|
||||
// )
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// },
|
||||
{
|
||||
field: 'register',
|
||||
colProps: {
|
||||
span: 24
|
||||
},
|
||||
formItemProps: {
|
||||
slots: {
|
||||
default: () => {
|
||||
return (
|
||||
<>
|
||||
<div class="w-[100%]">
|
||||
<BaseButton
|
||||
type="primary"
|
||||
class="w-[100%]"
|
||||
loading={loading.value}
|
||||
onClick={loginRegister}
|
||||
>
|
||||
{t('login.register')}
|
||||
</BaseButton>
|
||||
</div>
|
||||
<div class="w-[100%] mt-15px">
|
||||
<BaseButton class="w-[100%]" onClick={toLogin}>
|
||||
{t('login.hasUser')}
|
||||
</BaseButton>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
])
|
||||
|
||||
const rules: FormRules = {
|
||||
username: [required()],
|
||||
password: [required()],
|
||||
check_password: [required()],
|
||||
code: [required()],
|
||||
iAgree: [required(), check()]
|
||||
}
|
||||
|
||||
const toLogin = () => {
|
||||
emit('to-login')
|
||||
}
|
||||
|
||||
const loading = ref(false)
|
||||
|
||||
const loginRegister = async () => {
|
||||
const formRef = await getElFormExpose()
|
||||
formRef?.validate(async (valid) => {
|
||||
if (valid) {
|
||||
try {
|
||||
loading.value = true
|
||||
const formData = await getFormData<RegisterUserType>()
|
||||
if (formData.password !== formData.check_password) {
|
||||
ElMessage.error('两次输入的密码不一致')
|
||||
}
|
||||
const res = await registerApi(formData)
|
||||
if (res) {
|
||||
toLogin()
|
||||
}
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Form
|
||||
:schema="schema"
|
||||
:rules="rules"
|
||||
label-position="top"
|
||||
hide-required-asterisk
|
||||
size="large"
|
||||
class="dark:(border-1 border-[var(--el-border-color)] border-solid)"
|
||||
@register="formRegister"
|
||||
/>
|
||||
</template>
|
||||
4
promo-ui2/src/views/Login/components/index.ts
Normal file
4
promo-ui2/src/views/Login/components/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import LoginForm from './LoginForm.vue'
|
||||
import RegisterForm from './RegisterForm.vue'
|
||||
|
||||
export { LoginForm, RegisterForm }
|
||||
105
promo-ui2/src/views/MyMoney/index.vue
Normal file
105
promo-ui2/src/views/MyMoney/index.vue
Normal file
@@ -0,0 +1,105 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true">
|
||||
<el-form-item label="时间">
|
||||
<el-date-picker
|
||||
v-model="dateRange"
|
||||
style="width: 240px"
|
||||
value-format="YYYY-MM-DD"
|
||||
type="daterange"
|
||||
range-separator="-"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" size="small" @click="handleQuery">查询</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<div class="grid-content ep-bg-purple-light" style="text-align: right">
|
||||
<el-text class="mx-1">我的历史总奖励:</el-text
|
||||
><el-text class="mx-1" type="success" size="large">{{ totalReward }}</el-text>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-table :data="tableData">
|
||||
<el-table-column label="奖励金额" prop="reward" />
|
||||
<el-table-column label="日期" prop="date" />
|
||||
</el-table>
|
||||
<el-pagination
|
||||
layout="prev, pager, next"
|
||||
:page-size="10"
|
||||
:total="total"
|
||||
@current-change="handleCurrentChange"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { queryPromoteWelfare } from '@/api/turnover'
|
||||
import {
|
||||
ElDatePicker,
|
||||
ElTable,
|
||||
ElTableColumn,
|
||||
ElForm,
|
||||
ElFormItem,
|
||||
ElButton,
|
||||
ElPagination,
|
||||
ElRow,
|
||||
ElCol,
|
||||
ElText
|
||||
} from 'element-plus'
|
||||
|
||||
// 定义查询参数
|
||||
const queryParams = reactive({
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
beginTime: undefined as string | undefined,
|
||||
endTime: undefined as string | undefined
|
||||
})
|
||||
|
||||
// 表格数据
|
||||
const tableData = ref([])
|
||||
|
||||
// 日期范围
|
||||
const dateRange = ref([])
|
||||
const total = ref(0)
|
||||
const totalReward = ref(0)
|
||||
|
||||
/** 获取表格数据 */
|
||||
const getTableData = () => {
|
||||
queryPromoteWelfare(queryParams).then((response) => {
|
||||
tableData.value = (response as any).data.rows
|
||||
total.value = response.data.total
|
||||
totalReward.value = response.data.totalReward
|
||||
})
|
||||
}
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
// queryParams.pageNum = 1 // 如果需要重置页码,取消注释此行
|
||||
if (dateRange.value !== null && dateRange.value.length === 2) {
|
||||
queryParams.beginTime = dateRange.value[0]
|
||||
queryParams.endTime = dateRange.value[1]
|
||||
} else {
|
||||
queryParams.beginTime = undefined
|
||||
queryParams.endTime = undefined
|
||||
}
|
||||
getTableData()
|
||||
}
|
||||
|
||||
const handleCurrentChange = (val: number) => {
|
||||
queryParams.pageNum = val
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
// 组件挂载时获取数据
|
||||
onMounted(() => {
|
||||
getTableData()
|
||||
})
|
||||
</script>
|
||||
148
promo-ui2/src/views/Personal/PersonalCenter/PersonalCenter.vue
Normal file
148
promo-ui2/src/views/Personal/PersonalCenter/PersonalCenter.vue
Normal file
@@ -0,0 +1,148 @@
|
||||
<script setup lang="ts">
|
||||
import { ContentWrap } from '@/components/ContentWrap'
|
||||
import { ref, unref } from 'vue'
|
||||
import { ElDivider, ElImage, ElTag, ElTabPane, ElTabs, ElButton, ElMessage } from 'element-plus'
|
||||
import defaultAvatar from '@/assets/imgs/avatar.jpg'
|
||||
import UploadAvatar from './components/UploadAvatar.vue'
|
||||
import { Dialog } from '@/components/Dialog'
|
||||
import EditInfo from './components/EditInfo.vue'
|
||||
import EditPassword from './components/EditPassword.vue'
|
||||
|
||||
const userInfo = ref()
|
||||
const fetchDetailUserApi = async () => {
|
||||
// 这里可以调用接口获取用户信息
|
||||
const data = {
|
||||
id: 1,
|
||||
username: 'admin',
|
||||
realName: 'admin',
|
||||
phoneNumber: '18888888888',
|
||||
email: '502431556@qq.com',
|
||||
avatarUrl: '',
|
||||
roleList: ['超级管理员']
|
||||
}
|
||||
userInfo.value = data
|
||||
}
|
||||
fetchDetailUserApi()
|
||||
|
||||
const activeName = ref('first')
|
||||
|
||||
const dialogVisible = ref(false)
|
||||
|
||||
const uploadAvatarRef = ref<ComponentRef<typeof UploadAvatar>>()
|
||||
const avatarLoading = ref(false)
|
||||
const saveAvatar = async () => {
|
||||
try {
|
||||
avatarLoading.value = true
|
||||
const base64 = unref(uploadAvatarRef)?.getBase64()
|
||||
console.log(base64)
|
||||
// 这里可以调用修改头像接口
|
||||
fetchDetailUserApi()
|
||||
ElMessage.success('修改成功')
|
||||
dialogVisible.value = false
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
} finally {
|
||||
avatarLoading.value = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex w-100% h-100%">
|
||||
<ContentWrap title="个人信息" class="w-400px">
|
||||
<div class="flex justify-center items-center">
|
||||
<div
|
||||
class="avatar w-[150px] h-[150px] relative cursor-pointer"
|
||||
@click="dialogVisible = true"
|
||||
>
|
||||
<ElImage
|
||||
class="w-[150px] h-[150px] rounded-full"
|
||||
:src="userInfo?.avatarUrl || defaultAvatar"
|
||||
fit="fill"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<ElDivider />
|
||||
<div class="flex justify-between items-center">
|
||||
<div>账号:</div>
|
||||
<div>{{ userInfo?.username }}</div>
|
||||
</div>
|
||||
<ElDivider />
|
||||
<div class="flex justify-between items-center">
|
||||
<div>昵称:</div>
|
||||
<div>{{ userInfo?.realName }}</div>
|
||||
</div>
|
||||
<ElDivider />
|
||||
<div class="flex justify-between items-center">
|
||||
<div>手机号码:</div>
|
||||
<div>{{ userInfo?.phoneNumber ?? '-' }}</div>
|
||||
</div>
|
||||
<ElDivider />
|
||||
<div class="flex justify-between items-center">
|
||||
<div>用户邮箱:</div>
|
||||
<div>{{ userInfo?.email ?? '-' }}</div>
|
||||
</div>
|
||||
<ElDivider />
|
||||
<div class="flex justify-between items-center">
|
||||
<div>所属角色:</div>
|
||||
<div>
|
||||
<template v-if="userInfo?.roleList?.length">
|
||||
<ElTag v-for="item in userInfo?.roleList || []" :key="item" class="ml-2 mb-w"
|
||||
>{{ item }}
|
||||
</ElTag>
|
||||
</template>
|
||||
<template v-else>-</template>
|
||||
</div>
|
||||
</div>
|
||||
<ElDivider />
|
||||
</ContentWrap>
|
||||
<ContentWrap title="基本资料" class="flex-[3] ml-20px">
|
||||
<ElTabs v-model="activeName">
|
||||
<ElTabPane label="基本信息" name="first">
|
||||
<EditInfo :user-info="userInfo" />
|
||||
</ElTabPane>
|
||||
<ElTabPane label="修改密码" name="second">
|
||||
<EditPassword />
|
||||
</ElTabPane>
|
||||
</ElTabs>
|
||||
</ContentWrap>
|
||||
</div>
|
||||
|
||||
<Dialog v-model="dialogVisible" title="修改头像" width="800px">
|
||||
<UploadAvatar ref="uploadAvatarRef" :url="userInfo?.avatarUrl || defaultAvatar" />
|
||||
|
||||
<template #footer>
|
||||
<ElButton type="primary" :loading="avatarLoading" @click="saveAvatar"> 保存 </ElButton>
|
||||
<ElButton @click="dialogVisible = false">关闭</ElButton>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.avatar {
|
||||
position: relative;
|
||||
|
||||
&::after {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
font-size: 50px;
|
||||
color: #fff;
|
||||
background-color: rgb(0 0 0 / 40%);
|
||||
border-radius: 50%;
|
||||
content: '+';
|
||||
opacity: 0;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
&::after {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,96 @@
|
||||
<script lang="ts" setup>
|
||||
import { FormSchema, Form } from '@/components/Form'
|
||||
import { useForm } from '@/hooks/web/useForm'
|
||||
import { useValidator } from '@/hooks/web/useValidator'
|
||||
import { reactive, ref, watch } from 'vue'
|
||||
import { ElDivider, ElMessage, ElMessageBox } from 'element-plus'
|
||||
|
||||
const props = defineProps({
|
||||
userInfo: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
})
|
||||
|
||||
const { required, phone, maxlength, email } = useValidator()
|
||||
|
||||
const formSchema = reactive<FormSchema[]>([
|
||||
{
|
||||
field: 'realName',
|
||||
label: '昵称',
|
||||
component: 'Input',
|
||||
colProps: {
|
||||
span: 24
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'phoneNumber',
|
||||
label: '手机号码',
|
||||
component: 'Input',
|
||||
colProps: {
|
||||
span: 24
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'email',
|
||||
label: '邮箱',
|
||||
component: 'Input',
|
||||
colProps: {
|
||||
span: 24
|
||||
}
|
||||
}
|
||||
])
|
||||
|
||||
const rules = reactive({
|
||||
realName: [required(), maxlength(50)],
|
||||
phoneNumber: [phone()],
|
||||
email: [email()]
|
||||
})
|
||||
|
||||
const { formRegister, formMethods } = useForm()
|
||||
const { setValues, getElFormExpose } = formMethods
|
||||
|
||||
watch(
|
||||
() => props.userInfo,
|
||||
(value) => {
|
||||
setValues(value)
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
deep: true
|
||||
}
|
||||
)
|
||||
|
||||
const saveLoading = ref(false)
|
||||
const save = async () => {
|
||||
const elForm = await getElFormExpose()
|
||||
const valid = await elForm?.validate().catch((err) => {
|
||||
console.log(err)
|
||||
})
|
||||
if (valid) {
|
||||
ElMessageBox.confirm('是否确认修改?', '提示', {
|
||||
confirmButtonText: '确认',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
})
|
||||
.then(async () => {
|
||||
try {
|
||||
saveLoading.value = true
|
||||
// 这里可以调用修改用户信息接口
|
||||
ElMessage.success('修改成功')
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
} finally {
|
||||
saveLoading.value = false
|
||||
}
|
||||
})
|
||||
.catch(() => {})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Form :rules="rules" @register="formRegister" :schema="formSchema" />
|
||||
<ElDivider />
|
||||
<BaseButton type="primary" @click="save">保存</BaseButton>
|
||||
</template>
|
||||
@@ -0,0 +1,110 @@
|
||||
<script setup lang="ts">
|
||||
import { Form, FormSchema } from '@/components/Form'
|
||||
import { useForm } from '@/hooks/web/useForm'
|
||||
import { reactive, ref } from 'vue'
|
||||
import { useValidator } from '@/hooks/web/useValidator'
|
||||
import { ElMessage, ElMessageBox, ElDivider } from 'element-plus'
|
||||
|
||||
const { required } = useValidator()
|
||||
|
||||
const formSchema = reactive<FormSchema[]>([
|
||||
{
|
||||
field: 'password',
|
||||
label: '旧密码',
|
||||
component: 'InputPassword',
|
||||
colProps: {
|
||||
span: 24
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'newPassword',
|
||||
label: '新密码',
|
||||
component: 'InputPassword',
|
||||
colProps: {
|
||||
span: 24
|
||||
},
|
||||
componentProps: {
|
||||
strength: true
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'newPassword2',
|
||||
label: '确认新密码',
|
||||
component: 'InputPassword',
|
||||
colProps: {
|
||||
span: 24
|
||||
},
|
||||
componentProps: {
|
||||
strength: true
|
||||
}
|
||||
}
|
||||
])
|
||||
|
||||
const rules = reactive({
|
||||
password: [required()],
|
||||
newPassword: [
|
||||
required(),
|
||||
{
|
||||
asyncValidator: async (_, val, callback) => {
|
||||
const formData = await getFormData()
|
||||
const { newPassword2 } = formData
|
||||
if (val !== newPassword2) {
|
||||
callback(new Error('新密码与确认新密码不一致'))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
newPassword2: [
|
||||
required(),
|
||||
{
|
||||
asyncValidator: async (_, val, callback) => {
|
||||
const formData = await getFormData()
|
||||
const { newPassword } = formData
|
||||
if (val !== newPassword) {
|
||||
callback(new Error('确认新密码与新密码不一致'))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
const { formRegister, formMethods } = useForm()
|
||||
const { getFormData, getElFormExpose } = formMethods
|
||||
|
||||
const saveLoading = ref(false)
|
||||
const save = async () => {
|
||||
const elForm = await getElFormExpose()
|
||||
const valid = await elForm?.validate().catch((err) => {
|
||||
console.log(err)
|
||||
})
|
||||
if (valid) {
|
||||
ElMessageBox.confirm('是否确认修改?', '提示', {
|
||||
confirmButtonText: '确认',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
})
|
||||
.then(async () => {
|
||||
try {
|
||||
saveLoading.value = true
|
||||
// 这里可以调用修改密码的接口
|
||||
ElMessage.success('修改成功')
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
} finally {
|
||||
saveLoading.value = false
|
||||
}
|
||||
})
|
||||
.catch(() => {})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Form :rules="rules" @register="formRegister" :schema="formSchema" />
|
||||
<ElDivider />
|
||||
<BaseButton type="primary" @click="save">确认修改</BaseButton>
|
||||
</template>
|
||||
@@ -0,0 +1,30 @@
|
||||
<script setup lang="ts">
|
||||
import { ImageCropping } from '@/components/ImageCropping'
|
||||
import { ref, unref } from 'vue'
|
||||
|
||||
defineProps({
|
||||
url: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
})
|
||||
|
||||
const fileUrl = ref('')
|
||||
|
||||
const CropperRef = ref<ComponentRef<typeof ImageCropping>>()
|
||||
|
||||
const getBase64 = () => {
|
||||
const base64 = unref(CropperRef)?.cropperExpose?.getCroppedCanvas()?.toDataURL() ?? ''
|
||||
return base64
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
getBase64
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<ImageCropping ref="CropperRef" :image-url="fileUrl || url" />
|
||||
</div>
|
||||
</template>
|
||||
30
promo-ui2/src/views/Redirect/Redirect.vue
Normal file
30
promo-ui2/src/views/Redirect/Redirect.vue
Normal file
@@ -0,0 +1,30 @@
|
||||
<template>
|
||||
<div></div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { unref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
const { currentRoute, replace } = useRouter()
|
||||
|
||||
const { params, query } = unref(currentRoute)
|
||||
const { path, _redirect_type = 'path' } = params
|
||||
|
||||
Reflect.deleteProperty(params, '_redirect_type')
|
||||
Reflect.deleteProperty(params, 'path')
|
||||
|
||||
const _path = Array.isArray(path) ? path.join('/') : path
|
||||
|
||||
if (_redirect_type === 'name') {
|
||||
replace({
|
||||
name: _path,
|
||||
query,
|
||||
params
|
||||
})
|
||||
} else {
|
||||
replace({
|
||||
path: _path.startsWith('/') ? _path : '/' + _path,
|
||||
query
|
||||
})
|
||||
}
|
||||
</script>
|
||||
89
promo-ui2/src/views/Turnover/index.vue
Normal file
89
promo-ui2/src/views/Turnover/index.vue
Normal file
@@ -0,0 +1,89 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true">
|
||||
<el-form-item label="时间">
|
||||
<el-date-picker
|
||||
v-model="dateRange"
|
||||
style="width: 240px"
|
||||
value-format="YYYY-MM-DD"
|
||||
type="daterange"
|
||||
range-separator="-"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="主播ID">
|
||||
<el-input v-model="queryParams.parentId" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="主播昵称">
|
||||
<el-input v-model="queryParams.parentNickName" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" size="small" @click="handleQuery">查询</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<el-table :data="tableData">
|
||||
<el-table-column label="主播ID" prop="userId" />
|
||||
<el-table-column label="主播昵称" prop="nickName" />
|
||||
<!-- <el-table-column label="日消费金额" prop="dailyTotal" /> -->
|
||||
<el-table-column label="主播下级总消费" prop="totalByParent" />
|
||||
<!-- <el-table-column label="日期" prop="date" /> -->
|
||||
</el-table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { getAnchorPromotionData } from '@/api/turnover'
|
||||
import {
|
||||
ElDatePicker,
|
||||
ElTable,
|
||||
ElTableColumn,
|
||||
ElForm,
|
||||
ElFormItem,
|
||||
ElInput,
|
||||
ElButton
|
||||
} from 'element-plus'
|
||||
|
||||
// 定义查询参数
|
||||
const queryParams = reactive({
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
beginTime: undefined as string | undefined,
|
||||
endTime: undefined as string | undefined,
|
||||
parentId: undefined as number | undefined,
|
||||
anchorName: undefined as string | undefined,
|
||||
parentNickName: undefined as string | undefined
|
||||
})
|
||||
|
||||
// 表格数据
|
||||
const tableData = ref([])
|
||||
|
||||
// 日期范围
|
||||
const dateRange = ref([])
|
||||
|
||||
/** 获取表格数据 */
|
||||
const getTableData = () => {
|
||||
getAnchorPromotionData(queryParams).then((response) => {
|
||||
tableData.value = (response as any).rows
|
||||
})
|
||||
}
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
// queryParams.pageNum = 1 // 如果需要重置页码,取消注释此行
|
||||
if (dateRange.value !== null && dateRange.value.length === 2) {
|
||||
queryParams.beginTime = dateRange.value[0]
|
||||
queryParams.endTime = dateRange.value[1]
|
||||
} else {
|
||||
queryParams.beginTime = undefined
|
||||
queryParams.endTime = undefined
|
||||
}
|
||||
getTableData()
|
||||
}
|
||||
|
||||
// 组件挂载时获取数据
|
||||
onMounted(() => {
|
||||
getTableData()
|
||||
})
|
||||
</script>
|
||||
162
promo-ui2/src/views/User/index.vue
Normal file
162
promo-ui2/src/views/User/index.vue
Normal file
@@ -0,0 +1,162 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<el-descriptions title="用户信息" :column="1" size="default" border>
|
||||
<el-descriptions-item label="手机号">{{ form.userName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="推广码"
|
||||
>{{ form.invitationCode }}
|
||||
<el-button type="primary" @click="editInvitationCode">编辑</el-button></el-descriptions-item
|
||||
>
|
||||
<el-descriptions-item label="邀请链接">{{ shareLink }}</el-descriptions-item>
|
||||
<el-descriptions-item label="余额">{{ form.accountAmount }}</el-descriptions-item>
|
||||
<el-descriptions-item label="积分">{{ form.accountCredits }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<el-button type="primary" @click="rechargeDialogVisible = true">提取余额</el-button>
|
||||
|
||||
<el-dialog v-model="dialogVisible" title="编辑推广码" width="500">
|
||||
<el-form :model="form" label-width="auto" style="max-width: 600px">
|
||||
<el-form-item label="推广码">
|
||||
<el-input v-model="form.invitationCode" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="dialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="onSubmit()"> 确认 </el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog v-model="rechargeDialogVisible" title="生成卡密" width="500">
|
||||
<el-form ref="form2" :model="generateForm" label-width="100px">
|
||||
<el-form-item label="生成数量" prop="num">
|
||||
<el-input-number v-model="generateForm.num" :min="0" :step="1"></el-input-number>
|
||||
</el-form-item>
|
||||
<el-form-item label="金额(元)" prop="rechargeListId">
|
||||
<el-select
|
||||
v-model="generateForm.rechargeListId"
|
||||
filterable
|
||||
placeholder="请选择充值列表ID"
|
||||
clearable
|
||||
>
|
||||
<el-option
|
||||
v-for="item in rechangeList"
|
||||
:key="item.id"
|
||||
:label="item.price"
|
||||
:value="item.id"
|
||||
>
|
||||
{{ item.price }}
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="rechargeDialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="onGenerateCard()"> 确认 </el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog v-model="cardDialogVisible" title="卡密结果" width="500">
|
||||
<div v-for="(item, index) in overList" :key="index" style="margin-top: 1px">{{ item }}</div>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="cardDialogVisible = false">确定</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import {
|
||||
ElInputNumber,
|
||||
ElSelect,
|
||||
ElOption,
|
||||
ElDescriptions,
|
||||
ElDescriptionsItem,
|
||||
ElForm,
|
||||
ElFormItem,
|
||||
ElInput,
|
||||
ElButton,
|
||||
ElMessage,
|
||||
ElDialog,
|
||||
} from 'element-plus'
|
||||
|
||||
import {
|
||||
getInfo,
|
||||
updateInvitationCode,
|
||||
getShareDomain,
|
||||
getRechargeListApi,
|
||||
generateCardListApi
|
||||
} from '@/api/user'
|
||||
import { useUserStore } from '@/store/modules/user'
|
||||
const userStore = useUserStore()
|
||||
|
||||
// 定义响应式状态
|
||||
const form = reactive({} as any)
|
||||
const shareLink = ref('')
|
||||
const dialogVisible = ref(false)
|
||||
const rechargeDialogVisible = ref(false)
|
||||
const generateForm = reactive({
|
||||
num: 0,
|
||||
rechargeListId: 0
|
||||
})
|
||||
const rechangeList = reactive([] as any)
|
||||
const overList = reactive([] as any)
|
||||
const cardDialogVisible = ref(false)
|
||||
|
||||
const freshUserInfo = async () => {
|
||||
const res = await getInfo()
|
||||
Object.assign(form, res.data)
|
||||
shareLink.value = await getShareLink()
|
||||
console.log('用户信息已刷新', form)
|
||||
}
|
||||
|
||||
const onSubmit = async () => {
|
||||
rechargeDialogVisible.value = false
|
||||
// 这里可以添加表单提交逻辑,例如调用 API 更新用户信息
|
||||
await updateInvitationCode({
|
||||
username: form.userName,
|
||||
invitationCode: form.invitationCode
|
||||
})
|
||||
ElMessage.success('信息已更新')
|
||||
freshUserInfo() // 刷新用户信息
|
||||
}
|
||||
|
||||
const editInvitationCode = () => {
|
||||
dialogVisible.value = true
|
||||
}
|
||||
const getShareLink = async () => {
|
||||
const shareDomainRes = await getShareDomain()
|
||||
let shareLink = `${shareDomainRes.data.shareDomain}`.replace('${code}', form.invitationCode)
|
||||
if (form.userType === '01') {
|
||||
shareLink = `${shareDomainRes.data.zhuboShareDomain}`.replace('${code}', form.invitationCode)
|
||||
userStore.userInfo!.shareDomain = shareDomainRes.data.zhuboShareDomain
|
||||
} else {
|
||||
shareLink = `${shareDomainRes.data.zhaoshaoShareDomain}`.replace('${code}', form.invitationCode)
|
||||
userStore.userInfo!.shareDomain = shareDomainRes.data.zhaoshaoShareDomain
|
||||
}
|
||||
userStore.userInfo!.shareLink = shareLink
|
||||
return shareLink
|
||||
}
|
||||
|
||||
const onGenerateCard = async () => {
|
||||
rechargeDialogVisible.value = false
|
||||
cardDialogVisible.value = true
|
||||
const res = await generateCardListApi(generateForm.rechargeListId, generateForm.num)
|
||||
Object.assign(overList, res.data)
|
||||
freshUserInfo()
|
||||
}
|
||||
|
||||
const getRechargeList = async () => {
|
||||
const res = await getRechargeListApi()
|
||||
Object.assign(rechangeList, res.rows)
|
||||
}
|
||||
|
||||
// 组件挂载时初始化
|
||||
onMounted(() => {
|
||||
freshUserInfo()
|
||||
getRechargeList()
|
||||
})
|
||||
</script>
|
||||
26
promo-ui2/src/views/hooks/useClipboard.vue
Normal file
26
promo-ui2/src/views/hooks/useClipboard.vue
Normal file
@@ -0,0 +1,26 @@
|
||||
<script setup lang="ts">
|
||||
import { ContentWrap } from '@/components/ContentWrap'
|
||||
import { useClipboard } from '@/hooks/web/useClipboard'
|
||||
import { ElInput } from 'element-plus'
|
||||
import { ref } from 'vue'
|
||||
|
||||
const { copy, copied, text, isSupported } = useClipboard()
|
||||
|
||||
const source = ref('')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ContentWrap title="useClipboard">
|
||||
<ElInput v-model="source" placeholder="请输入要复制的内容" />
|
||||
<div v-if="isSupported">
|
||||
<BaseButton @click="copy(source)" type="primary" class="mt-20px">
|
||||
<span v-if="!copied">复制</span>
|
||||
<span v-else>已复制</span>
|
||||
</BaseButton>
|
||||
<p>
|
||||
当前已复制: <code>{{ text || 'none' }}</code>
|
||||
</p>
|
||||
</div>
|
||||
<p v-else> 你的浏览器不支持 Clipboard API </p>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
186
promo-ui2/src/views/hooks/useCrudSchemas.vue
Normal file
186
promo-ui2/src/views/hooks/useCrudSchemas.vue
Normal file
@@ -0,0 +1,186 @@
|
||||
<script setup lang="ts">
|
||||
import { CrudSchema, useCrudSchemas } from '@/hooks/web/useCrudSchemas'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { reactive } from 'vue'
|
||||
import { JsonEditor } from '@/components/JsonEditor'
|
||||
import { ContentWrap } from '@/components/ContentWrap'
|
||||
import { ElRow, ElCol } from 'element-plus'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const crudSchemas = reactive<CrudSchema[]>([
|
||||
{
|
||||
field: 'selection',
|
||||
search: {
|
||||
hidden: true
|
||||
},
|
||||
form: {
|
||||
hidden: true
|
||||
},
|
||||
detail: {
|
||||
hidden: true
|
||||
},
|
||||
table: {
|
||||
type: 'selection'
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'index',
|
||||
label: t('tableDemo.index'),
|
||||
type: 'index',
|
||||
search: {
|
||||
hidden: true
|
||||
},
|
||||
form: {
|
||||
hidden: true
|
||||
},
|
||||
detail: {
|
||||
hidden: true
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'title',
|
||||
label: t('tableDemo.title'),
|
||||
search: {
|
||||
component: 'Input'
|
||||
},
|
||||
form: {
|
||||
component: 'Input',
|
||||
colProps: {
|
||||
span: 24
|
||||
}
|
||||
},
|
||||
detail: {
|
||||
span: 24
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'author',
|
||||
label: t('tableDemo.author'),
|
||||
search: {
|
||||
hidden: true
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'display_time',
|
||||
label: t('tableDemo.displayTime'),
|
||||
search: {
|
||||
hidden: true
|
||||
},
|
||||
form: {
|
||||
component: 'DatePicker',
|
||||
componentProps: {
|
||||
type: 'datetime',
|
||||
valueFormat: 'YYYY-MM-DD HH:mm:ss'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'importance',
|
||||
label: t('tableDemo.importance'),
|
||||
search: {
|
||||
hidden: true
|
||||
},
|
||||
form: {
|
||||
component: 'Select',
|
||||
componentProps: {
|
||||
style: {
|
||||
width: '100%'
|
||||
},
|
||||
options: [
|
||||
{
|
||||
label: '重要',
|
||||
value: 3
|
||||
},
|
||||
{
|
||||
label: '良好',
|
||||
value: 2
|
||||
},
|
||||
{
|
||||
label: '一般',
|
||||
value: 1
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'pageviews',
|
||||
label: t('tableDemo.pageviews'),
|
||||
search: {
|
||||
hidden: true
|
||||
},
|
||||
form: {
|
||||
component: 'InputNumber',
|
||||
value: 0
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'content',
|
||||
label: t('exampleDemo.content'),
|
||||
search: {
|
||||
hidden: true
|
||||
},
|
||||
table: {
|
||||
show: false
|
||||
},
|
||||
form: {
|
||||
component: 'Editor',
|
||||
colProps: {
|
||||
span: 24
|
||||
}
|
||||
},
|
||||
detail: {
|
||||
span: 24
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'action',
|
||||
width: '260px',
|
||||
label: t('tableDemo.action'),
|
||||
search: {
|
||||
hidden: true
|
||||
},
|
||||
form: {
|
||||
hidden: true
|
||||
},
|
||||
detail: {
|
||||
hidden: true
|
||||
}
|
||||
}
|
||||
])
|
||||
|
||||
const { allSchemas } = useCrudSchemas(crudSchemas)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ContentWrap title="useCrudSchemas">
|
||||
<ElRow :gutter="20">
|
||||
<ElCol :span="24">
|
||||
<ContentWrap title="原始数据数据" class="mt-20px">
|
||||
<JsonEditor v-model="crudSchemas" />
|
||||
</ContentWrap>
|
||||
</ElCol>
|
||||
<ElCol :span="24">
|
||||
<ContentWrap title="查询组件数据结构" class="mt-20px">
|
||||
<JsonEditor v-model="allSchemas.searchSchema" />
|
||||
</ContentWrap>
|
||||
</ElCol>
|
||||
<ElCol :span="24">
|
||||
<ContentWrap title="表单组件数据结构" class="mt-20px">
|
||||
<JsonEditor v-model="allSchemas.formSchema" />
|
||||
</ContentWrap>
|
||||
</ElCol>
|
||||
<ElCol :span="24">
|
||||
<ContentWrap title="表格组件数据结构" class="mt-20px">
|
||||
<JsonEditor v-model="allSchemas.tableColumns" />
|
||||
</ContentWrap>
|
||||
</ElCol>
|
||||
<ElCol :span="24">
|
||||
<ContentWrap title="详情组件数据结构" class="mt-20px">
|
||||
<JsonEditor v-model="allSchemas.detailSchema" />
|
||||
</ContentWrap>
|
||||
</ElCol>
|
||||
</ElRow>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
12
promo-ui2/src/views/hooks/useNetwork.vue
Normal file
12
promo-ui2/src/views/hooks/useNetwork.vue
Normal file
@@ -0,0 +1,12 @@
|
||||
<script setup lang="ts">
|
||||
import { ContentWrap } from '@/components/ContentWrap'
|
||||
import { useNetwork } from '@/hooks/web/useNetwork'
|
||||
|
||||
const { online } = useNetwork()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ContentWrap title="useNetwork">
|
||||
当前网络状态: <code>{{ online ? '已连接' : '已断开' }}</code>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
59
promo-ui2/src/views/hooks/useTagsView.vue
Normal file
59
promo-ui2/src/views/hooks/useTagsView.vue
Normal file
@@ -0,0 +1,59 @@
|
||||
<script setup lang="ts">
|
||||
import { ContentWrap } from '@/components/ContentWrap'
|
||||
import { useTagsView } from '@/hooks/web/useTagsView'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
const { push } = useRouter()
|
||||
|
||||
const { closeAll, closeLeft, closeRight, closeOther, closeCurrent, refreshPage, setTitle } =
|
||||
useTagsView()
|
||||
|
||||
const closeAllTabs = () => {
|
||||
closeAll(() => {
|
||||
push('/dashboard/analysis')
|
||||
})
|
||||
}
|
||||
|
||||
const closeLeftTabs = () => {
|
||||
closeLeft()
|
||||
}
|
||||
|
||||
const closeRightTabs = () => {
|
||||
closeRight()
|
||||
}
|
||||
|
||||
const closeOtherTabs = () => {
|
||||
closeOther()
|
||||
}
|
||||
|
||||
const refresh = () => {
|
||||
refreshPage()
|
||||
}
|
||||
|
||||
const closeCurrentTab = () => {
|
||||
closeCurrent(undefined, () => {
|
||||
push('/dashboard/analysis')
|
||||
})
|
||||
}
|
||||
|
||||
const setTabTitle = () => {
|
||||
setTitle(new Date().getTime().toString())
|
||||
}
|
||||
|
||||
const setAnalysisTitle = () => {
|
||||
setTitle(`分析页-${new Date().getTime().toString()}`, '/dashboard/analysis')
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ContentWrap title="useTagsView">
|
||||
<BaseButton type="primary" @click="closeAllTabs"> 关闭所有标签页 </BaseButton>
|
||||
<BaseButton type="primary" @click="closeLeftTabs"> 关闭左侧标签页 </BaseButton>
|
||||
<BaseButton type="primary" @click="closeRightTabs"> 关闭右侧标签页 </BaseButton>
|
||||
<BaseButton type="primary" @click="closeOtherTabs"> 关闭其他标签页 </BaseButton>
|
||||
<BaseButton type="primary" @click="closeCurrentTab"> 关闭当前标签页 </BaseButton>
|
||||
<BaseButton type="primary" @click="refresh"> 刷新当前标签页 </BaseButton>
|
||||
<BaseButton type="primary" @click="setTabTitle"> 修改当前标题 </BaseButton>
|
||||
<BaseButton type="primary" @click="setAnalysisTitle"> 修改分析页标题 </BaseButton>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
80
promo-ui2/src/views/hooks/useValidator.vue
Normal file
80
promo-ui2/src/views/hooks/useValidator.vue
Normal file
@@ -0,0 +1,80 @@
|
||||
<script setup lang="ts">
|
||||
import { ContentWrap } from '@/components/ContentWrap'
|
||||
import { Form, FormSchema } from '@/components/Form'
|
||||
import { useValidator } from '@/hooks/web/useValidator'
|
||||
import { useForm } from '@/hooks/web/useForm'
|
||||
import { reactive } from 'vue'
|
||||
import { FormItemRule } from 'element-plus'
|
||||
|
||||
const { formRegister, formMethods } = useForm()
|
||||
|
||||
const { getFormData } = formMethods
|
||||
|
||||
const { required, lengthRange, notSpace, notSpecialCharacters } = useValidator()
|
||||
|
||||
const formSchema = reactive<FormSchema[]>([
|
||||
{
|
||||
field: 'field1',
|
||||
label: '必填',
|
||||
component: 'Input'
|
||||
},
|
||||
{
|
||||
field: 'field2',
|
||||
label: '长度范围',
|
||||
component: 'Input'
|
||||
},
|
||||
{
|
||||
field: 'field3',
|
||||
label: '不能有空格',
|
||||
component: 'Input'
|
||||
},
|
||||
{
|
||||
field: 'field4',
|
||||
label: '不能有特殊字符',
|
||||
component: 'Input'
|
||||
},
|
||||
{
|
||||
field: 'field5',
|
||||
label: '是否相等-值1',
|
||||
component: 'Input'
|
||||
},
|
||||
{
|
||||
field: 'field6',
|
||||
label: '是否相等-值2',
|
||||
component: 'Input'
|
||||
}
|
||||
])
|
||||
|
||||
const rules = reactive<{
|
||||
[key: string]: FormItemRule[]
|
||||
}>({
|
||||
field1: [required()],
|
||||
field2: [
|
||||
lengthRange({
|
||||
min: 2,
|
||||
max: 5
|
||||
})
|
||||
],
|
||||
field3: [notSpace()],
|
||||
field4: [notSpecialCharacters()],
|
||||
field5: [
|
||||
{
|
||||
asyncValidator: async (_, val, callback) => {
|
||||
const formData = await getFormData()
|
||||
const { field6 } = formData
|
||||
if (val !== field6) {
|
||||
callback(new Error('两个值不相等'))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ContentWrap title="useValidator">
|
||||
<Form :schema="formSchema" :rules="rules" @register="formRegister" />
|
||||
</ContentWrap>
|
||||
</template>
|
||||
31
promo-ui2/src/views/hooks/useWatermark.vue
Normal file
31
promo-ui2/src/views/hooks/useWatermark.vue
Normal file
@@ -0,0 +1,31 @@
|
||||
<script setup lang="ts">
|
||||
import { ContentWrap } from '@/components/ContentWrap'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { useWatermark } from '@/hooks/web/useWatermark'
|
||||
import { computed, onBeforeUnmount } from 'vue'
|
||||
import { useAppStore } from '@/store/modules/app'
|
||||
|
||||
const appStore = useAppStore()
|
||||
|
||||
const title = computed(() => appStore.getTitle)
|
||||
|
||||
const { setWatermark, clear } = useWatermark()
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
clear()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ContentWrap title="useWatermark">
|
||||
<BaseButton type="primary" @click="setWatermark(title)">
|
||||
{{ t('watermarkDemo.createdWatermark') }}
|
||||
</BaseButton>
|
||||
<BaseButton type="danger" @click="clear">{{ t('watermarkDemo.clearWatermark') }}</BaseButton>
|
||||
<BaseButton type="warning" @click="setWatermark(`New${title}`)">
|
||||
{{ t('watermarkDemo.resetWatermark') }}
|
||||
</BaseButton>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
209
promo-ui2/src/views/subFlow/index.vue
Normal file
209
promo-ui2/src/views/subFlow/index.vue
Normal file
@@ -0,0 +1,209 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<el-page-header @back="goBack" v-if="userId = parentId" />
|
||||
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true">
|
||||
<el-form-item label="时间">
|
||||
<el-form-item label="下级玩家ID">
|
||||
<el-input v-model="queryParams.playerId" clearable />
|
||||
</el-form-item>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="el-icon-search" size="small" @click="handleQuery"
|
||||
>查询</el-button
|
||||
>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-table
|
||||
:data="alltableData"
|
||||
style="width: 100%; margin-bottom: 20px; margin-top: 10px"
|
||||
row-key="userId"
|
||||
border
|
||||
default-expand-all
|
||||
:tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
|
||||
>
|
||||
<el-table-column prop="userId" label="用户ID" />
|
||||
<el-table-column prop="userName" label="用户名" />
|
||||
<el-table-column prop="nickName" label="昵称" />
|
||||
<el-table-column prop="userType" label="用户类型">
|
||||
<template #default="scope">
|
||||
<span v-if="scope.row.userType === '01'">主播</span>
|
||||
<span v-else-if="scope.row.userType === '02'">玩家</span>
|
||||
<span v-else>未知类型</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="createTime" label="创建时间" />
|
||||
<el-table-column prop="totalRecharge" label="总充值" />
|
||||
<el-table-column prop="totalNormGameCost" label="普通游戏消费" />
|
||||
<el-table-column prop="totalSpecialGameCost" label="特殊游戏消费" />
|
||||
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
v-if="scope.row.userType === '02'"
|
||||
size="small"
|
||||
type="primary"
|
||||
link
|
||||
icon="el-icon-money"
|
||||
@click="getPurchaseData(scope.row.userId)"
|
||||
>流水明细</el-button
|
||||
>
|
||||
<!-- <el-button
|
||||
v-if="scope.row.userType === '01'"
|
||||
size="small"
|
||||
type="primary"
|
||||
link
|
||||
icon="el-icon-data-line"
|
||||
@click="getCommissionRateData(scope.row.userId)"
|
||||
>佣金比例</el-button
|
||||
> -->
|
||||
</template>
|
||||
</el-table-column>
|
||||
<!-- <el-table-column label="下级" align="center" class-name="small-padding fixed-width">
|
||||
<template #default="scope">
|
||||
<el-button size="small" link icon="el-icon-money" @click="getgengnduo(scope.row.userId)"
|
||||
>查看下级</el-button
|
||||
>
|
||||
</template>
|
||||
</el-table-column> -->
|
||||
</el-table>
|
||||
<el-pagination
|
||||
layout="prev, pager, next"
|
||||
:page-size="10"
|
||||
:total="total"
|
||||
@current-change="handleCurrentChange"
|
||||
/>
|
||||
|
||||
<el-dialog title="流水明细" v-model="dialogTableVisible" align-center>
|
||||
<Transaction :user-id="subFlowUserId" />
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog title="佣金比例" v-model="dialogCommissionRateVisible">
|
||||
<span style="line-height: 30px">指定下级佣金比例,设置后将无法修改!</span>
|
||||
<el-input v-model="editCommissionRateBody.commissionRate" placeholder="请输入佣金比例" />
|
||||
<template v-slot:footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button type="primary" size="default" @click="editCommissionRate">修改</el-button>
|
||||
<el-button size="default" @click="dialogCommissionRateVisible = false">取消</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import {
|
||||
ElPageHeader,
|
||||
ElDatePicker,
|
||||
ElTable,
|
||||
ElTableColumn,
|
||||
ElForm,
|
||||
ElFormItem,
|
||||
ElInput,
|
||||
ElButton,
|
||||
ElMessage,
|
||||
ElDialog,
|
||||
ElPagination
|
||||
} from 'element-plus'
|
||||
import {
|
||||
getSubBranches,
|
||||
getPurchaseByUserId,
|
||||
getCommissionRateByUserId,
|
||||
updateCommissionRate
|
||||
} from '@/api/subflow'
|
||||
|
||||
import Transaction from './transaction.vue'
|
||||
|
||||
import { getInfo } from '@/api/user'
|
||||
|
||||
// 定义响应式状态
|
||||
const tableData = ref([])
|
||||
const alltableData = ref([] as any)
|
||||
const dialogTableVisible = ref(false)
|
||||
const dialogCommissionRateVisible = ref(false)
|
||||
const userId = ref('')
|
||||
const parentId = ref('')
|
||||
const adddd = ref([])
|
||||
const subFlowUserId = ref('')
|
||||
const total = ref(0)
|
||||
const currentPage = ref(0)
|
||||
// 定义查询参数
|
||||
const queryParams = reactive({
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
userId: undefined as number | undefined | string,
|
||||
playerId: undefined as number | undefined | string
|
||||
})
|
||||
|
||||
const editCommissionRateBody = reactive({
|
||||
userId: '',
|
||||
commissionRate: ''
|
||||
})
|
||||
|
||||
const handleCurrentChange = (val: number) => {
|
||||
currentPage.value = val
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
/** 获取下级数据 */
|
||||
const getSubBranchesData = () => {
|
||||
queryParams.userId = userId.value
|
||||
getSubBranches(queryParams).then((response) => {
|
||||
tableData.value = response.data.rows
|
||||
total.value = response.data.total
|
||||
alltableData.value = tableData.value
|
||||
})
|
||||
}
|
||||
|
||||
/** 返回上一级 */
|
||||
const goBack = () => {
|
||||
/* console.log(alltableData.value) */
|
||||
if (alltableData.value.length == 0) {
|
||||
const a: any = tableData.value.filter((item: any) => item.userId === parentId.value)
|
||||
console.log(a)
|
||||
alltableData.value = tableData.value.filter((item: any) => item.parentId === a[0].parentId)
|
||||
} else {
|
||||
const a: any = tableData.value.filter(
|
||||
(item: any) => item.userId === alltableData.value[0].parentId
|
||||
)
|
||||
console.log(a)
|
||||
alltableData.value = tableData.value.filter((item: any) => item.parentId === a[0].parentId)
|
||||
}
|
||||
}
|
||||
|
||||
/** 获取采购数据 */
|
||||
const getPurchaseData = (userId) => {
|
||||
dialogTableVisible.value = true
|
||||
subFlowUserId.value = userId
|
||||
}
|
||||
|
||||
/** 获取并准备编辑佣金比例 */
|
||||
const getCommissionRateData = (userId) => {
|
||||
editCommissionRateBody.userId = userId
|
||||
getCommissionRateByUserId(userId).then((response) => {
|
||||
editCommissionRateBody.commissionRate = response.data.commissionRate
|
||||
})
|
||||
dialogCommissionRateVisible.value = true
|
||||
}
|
||||
|
||||
/** 提交修改佣金比例 */
|
||||
const editCommissionRate = () => {
|
||||
updateCommissionRate(editCommissionRateBody).then((response) => {
|
||||
ElMessage({
|
||||
message: '修改成功',
|
||||
type: 'success'
|
||||
})
|
||||
dialogCommissionRateVisible.value = false
|
||||
})
|
||||
}
|
||||
|
||||
const handleQuery = () => {
|
||||
getInfo().then((res) => {
|
||||
userId.value = res.data.userId
|
||||
getSubBranchesData()
|
||||
})
|
||||
}
|
||||
|
||||
// 组件挂载时初始化
|
||||
onMounted(() => {
|
||||
handleQuery()
|
||||
})
|
||||
</script>
|
||||
108
promo-ui2/src/views/subFlow/transaction.vue
Normal file
108
promo-ui2/src/views/subFlow/transaction.vue
Normal file
@@ -0,0 +1,108 @@
|
||||
<template>
|
||||
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true">
|
||||
<el-form-item label="时间">
|
||||
<el-date-picker
|
||||
v-model="dateRange"
|
||||
style="width: 240px"
|
||||
value-format="YYYY-MM-DD"
|
||||
type="daterange"
|
||||
range-separator="-"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="el-icon-search" size="small" @click="handleQuery"
|
||||
>查询</el-button
|
||||
>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<el-table :data="gridData" style="width: 100%">
|
||||
<el-table-column property="date" label="日期" width="150" />
|
||||
<el-table-column property="dailyRecharge" label="日充值" width="200" />
|
||||
<el-table-column property="dailyNormGameCost" label="日普通游戏消费" />
|
||||
<el-table-column property="dailySpecialGameCost" label="日特殊游戏消费" />
|
||||
</el-table>
|
||||
<el-pagination
|
||||
layout="prev, pager, next"
|
||||
:page-size="10"
|
||||
:total="total"
|
||||
@current-change="handleCurrentChange"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
ElTable,
|
||||
ElTableColumn,
|
||||
ElForm,
|
||||
ElFormItem,
|
||||
ElButton,
|
||||
ElPagination,
|
||||
ElDatePicker
|
||||
} from 'element-plus'
|
||||
|
||||
import { getPurchaseByUserId } from '@/api/subflow/index'
|
||||
|
||||
import { onMounted, reactive, ref, watch } from 'vue'
|
||||
|
||||
// 接收父组件传递的数据
|
||||
const props = defineProps<{
|
||||
userId: string | number
|
||||
}>()
|
||||
|
||||
const gridData = ref([])
|
||||
// 定义查询参数
|
||||
const queryParams = reactive({
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
beginTime: undefined as string | undefined,
|
||||
endTime: undefined as string | undefined,
|
||||
userId: undefined as number | undefined | string
|
||||
})
|
||||
// 日期范围
|
||||
const dateRange = ref([])
|
||||
const total = ref(0)
|
||||
const currentPage = ref(1)
|
||||
|
||||
watch(
|
||||
() => props.userId,
|
||||
(newVal) => {
|
||||
if (newVal) {
|
||||
handleQuery()
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
const handleCurrentChange = (val: number) => {
|
||||
currentPage.value = val
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
const getPurchaseData = () => {
|
||||
getPurchaseByUserId(queryParams).then((response) => {
|
||||
gridData.value = response.rows
|
||||
total.value = response.total
|
||||
})
|
||||
}
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.userId = props.userId
|
||||
queryParams.pageSize = 10
|
||||
queryParams.pageNum = currentPage.value
|
||||
|
||||
if (dateRange.value !== null && dateRange.value.length === 2) {
|
||||
queryParams.beginTime = dateRange.value[0]
|
||||
queryParams.endTime = dateRange.value[1]
|
||||
} else {
|
||||
queryParams.beginTime = undefined
|
||||
queryParams.endTime = undefined
|
||||
}
|
||||
getPurchaseData()
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
handleQuery()
|
||||
})
|
||||
</script>
|
||||
Reference in New Issue
Block a user