Compare commits
No commits in common. "7f02e3bc97ae0992f857ea3cf7fd3d9cc4f9bbb1" and "b490b6c9c5f8507fb39801a92e0aa7d95d4e7181" have entirely different histories.
7f02e3bc97
...
b490b6c9c5
2
.env
2
.env
|
|
@ -2,7 +2,7 @@
|
||||||
# 应用配置
|
# 应用配置
|
||||||
# =====================================================================================
|
# =====================================================================================
|
||||||
# 网站标题
|
# 网站标题
|
||||||
VITE_TITLE = 绝弹项目管理
|
VITE_TITLE = 绝弹管理后台
|
||||||
# 网站副标题
|
# 网站副标题
|
||||||
VITE_SUBTITLE = 快速开发web应用的模板工具
|
VITE_SUBTITLE = 快速开发web应用的模板工具
|
||||||
# 接口前缀 说明:参见 axios 的 baseURL
|
# 接口前缀 说明:参见 axios 的 baseURL
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ env:
|
||||||
# 部署服务器密码, 例如: 123456
|
# 部署服务器密码, 例如: 123456
|
||||||
deploy_pass: ${{ secrets.DEPLOY_PASS }}
|
deploy_pass: ${{ secrets.DEPLOY_PASS }}
|
||||||
# 要更新的 docker 服务名称, 例如: demo_web
|
# 要更新的 docker 服务名称, 例如: demo_web
|
||||||
deploy_name: appnify_web
|
deploy_name: demo_web
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
|
|
@ -42,40 +42,43 @@ jobs:
|
||||||
steps:
|
steps:
|
||||||
- name: 检出代码
|
- name: 检出代码
|
||||||
id: checkout
|
id: checkout
|
||||||
uses: actions/checkout@v3
|
uses: https://git.dev.juetan.cn/mirror/checkout@v3
|
||||||
|
|
||||||
# - name: 设置NodeJS环境
|
- name: 设置环境
|
||||||
# uses: actions/setup-node@v2
|
uses: https://git.dev.juetan.cn/mirror/setup-node@v2
|
||||||
|
|
||||||
# - name: 安装Npm依赖
|
- name: 安装依赖
|
||||||
# run: npm install --registry https://registry.npmmirror.com/
|
run: |
|
||||||
|
npm install --registry https://registry.npmmirror.com/
|
||||||
|
|
||||||
# - name: 构建产物
|
- name: 构建产物
|
||||||
# run: npm run build
|
run: npm run build
|
||||||
|
|
||||||
# - name: 打印产物目录
|
- name: 打印目录
|
||||||
# run: ls ./dist
|
run: ls ./dist
|
||||||
|
|
||||||
- name: 构建Docker镜像
|
- name: 构建镜像
|
||||||
run: docker build -t ${{ env.docker_name }}:latest .
|
run: |
|
||||||
|
docker build -t ${{ env.docker_name }}:latest .
|
||||||
|
|
||||||
- name: 登陆Docker镜像仓库
|
- name: 登陆镜像
|
||||||
run: docker login -u "${{ env.docker_user }}" -p "${{ env.docker_pass }}" ${{ env.docker_host }}
|
run: |
|
||||||
|
docker login -u "${{ env.docker_user }}" -p "${{ env.docker_pass }}" ${{ env.docker_host }}
|
||||||
|
|
||||||
- name: 推送Docker镜像到仓库
|
- name: 推送镜像
|
||||||
shell: bash
|
shell: bash
|
||||||
run: docker push ${{ env.docker_name }}:latest
|
run: |
|
||||||
|
docker push ${{ env.docker_name }}:latest
|
||||||
|
|
||||||
- name: 打上Docker镜像版本标签并推送到仓库
|
- name: 标记镜像
|
||||||
if: gitea.ref_type == 'tag'
|
if: gitea.ref_type == 'tag'
|
||||||
run: |
|
run: |
|
||||||
echo "当前推送版本:${{ gitea.ref_name }}"
|
echo "当前推送版本:${{ gitea.ref_name }}"
|
||||||
docker tag ${{ env.docker_name }}:latest ${{ env.docker_name }}:${{ gitea.ref_name }}
|
docker tag ${{ env.docker_name }}:latest ${{ env.docker_name }}:${{ gitea.ref_name }}
|
||||||
docker push ${{ env.docker_name }}:${{ gitea.ref_name }}
|
docker push ${{ env.docker_name }}:${{ gitea.ref_name }}
|
||||||
|
|
||||||
- name: 登陆到部署环境执行更新命令
|
- name: 更新服务
|
||||||
uses: appleboy/ssh-action@v1.0.0
|
uses: https://git.dev.juetan.cn/mirror/ssh-action@v1.0.0
|
||||||
if: false
|
|
||||||
with:
|
with:
|
||||||
host: ${{ env.deploy_host }}
|
host: ${{ env.deploy_host }}
|
||||||
port: ${{ env.deploy_port }}
|
port: ${{ env.deploy_port }}
|
||||||
|
|
|
||||||
13
Dockerfile
13
Dockerfile
|
|
@ -1,16 +1,15 @@
|
||||||
FROM node:20-alpine as builder
|
FROM node:20-alpine as build
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY package.json .
|
COPY ./package.json .
|
||||||
COPY pnpm-lock.yaml .
|
COPY ./pnpm-lock.yaml .
|
||||||
COPY .npmrc .
|
|
||||||
RUN corepack enable
|
RUN corepack enable
|
||||||
RUN pnpm install
|
RUN pnpm install --registry https://registry.npmmirror.com/
|
||||||
COPY . .
|
COPY . .
|
||||||
RUN pnpm build
|
RUN pnpm build
|
||||||
|
|
||||||
FROM nginx:alpine
|
FROM nginx:alpine
|
||||||
COPY --from=builder /app/dist /usr/share/nginx/html
|
COPY --from=build /app/dist /usr/share/nginx/html
|
||||||
COPY --from=builder /app/.github/nginx.conf /etc/nginx/conf.d/default.conf
|
COPY --from=build /app/.github/nginx.conf /etc/nginx/conf.d/default.conf
|
||||||
|
|
||||||
EXPOSE 80
|
EXPOSE 80
|
||||||
CMD ["nginx", "-g", "daemon off;"]
|
CMD ["nginx", "-g", "daemon off;"]
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="h-full overflow-hidden grid grid-rows-[auto_1fr]">
|
<div>
|
||||||
<div class="bg-white px-4 py-2">
|
<div class="bg-white px-4 py-2">
|
||||||
<div class="flex justify-between gap-4">
|
<div class="flex justify-between gap-4">
|
||||||
<BreadCrumb></BreadCrumb>
|
<BreadCrumb></BreadCrumb>
|
||||||
|
|
@ -9,11 +9,9 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<slot name="content">
|
<slot name="content">
|
||||||
<a-scrollbar outer-class="h-full overflow-hidden" class="h-full overflow-auto" type="track">
|
<div class="m-4 p-4 bg-white">
|
||||||
<div class="m-4 p-4 bg-white rounded overflow-hidden">
|
<slot></slot>
|
||||||
<slot></slot>
|
</div>
|
||||||
</div>
|
|
||||||
</a-scrollbar>
|
|
||||||
</slot>
|
</slot>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -22,4 +20,4 @@
|
||||||
import BreadCrumb from "./bread-crumb.vue";
|
import BreadCrumb from "./bread-crumb.vue";
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped></style>
|
<style scoped></style>
|
||||||
|
|
@ -1,156 +0,0 @@
|
||||||
<template>
|
|
||||||
<bread-page>
|
|
||||||
<template #content>
|
|
||||||
<div class="h-full w-full grid grid-cols-[auto_1fr] gap-4 p-4">
|
|
||||||
<div class="bg-white w-[256px]">
|
|
||||||
<div class="flex items-center justify-between gap-2 px-4 h-14">
|
|
||||||
<span class="text-base">菜单列表</span>
|
|
||||||
<div>
|
|
||||||
<a-button>
|
|
||||||
<template #icon>
|
|
||||||
<i class="icon-park-outline-plus"></i>
|
|
||||||
</template>
|
|
||||||
</a-button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<a-tree
|
|
||||||
:data="menus"
|
|
||||||
:default-expand-all="true"
|
|
||||||
:block-node="true"
|
|
||||||
:field-names="{
|
|
||||||
icon: undefined,
|
|
||||||
title: 'name',
|
|
||||||
key: 'id',
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<template #title="node">
|
|
||||||
<div class="group flex-1 flex items-center justify-between gap-2">
|
|
||||||
<div @click="onEdit(node)">
|
|
||||||
<!-- <a-tag :color="MenuTypes.fmt(node.type, 'color')" size="small" :bordered="true">
|
|
||||||
{{ MenuTypes.fmt(node.type) }}
|
|
||||||
</a-tag> -->
|
|
||||||
<i :class="node.icon" class="ml-2"></i>
|
|
||||||
{{ node.name }}
|
|
||||||
</div>
|
|
||||||
<div class="hidden group-hover:block">
|
|
||||||
<i
|
|
||||||
v-if="node.type === MenuType.MENU"
|
|
||||||
class="text-sm text-gray-400 hover:text-gray-700 icon-park-outline-plus"
|
|
||||||
></i>
|
|
||||||
<i class="text-sm text-gray-400 hover:text-gray-700 icon-park-outline-delete"></i>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</a-tree>
|
|
||||||
</div>
|
|
||||||
<div class="bg-white">
|
|
||||||
<a-card title="菜单信息" :bordered="false">
|
|
||||||
<Form ref="formRef" v-bind="form"></Form>
|
|
||||||
</a-card>
|
|
||||||
<a-divider :margin="0"></a-divider>
|
|
||||||
<div class="px-4 mt-4">
|
|
||||||
<btn-table></btn-table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</bread-page>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="tsx">
|
|
||||||
import { Menu, api } from "@/api";
|
|
||||||
import { useForm, Form, useAniTable, FormInstance } from "@/components";
|
|
||||||
import { MenuType, MenuTypes } from "@/constants/menu";
|
|
||||||
|
|
||||||
const formRef = ref<FormInstance | null>(null);
|
|
||||||
const menus = ref<any[]>([]);
|
|
||||||
const treeEach = (tree: any[], fn: any) => {
|
|
||||||
for (const item of tree) {
|
|
||||||
if (item.children) {
|
|
||||||
treeEach(item.children, fn);
|
|
||||||
}
|
|
||||||
fn(item);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const onEdit = (row: any) => {
|
|
||||||
formRef.value?.setModel(row);
|
|
||||||
(btn.props as any).data = row.buttons;
|
|
||||||
};
|
|
||||||
|
|
||||||
onMounted(async () => {
|
|
||||||
const res = await api.menu.getMenus({ tree: true });
|
|
||||||
const data = res.data.data ?? [];
|
|
||||||
treeEach(data, (item: Menu) => {
|
|
||||||
if (item.type === MenuType.BUTTON) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (item.type === MenuType.PAGE) {
|
|
||||||
(item as any).buttons = (item as any).children;
|
|
||||||
delete (item as any).children;
|
|
||||||
}
|
|
||||||
(item as any).iconRender = () => <i class={item.icon} />;
|
|
||||||
});
|
|
||||||
menus.value = data;
|
|
||||||
});
|
|
||||||
|
|
||||||
const form = useForm({
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
field: "name",
|
|
||||||
label: "菜单名称",
|
|
||||||
type: "input",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
field: "icon",
|
|
||||||
label: "菜单图标",
|
|
||||||
type: "input",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
async submit(arg) {
|
|
||||||
console.log(arg);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const [btnTable, btn] = useAniTable({
|
|
||||||
columns: [
|
|
||||||
{
|
|
||||||
title: " 名称",
|
|
||||||
dataIndex: "name",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "标识",
|
|
||||||
dataIndex: "code",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "操作",
|
|
||||||
type: "button",
|
|
||||||
width: 140,
|
|
||||||
buttons: [
|
|
||||||
{
|
|
||||||
type: "modify",
|
|
||||||
text: "修改",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: "删除",
|
|
||||||
type: "delete",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
create: {},
|
|
||||||
modify: {},
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="less" scoped></style>
|
|
||||||
|
|
||||||
<route lang="json">
|
|
||||||
{
|
|
||||||
"meta": {
|
|
||||||
"sort": 10302,
|
|
||||||
"title": "菜单管理",
|
|
||||||
"icon": "icon-park-outline-add-subtract"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</route>
|
|
||||||
|
|
@ -16,7 +16,7 @@
|
||||||
<a-doption>保存为图片</a-doption>
|
<a-doption>保存为图片</a-doption>
|
||||||
</template>
|
</template>
|
||||||
</a-dropdown-button>
|
</a-dropdown-button>
|
||||||
<a-button type="outline" status="danger">退出</a-button>
|
<a-button status="danger">退出</a-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -143,7 +143,7 @@ export const FormModal = defineComponent({
|
||||||
}
|
}
|
||||||
if (typeof props.trigger === "object") {
|
if (typeof props.trigger === "object") {
|
||||||
content = (
|
content = (
|
||||||
<Button type="primary" {...props.trigger.buttonProps}>
|
<Button type="primary" {...omit(props.trigger, "text")}>
|
||||||
{props.trigger?.text || "新增"}
|
{props.trigger?.text || "新增"}
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
|
|
@ -176,7 +176,6 @@ export const FormModal = defineComponent({
|
||||||
onBeforeOk={this.onBeforeOk}
|
onBeforeOk={this.onBeforeOk}
|
||||||
onClose={this.onClose}
|
onClose={this.onClose}
|
||||||
title={this.modalTitle}
|
title={this.modalTitle}
|
||||||
class="ani-form-modal"
|
|
||||||
>
|
>
|
||||||
{this.visible && (
|
{this.visible && (
|
||||||
<Form ref={(el: any) => (this.formRef = el)} {...this.formProps} model={this.model} items={this.items}>
|
<Form ref={(el: any) => (this.formRef = el)} {...this.formProps} model={this.model} items={this.items}>
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ const defineColumn = <T extends TableColumn>(column: T) => {
|
||||||
export const updateColumn = defineColumn({
|
export const updateColumn = defineColumn({
|
||||||
title: "更新者",
|
title: "更新者",
|
||||||
dataIndex: "createdAt",
|
dataIndex: "createdAt",
|
||||||
width: 190,
|
width: 200,
|
||||||
render({ record }) {
|
render({ record }) {
|
||||||
return (
|
return (
|
||||||
<div class="flex flex-col overflow-hidden">
|
<div class="flex flex-col overflow-hidden">
|
||||||
|
|
@ -24,7 +24,7 @@ export const updateColumn = defineColumn({
|
||||||
export const createColumn = defineColumn({
|
export const createColumn = defineColumn({
|
||||||
title: "创建者",
|
title: "创建者",
|
||||||
dataIndex: "createdAt",
|
dataIndex: "createdAt",
|
||||||
width: 190,
|
width: 200,
|
||||||
render({ record }) {
|
render({ record }) {
|
||||||
return (
|
return (
|
||||||
<div class="flex flex-col overflow-hidden">
|
<div class="flex flex-col overflow-hidden">
|
||||||
|
|
|
||||||
|
|
@ -72,7 +72,6 @@ export const Table = defineComponent({
|
||||||
},
|
},
|
||||||
setup(props) {
|
setup(props) {
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
const tableRef = ref<InstanceType<typeof BaseTable>>()
|
|
||||||
const searchRef = ref<FormInstance>();
|
const searchRef = ref<FormInstance>();
|
||||||
const createRef = ref<FormModalInstance>();
|
const createRef = ref<FormModalInstance>();
|
||||||
const modifyRef = ref<FormModalInstance>();
|
const modifyRef = ref<FormModalInstance>();
|
||||||
|
|
@ -143,7 +142,6 @@ export const Table = defineComponent({
|
||||||
const state = {
|
const state = {
|
||||||
loading,
|
loading,
|
||||||
inlined,
|
inlined,
|
||||||
tableRef,
|
|
||||||
searchRef,
|
searchRef,
|
||||||
createRef,
|
createRef,
|
||||||
modifyRef,
|
modifyRef,
|
||||||
|
|
@ -162,12 +160,12 @@ export const Table = defineComponent({
|
||||||
return (
|
return (
|
||||||
<div class="table w-full">
|
<div class="table w-full">
|
||||||
{!this.inlined && (
|
{!this.inlined && (
|
||||||
<div class="border-b pb-0 border-slate-200 mb-3">
|
<div class="border-b pb-2 border-slate-200 mb-5">
|
||||||
<Form ref="searchRef" class="!grid grid-cols-4 gap-x-6" {...this.search}></Form>
|
<Form ref="searchRef" class="!grid grid-cols-4 gap-x-6" {...this.search}></Form>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div class={`mb-3 flex toolbar justify-between ${!this.inlined && "mt-2"}`}>
|
<div class={`mb-3 flex justify-between ${!this.inlined && "mt-2"}`}>
|
||||||
<div class={`${this.create || this.$slots.action ? null : "!hidden"} flex-1 flex gap-2 `}>
|
<div class={`${this.create || this.$slots.action ? null : "!hidden"} flex-1 flex gap-2 `}>
|
||||||
{this.create && (
|
{this.create && (
|
||||||
<FormModal {...(this.create as any)} ref="createRef" onSubmited={this.reloadData}></FormModal>
|
<FormModal {...(this.create as any)} ref="createRef" onSubmited={this.reloadData}></FormModal>
|
||||||
|
|
@ -186,7 +184,6 @@ export const Table = defineComponent({
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<BaseTable
|
<BaseTable
|
||||||
ref="tableRef"
|
|
||||||
row-key="id"
|
row-key="id"
|
||||||
bordered={false}
|
bordered={false}
|
||||||
{...this.$attrs}
|
{...this.$attrs}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { delConfirm } from "@/utils";
|
import { delConfirm } from "@/utils";
|
||||||
import { Divider, Doption, Dropdown, Link, Message, TableColumnData } from "@arco-design/web-vue";
|
import { Doption, Dropdown, Link, Message, TableColumnData } from "@arco-design/web-vue";
|
||||||
import { isArray, merge } from "lodash-es";
|
import { isArray, merge } from "lodash-es";
|
||||||
import { Component, Ref, reactive } from "vue";
|
import { Component, Ref, reactive } from "vue";
|
||||||
import { useFormModal } from "../form";
|
import { useFormModal } from "../form";
|
||||||
|
|
@ -75,21 +75,18 @@ export const useTable = (optionsOrFn: UseTableOptions | (() => UseTableOptions))
|
||||||
buttons.push(merge({}, config.columnButtonBase));
|
buttons.push(merge({}, config.columnButtonBase));
|
||||||
}
|
}
|
||||||
column.render = (columnData) => {
|
column.render = (columnData) => {
|
||||||
return column.buttons?.map((btn, index) => {
|
return column.buttons?.map((btn) => {
|
||||||
if (btn.visible?.(columnData) === false) {
|
if (btn.visible?.(columnData) === false) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<>
|
<Link
|
||||||
{index !== 0 ? <Divider direction="vertical" margin={2} class="!border-gray-300"></Divider> : null}
|
{...btn.buttonProps}
|
||||||
<Link
|
onClick={() => onClick(btn, columnData, getTable)}
|
||||||
{...btn.buttonProps}
|
disabled={btn.disabled?.(columnData)}
|
||||||
onClick={() => onClick(btn, columnData, getTable)}
|
>
|
||||||
disabled={btn.disabled?.(columnData)}
|
{btn.text}
|
||||||
>
|
</Link>
|
||||||
{btn.text}
|
|
||||||
</Link>
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ export default defineComponent({
|
||||||
const icon = route.icon ? () => <i class={route.icon} /> : null;
|
const icon = route.icon ? () => <i class={route.icon} /> : null;
|
||||||
const node: any = route.children?.length ? (
|
const node: any = route.children?.length ? (
|
||||||
<>
|
<>
|
||||||
<div class="px-2"><a-divider margin={6} class="!border-slate-100"></a-divider></div>
|
<div class="px-2"><a-divider margin={6}></a-divider></div>
|
||||||
{this.renderItem(route?.children)}
|
{this.renderItem(route?.children)}
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,6 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useAniFormModal } from "@/components";
|
import { useAniFormModal } from "@/components";
|
||||||
import { useUserStore } from "@/store";
|
import { useUserStore } from "@/store";
|
||||||
import { delConfirm } from "@/utils";
|
|
||||||
import { Message } from "@arco-design/web-vue";
|
import { Message } from "@arco-design/web-vue";
|
||||||
|
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
|
|
@ -57,7 +56,6 @@ const route = useRoute();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const logout = async () => {
|
const logout = async () => {
|
||||||
await delConfirm('退出后将跳转到登录页面,确定退出吗?')
|
|
||||||
userStore.clearUser();
|
userStore.clearUser();
|
||||||
Message.success("提示:已退出登陆!");
|
Message.success("提示:已退出登陆!");
|
||||||
router.push({ path: "/login", query: { redirect: route.path } });
|
router.push({ path: "/login", query: { redirect: route.path } });
|
||||||
|
|
|
||||||
|
|
@ -3,17 +3,17 @@
|
||||||
<a-layout-header
|
<a-layout-header
|
||||||
class="h-13 overflow-hidden flex justify-between items-center gap-4 px-2 pr-4 border-b border-slate-200 bg-white dark:bg-slate-800 dark:border-slate-700"
|
class="h-13 overflow-hidden flex justify-between items-center gap-4 px-2 pr-4 border-b border-slate-200 bg-white dark:bg-slate-800 dark:border-slate-700"
|
||||||
>
|
>
|
||||||
<div class="h-13 flex items-center">
|
<div class="h-13 flex items-center border-b border-slate-200 dark:border-slate-800">
|
||||||
<router-link to="/" class="px-2 py-1 rounded flex items-center gap-2 text-slate-700">
|
<router-link to="/" class="px-2 py-1 rounded flex items-center gap-2 text-slate-700 hover:bg-slate-100">
|
||||||
<img src="/favicon.ico" alt="" width="22" height="22" class="" />
|
<img src="/favicon.ico" alt="" width="22" height="22" class="" />
|
||||||
<h1 class="relative text-lg leading-[19px] dark:text-white m-0 p-0">
|
<h1 class="relative text-lg leading-[19px] dark:text-white m-0 p-0">
|
||||||
{{ appStore.title }}
|
{{ appStore.title }}
|
||||||
<!-- <span
|
<span
|
||||||
v-if="isDev"
|
v-if="isDev"
|
||||||
class="absolute -right-14 -top-1 text-xs font-normal text-brand-500 bg-brand-50 px-1.5 rounded-full"
|
class="absolute -right-14 -top-1 text-xs font-normal text-brand-500 bg-brand-50 px-1.5 rounded-full"
|
||||||
>
|
>
|
||||||
本地版
|
本地版
|
||||||
</span> -->
|
</span>
|
||||||
</h1>
|
</h1>
|
||||||
</router-link>
|
</router-link>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -35,7 +35,7 @@
|
||||||
|
|
||||||
<a-layout class="flex flex-1 overflow-hidden">
|
<a-layout class="flex flex-1 overflow-hidden">
|
||||||
<a-layout-sider
|
<a-layout-sider
|
||||||
class="h-full overflow-hidden dark:bg-slate-800 border-r border-slate-100 dark:border-slate-700"
|
class="h-full overflow-hidden dark:bg-slate-800 border-r border-slate-200 dark:border-slate-700"
|
||||||
:width="224"
|
:width="224"
|
||||||
:collapsed-width="52"
|
:collapsed-width="52"
|
||||||
:collapsible="true"
|
:collapsible="true"
|
||||||
|
|
@ -43,12 +43,9 @@
|
||||||
:hide-trigger="false"
|
:hide-trigger="false"
|
||||||
@collapse="onCollapse"
|
@collapse="onCollapse"
|
||||||
>
|
>
|
||||||
<a-scrollbar outer-class="h-full overflow-hidden" class="h-full overflow-hidden pt-1">
|
<a-scrollbar outer-class="h-full overflow-hidden" class="h-full overflow-hidden pt-2">
|
||||||
<Menu />
|
<Menu />
|
||||||
</a-scrollbar>
|
</a-scrollbar>
|
||||||
<template #trigger="{ collapsed }">
|
|
||||||
<i :class="collapsed ? `icon-park-outline-expand-left` : 'icon-park-outline-expand-right'" class="text-gray-400 text-base hover:text-gray-700"></i>
|
|
||||||
</template>
|
|
||||||
</a-layout-sider>
|
</a-layout-sider>
|
||||||
<a-layout class="layout-content flex-1">
|
<a-layout class="layout-content flex-1">
|
||||||
<a-layout-content class="overflow-x-auto">
|
<a-layout-content class="overflow-x-auto">
|
||||||
|
|
@ -204,7 +201,7 @@ const tagItems = [
|
||||||
// 导致部分内容被截取
|
// 导致部分内容被截取
|
||||||
// min-height: 100vh;
|
// min-height: 100vh;
|
||||||
overflow-y: hidden;
|
overflow-y: hidden;
|
||||||
background-color: #e4ebf1;
|
background-color: var(--color-fill-2);
|
||||||
transition: padding 0.2s cubic-bezier(0.34, 0.69, 0.1, 1);
|
transition: padding 0.2s cubic-bezier(0.34, 0.69, 0.1, 1);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -101,10 +101,10 @@ const onSubmitForm = async () => {
|
||||||
try {
|
try {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
const res = await api.auth.login(model);
|
const res = await api.auth.login(model);
|
||||||
userStore.setAccessToken(res.data.data as unknown as string);
|
userStore.setUser(res.data.data);
|
||||||
Notification.success({
|
Notification.success({
|
||||||
title: "提示",
|
title: "提示",
|
||||||
content: `登陆成功!`,
|
content: `欢迎回来,${res.data.data.nickname}!`,
|
||||||
});
|
});
|
||||||
router.push({ path: (route.query.redirect as string) || "/" });
|
router.push({ path: (route.query.redirect as string) || "/" });
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,10 @@
|
||||||
<template>
|
<template>
|
||||||
<bread-page>
|
<bread-page>
|
||||||
<template #content>
|
<iframe
|
||||||
<iframe
|
src="https://apifox.com/apidoc/shared-f1ea65e6-cee8-4fe3-949f-288a7cd1af49"
|
||||||
src="https://apifox.com/apidoc/shared-f1ea65e6-cee8-4fe3-949f-288a7cd1af49"
|
frameborder="0"
|
||||||
frameborder="0"
|
class="w-full h-full"
|
||||||
class="w-full h-full"
|
></iframe>
|
||||||
></iframe>
|
|
||||||
</template>
|
|
||||||
</bread-page>
|
</bread-page>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -65,10 +65,10 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import doc from "@/dd.json";
|
||||||
|
import editorModal from "./editor.vue";
|
||||||
import ejs from "ejs";
|
import ejs from "ejs";
|
||||||
import doc from "./components/data.json";
|
import template from "./page.ejs?raw";
|
||||||
import editorModal from "./components/editor.vue";
|
|
||||||
import template from "./components/page.ejs?raw";
|
|
||||||
|
|
||||||
const content = ref("");
|
const content = ref("");
|
||||||
const { tags, routes } = doc;
|
const { tags, routes } = doc;
|
||||||
|
|
@ -85,8 +85,8 @@ const onChange = (value: string | number) => {
|
||||||
|
|
||||||
const onOpen = () => {
|
const onOpen = () => {
|
||||||
const data = {
|
const data = {
|
||||||
tag: "",
|
tag: '',
|
||||||
operationId: "",
|
operationId: '',
|
||||||
create: {},
|
create: {},
|
||||||
select: {},
|
select: {},
|
||||||
modify: {},
|
modify: {},
|
||||||
|
|
@ -106,6 +106,7 @@ const onOpen = () => {
|
||||||
data.delete = route;
|
data.delete = route;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
console.log(data);
|
||||||
content.value = ejs.render(template, data);
|
content.value = ejs.render(template, data);
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -17,7 +17,7 @@
|
||||||
class="group flex items-center justify-between gap-1 h-8 rounded mb-2 pl-3 hover:bg-gray-100 cursor-pointer"
|
class="group flex items-center justify-between gap-1 h-8 rounded mb-2 pl-3 hover:bg-gray-100 cursor-pointer"
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<i class="icon-park-outline-folder-close align-[-2px]"></i>
|
<i class="icon-park-outline-folder-close"></i>
|
||||||
{{ item.title }}
|
{{ item.title }}
|
||||||
<span class="text-xs text-gray-500"> ({{ item.count }}) </span>
|
<span class="text-xs text-gray-500"> ({{ item.count }}) </span>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,277 +1,44 @@
|
||||||
<template>
|
<template>
|
||||||
<a-button type="primary" @click="visible = true"> 上传文件 </a-button>
|
<a-modal v-model:visible="modal.visible" title="上传文件" title-align="start" :footer="false">
|
||||||
<a-modal
|
<a-upload :custom-request="upload" draggable action="/api/v1/upload"></a-upload>
|
||||||
v-model:visible="visible"
|
|
||||||
title="上传文件"
|
|
||||||
title-align="start"
|
|
||||||
:width="860"
|
|
||||||
:mask-closable="false"
|
|
||||||
:on-before-cancel="onBeforeCancel"
|
|
||||||
@close="onClose"
|
|
||||||
>
|
|
||||||
<div class="mb-2 flex items-center gap-4">
|
|
||||||
<a-upload
|
|
||||||
ref="uploadRef"
|
|
||||||
class="upload"
|
|
||||||
v-model:file-list="fileList"
|
|
||||||
:multiple="true"
|
|
||||||
:custom-request="upload"
|
|
||||||
:auto-upload="false"
|
|
||||||
:show-file-list="false"
|
|
||||||
@success="onUploadSuccess"
|
|
||||||
@error="onUploadError"
|
|
||||||
>
|
|
||||||
<template #upload-button>
|
|
||||||
<a-button type="outline"> 选择文件 </a-button>
|
|
||||||
</template>
|
|
||||||
</a-upload>
|
|
||||||
<div class="flex-1 flex items-center text-gray-400">
|
|
||||||
归类为:
|
|
||||||
<span>
|
|
||||||
<a-select v-model="group" :bordered="false" :options="groupOptions"></a-select>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<ul v-if="fileList.length" class="h-[424px] overflow-hidden p-0 m-0">
|
|
||||||
<a-scrollbar outer-class="h-full overflow-hidden" class="h-full overflow-auto pr-[20px] divide-y">
|
|
||||||
<li v-for="item in fileList" :key="item.uid" class="flex items-center gap-2 py-3">
|
|
||||||
<div class="flex-1 overflow-hidden">
|
|
||||||
<div class="truncate text-slate-900">
|
|
||||||
{{ item.name }}
|
|
||||||
</div>
|
|
||||||
<div class="flex items-center justify-between gap-2 text-gray-400 mb-[-4px] mt-1">
|
|
||||||
<span class="text-xs text-gray-400">
|
|
||||||
{{ numeral(item.file?.size).format("0 b") }}
|
|
||||||
</span>
|
|
||||||
<span class="text-xs">
|
|
||||||
<span v-if="item.status === 'init'"> </span>
|
|
||||||
<span v-else-if="item.status === 'uploading'">
|
|
||||||
<span class="text-xs">
|
|
||||||
速度:{{ numeral(fileMap.get(item.uid)?.speed || 0).format("0 b") }}/s, 进度:{{
|
|
||||||
Math.floor((item.percent || 0) * 100)
|
|
||||||
}}
|
|
||||||
%
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
<span v-else-if="item.status === 'done'" class="text-green-600">
|
|
||||||
完成(耗时:{{ fileMap.get(item.uid)?.cost || 0 }}秒)
|
|
||||||
</span>
|
|
||||||
<span v-else="item.status === 'error'" class="text-red-500">
|
|
||||||
失败(原因:{{ fileMap.get(item.uid)?.error }})
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<a-progress :percent="Math.floor((item.percent || 0) * 100) / 100" :show-text="false"></a-progress>
|
|
||||||
</div>
|
|
||||||
<div v-show="item.status !== 'done'">
|
|
||||||
<a-link v-show="item.status === 'uploading'" @click="pauseItem(item)">停止</a-link>
|
|
||||||
<a-link v-show="item.status === 'error'" @click="retryItem(item)">重试</a-link>
|
|
||||||
<a-link v-show="item.status === 'init' || item.status === 'error'" @click="removeItem(item)">删除</a-link>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
</a-scrollbar>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<div v-else class="h-[424px] flex items-center justify-center">
|
|
||||||
<a-empty description="选择文件后显示"></a-empty>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<template #footer>
|
|
||||||
<div class="flex justify-between gap-2 items-center">
|
|
||||||
<div class="text-gray-400">已上传 {{ stat.doneCount }}/{{ fileList.length }} 项</div>
|
|
||||||
<div class="space-x-2">
|
|
||||||
<a-button type="text" :disabled="!fileList.length || Boolean(stat.uploadingCount)" @click="clearUploaded">
|
|
||||||
清空
|
|
||||||
</a-button>
|
|
||||||
<a-button type="primary" :disabled="!fileList.length || !stat.initCount" @click="startUpload">
|
|
||||||
开始上传
|
|
||||||
</a-button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</a-modal>
|
</a-modal>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { RequestParams, api } from "@/api";
|
import { api } from "@/api";
|
||||||
import { delConfirm } from "@/utils";
|
import { RequestOption } from "@arco-design/web-vue";
|
||||||
import { FileItem, Message, RequestOption, UploadInstance } from "@arco-design/web-vue";
|
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import numeral from "numeral";
|
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const modal = ref({
|
||||||
(event: "success", item: FileItem): void;
|
visible: false,
|
||||||
(event: "close", count: number): void;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const visible = ref(false);
|
|
||||||
const uploadRef = ref<UploadInstance | null>(null);
|
|
||||||
const fileList = ref<FileItem[]>([]);
|
|
||||||
const fileMap = reactive<
|
|
||||||
Map<
|
|
||||||
string,
|
|
||||||
{
|
|
||||||
lastTime: number;
|
|
||||||
lastLoaded: number;
|
|
||||||
speed: number;
|
|
||||||
cost: number;
|
|
||||||
error: string;
|
|
||||||
} | null
|
|
||||||
>
|
|
||||||
>(new Map());
|
|
||||||
|
|
||||||
const stat = computed(() => {
|
|
||||||
const result = {
|
|
||||||
initCount: 0,
|
|
||||||
doneCount: 0,
|
|
||||||
uploadingCount: 0,
|
|
||||||
errorCount: 0,
|
|
||||||
};
|
|
||||||
for (const item of fileList.value) {
|
|
||||||
if (item.status === "init") result.initCount++;
|
|
||||||
if (item.status === "uploading") result.uploadingCount++;
|
|
||||||
if (item.status === "done") result.doneCount++;
|
|
||||||
if (item.status === "error") result.errorCount++;
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
|
||||||
* 开始上传
|
|
||||||
*/
|
|
||||||
const startUpload = () => {
|
|
||||||
uploadRef.value?.submit();
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 中止上传
|
|
||||||
* @param item 文件
|
|
||||||
*/
|
|
||||||
const pauseItem = (item: FileItem) => {
|
|
||||||
uploadRef.value?.abort(item);
|
|
||||||
const file = fileMap.get(item.uid);
|
|
||||||
if (file) {
|
|
||||||
file.error = "手动中止";
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 移除文件
|
|
||||||
* @param item 文件
|
|
||||||
*/
|
|
||||||
const removeItem = (item: FileItem) => {
|
|
||||||
const index = fileList.value.findIndex((i) => i.uid === item.uid);
|
|
||||||
if (index > -1) {
|
|
||||||
fileList.value.splice(index, 1);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 重新上传
|
|
||||||
* @param item 文件
|
|
||||||
*/
|
|
||||||
const retryItem = (item: FileItem) => {
|
|
||||||
uploadRef.value?.submit(item);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 清空已上传
|
|
||||||
*/
|
|
||||||
const clearUploaded = async () => {
|
|
||||||
if (stat.value.doneCount !== fileList.value.length) {
|
|
||||||
await delConfirm("当前有未上传完成的文件,是否继续清空?");
|
|
||||||
}
|
|
||||||
fileList.value = [];
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 上传成功后处理
|
|
||||||
* @param item 文件
|
|
||||||
*/
|
|
||||||
const onUploadSuccess = (item: FileItem) => {
|
|
||||||
emit("success", item);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 上传失败后处理
|
|
||||||
* @param item 文件
|
|
||||||
*/
|
|
||||||
const onUploadError = (item: FileItem) => {
|
|
||||||
const file = fileMap.get(item.uid);
|
|
||||||
if (file) {
|
|
||||||
file.error = item.response?.data?.message || "网络异常";
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 关闭前检测
|
|
||||||
*/
|
|
||||||
const onBeforeCancel = () => {
|
|
||||||
if (fileList.value.some((i) => i.status === "uploading")) {
|
|
||||||
Message.warning("提示:文件上传中,请稍后再试!");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 关闭后处理
|
|
||||||
*/
|
|
||||||
const onClose = () => {
|
|
||||||
fileMap.clear();
|
|
||||||
fileList.value = [];
|
|
||||||
emit("close", stat.value.doneCount);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 自定义上传逻辑
|
|
||||||
* @param option
|
|
||||||
*/
|
|
||||||
const upload = (option: RequestOption) => {
|
const upload = (option: RequestOption) => {
|
||||||
const { fileItem, onError, onProgress, onSuccess } = option;
|
const { fileItem, onError, onProgress, onSuccess } = option;
|
||||||
const source = axios.CancelToken.source();
|
const source = axios.CancelToken.source();
|
||||||
if (!fileMap.has(fileItem.uid)) {
|
|
||||||
fileMap.set(fileItem.uid, {
|
|
||||||
lastTime: Date.now(),
|
|
||||||
lastLoaded: 0,
|
|
||||||
cost: 0,
|
|
||||||
speed: 0,
|
|
||||||
error: "网络异常",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
const item = fileMap.get(fileItem.uid)!;
|
|
||||||
const startTime = Date.now();
|
|
||||||
const up = async () => {
|
|
||||||
const data = { file: fileItem.file as any };
|
|
||||||
const params: RequestParams = {
|
|
||||||
onUploadProgress(e) {
|
|
||||||
let percent = 0;
|
|
||||||
const { lastTime, lastLoaded } = item;
|
|
||||||
if (e.total && e.total > 0) {
|
|
||||||
percent = e.loaded / e.total;
|
|
||||||
const nowTime = Date.now();
|
|
||||||
const diff = (e.loaded - lastLoaded) / (nowTime - lastTime);
|
|
||||||
const speed = Math.floor(diff * 1000);
|
|
||||||
item.speed = speed;
|
|
||||||
item.lastLoaded = e.loaded;
|
|
||||||
item.lastTime = nowTime;
|
|
||||||
}
|
|
||||||
onProgress(percent, e as any);
|
|
||||||
},
|
|
||||||
cancelToken: source.token,
|
|
||||||
};
|
|
||||||
try {
|
|
||||||
const res = await api.file.addFile(data, params);
|
|
||||||
const currentTime = Date.now();
|
|
||||||
item.cost = Math.floor((currentTime - startTime) / 1000);
|
|
||||||
onSuccess(res);
|
|
||||||
} catch (e) {
|
|
||||||
onError(e);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
if (fileItem.file) {
|
if (fileItem.file) {
|
||||||
up();
|
api.file
|
||||||
|
.addFile(
|
||||||
|
{
|
||||||
|
file: fileItem.file,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
onUploadProgress(e) {
|
||||||
|
let percent = 0;
|
||||||
|
if (e.total && e.total > 0) {
|
||||||
|
percent = e.loaded / e.total;
|
||||||
|
}
|
||||||
|
onProgress(percent, e as any);
|
||||||
|
},
|
||||||
|
cancelToken: source.token,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.then((res) => {
|
||||||
|
onSuccess(res);
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
onError(e);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
abort() {
|
abort() {
|
||||||
|
|
@ -282,22 +49,9 @@ const upload = (option: RequestOption) => {
|
||||||
|
|
||||||
defineExpose({
|
defineExpose({
|
||||||
open: () => {
|
open: () => {
|
||||||
visible.value = true;
|
modal.value.visible = true;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO
|
|
||||||
const group = ref("default");
|
|
||||||
const groupOptions = [
|
|
||||||
{
|
|
||||||
label: "默认分类",
|
|
||||||
value: "default",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "视频分类",
|
|
||||||
value: "video",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped></style>
|
<style scoped></style>
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,20 @@
|
||||||
<template>
|
<template>
|
||||||
<BreadPage>
|
<BreadPage>
|
||||||
<div class="overflow-hidden h-full grid grid-cols-[auto_1fr] gap-4">
|
<div class="overflow-hidden grid grid-cols-[auto_auto_1fr]">
|
||||||
<ani-group></ani-group>
|
<ani-group></ani-group>
|
||||||
|
<a-divider direction="vertical" :margin="16"></a-divider>
|
||||||
<div>
|
<div>
|
||||||
<Table v-bind="table">
|
<Table v-bind="table">
|
||||||
<template #action>
|
<template #action>
|
||||||
<ani-upload></ani-upload>
|
<a-button type="primary" @click="uploadRef?.open()">
|
||||||
<a-button type="outline" status="danger" :disabled="!selected.length" @click="onDeleteMany">
|
<template #icon>
|
||||||
批量删除
|
<i class="icon-park-outline-upload"></i>
|
||||||
|
</template>
|
||||||
|
上传
|
||||||
</a-button>
|
</a-button>
|
||||||
|
<ani-upload ref="uploadRef"></ani-upload>
|
||||||
</template>
|
</template>
|
||||||
</Table>
|
</Table>
|
||||||
<a-image-preview v-model:visible="visible" :src="image"></a-image-preview>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</BreadPage>
|
</BreadPage>
|
||||||
|
|
@ -19,27 +22,29 @@
|
||||||
|
|
||||||
<script setup lang="tsx">
|
<script setup lang="tsx">
|
||||||
import { api } from "@/api";
|
import { api } from "@/api";
|
||||||
import { Table, createColumn, updateColumn, useTable } from "@/components";
|
import { Table, useAniFormModal, useTable } from "@/components";
|
||||||
import { delConfirm } from "@/utils";
|
import { dayjs } from "@/libs/dayjs";
|
||||||
import numeral from "numeral";
|
import numeral from "numeral";
|
||||||
import AniGroup from "./components/group.vue";
|
import AniGroup from './components/group.vue';
|
||||||
import AniUpload from "./components/upload.vue";
|
import AniUpload from "./components/upload.vue";
|
||||||
|
|
||||||
const visible = ref(false);
|
const [typeModal, typeCtx] = useAniFormModal({
|
||||||
const image = ref("");
|
title: "修改分组",
|
||||||
const selected = ref<number[]>([]);
|
trigger: false,
|
||||||
const preview = (record: any) => {
|
modalProps: {
|
||||||
if (!record.mimetype.startsWith("image")) {
|
width: 432,
|
||||||
window.open(record.path, "_blank");
|
},
|
||||||
return;
|
items: [
|
||||||
}
|
{
|
||||||
image.value = record.path;
|
field: "name",
|
||||||
visible.value = true;
|
label: "分组名称",
|
||||||
};
|
type: "input",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
submit: async () => {},
|
||||||
|
});
|
||||||
|
|
||||||
const onDeleteMany = async () => {
|
const uploadRef = ref<InstanceType<typeof AniUpload>>();
|
||||||
await delConfirm();
|
|
||||||
};
|
|
||||||
|
|
||||||
const getIcon = (mimetype: string) => {
|
const getIcon = (mimetype: string) => {
|
||||||
if (mimetype.startsWith("image")) {
|
if (mimetype.startsWith("image")) {
|
||||||
|
|
@ -61,45 +66,32 @@ const table = useTable({
|
||||||
data: async (model, paging) => {
|
data: async (model, paging) => {
|
||||||
return api.file.getFiles();
|
return api.file.getFiles();
|
||||||
},
|
},
|
||||||
tableProps: {
|
|
||||||
rowSelection: {
|
|
||||||
showCheckedAll: true,
|
|
||||||
},
|
|
||||||
onSelectionChange(rowKeys) {
|
|
||||||
selected.value = rowKeys as number[];
|
|
||||||
},
|
|
||||||
},
|
|
||||||
columns: [
|
columns: [
|
||||||
{
|
{
|
||||||
title: "文件名称",
|
title: "文件名称",
|
||||||
dataIndex: "name",
|
dataIndex: "name",
|
||||||
render({ record }) {
|
render({ record }) {
|
||||||
return (
|
return (
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center">
|
||||||
<div>
|
<div>
|
||||||
{record.mimetype.startsWith("image") ? (
|
<i class={`${getIcon(record.mimetype)} text-3xl mr-2`}></i>
|
||||||
<a-avatar size={32} shape="square">
|
|
||||||
<img src={record.path}></img>
|
|
||||||
</a-avatar>
|
|
||||||
) : (
|
|
||||||
<i class={`${getIcon(record.mimetype)} text-3xl mr-2`}></i>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col overflow-hidden">
|
<div class="flex flex-col overflow-hidden">
|
||||||
<span
|
<span>{record.name}</span>
|
||||||
class="hover:text-brand-500 hover:decoration-underline underline-offset-2 cursor-pointer"
|
<span class="text-gray-400 text-xs truncate">
|
||||||
onClick={() => preview(record)}
|
{numeral(record.size).format("0 b")}
|
||||||
>
|
|
||||||
{record.name}
|
|
||||||
</span>
|
</span>
|
||||||
<span class="text-gray-400 text-xs truncate">{numeral(record.size).format("0 b")}</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
createColumn,
|
{
|
||||||
updateColumn,
|
title: "上传时间",
|
||||||
|
dataIndex: "createdAt",
|
||||||
|
width: 200,
|
||||||
|
render: ({ record }) => dayjs(record.createdAt).format(),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
type: "button",
|
type: "button",
|
||||||
title: "操作",
|
title: "操作",
|
||||||
|
|
@ -126,8 +118,7 @@ const table = useTable({
|
||||||
field: "name",
|
field: "name",
|
||||||
label: "文件名称",
|
label: "文件名称",
|
||||||
type: "search",
|
type: "search",
|
||||||
searchable: true,
|
enableLoad: true,
|
||||||
enterable: true,
|
|
||||||
itemProps: {
|
itemProps: {
|
||||||
hideLabel: true,
|
hideLabel: true,
|
||||||
},
|
},
|
||||||
|
|
@ -137,28 +128,6 @@ const table = useTable({
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
modify: {
|
|
||||||
title: "修改素材",
|
|
||||||
modalProps: {
|
|
||||||
width: 580,
|
|
||||||
},
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
field: "name",
|
|
||||||
label: "名称",
|
|
||||||
type: "input",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
field: "description",
|
|
||||||
label: "描述",
|
|
||||||
type: "textarea",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
submit: ({ model }) => {
|
|
||||||
console.log(model);
|
|
||||||
return api.file.setFile(model.id, model);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,127 +0,0 @@
|
||||||
<template>
|
|
||||||
<div class="w-[210px] h-full overflow-hidden grid grid-rows-[auto_1fr]">
|
|
||||||
<div class="flex gap-2">
|
|
||||||
<a-input-search allow-clear placeholder="字典类型" class="mb-2"></a-input-search>
|
|
||||||
<a-button @click="formCtx.open">
|
|
||||||
<template #icon>
|
|
||||||
<i class="icon-park-outline-add"></i>
|
|
||||||
</template>
|
|
||||||
</a-button>
|
|
||||||
<form-modal></form-modal>
|
|
||||||
</div>
|
|
||||||
<a-scrollbar outer-class="h-full overflow-hidden" class="h-full overflow-auto">
|
|
||||||
<ul class="pl-0 mt-0">
|
|
||||||
<li
|
|
||||||
v-for="item in list"
|
|
||||||
:key="item.code"
|
|
||||||
:class="{ active: item.id === current?.id }"
|
|
||||||
class="group flex items-center justify-between gap-1 h-8 rounded mb-2 pl-3 hover:bg-gray-100 cursor-pointer"
|
|
||||||
>
|
|
||||||
<div class="flex-1 h-full flex items-center gap-2 overflow-hidden" @click="emit('change', item)">
|
|
||||||
<i class="icon-park-outline-folder-close align-[-2px]"></i>
|
|
||||||
<span class="flex-1 truncate">{{ item.name }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="">
|
|
||||||
<a-dropdown>
|
|
||||||
<a-button size="small" type="text">
|
|
||||||
<template #icon>
|
|
||||||
<i class="icon-park-outline-more-one text-gray-400 hover:text-gray-700"></i>
|
|
||||||
</template>
|
|
||||||
</a-button>
|
|
||||||
<template #content>
|
|
||||||
<a-doption @click="formCtx.open(item)">
|
|
||||||
<template #icon>
|
|
||||||
<i class="icon-park-outline-edit"></i>
|
|
||||||
</template>
|
|
||||||
修改
|
|
||||||
</a-doption>
|
|
||||||
<a-doption class="!text-red-500" @click="onDeleteRow(item)">
|
|
||||||
<template #icon>
|
|
||||||
<i class="icon-park-outline-delete"></i>
|
|
||||||
</template>
|
|
||||||
删除
|
|
||||||
</a-doption>
|
|
||||||
</template>
|
|
||||||
</a-dropdown>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</a-scrollbar>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { DictType, api } from "@/api";
|
|
||||||
import { useAniFormModal } from "@/components";
|
|
||||||
import { delConfirm } from "@/utils";
|
|
||||||
import { Message } from "@arco-design/web-vue";
|
|
||||||
import { PropType } from "vue";
|
|
||||||
|
|
||||||
defineProps({
|
|
||||||
current: {
|
|
||||||
type: Object as PropType<DictType>,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const emit = defineEmits(["change"]);
|
|
||||||
const list = ref<DictType[]>([]);
|
|
||||||
|
|
||||||
const updateDictTypes = async () => {
|
|
||||||
const res = await api.dictType.getDictTypes({ size: 0 });
|
|
||||||
list.value = res.data.data ?? [];
|
|
||||||
list.value.length && emit("change", list.value[0]);
|
|
||||||
};
|
|
||||||
|
|
||||||
onMounted(updateDictTypes);
|
|
||||||
|
|
||||||
const onDeleteRow = async (row: DictType) => {
|
|
||||||
await delConfirm();
|
|
||||||
const res = await api.dictType.delDictType(row.id);
|
|
||||||
Message.success(res.data.message);
|
|
||||||
};
|
|
||||||
|
|
||||||
const [formModal, formCtx] = useAniFormModal({
|
|
||||||
title: ({ model }) => (!model.id ? "新建字典类型" : "修改字典类型"),
|
|
||||||
trigger: false,
|
|
||||||
modalProps: {
|
|
||||||
width: 580,
|
|
||||||
},
|
|
||||||
model: {
|
|
||||||
id: undefined,
|
|
||||||
},
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
field: "name",
|
|
||||||
label: "名称",
|
|
||||||
type: "input",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
field: "code",
|
|
||||||
label: "唯一编码",
|
|
||||||
type: "input",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
field: "description",
|
|
||||||
label: "备注信息",
|
|
||||||
type: "textarea",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
submit: async ({ model }) => {
|
|
||||||
let res;
|
|
||||||
if (model.id) {
|
|
||||||
res = await api.dictType.setDictType(model.id, model);
|
|
||||||
} else {
|
|
||||||
res = await api.dictType.addDictType(model);
|
|
||||||
}
|
|
||||||
updateDictTypes();
|
|
||||||
return res;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="less" scoped>
|
|
||||||
.active {
|
|
||||||
color: rgb(var(--primary-6));
|
|
||||||
background-color: rgb(var(--primary-1));
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
@ -1,139 +0,0 @@
|
||||||
<template>
|
|
||||||
<div class="h-full w-full grid grid-rows-[auto_1fr] overflow-hidden">
|
|
||||||
<div class="py-2 px-4 bg-white">
|
|
||||||
<bread-crumb></bread-crumb>
|
|
||||||
</div>
|
|
||||||
<div class="grid grid-cols-[auto_1fr] gap-4 overflow-hidden bg-white p-4 m-4 rounded">
|
|
||||||
<div>
|
|
||||||
<ani-group :current="current" @change="onTypeChange"></ani-group>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<a-alert :show-icon="false" class="mb-3 !border-brand-500">
|
|
||||||
<span class="text-brand-500 font-bold">{{ current?.name }}</span>
|
|
||||||
<div class="mt-1">描述:{{ current?.description }}</div>
|
|
||||||
</a-alert>
|
|
||||||
<dict-table></dict-table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="tsx">
|
|
||||||
import { DictType, api } from "@/api";
|
|
||||||
import { createColumn, updateColumn, useAniTable } from "@/components";
|
|
||||||
import aniGroup from "./components/group.vue";
|
|
||||||
|
|
||||||
const current = ref<DictType>();
|
|
||||||
const onTypeChange = (item: DictType) => {
|
|
||||||
current.value = item;
|
|
||||||
dict.refresh();
|
|
||||||
};
|
|
||||||
|
|
||||||
const [dictTable, dict] = useAniTable({
|
|
||||||
async data(search, paging) {
|
|
||||||
return api.dict.getDicts({ ...search, ...paging, typeId: current.value?.id } as any);
|
|
||||||
},
|
|
||||||
columns: [
|
|
||||||
{
|
|
||||||
title: "字典项",
|
|
||||||
dataIndex: "name",
|
|
||||||
render: ({ record }) => {
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<div>
|
|
||||||
<span class="text-gray-900">{record.name}</span>: {record.code}
|
|
||||||
</div>
|
|
||||||
<div class="text-gray-400 text-xs">{record.description}</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
createColumn,
|
|
||||||
updateColumn,
|
|
||||||
{
|
|
||||||
title: "操作",
|
|
||||||
type: "button",
|
|
||||||
width: 140,
|
|
||||||
buttons: [
|
|
||||||
{
|
|
||||||
type: "modify",
|
|
||||||
text: "修改",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "delete",
|
|
||||||
text: "删除",
|
|
||||||
onClick: ({ record }) => {
|
|
||||||
return api.dict.delDict(record.id);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
search: {
|
|
||||||
button: false,
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
field: "name",
|
|
||||||
label: "名称",
|
|
||||||
type: "search",
|
|
||||||
searchable: true,
|
|
||||||
enterable: true,
|
|
||||||
nodeProps: {
|
|
||||||
placeholder: "字典名称",
|
|
||||||
},
|
|
||||||
itemProps: {
|
|
||||||
hideLabel: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
create: {
|
|
||||||
title: '新增字典',
|
|
||||||
model: {
|
|
||||||
typeId: undefined,
|
|
||||||
},
|
|
||||||
modalProps: {
|
|
||||||
width: 580,
|
|
||||||
},
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
field: "name",
|
|
||||||
label: "字典名",
|
|
||||||
type: "input",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
field: "code",
|
|
||||||
label: "字典值",
|
|
||||||
type: "input",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
field: "description",
|
|
||||||
label: "备注",
|
|
||||||
type: "textarea",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
submit: async ({ model }) => {
|
|
||||||
return api.dict.addDict({ ...model, typeId: current.value?.id });
|
|
||||||
},
|
|
||||||
},
|
|
||||||
modify: {
|
|
||||||
extend: true,
|
|
||||||
title: "修改字典",
|
|
||||||
submit: async ({ model }) => {
|
|
||||||
return api.dict.setDict(model.id, { ...model, typeId: current.value?.id });
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="less" scoped></style>
|
|
||||||
|
|
||||||
<route lang="json">
|
|
||||||
{
|
|
||||||
"meta": {
|
|
||||||
"sort": 20010,
|
|
||||||
"title": "字典管理",
|
|
||||||
"icon": "icon-park-outline-spanner"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</route>
|
|
||||||
|
|
@ -1,11 +1,16 @@
|
||||||
<template>
|
<template>
|
||||||
<BreadPage>
|
<BreadPage>
|
||||||
<Table v-bind="table">
|
<div class="">
|
||||||
<template #action>
|
<div class="">
|
||||||
<a-button type="primary" @click="visible = true">添加</a-button>
|
<a-alert :closable="true" class="mb-4"> 仅展示近 90 天内的数据,如需查看更多数据,请联系管理员。 </a-alert>
|
||||||
<ani-editor v-model:visible="visible"></ani-editor>
|
<Table v-bind="table">
|
||||||
</template>
|
<template #action>
|
||||||
</Table>
|
<a-button type="primary" @click="visible = true">添加</a-button>
|
||||||
|
<ani-editor v-model:visible="visible"></ani-editor>
|
||||||
|
</template>
|
||||||
|
</Table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</BreadPage>
|
</BreadPage>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
@ -52,46 +57,21 @@ const table = useTable({
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
title: "登陆地址",
|
|
||||||
dataIndex: "ip",
|
|
||||||
width: 200,
|
|
||||||
render({ record }) {
|
|
||||||
return (
|
|
||||||
<div class="flex flex-col overflow-hidden">
|
|
||||||
<span>{record.addr || "未知"}</span>
|
|
||||||
<span class="text-gray-400 text-xs truncate">{record.ip}</span>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
title: "操作系统",
|
title: "操作系统",
|
||||||
dataIndex: "os",
|
dataIndex: "os",
|
||||||
width: 200,
|
width: 160,
|
||||||
render({ record }) {
|
|
||||||
const [os, version] = record.os.split(" ");
|
|
||||||
return (
|
|
||||||
<div class="flex flex-col overflow-hidden">
|
|
||||||
<span>{os || "未知"}</span>
|
|
||||||
<span class="text-gray-400 text-xs truncate">{version}</span>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "浏览器",
|
title: "浏览器",
|
||||||
dataIndex: "browser",
|
dataIndex: "browser",
|
||||||
|
width: 160,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "登陆地址",
|
||||||
|
dataIndex: "ip",
|
||||||
width: 200,
|
width: 200,
|
||||||
render({ record }) {
|
render: ({ record }) => `${record.addr || "未知"}(${record.ip})`,
|
||||||
const [browser, version] = record.browser.split(" ");
|
|
||||||
return (
|
|
||||||
<div class="flex flex-col overflow-hidden">
|
|
||||||
<span>{browser || "未知"}</span>
|
|
||||||
<span class="text-gray-400 text-xs truncate">v{version}</span>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
search: {
|
search: {
|
||||||
|
|
|
||||||
|
|
@ -1,87 +1,69 @@
|
||||||
<template>
|
<template>
|
||||||
<bread-page class="">
|
<bread-page class="">
|
||||||
<menu-table>
|
<Table v-bind="table">
|
||||||
<template #action>
|
<template #action>
|
||||||
<a-button type="outline">展开/折叠</a-button>
|
<a-button type="outline">展开/折叠</a-button>
|
||||||
</template>
|
</template>
|
||||||
</menu-table>
|
</Table>
|
||||||
</bread-page>
|
</bread-page>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="tsx">
|
<script setup lang="tsx">
|
||||||
import { api } from "@/api";
|
import { api } from "@/api";
|
||||||
import { createColumn, updateColumn, useAniTable } from "@/components";
|
import { Table, createColumn, updateColumn, useTable } from "@/components";
|
||||||
import { MenuTypes, MenuType } from "@/constants/menu";
|
import { MenuTypes, MenuType } from "@/constants/menu";
|
||||||
import { flatedMenus } from "@/router";
|
import { flatedMenus } from "@/router";
|
||||||
|
|
||||||
const menuArr = flatedMenus.map((i) => ({ label: i.title, value: i.id }));
|
const menuArr = flatedMenus.map((i) => ({ label: i.title, value: i.id }));
|
||||||
|
|
||||||
const expanded = ref(false);
|
const table = useTable({
|
||||||
const toggleExpand = () => {
|
|
||||||
expanded.value = !expanded.value;
|
|
||||||
menu.tableRef.value?.tableRef?.expandAll(expanded.value);
|
|
||||||
};
|
|
||||||
|
|
||||||
const [menuTable, menu] = useAniTable({
|
|
||||||
data: (search, paging) => {
|
data: (search, paging) => {
|
||||||
return api.menu.getMenus({ ...search, ...paging, tree: true });
|
return api.menu.getMenus({ ...search, ...paging, tree: true });
|
||||||
},
|
},
|
||||||
tableProps: {
|
|
||||||
defaultExpandAllRows: true,
|
|
||||||
},
|
|
||||||
columns: [
|
columns: [
|
||||||
{
|
{
|
||||||
title: () => {
|
title: "菜单名称",
|
||||||
return (
|
|
||||||
<span>
|
|
||||||
菜单名称
|
|
||||||
<a-link class="ml-1 select-none" onClick={toggleExpand}>
|
|
||||||
{expanded.value ? "收起全部" : "展开全部"}
|
|
||||||
</a-link>
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
dataIndex: "name",
|
dataIndex: "name",
|
||||||
render({ record }) {
|
width: 180,
|
||||||
let id = "";
|
},
|
||||||
if (record.type === MenuType.PAGE) {
|
{
|
||||||
id = ` => ${record.path}`;
|
title: "类型",
|
||||||
}
|
dataIndex: "description",
|
||||||
if (record.type === MenuType.BUTTON) {
|
align: "center",
|
||||||
id = ` => ${record.code}`;
|
width: 120,
|
||||||
}
|
render: ({ record }) => (
|
||||||
return (
|
<a-tag color={MenuTypes.fmt(record.type, "color")}>
|
||||||
<div class="flex items-center gap-1">
|
{{
|
||||||
<a-tag bordered color={MenuTypes.fmt(record.type, "color")}>
|
icon: <i class={record.icon}></i>,
|
||||||
{{
|
default: () => MenuTypes.fmt(record.type),
|
||||||
default: () => MenuTypes.fmt(record.type),
|
}}
|
||||||
}}
|
</a-tag>
|
||||||
</a-tag>
|
),
|
||||||
<div class="flex-1 flex overflow-hidden ml-1">
|
},
|
||||||
<div class="flex-1">
|
{
|
||||||
<i class={`${record.icon} mr-1`}></i>
|
title: "访问路径",
|
||||||
<span>{record.name ?? "无"}</span>
|
dataIndex: "path",
|
||||||
<span class="text-gray-400 text-xs truncate">{id}</span>
|
},
|
||||||
</div>
|
{
|
||||||
<a-switch checked-color="#3c9" size="small"></a-switch>
|
title: "启用",
|
||||||
</div>
|
dataIndex: "createdAt",
|
||||||
</div>
|
width: 80,
|
||||||
);
|
align: "center",
|
||||||
},
|
render: ({ record }) => <a-switch size="small" checked-color="#3c9"></a-switch>,
|
||||||
},
|
},
|
||||||
createColumn,
|
createColumn,
|
||||||
updateColumn,
|
updateColumn,
|
||||||
{
|
{
|
||||||
title: "操作",
|
title: "操作",
|
||||||
type: "button",
|
type: "button",
|
||||||
width: 200,
|
width: 184,
|
||||||
buttons: [
|
buttons: [
|
||||||
{
|
{
|
||||||
type: "modify",
|
type: "modify",
|
||||||
text: "修改",
|
text: "修改",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: "新增子项",
|
text: "新增下级",
|
||||||
disabled: ({ record }) => record.type === MenuType.BUTTON,
|
disabled: ({ record }) => record.type === MenuType.BUTTON,
|
||||||
onClick: ({ record }) => {
|
onClick: ({ record }) => {
|
||||||
console.log(record);
|
console.log(record);
|
||||||
|
|
@ -97,6 +79,9 @@ const [menuTable, menu] = useAniTable({
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
pagination: {
|
||||||
|
visible: false,
|
||||||
|
},
|
||||||
search: {
|
search: {
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
|
|
@ -119,14 +104,26 @@ const [menuTable, menu] = useAniTable({
|
||||||
class: "!grid grid-cols-2 gap-x-4",
|
class: "!grid grid-cols-2 gap-x-4",
|
||||||
},
|
},
|
||||||
items: [
|
items: [
|
||||||
|
{
|
||||||
|
field: "type",
|
||||||
|
initial: 1,
|
||||||
|
label: "类型",
|
||||||
|
type: "radio",
|
||||||
|
options: MenuTypes.raw,
|
||||||
|
nodeProps: {
|
||||||
|
type: "button",
|
||||||
|
class: "w-full",
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
field: "parentId",
|
field: "parentId",
|
||||||
initial: 0,
|
initial: 0,
|
||||||
label: "父级",
|
label: "父级",
|
||||||
type: "treeSelect",
|
type: "treeSelect",
|
||||||
async options() {
|
async options(arg) {
|
||||||
const res = await api.menu.getMenus({ size: 0, tree: true });
|
const res = await api.menu.getMenus({ size: 0, tree: true });
|
||||||
const data = res.data.data;
|
const data = res.data.data;
|
||||||
|
console.log(arg);
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
id: 0,
|
id: 0,
|
||||||
|
|
@ -143,17 +140,6 @@ const [menuTable, menu] = useAniTable({
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
field: "type",
|
|
||||||
initial: 1,
|
|
||||||
label: "类型",
|
|
||||||
type: "radio",
|
|
||||||
options: MenuTypes.raw,
|
|
||||||
nodeProps: {
|
|
||||||
type: "button",
|
|
||||||
class: "w-full",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
field: "name",
|
field: "name",
|
||||||
label: "名称",
|
label: "名称",
|
||||||
|
|
@ -223,18 +209,12 @@ const [menuTable, menu] = useAniTable({
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less">
|
<style lang="less"></style>
|
||||||
.arco-table-cell-expand-icon {
|
|
||||||
span.arco-table-cell-inline-icon {
|
|
||||||
margin-right: 6px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<route lang="json">
|
<route lang="json">
|
||||||
{
|
{
|
||||||
"meta": {
|
"meta": {
|
||||||
"sort": 10302,
|
"sort": 10201,
|
||||||
"title": "菜单管理",
|
"title": "菜单管理",
|
||||||
"icon": "icon-park-outline-add-subtract"
|
"icon": "icon-park-outline-add-subtract"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,118 @@
|
||||||
|
<template>
|
||||||
|
<BreadPage>
|
||||||
|
<Table v-bind="table"></Table>
|
||||||
|
</BreadPage>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="tsx">
|
||||||
|
import { api } from "@/api";
|
||||||
|
import { Table, createColumn, updateColumn, useTable } from "@/components";
|
||||||
|
|
||||||
|
const table = useTable({
|
||||||
|
data: async (model, paging) => {
|
||||||
|
return api.permission.getPermissions();
|
||||||
|
},
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
title: "权限名称",
|
||||||
|
dataIndex: "username",
|
||||||
|
width: 200,
|
||||||
|
render({ record }) {
|
||||||
|
return (
|
||||||
|
<div class="flex flex-col overflow-hidden">
|
||||||
|
<span>{record.name}</span>
|
||||||
|
<span class="text-gray-400 text-xs truncate">@{record.slug}</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "权限描述",
|
||||||
|
dataIndex: "description",
|
||||||
|
},
|
||||||
|
createColumn,
|
||||||
|
updateColumn,
|
||||||
|
{
|
||||||
|
title: "操作",
|
||||||
|
type: "button",
|
||||||
|
width: 110,
|
||||||
|
buttons: [
|
||||||
|
{
|
||||||
|
type: "modify",
|
||||||
|
text: "修改",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'delete',
|
||||||
|
text: '删除',
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
search: {
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
field: "name",
|
||||||
|
label: "权限名称",
|
||||||
|
type: "input",
|
||||||
|
required: false,
|
||||||
|
nodeProps: {
|
||||||
|
placeholder: '请输入名称关键字'
|
||||||
|
},
|
||||||
|
itemProps: {
|
||||||
|
hideLabel: true,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
create: {
|
||||||
|
title: "添加权限",
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
field: "name",
|
||||||
|
label: "角色名称",
|
||||||
|
type: "input",
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: "slug",
|
||||||
|
label: "角色标识",
|
||||||
|
type: "input",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: "description",
|
||||||
|
label: "个人描述",
|
||||||
|
type: "textarea",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
modalProps: {
|
||||||
|
width: 580,
|
||||||
|
maskClosable: false,
|
||||||
|
},
|
||||||
|
formProps: {
|
||||||
|
layout: "vertical",
|
||||||
|
},
|
||||||
|
submit: ({ model }) => {
|
||||||
|
return api.permission.addPermission(model);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
modify: {
|
||||||
|
extend: true,
|
||||||
|
title: "修改权限",
|
||||||
|
submit: ({ model }) => {
|
||||||
|
return api.permission.setPermission(model.id, model);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped></style>
|
||||||
|
|
||||||
|
<route lang="json">
|
||||||
|
{
|
||||||
|
"meta": {
|
||||||
|
"sort": 10303,
|
||||||
|
"title": "权限管理",
|
||||||
|
"icon": "icon-park-outline-permissions"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</route>
|
||||||
|
|
@ -21,7 +21,7 @@ const [roleTable, roleCtx] = useAniTable({
|
||||||
return (
|
return (
|
||||||
<div class="flex flex-col overflow-hidden">
|
<div class="flex flex-col overflow-hidden">
|
||||||
<span>{record.name}</span>
|
<span>{record.name}</span>
|
||||||
<span class="text-gray-400 text-xs truncate">#{record.slug}</span>
|
<span class="text-gray-400 text-xs truncate">@{record.slug}</span>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
@ -35,7 +35,7 @@ const [roleTable, roleCtx] = useAniTable({
|
||||||
{
|
{
|
||||||
title: "操作",
|
title: "操作",
|
||||||
type: "button",
|
type: "button",
|
||||||
width: 200,
|
width: 184,
|
||||||
buttons: [
|
buttons: [
|
||||||
{
|
{
|
||||||
type: "modify",
|
type: "modify",
|
||||||
|
|
@ -88,19 +88,19 @@ const [roleTable, roleCtx] = useAniTable({
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: "code",
|
field: "slug",
|
||||||
label: "角色标识",
|
label: "角色标识",
|
||||||
type: "input",
|
type: "input",
|
||||||
},
|
},
|
||||||
// {
|
{
|
||||||
// field: "menuIds",
|
field: "permissionIds",
|
||||||
// label: "关联权限",
|
label: "关联权限",
|
||||||
// type: "select",
|
type: "select",
|
||||||
// options: () => api.menu.getMenus({ size: 0 }),
|
options: () => api.permission.getPermissions(),
|
||||||
// nodeProps: {
|
nodeProps: {
|
||||||
// multiple: true,
|
multiple: true,
|
||||||
// },
|
},
|
||||||
// },
|
},
|
||||||
{
|
{
|
||||||
field: "description",
|
field: "description",
|
||||||
label: "个人描述",
|
label: "个人描述",
|
||||||
|
|
|
||||||
|
|
@ -14,12 +14,10 @@
|
||||||
@progress="onProgress"
|
@progress="onProgress"
|
||||||
>
|
>
|
||||||
<template #upload-button>
|
<template #upload-button>
|
||||||
<a-link>选择文件</a-link>
|
<a-link>选择文件...</a-link>
|
||||||
<a-divider direction="vertical" :margin="4"></a-divider>
|
|
||||||
<a-link>上传文件</a-link>
|
|
||||||
</template>
|
</template>
|
||||||
</a-upload>
|
</a-upload>
|
||||||
<div class="text-gray-400 text-xs">请选择大小不超过5MB,.png, .jpg, .webp格式的图片</div>
|
<div class="text-gray-400 text-xs">请选择不超过5MB,.png, .jpg, .webp格式的图片</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -8,8 +8,9 @@
|
||||||
<script setup lang="tsx">
|
<script setup lang="tsx">
|
||||||
import { api } from "@/api";
|
import { api } from "@/api";
|
||||||
import { Table, createColumn, updateColumn, useTable } from "@/components";
|
import { Table, createColumn, updateColumn, useTable } from "@/components";
|
||||||
import InputAvatar from "./components/avatar.vue";
|
import InputAvatar from "./avatar.vue";
|
||||||
import { usePassworModal } from "./components/password";
|
import { usePassworModal } from "./password";
|
||||||
|
import { MenuType } from "@/constants/menu";
|
||||||
|
|
||||||
const [passModal, passCtx] = usePassworModal();
|
const [passModal, passCtx] = usePassworModal();
|
||||||
|
|
||||||
|
|
@ -21,10 +22,11 @@ const table = useTable({
|
||||||
{
|
{
|
||||||
title: "用户昵称",
|
title: "用户昵称",
|
||||||
dataIndex: "username",
|
dataIndex: "username",
|
||||||
|
width: 180,
|
||||||
render: ({ record }) => (
|
render: ({ record }) => (
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<a-avatar size={32} class="!bg-brand-500">
|
<a-avatar size={32}>
|
||||||
{record.avatar?.startsWith("/") ? <img src={record.avatar} alt="" /> : record.nickname?.[0]}
|
<img src={record.avatar} alt="" />
|
||||||
</a-avatar>
|
</a-avatar>
|
||||||
<span class="ml-2 flex-1 flex flex-col overflow-hidden">
|
<span class="ml-2 flex-1 flex flex-col overflow-hidden">
|
||||||
<span>{record.nickname}</span>
|
<span>{record.nickname}</span>
|
||||||
|
|
@ -33,6 +35,10 @@ const table = useTable({
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: "用户描述",
|
||||||
|
dataIndex: "description",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: "用户邮箱",
|
title: "用户邮箱",
|
||||||
dataIndex: "email",
|
dataIndex: "email",
|
||||||
|
|
@ -43,7 +49,7 @@ const table = useTable({
|
||||||
{
|
{
|
||||||
title: "操作",
|
title: "操作",
|
||||||
type: "button",
|
type: "button",
|
||||||
width: 200,
|
width: 180,
|
||||||
buttons: [
|
buttons: [
|
||||||
{
|
{
|
||||||
type: "modify",
|
type: "modify",
|
||||||
|
|
@ -68,53 +74,25 @@ const table = useTable({
|
||||||
search: {
|
search: {
|
||||||
button: true,
|
button: true,
|
||||||
items: [
|
items: [
|
||||||
// {
|
|
||||||
// field: "nickname",
|
|
||||||
// label: "用户昵称",
|
|
||||||
// type: "input",
|
|
||||||
// nodeProps: {
|
|
||||||
// placeholder: '用户昵称'
|
|
||||||
// },
|
|
||||||
// itemProps: {
|
|
||||||
// hideLabel: true
|
|
||||||
// }
|
|
||||||
// },
|
|
||||||
{
|
{
|
||||||
field: "nickname",
|
field: "nickname",
|
||||||
label: "用户昵称",
|
label: "用户昵称",
|
||||||
type: "input",
|
type: "search",
|
||||||
},
|
searchable: true,
|
||||||
{
|
enterable: true,
|
||||||
field: "nickname",
|
itemProps: {
|
||||||
label: "用户昵称",
|
hideLabel: true,
|
||||||
type: "input",
|
},
|
||||||
},
|
nodeProps: {
|
||||||
{
|
placeholder: "用户昵称",
|
||||||
field: "nickname",
|
},
|
||||||
label: "用户昵称",
|
|
||||||
type: "input",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
field: "nickname",
|
|
||||||
label: "用户昵称",
|
|
||||||
type: "input",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
field: "nickname",
|
|
||||||
label: "用户昵称",
|
|
||||||
type: "input",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
field: "nickname",
|
|
||||||
label: "用户昵称",
|
|
||||||
type: "input",
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
create: {
|
create: {
|
||||||
title: "新建用户",
|
title: "新建用户",
|
||||||
modalProps: {
|
modalProps: {
|
||||||
width: 820,
|
width: 732,
|
||||||
maskClosable: false,
|
maskClosable: false,
|
||||||
},
|
},
|
||||||
formProps: {
|
formProps: {
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,8 @@ export const usePassworModal = () => {
|
||||||
field: "password",
|
field: "password",
|
||||||
label: ({ model }) => (
|
label: ({ model }) => (
|
||||||
<span>
|
<span>
|
||||||
设置 <span class="text-brand-500 font-semibold">{model.nickname}</span> 的新密码:
|
设置 <span class="text-brand-500 font-semibold">{model.nickname}</span>
|
||||||
|
的新密码
|
||||||
</span>
|
</span>
|
||||||
),
|
),
|
||||||
type: "input",
|
type: "input",
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { LoginedUserVo } from "@/api";
|
||||||
import { defineStore } from "pinia";
|
import { defineStore } from "pinia";
|
||||||
|
|
||||||
export const useUserStore = defineStore({
|
export const useUserStore = defineStore({
|
||||||
|
|
@ -16,7 +17,7 @@ export const useUserStore = defineStore({
|
||||||
* 用户昵称
|
* 用户昵称
|
||||||
*/
|
*/
|
||||||
nickname: "绝弹",
|
nickname: "绝弹",
|
||||||
/** `
|
/** `
|
||||||
* 用户头像地址
|
* 用户头像地址
|
||||||
*/
|
*/
|
||||||
avatar: "https://github.com/juetan.png",
|
avatar: "https://github.com/juetan.png",
|
||||||
|
|
@ -38,21 +39,17 @@ export const useUserStore = defineStore({
|
||||||
this.accessToken = token;
|
this.accessToken = token;
|
||||||
},
|
},
|
||||||
|
|
||||||
setAccessToken(token: string) {
|
|
||||||
this.accessToken = token;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 清除用户信息
|
* 清除用户信息
|
||||||
*/
|
*/
|
||||||
clearUser() {
|
clearUser() {
|
||||||
this.$reset();
|
this.$reset()
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 设置用户信息
|
* 设置用户信息
|
||||||
*/
|
*/
|
||||||
setUser(user: any) {
|
setUser(user: LoginedUserVo) {
|
||||||
this.id = user.id;
|
this.id = user.id;
|
||||||
this.username = user.username;
|
this.username = user.username;
|
||||||
this.nickname = user.nickname;
|
this.nickname = user.nickname;
|
||||||
|
|
|
||||||
|
|
@ -5,20 +5,6 @@
|
||||||
body {
|
body {
|
||||||
// --border-radius-small: 4px;
|
// --border-radius-small: 4px;
|
||||||
|
|
||||||
.arco-icon-hover::before {
|
|
||||||
width: 28px;
|
|
||||||
height: 28px;
|
|
||||||
border-radius: var(--border-radius-small);
|
|
||||||
}
|
|
||||||
|
|
||||||
div.arco-dropdown {
|
|
||||||
border: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.arco-divider-horizontal {
|
|
||||||
border-color: var(--color-neutral-2);
|
|
||||||
}
|
|
||||||
|
|
||||||
li.arco-dropdown-option {
|
li.arco-dropdown-option {
|
||||||
line-height: 32px;
|
line-height: 32px;
|
||||||
width: calc(100% - 8px);
|
width: calc(100% - 8px);
|
||||||
|
|
@ -37,7 +23,7 @@ body {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
.arco-modal-header {
|
.arco-modal-header {
|
||||||
background: var(--color-fill-3);
|
background: var(--color-fill-2);
|
||||||
border-bottom: none;
|
border-bottom: none;
|
||||||
}
|
}
|
||||||
.arco-modal-footer {
|
.arco-modal-footer {
|
||||||
|
|
@ -73,7 +59,7 @@ body {
|
||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
}
|
}
|
||||||
[class^="icon-"] {
|
[class^="icon-"] {
|
||||||
font-size: 14px;
|
font-size: 16px;
|
||||||
vertical-align: -2px;
|
vertical-align: -2px;
|
||||||
}
|
}
|
||||||
.arco-menu-item {
|
.arco-menu-item {
|
||||||
|
|
@ -94,7 +80,7 @@ body {
|
||||||
.arco-menu-inner {
|
.arco-menu-inner {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
.arco-menu-icon {
|
.arco-menu-icon {
|
||||||
margin-right: 10px;
|
margin-right: 8px;
|
||||||
}
|
}
|
||||||
.arco-menu-inline-header:hover {
|
.arco-menu-inline-header:hover {
|
||||||
background-color: var(--color-fill-2);
|
background-color: var(--color-fill-2);
|
||||||
|
|
@ -135,10 +121,6 @@ body {
|
||||||
.arco-form-item-layout-inline:last-child {
|
.arco-form-item-layout-inline:last-child {
|
||||||
margin-right: 0;
|
margin-right: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ani-form-modal .arco-modal-body {
|
|
||||||
padding-bottom: 8px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
.dark {
|
.dark {
|
||||||
.arco-menu-item.arco-menu-selected {
|
.arco-menu-item.arco-menu-selected {
|
||||||
|
|
|
||||||
|
|
@ -8,14 +8,10 @@
|
||||||
[class*=" icon-"],
|
[class*=" icon-"],
|
||||||
[class^="icon-"] {
|
[class^="icon-"] {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
vertical-align: -2px;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
|
|
||||||
.table .arco-form-item-layout-inline {
|
.table .arco-form-item-layout-inline {
|
||||||
margin-right: 8px;
|
margin-right: 8px;
|
||||||
margin-bottom: 18px;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.toolbar .arco-form-item-layout-inline {
|
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,22 +7,28 @@ export {}
|
||||||
|
|
||||||
declare module '@vue/runtime-core' {
|
declare module '@vue/runtime-core' {
|
||||||
export interface GlobalComponents {
|
export interface GlobalComponents {
|
||||||
|
AAlert: typeof import('@arco-design/web-vue')['Alert']
|
||||||
|
AAutoComplete: typeof import('@arco-design/web-vue')['AutoComplete']
|
||||||
AAvatar: typeof import('@arco-design/web-vue')['Avatar']
|
AAvatar: typeof import('@arco-design/web-vue')['Avatar']
|
||||||
ABreadcrumb: typeof import('@arco-design/web-vue')['Breadcrumb']
|
ABreadcrumb: typeof import('@arco-design/web-vue')['Breadcrumb']
|
||||||
ABreadcrumbItem: typeof import('@arco-design/web-vue')['BreadcrumbItem']
|
ABreadcrumbItem: typeof import('@arco-design/web-vue')['BreadcrumbItem']
|
||||||
AButton: typeof import('@arco-design/web-vue')['Button']
|
AButton: typeof import('@arco-design/web-vue')['Button']
|
||||||
ACard: typeof import('@arco-design/web-vue')['Card']
|
ACard: typeof import('@arco-design/web-vue')['Card']
|
||||||
ACheckbox: typeof import('@arco-design/web-vue')['Checkbox']
|
ACheckbox: typeof import('@arco-design/web-vue')['Checkbox']
|
||||||
|
ACheckboxGroup: typeof import('@arco-design/web-vue')['CheckboxGroup']
|
||||||
AConfigProvider: typeof import('@arco-design/web-vue')['ConfigProvider']
|
AConfigProvider: typeof import('@arco-design/web-vue')['ConfigProvider']
|
||||||
|
ADatePicker: typeof import('@arco-design/web-vue')['DatePicker']
|
||||||
ADivider: typeof import('@arco-design/web-vue')['Divider']
|
ADivider: typeof import('@arco-design/web-vue')['Divider']
|
||||||
ADoption: typeof import('@arco-design/web-vue')['Doption']
|
ADoption: typeof import('@arco-design/web-vue')['Doption']
|
||||||
ADrawer: typeof import('@arco-design/web-vue')['Drawer']
|
ADrawer: typeof import('@arco-design/web-vue')['Drawer']
|
||||||
ADropdown: typeof import('@arco-design/web-vue')['Dropdown']
|
ADropdown: typeof import('@arco-design/web-vue')['Dropdown']
|
||||||
|
ADropdownButton: typeof import('@arco-design/web-vue')['DropdownButton']
|
||||||
AEmpty: typeof import('@arco-design/web-vue')['Empty']
|
AEmpty: typeof import('@arco-design/web-vue')['Empty']
|
||||||
AForm: typeof import('@arco-design/web-vue')['Form']
|
AForm: typeof import('@arco-design/web-vue')['Form']
|
||||||
AFormItem: typeof import('@arco-design/web-vue')['FormItem']
|
AFormItem: typeof import('@arco-design/web-vue')['FormItem']
|
||||||
AImagePreview: typeof import('@arco-design/web-vue')['ImagePreview']
|
AImage: typeof import('@arco-design/web-vue')['Image']
|
||||||
AInput: typeof import('@arco-design/web-vue')['Input']
|
AInput: typeof import('@arco-design/web-vue')['Input']
|
||||||
|
AInputNumber: typeof import('@arco-design/web-vue')['InputNumber']
|
||||||
AInputPassword: typeof import('@arco-design/web-vue')['InputPassword']
|
AInputPassword: typeof import('@arco-design/web-vue')['InputPassword']
|
||||||
AInputSearch: typeof import('@arco-design/web-vue')['InputSearch']
|
AInputSearch: typeof import('@arco-design/web-vue')['InputSearch']
|
||||||
ALayout: typeof import('@arco-design/web-vue')['Layout']
|
ALayout: typeof import('@arco-design/web-vue')['Layout']
|
||||||
|
|
@ -30,15 +36,28 @@ declare module '@vue/runtime-core' {
|
||||||
ALayoutHeader: typeof import('@arco-design/web-vue')['LayoutHeader']
|
ALayoutHeader: typeof import('@arco-design/web-vue')['LayoutHeader']
|
||||||
ALayoutSider: typeof import('@arco-design/web-vue')['LayoutSider']
|
ALayoutSider: typeof import('@arco-design/web-vue')['LayoutSider']
|
||||||
ALink: typeof import('@arco-design/web-vue')['Link']
|
ALink: typeof import('@arco-design/web-vue')['Link']
|
||||||
|
AList: typeof import('@arco-design/web-vue')['List']
|
||||||
|
AListItem: typeof import('@arco-design/web-vue')['ListItem']
|
||||||
|
AListItemMeta: typeof import('@arco-design/web-vue')['ListItemMeta']
|
||||||
AMenu: typeof import('@arco-design/web-vue')['Menu']
|
AMenu: typeof import('@arco-design/web-vue')['Menu']
|
||||||
AMenuItem: typeof import('@arco-design/web-vue')['MenuItem']
|
AMenuItem: typeof import('@arco-design/web-vue')['MenuItem']
|
||||||
AModal: typeof import('@arco-design/web-vue')['Modal']
|
AModal: typeof import('@arco-design/web-vue')['Modal']
|
||||||
|
APagination: typeof import('@arco-design/web-vue')['Pagination']
|
||||||
|
APopover: typeof import('@arco-design/web-vue')['Popover']
|
||||||
AProgress: typeof import('@arco-design/web-vue')['Progress']
|
AProgress: typeof import('@arco-design/web-vue')['Progress']
|
||||||
|
ARadio: typeof import('@arco-design/web-vue')['Radio']
|
||||||
|
ARadioGroup: typeof import('@arco-design/web-vue')['RadioGroup']
|
||||||
AScrollbar: typeof import('@arco-design/web-vue')['Scrollbar']
|
AScrollbar: typeof import('@arco-design/web-vue')['Scrollbar']
|
||||||
ASelect: typeof import('@arco-design/web-vue')['Select']
|
ASelect: typeof import('@arco-design/web-vue')['Select']
|
||||||
ASpace: typeof import('@arco-design/web-vue')['Space']
|
ASpace: typeof import('@arco-design/web-vue')['Space']
|
||||||
ASpin: typeof import('@arco-design/web-vue')['Spin']
|
ASpin: typeof import('@arco-design/web-vue')['Spin']
|
||||||
|
ASwitch: typeof import('@arco-design/web-vue')['Switch']
|
||||||
|
ATabPane: typeof import('@arco-design/web-vue')['TabPane']
|
||||||
|
ATabs: typeof import('@arco-design/web-vue')['Tabs']
|
||||||
|
ATag: typeof import('@arco-design/web-vue')['Tag']
|
||||||
|
ATextarea: typeof import('@arco-design/web-vue')['Textarea']
|
||||||
ATooltip: typeof import('@arco-design/web-vue')['Tooltip']
|
ATooltip: typeof import('@arco-design/web-vue')['Tooltip']
|
||||||
|
ATree: typeof import('@arco-design/web-vue')['Tree']
|
||||||
AUpload: typeof import('@arco-design/web-vue')['Upload']
|
AUpload: typeof import('@arco-design/web-vue')['Upload']
|
||||||
BaseOption: typeof import('./../components/editor/components/BaseOption.vue')['default']
|
BaseOption: typeof import('./../components/editor/components/BaseOption.vue')['default']
|
||||||
Block: typeof import('./../components/editor/panel-main/components/block.vue')['default']
|
Block: typeof import('./../components/editor/panel-main/components/block.vue')['default']
|
||||||
|
|
@ -62,7 +81,6 @@ declare module '@vue/runtime-core' {
|
||||||
Render: typeof import('./../components/editor/blocks/date/render.vue')['default']
|
Render: typeof import('./../components/editor/blocks/date/render.vue')['default']
|
||||||
RouterLink: typeof import('vue-router')['RouterLink']
|
RouterLink: typeof import('vue-router')['RouterLink']
|
||||||
RouterView: typeof import('vue-router')['RouterView']
|
RouterView: typeof import('vue-router')['RouterView']
|
||||||
'Temp.dev1': typeof import('./../components/breadcrumb/temp.dev1.vue')['default']
|
|
||||||
Texter: typeof import('./../components/editor/panel-main/components/texter.vue')['default']
|
Texter: typeof import('./../components/editor/panel-main/components/texter.vue')['default']
|
||||||
Toast: typeof import('./../components/toast/toast.vue')['default']
|
Toast: typeof import('./../components/toast/toast.vue')['default']
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,10 +20,11 @@ const delConfirm = (config: DelOptions = {}) => {
|
||||||
if (typeof config === "string") {
|
if (typeof config === "string") {
|
||||||
config = { content: config };
|
config = { content: config };
|
||||||
}
|
}
|
||||||
const merged = merge({}, delOptions, config);
|
const merged = merge(delOptions, config);
|
||||||
return new Promise<void>((onOk: () => void, onCancel) => {
|
return new Promise<void>((onOk: () => void, onCancel) => {
|
||||||
Modal.open({ ...merged, onOk, onCancel });
|
Modal.open({ ...merged, onOk, onCancel });
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export { delConfirm };
|
export { delConfirm };
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,3 @@
|
||||||
/**
|
|
||||||
* 列表转树结构
|
|
||||||
* @param list 数组
|
|
||||||
* @param id ID key
|
|
||||||
* @param pid 父级key
|
|
||||||
* @param cid 子项key
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
export const listToTree = (list: any[], id = "id", pid = "parentId", cid = "children") => {
|
export const listToTree = (list: any[], id = "id", pid = "parentId", cid = "children") => {
|
||||||
const map = list.reduce((res, v) => ((res[v[id]] = v), res), {});
|
const map = list.reduce((res, v) => ((res[v[id]] = v), res), {});
|
||||||
return list.filter((item) => {
|
return list.filter((item) => {
|
||||||
|
|
@ -18,18 +10,11 @@ export const listToTree = (list: any[], id = "id", pid = "parentId", cid = "chil
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
export function treeEach(tree: any[], fn: (item: any) => void) {
|
||||||
* 遍历树结构
|
|
||||||
* @param tree 数组
|
|
||||||
* @param fn 函数
|
|
||||||
* @param before 是否广度遍历
|
|
||||||
*/
|
|
||||||
export function treeEach(tree: any[], fn: (item: any) => void, before = true) {
|
|
||||||
for (const item of tree) {
|
for (const item of tree) {
|
||||||
before && fn(item);
|
fn(item);
|
||||||
if (item.children) {
|
if (item.children) {
|
||||||
treeEach(item.children, fn);
|
treeEach(item.children, fn);
|
||||||
}
|
}
|
||||||
!before && fn(item);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue