mirror of
https://github.com/Teamlinker/Teamlinker.git
synced 2025-06-03 03:00:17 +00:00
new init
This commit is contained in:
parent
f70342db71
commit
a0f77c17d9
24
code/client/.gitignore
vendored
Normal file
24
code/client/.gitignore
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
16
code/client/README.md
Normal file
16
code/client/README.md
Normal file
@ -0,0 +1,16 @@
|
||||
# Vue 3 + TypeScript + Vite
|
||||
|
||||
This template should help get you started developing with Vue 3 and TypeScript in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
|
||||
|
||||
## Recommended IDE Setup
|
||||
|
||||
- [VS Code](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar)
|
||||
|
||||
## Type Support For `.vue` Imports in TS
|
||||
|
||||
Since TypeScript cannot handle type information for `.vue` imports, they are shimmed to be a generic Vue component type by default. In most cases this is fine if you don't really care about component prop types outside of templates. However, if you wish to get actual prop types in `.vue` imports (for example to get props validation when using manual `h(...)` calls), you can enable Volar's Take Over mode by following these steps:
|
||||
|
||||
1. Run `Extensions: Show Built-in Extensions` from VS Code's command palette, look for `TypeScript and JavaScript Language Features`, then right click and select `Disable (Workspace)`. By default, Take Over mode will enable itself if the default TypeScript extension is disabled.
|
||||
2. Reload the VS Code window by running `Developer: Reload Window` from the command palette.
|
||||
|
||||
You can learn more about Take Over mode [here](https://github.com/johnsoncodehk/volar/discussions/471).
|
@ -2,9 +2,8 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<!-- <link rel="icon" href="/favicon.ico" /> -->
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>teamlinker</title>
|
||||
<title>TeamLinker</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
29
code/client/package.json
Normal file
29
code/client/package.json
Normal file
@ -0,0 +1,29 @@
|
||||
{
|
||||
"name": "untitled5",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vue-tsc && vite build",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"pinia": "^2.0.28",
|
||||
"uuid": "^9.0.0",
|
||||
"vue": "^3.2.45",
|
||||
"vue-router": "^4.1.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@arco-design/web-vue": "^2.40.0",
|
||||
"@rollup/plugin-typescript": "^10.0.1",
|
||||
"@types/uuid": "^9.0.0",
|
||||
"@vitejs/plugin-vue": "^4.0.0",
|
||||
"rollup-plugin-typescript2": "^0.34.1",
|
||||
"tslib": "^2.4.1",
|
||||
"typescript": "^4.6.4",
|
||||
"vite": "^4.0.0",
|
||||
"vite-plugin-typescript": "^1.0.4",
|
||||
"vue-tsc": "^1.0.12"
|
||||
}
|
||||
}
|
11
code/client/src/App.vue
Normal file
11
code/client/src/App.vue
Normal file
@ -0,0 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import enUS from '@arco-design/web-vue/es/locale/lang/en-us'</script>
|
||||
<template>
|
||||
<a-config-provider :locale="enUS">
|
||||
<router-view></router-view>
|
||||
</a-config-provider>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
BIN
code/client/src/assert/back.png
Normal file
BIN
code/client/src/assert/back.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 91 KiB |
BIN
code/client/src/assert/back2.jpg
Normal file
BIN
code/client/src/assert/back2.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.6 MiB |
BIN
code/client/src/assert/back3.jpg
Normal file
BIN
code/client/src/assert/back3.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.6 MiB |
81
code/client/src/business/common/component/dialog/dialog.ts
Normal file
81
code/client/src/business/common/component/dialog/dialog.ts
Normal file
@ -0,0 +1,81 @@
|
||||
import {renderComponent} from "../../../../teamOS/common/util/component";
|
||||
import DialogView from "./dialogView.vue";
|
||||
import {AppContext, Component, inject, ref} from "vue";
|
||||
|
||||
export function onDialogOk(func:()=>void){
|
||||
const events:any=inject("dialogEvents");
|
||||
events.onOk=func;
|
||||
}
|
||||
|
||||
export function onDialogClose(func:()=>void) {
|
||||
const events:any=inject("dialogEvents");
|
||||
events.onClose=func;
|
||||
}
|
||||
export class Dialog {
|
||||
static open(el:HTMLElement,appContext: AppContext,title:string,component:Component,props?:object) {
|
||||
return new Promise((resolve,reject)=>{
|
||||
let ele=document.createElement("div")
|
||||
const events:{
|
||||
onOk:()=>any,
|
||||
onClose:()=>void
|
||||
}={
|
||||
onOk:null,
|
||||
onClose:null
|
||||
}
|
||||
const loading=ref(false);
|
||||
let destroyFunc=renderComponent(ele,DialogView,appContext,{
|
||||
props,
|
||||
component,
|
||||
title,
|
||||
events,
|
||||
onOk,
|
||||
onClose,
|
||||
loading
|
||||
});
|
||||
el.appendChild(ele);
|
||||
async function onOk(){
|
||||
if(events.onOk) {
|
||||
loading.value=true;
|
||||
let ret=await events.onOk();
|
||||
loading.value=false
|
||||
if(ret!==false) {
|
||||
destroyFunc();
|
||||
ele.parentNode.removeChild(ele);
|
||||
resolve(ret)
|
||||
}
|
||||
} else {
|
||||
destroyFunc();
|
||||
ele.parentNode.removeChild(ele);
|
||||
resolve(null);
|
||||
}
|
||||
}
|
||||
function onClose(){
|
||||
events.onClose?.();
|
||||
destroyFunc();
|
||||
ele.parentNode.removeChild(ele);
|
||||
resolve(null)
|
||||
}
|
||||
})
|
||||
}
|
||||
static confirm(el:HTMLElement,appContext: AppContext,content:string) {
|
||||
return new Promise((resolve,reject)=>{
|
||||
let ele=document.createElement("div")
|
||||
let destroyFunc=renderComponent(ele,DialogView,appContext,{
|
||||
onOk,
|
||||
onClose,
|
||||
title:content
|
||||
});
|
||||
el.appendChild(ele);
|
||||
async function onOk(){
|
||||
destroyFunc();
|
||||
ele.parentNode.removeChild(ele);
|
||||
resolve(true)
|
||||
}
|
||||
function onClose(){
|
||||
destroyFunc();
|
||||
ele.parentNode.removeChild(ele);
|
||||
resolve(false)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
<template>
|
||||
<div style="position: absolute;left: 0;top: 0;width: 100%;height: 100%;display: flex;justify-content: center;align-items: center;background-color: rgba(29,33,41,0.6)">
|
||||
<div style="background-color: white;width: 60%;height:auto;max-height: 80%;border-radius: 5px;">
|
||||
<div style="height: 35px;line-height: 35px;width: 100%;text-align: center;color: rgb(93,93,93);border-bottom: 1px solid gainsboro">
|
||||
<b>{{component?title:"Alert"}}</b>
|
||||
</div>
|
||||
<div style="width: 95%;overflow: auto;height: calc(100% - 80px);padding:10px">
|
||||
<component :is="component" v-bind="props.props" v-if="component"></component>
|
||||
<div v-else style="min-height: 60px;padding: 10px;font-size: medium">
|
||||
{{title}}
|
||||
</div>
|
||||
</div>
|
||||
<div style="height: 45px;width: 100%;display: flex;justify-content: flex-end;border-top: 1px solid gainsboro">
|
||||
<a-space size="medium">
|
||||
<a-button type="primary" @click="onOk" size="small" html-type="submit" :loading="props.loading?props.loading.value:false">{{component?"Ok":"Yes"}}</a-button>
|
||||
<a-button type="outline" style="margin-right: 10px" size="small" @click="onClose">{{component?"Close":"No"}}</a-button>
|
||||
</a-space>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {provide} from "vue";
|
||||
|
||||
const props=defineProps<{
|
||||
title:string,
|
||||
component?:any,
|
||||
props?:object,
|
||||
events?:{
|
||||
onOk:()=>any,
|
||||
onClose:()=>void
|
||||
},
|
||||
onOk:()=>void,
|
||||
onClose:()=>void,
|
||||
loading?:any
|
||||
}>()
|
||||
if(props.events) {
|
||||
provide("dialogEvents",props.events);
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
53
code/client/src/business/common/component/upload.vue
Normal file
53
code/client/src/business/common/component/upload.vue
Normal file
@ -0,0 +1,53 @@
|
||||
<template>
|
||||
<a-upload :custom-request="onUpload" :show-file-list="false" :accept="types">
|
||||
<template #upload-button>
|
||||
<div :class="`arco-upload-list-item`">
|
||||
<div class="arco-upload-list-picture custom-upload-avatar" v-if="uri">
|
||||
<img :src="uri" />
|
||||
<div class="arco-upload-list-picture-mask">
|
||||
<IconEdit />
|
||||
</div>
|
||||
</div>
|
||||
<div class="arco-upload-picture-card" v-else>
|
||||
<div class="arco-upload-picture-card-text">
|
||||
<IconPlus />
|
||||
<div style="margin-top: 10px; font-weight: 600">Upload</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</a-upload>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
||||
import {ref, watchEffect} from "vue";
|
||||
import {apiFile} from "../request/request";
|
||||
|
||||
const props=defineProps<{
|
||||
defaultUri?:string,
|
||||
types?:string
|
||||
}>()
|
||||
const emit=defineEmits<{
|
||||
(e:'upload',id:string):void
|
||||
}>()
|
||||
let uri=ref("");
|
||||
watchEffect(()=>{
|
||||
uri.value=props.defaultUri;
|
||||
})
|
||||
const onUpload=async (option)=>{
|
||||
const {onProgress, onError, onSuccess, fileItem, name} = option
|
||||
uri.value=URL.createObjectURL(fileItem.file);
|
||||
let ret=await apiFile.upload({
|
||||
file:fileItem.file
|
||||
})
|
||||
if(ret?.code==0) {
|
||||
onProgress(100)
|
||||
emit("upload",ret.data.id);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
162
code/client/src/business/common/request/request.ts
Normal file
162
code/client/src/business/common/request/request.ts
Normal file
@ -0,0 +1,162 @@
|
||||
import {ECommon_HttpApi_Method, ICommon_Http_Route_List} from "../../../../../common/routes/types";
|
||||
import field from "../../../../../common/routes/field"
|
||||
import file from "../../../../../common/routes/file"
|
||||
import issue from "../../../../../common/routes/issue"
|
||||
import issueType from "../../../../../common/routes/issueType"
|
||||
import organization from "../../../../../common/routes/organization"
|
||||
import project from "../../../../../common/routes/project"
|
||||
import release from "../../../../../common/routes/release"
|
||||
import team from "../../../../../common/routes/team"
|
||||
import workflow from "../../../../../common/routes/workflow"
|
||||
import user from "../../../../../common/routes/user"
|
||||
import {Ref} from "vue";
|
||||
|
||||
export type DCSType<T>={
|
||||
[key in keyof T]:key extends "created_by"|"modified_by"|"assigner_id"|"reporter_id"?{
|
||||
id:string,
|
||||
username:string,
|
||||
photo?:string,
|
||||
nickname?:string
|
||||
}:key extends "created_time"|"modified_time"?string:T[key] extends object?DCSType<T[key]>:T[key]
|
||||
}
|
||||
|
||||
let g_funcError:()=>void
|
||||
let g_authError:()=>void
|
||||
let g_responseError:(response:Response)=>void
|
||||
export function onRequestError(func:()=>void) {
|
||||
if(func) {
|
||||
g_funcError=func
|
||||
}
|
||||
}
|
||||
export function onAuthError(func:()=>void) {
|
||||
if(func) {
|
||||
g_authError=func;
|
||||
}
|
||||
}
|
||||
export function onResponseError(func:()=>void) {
|
||||
if(func) {
|
||||
g_responseError=func;
|
||||
}
|
||||
}
|
||||
export function generatorApi<T extends ICommon_Http_Route_List>(api:{
|
||||
baseUrl:string,
|
||||
routes:T
|
||||
}):{
|
||||
[name in keyof T]:keyof T[name]["req"] extends ""?(loading?:Ref<boolean>)=>Promise<{
|
||||
code:number,
|
||||
msg?:string,
|
||||
data:DCSType<T[name]["res"]>
|
||||
}>:(param:T[name]["req"],loading?:Ref<boolean>)=>Promise<{
|
||||
code:number,
|
||||
msg?:string,
|
||||
data:DCSType<T[name]["res"]>
|
||||
}>
|
||||
} {
|
||||
let baseUrl=api.baseUrl
|
||||
|
||||
let map:any={}
|
||||
for(let name in api.routes) {
|
||||
let route=api.routes[name];
|
||||
map[name]=async function(param?:any,loading?:Ref<boolean>):Promise<any> {
|
||||
if(loading) {
|
||||
loading.value=true;
|
||||
}
|
||||
let objBody:URLSearchParams|FormData
|
||||
let uri="/api"+baseUrl+route.path
|
||||
if(param) {
|
||||
for(let key in param) {
|
||||
let obj=param[key]
|
||||
if(obj===null || obj==undefined) {
|
||||
delete param[key]
|
||||
}
|
||||
}
|
||||
if(route.method==ECommon_HttpApi_Method.POST || route.method==ECommon_HttpApi_Method.PUT) {
|
||||
let isFormData=false
|
||||
for(let key in param) {
|
||||
if(param[key] instanceof File) {
|
||||
isFormData=true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(isFormData) {
|
||||
objBody=new FormData();
|
||||
for(let key in param) {
|
||||
let obj=param[key]
|
||||
if(obj instanceof File) {
|
||||
objBody.append(key,obj,obj.name)
|
||||
} else {
|
||||
objBody.append(key,obj);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
objBody = new URLSearchParams(param)
|
||||
}
|
||||
} else {
|
||||
uri+="?"+new URLSearchParams(param)
|
||||
}
|
||||
}
|
||||
try {
|
||||
if(!route.ignoreValidate && !sessionStorage.getItem("userToken")) {
|
||||
if(g_authError) {
|
||||
g_authError();
|
||||
}
|
||||
if(loading) {
|
||||
loading.value=false;
|
||||
}
|
||||
return ;
|
||||
}
|
||||
const response=await fetch(uri,{
|
||||
method:route.method,
|
||||
mode:"cors",
|
||||
cache:"no-cache",
|
||||
credentials:"include",
|
||||
redirect:"follow",
|
||||
headers:{
|
||||
...(!route.ignoreValidate && {
|
||||
"Authorization":"Bearer "+sessionStorage.getItem("userToken")
|
||||
}),
|
||||
},
|
||||
...(objBody && {
|
||||
body:objBody
|
||||
})
|
||||
})
|
||||
if(response.headers.get("token")) {
|
||||
sessionStorage.setItem("userToken",response.headers.get("token"))
|
||||
}
|
||||
if(!response.ok) {
|
||||
if(g_responseError) {
|
||||
g_responseError(response);
|
||||
if(loading) {
|
||||
loading.value=false
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
let ret=await response.json();
|
||||
if(loading) {
|
||||
loading.value=false
|
||||
}
|
||||
return ret;
|
||||
} catch (e) {
|
||||
if(g_funcError) {
|
||||
g_funcError()
|
||||
return null;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
export const apiUser=generatorApi(user)
|
||||
export const apiField=generatorApi(field)
|
||||
export const apiFile=generatorApi(file)
|
||||
export const apiTeam=generatorApi(team)
|
||||
export const apiProject=generatorApi(project)
|
||||
export const apiWorkflow=generatorApi(workflow)
|
||||
export const apiOrganization=generatorApi(organization)
|
||||
export const apiRelease=generatorApi(release)
|
||||
export const apiIssue=generatorApi(issue)
|
||||
export const apiIssueType=generatorApi(issueType)
|
||||
|
||||
|
16
code/client/src/business/common/store/store.ts
Normal file
16
code/client/src/business/common/store/store.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import {_GettersTree, defineStore, DefineStoreOptions, StateTree, Store} from "pinia";
|
||||
import {inject, provide} from "vue";
|
||||
|
||||
export function useStore<Id extends string, S extends StateTree = {}, G extends _GettersTree<S> = {}, A = {}>(id: Id, options: Omit<DefineStoreOptions<Id, S, G, A>, 'id'>): Store<Id, S, G, A> {
|
||||
let value=inject("store:"+id,null)
|
||||
if(!value) {
|
||||
let store=defineStore(id,options);
|
||||
let instance=store()
|
||||
provide("store:"+id,instance)
|
||||
return instance;
|
||||
} else {
|
||||
return value
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,8 @@
|
||||
<template>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts"></script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
@ -0,0 +1,70 @@
|
||||
<template>
|
||||
<a-form :model="data.form" style="width: 80%;margin-top: 20px" @submitSuccess="onSubmit">
|
||||
<a-form-item field="name" label="name" required>
|
||||
<a-input v-model="data.form.name"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item field="description" label="description">
|
||||
<a-textarea v-model="data.form.description" allow-clear></a-textarea>
|
||||
</a-form-item>
|
||||
<a-form-item field="photo" label="logo">
|
||||
<Upload types=".png,.jpg,.jpeg,.gif,.bmp,.svg" :default-uri="data.form.photo" @upload="onUpload"></Upload>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-button html-type="submit" :loading="loading">Submit</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {apiOrganization} from "../../../../common/request/request";
|
||||
import {onBeforeMount, reactive, ref} from "vue";
|
||||
import Upload from "../../../../common/component/upload.vue";
|
||||
import {Message} from "@arco-design/web-vue";
|
||||
import {useDesktopStore} from "../../../desktop/store/desktop";
|
||||
|
||||
const storeDesktop=useDesktopStore()
|
||||
const data=reactive({
|
||||
form:{
|
||||
name:"",
|
||||
description:"",
|
||||
photo:""
|
||||
}
|
||||
})
|
||||
const uploadUriId=ref("")
|
||||
const onUpload=(id:string)=> {
|
||||
uploadUriId.value=id
|
||||
}
|
||||
const loading=ref(false)
|
||||
|
||||
const onSubmit=async ()=>{
|
||||
let body={
|
||||
organizationId:sessionStorage.getItem("organizationId"),
|
||||
name:data.form.name,
|
||||
description: data.form.description,
|
||||
...(uploadUriId.value && {
|
||||
photo:uploadUriId.value
|
||||
})
|
||||
}
|
||||
let ret=await apiOrganization.update(body,loading)
|
||||
if(ret?.code==0) {
|
||||
Message.success("update success")
|
||||
storeDesktop.$update();
|
||||
} else {
|
||||
Message.error(ret?.msg??"unknown error")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
onBeforeMount(async ()=>{
|
||||
let ret=await apiOrganization.info({
|
||||
organizationId:sessionStorage.getItem("organizationId")
|
||||
})
|
||||
if(ret?.code==0) {
|
||||
data.form=ret.data
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
@ -0,0 +1,8 @@
|
||||
<template>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts"></script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
@ -0,0 +1,99 @@
|
||||
<template>
|
||||
<div>
|
||||
<a-form :model="form" style="width: 80%" ref="eleForm">
|
||||
<a-form-item field="name" label="name" required>
|
||||
<a-input v-model="form.name"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item field="description" label="description">
|
||||
<a-textarea v-model="form.description" allow-clear></a-textarea>
|
||||
</a-form-item>
|
||||
<a-form-item field="permissions" label="permissions">
|
||||
<a-space :wrap="true">
|
||||
<a-tag v-for="(item,index) in form.permissions" bordered color="arcoblue" checkable :checked="item.checked" @check="item.checked=!item.checked">{{item.name}}</a-tag>
|
||||
</a-space>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
||||
import {onDialogOk} from "../../../../common/component/dialog/dialog";
|
||||
import {getAllPermissions, Permission_Base, Permission_Types} from "../../../../../../../common/permission/permission";
|
||||
import {reactive, ref} from "vue";
|
||||
import {apiOrganization} from "../../../../common/request/request";
|
||||
import {Message} from "@arco-design/web-vue";
|
||||
|
||||
const eleForm=ref(null)
|
||||
const props=defineProps<{
|
||||
type:"edit"|"add"
|
||||
item?:{
|
||||
id:string
|
||||
name:string,
|
||||
description:string,
|
||||
permissions:Permission_Base[]
|
||||
}
|
||||
}>()
|
||||
const form=reactive(props.type=="edit"?{
|
||||
name:props.item.name,
|
||||
description:props.item.description,
|
||||
permissions:getAllPermissions(Permission_Types.Organization).filter(item=>{
|
||||
if(item.name!=="ADMIN") {
|
||||
return true;
|
||||
}
|
||||
}).map(item=>{
|
||||
return {
|
||||
...item,
|
||||
checked:props.item.permissions.map(item=>item.name).includes(item.name)
|
||||
}
|
||||
})
|
||||
}:{
|
||||
name:"",
|
||||
description:"",
|
||||
permissions:getAllPermissions(Permission_Types.Organization).filter(item=>{
|
||||
if(item.name!=="ADMIN") {
|
||||
return true;
|
||||
}
|
||||
}).map(item=>{
|
||||
return {
|
||||
...item,
|
||||
checked:false
|
||||
}
|
||||
})
|
||||
});
|
||||
onDialogOk(async ()=>{
|
||||
let ret=await eleForm.value.validate()
|
||||
if(ret) {
|
||||
return false;
|
||||
}
|
||||
let value=form.permissions.filter(item=>{
|
||||
if(item.checked) {
|
||||
return true
|
||||
}
|
||||
}).reduce((previousValue, currentValue, currentIndex, array)=>{
|
||||
return previousValue | currentValue.value
|
||||
},0)
|
||||
let res=await (props.type=="edit"?apiOrganization.editRole({
|
||||
roleId:props.item.id,
|
||||
name:form.name,
|
||||
description:form.description,
|
||||
value:value
|
||||
}):apiOrganization.addRole({
|
||||
organizationId:sessionStorage.getItem("organizationId"),
|
||||
name:form.name,
|
||||
description:form.description,
|
||||
value:value
|
||||
}))
|
||||
if(res?.code==0) {
|
||||
Message.success("update success")
|
||||
return true
|
||||
} else {
|
||||
Message.error(res.msg);
|
||||
return false
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
@ -0,0 +1,102 @@
|
||||
<template>
|
||||
<div>
|
||||
<a-form :model="form" style="width: 80%" ref="eleForm">
|
||||
<a-form-item field="name" label="name" required>
|
||||
<a-input v-model="form.name"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item field="description" label="description">
|
||||
<a-textarea v-model="form.description" allow-clear></a-textarea>
|
||||
</a-form-item>
|
||||
<a-form-item field="permissions" label="permissions">
|
||||
<a-space :wrap="true">
|
||||
<a-tag v-for="(item,index) in form.permissions" bordered color="arcoblue" checkable :checked="item.checked" @check="item.checked=!item.checked">{{item.name}}</a-tag>
|
||||
</a-space>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
||||
import {onDialogOk} from "../../../../common/component/dialog/dialog";
|
||||
import {getAllPermissions, Permission_Base, Permission_Types} from "../../../../../../../common/permission/permission";
|
||||
import {reactive, ref} from "vue";
|
||||
import {apiProject} from "../../../../common/request/request";
|
||||
import {Message} from "@arco-design/web-vue";
|
||||
|
||||
const eleForm=ref(null)
|
||||
const props=defineProps<{
|
||||
projectId?:string
|
||||
type:"edit"|"add"
|
||||
item?:{
|
||||
id:string
|
||||
name:string,
|
||||
description:string,
|
||||
permissions:Permission_Base[]
|
||||
}
|
||||
}>()
|
||||
const form=reactive(props.type=="edit"?{
|
||||
name:props.item.name,
|
||||
description:props.item.description,
|
||||
permissions:getAllPermissions(Permission_Types.Project).filter(item=>{
|
||||
if(item.name!=="ADMIN") {
|
||||
return true;
|
||||
}
|
||||
}).map(item=>{
|
||||
return {
|
||||
...item,
|
||||
checked:props.item.permissions.map(item=>item.name).includes(item.name)
|
||||
}
|
||||
})
|
||||
}:{
|
||||
name:"",
|
||||
description:"",
|
||||
permissions:getAllPermissions(Permission_Types.Project).filter(item=>{
|
||||
if(item.name!=="ADMIN") {
|
||||
return true;
|
||||
}
|
||||
}).map(item=>{
|
||||
return {
|
||||
...item,
|
||||
checked:false
|
||||
}
|
||||
})
|
||||
});
|
||||
onDialogOk(async ()=>{
|
||||
let ret=await eleForm.value.validate()
|
||||
if(ret) {
|
||||
return false;
|
||||
}
|
||||
let value=form.permissions.filter(item=>{
|
||||
if(item.checked) {
|
||||
return true
|
||||
}
|
||||
}).reduce((previousValue, currentValue, currentIndex, array)=>{
|
||||
return previousValue | currentValue.value
|
||||
},0)
|
||||
let res=await (props.type=="edit"?apiProject.editRole({
|
||||
roleId:props.item.id,
|
||||
name:form.name,
|
||||
description:form.description,
|
||||
value:value
|
||||
}):apiProject.addRole({
|
||||
...(props.projectId && {
|
||||
projectId:props.projectId
|
||||
}),
|
||||
name:form.name,
|
||||
description:form.description,
|
||||
value:value
|
||||
}))
|
||||
if(res?.code==0) {
|
||||
Message.success("update success")
|
||||
return true
|
||||
} else {
|
||||
Message.error(res.msg);
|
||||
return false
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
@ -0,0 +1,102 @@
|
||||
<template>
|
||||
<div>
|
||||
<a-form :model="form" style="width: 80%" ref="eleForm">
|
||||
<a-form-item field="name" label="name" required>
|
||||
<a-input v-model="form.name"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item field="description" label="description">
|
||||
<a-textarea v-model="form.description" allow-clear></a-textarea>
|
||||
</a-form-item>
|
||||
<a-form-item field="permissions" label="permissions">
|
||||
<a-space :wrap="true">
|
||||
<a-tag v-for="(item,index) in form.permissions" bordered color="arcoblue" checkable :checked="item.checked" @check="item.checked=!item.checked">{{item.name}}</a-tag>
|
||||
</a-space>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
||||
import {onDialogOk} from "../../../../common/component/dialog/dialog";
|
||||
import {getAllPermissions, Permission_Base, Permission_Types} from "../../../../../../../common/permission/permission";
|
||||
import {reactive, ref} from "vue";
|
||||
import {apiTeam} from "../../../../common/request/request";
|
||||
import {Message} from "@arco-design/web-vue";
|
||||
|
||||
const eleForm=ref(null)
|
||||
const props=defineProps<{
|
||||
teamId?:string
|
||||
type:"edit"|"add"
|
||||
item?:{
|
||||
id:string
|
||||
name:string,
|
||||
description:string,
|
||||
permissions:Permission_Base[]
|
||||
}
|
||||
}>()
|
||||
const form=reactive(props.type=="edit"?{
|
||||
name:props.item.name,
|
||||
description:props.item.description,
|
||||
permissions:getAllPermissions(Permission_Types.Team).filter(item=>{
|
||||
if(item.name!=="ADMIN") {
|
||||
return true;
|
||||
}
|
||||
}).map(item=>{
|
||||
return {
|
||||
...item,
|
||||
checked:props.item.permissions.map(item=>item.name).includes(item.name)
|
||||
}
|
||||
})
|
||||
}:{
|
||||
name:"",
|
||||
description:"",
|
||||
permissions:getAllPermissions(Permission_Types.Team).filter(item=>{
|
||||
if(item.name!=="ADMIN") {
|
||||
return true;
|
||||
}
|
||||
}).map(item=>{
|
||||
return {
|
||||
...item,
|
||||
checked:false
|
||||
}
|
||||
})
|
||||
});
|
||||
onDialogOk(async ()=>{
|
||||
let ret=await eleForm.value.validate()
|
||||
if(ret) {
|
||||
return false;
|
||||
}
|
||||
let value=form.permissions.filter(item=>{
|
||||
if(item.checked) {
|
||||
return true
|
||||
}
|
||||
}).reduce((previousValue, currentValue, currentIndex, array)=>{
|
||||
return previousValue | currentValue.value
|
||||
},0)
|
||||
let res=await (props.type=="edit"?apiTeam.editRole({
|
||||
roleId:props.item.id,
|
||||
name:form.name,
|
||||
description:form.description,
|
||||
value:value
|
||||
}):apiTeam.addRole({
|
||||
...(props.teamId && {
|
||||
teamId:props.teamId
|
||||
}),
|
||||
name:form.name,
|
||||
description:form.description,
|
||||
value:value
|
||||
}))
|
||||
if(res?.code==0) {
|
||||
Message.success("update success")
|
||||
return true
|
||||
} else {
|
||||
Message.error(res.msg);
|
||||
return false
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
@ -0,0 +1,140 @@
|
||||
<template>
|
||||
<div ref="root">
|
||||
<a-button @click="onAdd" type="primary" style="margin-bottom: 10px">Add</a-button>
|
||||
<a-table :columns="columns" :data="data" :pagination="false">
|
||||
<template #description="{record}">
|
||||
{{record.description}}
|
||||
</template>
|
||||
<template #permission="{record}">
|
||||
<a-space wrap>
|
||||
<a-tag v-for="item in record.permissions.map(item=>item.name)">{{item}}</a-tag>
|
||||
</a-space>
|
||||
</template>
|
||||
<template #reserved="{record}">
|
||||
<icon-check v-if="record.reserved" style="color: green"></icon-check>
|
||||
<icon-close v-else style="color: red"></icon-close>
|
||||
</template>
|
||||
<template #operation="{record}">
|
||||
<template v-if="!record.reserved">
|
||||
<a-space wrap>
|
||||
<a-button type="primary" size="small" @click="onEdit(record)">manage</a-button>
|
||||
<a-button status="danger" size="small" @click="onDelete(record)">delete</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {getCurrentInstance, markRaw, onBeforeMount, ref} from "vue";
|
||||
import {apiOrganization} from "../../../../common/request/request";
|
||||
import {Dialog} from "../../../../common/component/dialog/dialog";
|
||||
import EditOrganizationRole from "./editOrganizationRole.vue";
|
||||
import {Permission_Base, Permission_Types} from "../../../../../../../common/permission/permission";
|
||||
import {Message} from "@arco-design/web-vue";
|
||||
|
||||
type Item={
|
||||
id:string,
|
||||
name:string,
|
||||
reserved:number,
|
||||
description:string,
|
||||
permissions:Permission_Base[]
|
||||
}
|
||||
const columns=[
|
||||
{
|
||||
title:"name",
|
||||
dataIndex:"name"
|
||||
},
|
||||
{
|
||||
title:"description",
|
||||
slotName:"description"
|
||||
},
|
||||
{
|
||||
title:"permission",
|
||||
slotName:"permission"
|
||||
},
|
||||
{
|
||||
title:"reserved",
|
||||
slotName: "reserved"
|
||||
},
|
||||
{
|
||||
title:"operation",
|
||||
slotName: "operation"
|
||||
}
|
||||
]
|
||||
const data=ref<Item[]>([])
|
||||
const root=ref(null)
|
||||
const appContext=getCurrentInstance().appContext
|
||||
const onAdd=async ()=>{
|
||||
let ret=await Dialog.open(root.value,appContext,"Add Role",markRaw(EditOrganizationRole),{
|
||||
type:"add"
|
||||
})
|
||||
if(ret) {
|
||||
init()
|
||||
}
|
||||
}
|
||||
const onEdit=async (item:Item) =>{
|
||||
let ret=await Dialog.open(root.value,appContext,"Edit Role",markRaw(EditOrganizationRole),{
|
||||
type:"edit",
|
||||
item:{
|
||||
name:item.name,
|
||||
id:item.id,
|
||||
description:item.description,
|
||||
permissions:item.permissions
|
||||
}
|
||||
})
|
||||
if(ret) {
|
||||
init()
|
||||
}
|
||||
}
|
||||
const onDelete=async (item:Item)=>{
|
||||
let ret=await Dialog.confirm(root.value,appContext,"Do you want to delete this role?")
|
||||
if(ret) {
|
||||
let res=await apiOrganization.removeRole({
|
||||
roleId:item.id
|
||||
})
|
||||
if(res?.code==0) {
|
||||
Message.success("remove success")
|
||||
init()
|
||||
} else {
|
||||
Message.error(res.msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
const init=async ()=>{
|
||||
let ret=await apiOrganization.listRole({
|
||||
organizationId:sessionStorage.getItem("organizationId")
|
||||
})
|
||||
if(ret?.code==0) {
|
||||
let value=ret.data;
|
||||
let arr:Item[]=[]
|
||||
arr.push({
|
||||
name:value.admin.name,
|
||||
id:value.admin.id,
|
||||
reserved:value.admin.reserved,
|
||||
description:value.admin.description,
|
||||
permissions:[{
|
||||
name:Permission_Types.Organization.ADMIN.name,
|
||||
description:Permission_Types.Organization.ADMIN.description,
|
||||
value:Permission_Types.Organization.ADMIN.value
|
||||
}]
|
||||
})
|
||||
for(let obj of value.users) {
|
||||
arr.push({
|
||||
name:obj.name,
|
||||
id:obj.id,
|
||||
reserved:obj.reserved,
|
||||
description:obj.description,
|
||||
permissions:obj.permissions as Permission_Base[]
|
||||
})
|
||||
}
|
||||
data.value=arr
|
||||
}
|
||||
}
|
||||
onBeforeMount(init)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
@ -0,0 +1,83 @@
|
||||
<template>
|
||||
<div ref="root">
|
||||
<a-input-search style="width: 300px;margin-bottom: 10px" placeholder="please type project name" @search="search" search-button></a-input-search>
|
||||
<a-table :columns="columns" :data="data" @pageChange="onPageChange" :pagination="pagination">
|
||||
<template #operation="{record}">
|
||||
<a-button type="primary" size="small" @click="onEdit(record)">manage role</a-button>
|
||||
</template>
|
||||
</a-table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {reactive, ref} from "vue";
|
||||
import {ICommon_Model_Project} from "../../../../../../../common/model/project";
|
||||
import {apiProject, DCSType} from "../../../../common/request/request";
|
||||
import {getCurrentNavigator} from "../../../../../teamOS/common/component/navigator/navigator";
|
||||
|
||||
const columns=[
|
||||
{
|
||||
title:"name",
|
||||
dataIndex:"name"
|
||||
},
|
||||
{
|
||||
title:"description",
|
||||
dataIndex:"description"
|
||||
},
|
||||
{
|
||||
title:"keyword",
|
||||
dataIndex:"keyword"
|
||||
},
|
||||
{
|
||||
title:"operation",
|
||||
slotName: "operation"
|
||||
}
|
||||
]
|
||||
let data=ref<DCSType<ICommon_Model_Project[]>>([])
|
||||
const root=ref(null)
|
||||
const pagination=reactive({
|
||||
total:0,
|
||||
current:1,
|
||||
pageSize:10
|
||||
})
|
||||
const keyword=ref("");
|
||||
const search=async (key:string)=>{
|
||||
keyword.value=key;
|
||||
let ret=await apiProject.list({
|
||||
...(key && {
|
||||
keyword:key
|
||||
}),
|
||||
page:0,
|
||||
size:10
|
||||
})
|
||||
if(ret?.code==0) {
|
||||
data.value=ret.data.data
|
||||
pagination.total=ret.data.count;
|
||||
pagination.current=ret.data.page+1
|
||||
}
|
||||
}
|
||||
let navigator=getCurrentNavigator();
|
||||
const onEdit=async (item:ICommon_Model_Project)=>{
|
||||
navigator.push("projectGlobalRole",{
|
||||
projectId:item.id
|
||||
},"Project Role")
|
||||
}
|
||||
const onPageChange=async (page:number)=>{
|
||||
let ret=await apiProject.list({
|
||||
...(keyword.value && {
|
||||
keyword:keyword.value
|
||||
}),
|
||||
page:page-1,
|
||||
size:10
|
||||
})
|
||||
if(ret?.code==0) {
|
||||
data.value=ret.data.data
|
||||
pagination.total=ret.data.count;
|
||||
pagination.current=ret.data.page+1
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
@ -0,0 +1,155 @@
|
||||
<template>
|
||||
<div ref="root">
|
||||
<a-button @click="onAdd" type="primary" style="margin-bottom: 10px">Add</a-button>
|
||||
<a-table :columns="columns" :data="data" :pagination="false">
|
||||
<template #description="{record}">
|
||||
{{record.description}}
|
||||
</template>
|
||||
<template #permission="{record}">
|
||||
<a-space wrap>
|
||||
<a-tag v-for="item in record.permissions.map(item=>item.name)">{{item}}</a-tag>
|
||||
</a-space>
|
||||
</template>
|
||||
<template #reserved="{record}">
|
||||
<icon-check v-if="record.reserved" style="color: green"></icon-check>
|
||||
<icon-close v-else style="color: red"></icon-close>
|
||||
</template>
|
||||
<template #operation="{record}">
|
||||
<template v-if="!record.reserved">
|
||||
<a-space wrap>
|
||||
<a-button type="primary" size="small" @click="onEdit(record)">manage</a-button>
|
||||
<a-button status="danger" size="small" @click="onDelete(record)">delete</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {getCurrentInstance, markRaw, onBeforeMount, ref} from "vue";
|
||||
import {apiProject} from "../../../../common/request/request";
|
||||
import {Dialog} from "../../../../common/component/dialog/dialog";
|
||||
import {Permission_Base, Permission_Types} from "../../../../../../../common/permission/permission";
|
||||
import {Message} from "@arco-design/web-vue";
|
||||
import EditProjectRole from "./editProjectRole.vue";
|
||||
|
||||
const props=defineProps<{
|
||||
projectId?:string
|
||||
}>()
|
||||
type Item={
|
||||
id:string,
|
||||
name:string,
|
||||
reserved:number,
|
||||
description:string,
|
||||
permissions:Permission_Base[]
|
||||
}
|
||||
const columns=[
|
||||
{
|
||||
title:"name",
|
||||
dataIndex:"name"
|
||||
},
|
||||
{
|
||||
title:"description",
|
||||
slotName:"description"
|
||||
},
|
||||
{
|
||||
title:"permission",
|
||||
slotName:"permission"
|
||||
},
|
||||
{
|
||||
title:"reserved",
|
||||
slotName: "reserved"
|
||||
},
|
||||
{
|
||||
title:"operation",
|
||||
slotName: "operation"
|
||||
}
|
||||
]
|
||||
const data=ref<Item[]>([])
|
||||
const root=ref(null)
|
||||
const appContext=getCurrentInstance().appContext
|
||||
const onAdd=async ()=>{
|
||||
let ret=await Dialog.open(root.value,appContext,"Add Role",markRaw(EditProjectRole),{
|
||||
type:"add",
|
||||
...(props.projectId && {
|
||||
projectId:props.projectId
|
||||
})
|
||||
})
|
||||
if(ret) {
|
||||
init()
|
||||
}
|
||||
}
|
||||
const onEdit=async (item:Item) =>{
|
||||
let ret=await Dialog.open(root.value,appContext,"Edit Role",markRaw(EditProjectRole),{
|
||||
type:"edit",
|
||||
item:{
|
||||
name:item.name,
|
||||
id:item.id,
|
||||
description:item.description,
|
||||
permissions:item.permissions
|
||||
}
|
||||
})
|
||||
if(ret) {
|
||||
init()
|
||||
}
|
||||
}
|
||||
const onDelete=async (item:Item)=>{
|
||||
let ret=await Dialog.confirm(root.value,appContext,"Do you want to delete this role?")
|
||||
if(ret) {
|
||||
let res=await apiProject.removeRole({
|
||||
roleId:item.id
|
||||
})
|
||||
if(res?.code==0) {
|
||||
Message.success("remove success")
|
||||
init()
|
||||
} else {
|
||||
Message.error(res.msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
const init=async ()=>{
|
||||
let ret=await apiProject.listRole({
|
||||
...(props.projectId && {
|
||||
projectId:props.projectId
|
||||
})
|
||||
})
|
||||
if(ret?.code==0) {
|
||||
let value=ret.data;
|
||||
let arr:Item[]=[]
|
||||
arr.push({
|
||||
name:value.admin.name,
|
||||
id:value.admin.id,
|
||||
reserved:value.admin.reserved,
|
||||
description:value.admin.description,
|
||||
permissions:[{
|
||||
name:Permission_Types.Project.ADMIN.name,
|
||||
description:Permission_Types.Project.ADMIN.description,
|
||||
value:Permission_Types.Project.ADMIN.value
|
||||
}]
|
||||
})
|
||||
if(props.projectId) {
|
||||
value.users=value.users.filter(item=>{
|
||||
if(!item.global) {
|
||||
return true
|
||||
}
|
||||
})
|
||||
}
|
||||
for(let obj of value.users) {
|
||||
arr.push({
|
||||
name:obj.name,
|
||||
id:obj.id,
|
||||
reserved:obj.reserved,
|
||||
description:obj.description,
|
||||
permissions:obj.permissions as Permission_Base[]
|
||||
})
|
||||
}
|
||||
data.value=arr
|
||||
}
|
||||
}
|
||||
onBeforeMount(init)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
@ -0,0 +1,83 @@
|
||||
<template>
|
||||
<div ref="root">
|
||||
<a-input-search style="width: 300px;margin-bottom: 10px" placeholder="please type team name" @search="search" search-button></a-input-search>
|
||||
<a-table :columns="columns" :data="data" @pageChange="onPageChange" :pagination="pagination">
|
||||
<template #operation="{record}">
|
||||
<a-button type="primary" size="small" @click="onEdit(record)">manage role</a-button>
|
||||
</template>
|
||||
</a-table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {reactive, ref} from "vue";
|
||||
import {apiTeam, DCSType} from "../../../../common/request/request";
|
||||
import {getCurrentNavigator} from "../../../../../teamOS/common/component/navigator/navigator";
|
||||
import {ICommon_Model_Team} from "../../../../../../../common/model/team";
|
||||
|
||||
const columns=[
|
||||
{
|
||||
title:"name",
|
||||
dataIndex:"name"
|
||||
},
|
||||
{
|
||||
title:"description",
|
||||
dataIndex:"description"
|
||||
},
|
||||
{
|
||||
title:"keyword",
|
||||
dataIndex:"keyword"
|
||||
},
|
||||
{
|
||||
title:"operation",
|
||||
slotName: "operation"
|
||||
}
|
||||
]
|
||||
let data=ref<DCSType<ICommon_Model_Team[]>>([])
|
||||
const root=ref(null)
|
||||
const pagination=reactive({
|
||||
total:0,
|
||||
current:1,
|
||||
pageSize:10
|
||||
})
|
||||
const keyword=ref("");
|
||||
const search=async (key:string)=>{
|
||||
keyword.value=key;
|
||||
let ret=await apiTeam.list({
|
||||
...(key && {
|
||||
keyword:key
|
||||
}),
|
||||
page:0,
|
||||
size:10
|
||||
})
|
||||
if(ret?.code==0) {
|
||||
data.value=ret.data.data
|
||||
pagination.total=ret.data.count;
|
||||
pagination.current=ret.data.page+1
|
||||
}
|
||||
}
|
||||
let navigator=getCurrentNavigator();
|
||||
const onEdit=async (item:ICommon_Model_Team)=>{
|
||||
navigator.push("teamGlobalRole",{
|
||||
teamId:item.id
|
||||
},"Team Role")
|
||||
}
|
||||
const onPageChange=async (page:number)=>{
|
||||
let ret=await apiTeam.list({
|
||||
...(keyword.value && {
|
||||
keyword:keyword.value
|
||||
}),
|
||||
page:page-1,
|
||||
size:10
|
||||
})
|
||||
if(ret?.code==0) {
|
||||
data.value=ret.data.data
|
||||
pagination.total=ret.data.count;
|
||||
pagination.current=ret.data.page+1
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
@ -0,0 +1,155 @@
|
||||
<template>
|
||||
<div ref="root">
|
||||
<a-button @click="onAdd" type="primary" style="margin-bottom: 10px">Add</a-button>
|
||||
<a-table :columns="columns" :data="data" :pagination="false">
|
||||
<template #description="{record}">
|
||||
{{record.description}}
|
||||
</template>
|
||||
<template #permission="{record}">
|
||||
<a-space wrap>
|
||||
<a-tag v-for="item in record.permissions.map(item=>item.name)">{{item}}</a-tag>
|
||||
</a-space>
|
||||
</template>
|
||||
<template #reserved="{record}">
|
||||
<icon-check v-if="record.reserved" style="color: green"></icon-check>
|
||||
<icon-close v-else style="color: red"></icon-close>
|
||||
</template>
|
||||
<template #operation="{record}">
|
||||
<template v-if="!record.reserved">
|
||||
<a-space wrap>
|
||||
<a-button type="primary" size="small" @click="onEdit(record)">manage</a-button>
|
||||
<a-button status="danger" size="small" @click="onDelete(record)">delete</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {getCurrentInstance, markRaw, onBeforeMount, ref} from "vue";
|
||||
import {apiTeam} from "../../../../common/request/request";
|
||||
import {Dialog} from "../../../../common/component/dialog/dialog";
|
||||
import {Permission_Base, Permission_Types} from "../../../../../../../common/permission/permission";
|
||||
import {Message} from "@arco-design/web-vue";
|
||||
import EditTeamRole from "./editTeamRole.vue";
|
||||
|
||||
const props=defineProps<{
|
||||
teamId?:string
|
||||
}>()
|
||||
type Item={
|
||||
id:string,
|
||||
name:string,
|
||||
reserved:number,
|
||||
description:string,
|
||||
permissions:Permission_Base[]
|
||||
}
|
||||
const columns=[
|
||||
{
|
||||
title:"name",
|
||||
dataIndex:"name"
|
||||
},
|
||||
{
|
||||
title:"description",
|
||||
slotName:"description"
|
||||
},
|
||||
{
|
||||
title:"permission",
|
||||
slotName:"permission"
|
||||
},
|
||||
{
|
||||
title:"reserved",
|
||||
slotName: "reserved"
|
||||
},
|
||||
{
|
||||
title:"operation",
|
||||
slotName: "operation"
|
||||
}
|
||||
]
|
||||
const data=ref<Item[]>([])
|
||||
const root=ref(null)
|
||||
const appContext=getCurrentInstance().appContext
|
||||
const onAdd=async ()=>{
|
||||
let ret=await Dialog.open(root.value,appContext,"Add Role",markRaw(EditTeamRole),{
|
||||
type:"add",
|
||||
...(props.teamId && {
|
||||
teamId:props.teamId
|
||||
})
|
||||
})
|
||||
if(ret) {
|
||||
init()
|
||||
}
|
||||
}
|
||||
const onEdit=async (item:Item) =>{
|
||||
let ret=await Dialog.open(root.value,appContext,"Edit Role",markRaw(EditTeamRole),{
|
||||
type:"edit",
|
||||
item:{
|
||||
name:item.name,
|
||||
id:item.id,
|
||||
description:item.description,
|
||||
permissions:item.permissions
|
||||
}
|
||||
})
|
||||
if(ret) {
|
||||
init()
|
||||
}
|
||||
}
|
||||
const onDelete=async (item:Item)=>{
|
||||
let ret=await Dialog.confirm(root.value,appContext,"Do you want to delete this role?")
|
||||
if(ret) {
|
||||
let res=await apiTeam.removeRole({
|
||||
roleId:item.id
|
||||
})
|
||||
if(res?.code==0) {
|
||||
Message.success("remove success")
|
||||
init()
|
||||
} else {
|
||||
Message.error(res.msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
const init=async ()=>{
|
||||
let ret=await apiTeam.roles({
|
||||
...(props.teamId && {
|
||||
teamId:props.teamId
|
||||
})
|
||||
})
|
||||
if(ret?.code==0) {
|
||||
let value=ret.data;
|
||||
let arr:Item[]=[]
|
||||
arr.push({
|
||||
name:value.admin.name,
|
||||
id:value.admin.id,
|
||||
reserved:value.admin.reserved,
|
||||
description:value.admin.description,
|
||||
permissions:[{
|
||||
name:Permission_Types.Team.ADMIN.name,
|
||||
description:Permission_Types.Team.ADMIN.description,
|
||||
value:Permission_Types.Team.ADMIN.value
|
||||
}]
|
||||
})
|
||||
if(props.teamId) {
|
||||
value.users=value.users.filter(item=>{
|
||||
if(!item.global) {
|
||||
return true
|
||||
}
|
||||
})
|
||||
}
|
||||
for(let obj of value.users) {
|
||||
arr.push({
|
||||
name:obj.name,
|
||||
id:obj.id,
|
||||
reserved:obj.reserved,
|
||||
description:obj.description,
|
||||
permissions:obj.permissions as Permission_Base[]
|
||||
})
|
||||
}
|
||||
data.value=arr
|
||||
}
|
||||
}
|
||||
onBeforeMount(init)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
106
code/client/src/business/controller/app/setting/setting.vue
Normal file
106
code/client/src/business/controller/app/setting/setting.vue
Normal file
@ -0,0 +1,106 @@
|
||||
<template>
|
||||
<a-layout style="height: 100%">
|
||||
<a-layout-sider :resize-directions="['right']">
|
||||
<a-menu style="width: 100%" @menu-item-click="onSubMenuClick">
|
||||
<a-sub-menu key="organization">
|
||||
<template #title>Organization</template>
|
||||
<a-menu-item key="organizationEdit">Edit</a-menu-item>
|
||||
</a-sub-menu>
|
||||
<a-sub-menu key="userTeam">
|
||||
<template #title>User & Team</template>
|
||||
<a-menu-item key="userManage">User Manage</a-menu-item>
|
||||
<a-menu-item key="teamManage">Team Manage</a-menu-item>
|
||||
<a-menu-item key="tagManage">Tag Manage</a-menu-item>
|
||||
</a-sub-menu>
|
||||
<a-sub-menu key="role">
|
||||
<template #title>Role</template>
|
||||
<a-menu-item key="organizationRole">Organization</a-menu-item>
|
||||
<a-sub-menu key="roleProject">
|
||||
<template #title>Project</template>
|
||||
<a-menu-item key="projectGlobalRole">Global</a-menu-item>
|
||||
<a-menu-item key="projectSpecificRole">Specific</a-menu-item>
|
||||
</a-sub-menu>
|
||||
<a-sub-menu key="roleTeam">
|
||||
<template #title>Team</template>
|
||||
<a-menu-item key="teamGlobalRole">Global</a-menu-item>
|
||||
<a-menu-item key="teamSpecificRole">Specific</a-menu-item>
|
||||
</a-sub-menu>
|
||||
</a-sub-menu>
|
||||
<a-sub-menu key="project">
|
||||
<template #title>Project</template>
|
||||
<a-menu-item key="projectManage">Manage</a-menu-item>
|
||||
</a-sub-menu>
|
||||
<a-sub-menu key="issueSolution">
|
||||
<template #title>Issue Solution</template>
|
||||
<a-menu-item key="issueSolution">Manage</a-menu-item>
|
||||
</a-sub-menu>
|
||||
</a-menu>
|
||||
</a-layout-sider>
|
||||
<a-layout-content style="flex-direction: column;display: flex;padding: 10px">
|
||||
<a-breadcrumb style="margin: 10px 0;flex: 0 1 auto">
|
||||
<a-breadcrumb-item v-for="item in pathList">{{item}}</a-breadcrumb-item>
|
||||
</a-breadcrumb>
|
||||
<a-divider margin="0"></a-divider>
|
||||
<div style="width: 100%;display: flex;flex: 1 1 auto;padding-top: 20px">
|
||||
<NavigatorContainer :routes="objComponent" ref="eleNavigator"></NavigatorContainer>
|
||||
</div>
|
||||
</a-layout-content>
|
||||
</a-layout>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {markRaw, onMounted, ref} from "vue";
|
||||
import IssueIndex from "./issue/index.vue"
|
||||
import OrganizationIndex from "./organization/index.vue"
|
||||
import ProjectIndex from "./project/index.vue"
|
||||
import UserIndex from "./user&team/user.vue"
|
||||
import TeamIndex from "./user&team/team.vue"
|
||||
import TagIndex from "./user&team/tag.vue"
|
||||
import RoleOrganizationIndex from "./role/organizationRoleList.vue"
|
||||
import RoleProjectList from "./role/projectList.vue"
|
||||
import RoleGlobalProjectIndex from "./role/projectRoleList.vue"
|
||||
import RoleTeamGlobalIndex from "./role/teamRoleList.vue"
|
||||
import RoleTeamList from "./role/teamList.vue"
|
||||
import NavigatorContainer from "../../../../teamOS/common/component/navigator/navigatorContainer.vue";
|
||||
|
||||
let objComponent={
|
||||
issueSolution:markRaw(IssueIndex),
|
||||
organizationEdit:markRaw(OrganizationIndex),
|
||||
projectManage:markRaw(ProjectIndex),
|
||||
userManage:markRaw(UserIndex),
|
||||
teamManage:markRaw(TeamIndex),
|
||||
tagManage:markRaw(TagIndex),
|
||||
organizationRole:markRaw(RoleOrganizationIndex),
|
||||
projectSpecificRole:markRaw(RoleProjectList),
|
||||
projectGlobalRole:markRaw(RoleGlobalProjectIndex),
|
||||
teamGlobalRole:markRaw(RoleTeamGlobalIndex),
|
||||
teamSpecificRole:markRaw(RoleTeamList),
|
||||
}
|
||||
let type=ref("")
|
||||
let objCrumb={
|
||||
organizationEdit:"Organization Edit",
|
||||
userManage:"User",
|
||||
teamManage:"Team",
|
||||
tagManage:"Tag",
|
||||
project:"Project",
|
||||
issueSolution:"Issue Solution",
|
||||
organizationRole:"Organization Role",
|
||||
projectGlobalRole:"Global Project Role",
|
||||
projectSpecificRole:"Project List",
|
||||
teamGlobalRole:"Team Role",
|
||||
teamSpecificRole:"Team List"
|
||||
}
|
||||
const eleNavigator=ref<InstanceType<typeof NavigatorContainer>>(null)
|
||||
let pathList=ref([]);
|
||||
onMounted(()=>{
|
||||
pathList.value=eleNavigator.value.navigator.getPath();
|
||||
})
|
||||
const onSubMenuClick=(key:string)=>{
|
||||
type.value=key;
|
||||
eleNavigator.value.navigator.replaceRoot(key,null,objCrumb[key]);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
@ -0,0 +1,49 @@
|
||||
<template>
|
||||
<a-form :model="form">
|
||||
<a-form-item field="name" label="name" required>
|
||||
<a-input v-model="form.name"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item field="description" label="description">
|
||||
<a-textarea v-model="form.description" allow-clear></a-textarea>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {ICommon_Model_Member_Tag} from "../../../../../../../common/model/member_tag";
|
||||
import {reactive} from "vue";
|
||||
import {onDialogOk} from "../../../../common/component/dialog/dialog";
|
||||
import {apiOrganization} from "../../../../common/request/request";
|
||||
import {Message} from "@arco-design/web-vue";
|
||||
|
||||
const props=defineProps<{
|
||||
type:"edit"|"add"
|
||||
item?:ICommon_Model_Member_Tag
|
||||
}>()
|
||||
let form=reactive<ICommon_Model_Member_Tag>(props.type=="edit"?JSON.parse(JSON.stringify(props.item)):{
|
||||
name:"",
|
||||
description:"",
|
||||
organization_id:sessionStorage.getItem("organizationId")
|
||||
})
|
||||
onDialogOk(async ()=>{
|
||||
let ret=await (props.type=="edit"?apiOrganization.editTag({
|
||||
memberTagId:form.id,
|
||||
name:form.name,
|
||||
description:form.description
|
||||
}):apiOrganization.addTag({
|
||||
name:form.name,
|
||||
description:form.description
|
||||
}))
|
||||
if(ret?.code==0) {
|
||||
Message.success("operation success")
|
||||
return true
|
||||
} else {
|
||||
Message.error(ret.msg)
|
||||
return false;
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
@ -0,0 +1,87 @@
|
||||
<template>
|
||||
<div ref="root">
|
||||
<a-button @click="onAdd" type="primary" style="margin-bottom: 10px">Add</a-button>
|
||||
<a-table :columns="columns" :data="data" :pagination="false">
|
||||
<template #operation="{record}">
|
||||
<template v-if="!record.reserved">
|
||||
<a-space wrap>
|
||||
<a-button type="primary" size="small" @click="onEdit(record)">manage</a-button>
|
||||
<a-button status="danger" size="small" @click="onDelete(record)">delete</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {getCurrentInstance, markRaw, onBeforeMount, ref} from "vue";
|
||||
import {ICommon_Model_Member_Tag} from "../../../../../../../common/model/member_tag";
|
||||
import {apiOrganization} from "../../../../common/request/request";
|
||||
import {Dialog} from "../../../../common/component/dialog/dialog";
|
||||
import EditTag from "./editTag.vue";
|
||||
import {Message} from "@arco-design/web-vue";
|
||||
|
||||
const columns=[
|
||||
{
|
||||
title:"name",
|
||||
dataIndex:"name"
|
||||
},
|
||||
{
|
||||
title:"description",
|
||||
dataIndex:"description"
|
||||
},
|
||||
{
|
||||
title:"operation",
|
||||
slotName: "operation"
|
||||
}
|
||||
]
|
||||
const data=ref<ICommon_Model_Member_Tag[]>([])
|
||||
const root=ref(null);
|
||||
const appContext=getCurrentInstance().appContext
|
||||
const search=async ()=>{
|
||||
let ret=await apiOrganization.listTag()
|
||||
if(ret?.code==0) {
|
||||
data.value=ret.data
|
||||
}
|
||||
}
|
||||
const onAdd=async ()=>{
|
||||
let ret=await Dialog.open(root.value,appContext,"Add Tag",markRaw(EditTag),{
|
||||
type:"add"
|
||||
})
|
||||
if(ret) {
|
||||
search();
|
||||
}
|
||||
}
|
||||
const onEdit=async (item:ICommon_Model_Member_Tag)=>{
|
||||
let ret=await Dialog.open(root.value,appContext,"Edit Tag",markRaw(EditTag),{
|
||||
type:"edit",
|
||||
item:item
|
||||
})
|
||||
if(ret) {
|
||||
search();
|
||||
}
|
||||
}
|
||||
const onDelete=async (item:ICommon_Model_Member_Tag)=>{
|
||||
let ret=await Dialog.confirm(root.value,appContext,"Do you want to delete this tag?")
|
||||
if(ret) {
|
||||
let res=await apiOrganization.removeTag({
|
||||
memberTagId:item.id
|
||||
})
|
||||
if(res?.code==0) {
|
||||
Message.success("delete success")
|
||||
search()
|
||||
} else {
|
||||
Message.error(res.msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onBeforeMount(()=>{
|
||||
search();
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
@ -0,0 +1,9 @@
|
||||
<template>
|
||||
|
||||
</template>
|
||||
|
||||
<script setup lang="ts"></script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
@ -0,0 +1,8 @@
|
||||
<template>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts"></script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
78
code/client/src/business/controller/desktop/desktop.vue
Normal file
78
code/client/src/business/controller/desktop/desktop.vue
Normal file
@ -0,0 +1,78 @@
|
||||
<template>
|
||||
<TeamOS/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
||||
import TeamOS from "../../../teamOS/index.vue";
|
||||
import img from "../../../assert/back.png"
|
||||
import {getDesktopInstance} from "../../../teamOS/teamOS";
|
||||
import {nextTick, onBeforeMount, watchEffect} from "vue";
|
||||
import {useDesktopStore} from "./store/desktop";
|
||||
import {useRouter} from "vue-router";
|
||||
|
||||
let desktop=getDesktopInstance().desktop;
|
||||
desktop.setBackgroundImage(img)
|
||||
const store=useDesktopStore()
|
||||
const router=useRouter();
|
||||
let iconManager=getDesktopInstance().iconManager
|
||||
|
||||
onBeforeMount(async ()=>{
|
||||
let isAuth=await store.isAuth();
|
||||
if(!isAuth) {
|
||||
await router.replace("login");
|
||||
return
|
||||
}
|
||||
await store.getOrganizationList()
|
||||
if(sessionStorage.getItem("organizationId")) {
|
||||
await store.enterOrganization(sessionStorage.getItem("organizationId"))
|
||||
}
|
||||
desktop.addEventListener("menuClick",async value => {
|
||||
await store.enterOrganization(value);
|
||||
})
|
||||
})
|
||||
watchEffect(()=>{
|
||||
if(store.organizationList) {
|
||||
let arr=[]
|
||||
if(store.organizationList.create.length>0) {
|
||||
arr.push({
|
||||
group:"Create",
|
||||
data:store.organizationList.create.map(item=>{
|
||||
return {
|
||||
name:item.name,
|
||||
value:item.id
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
if(store.organizationList.join.length>0) {
|
||||
arr.push({
|
||||
group:"Join",
|
||||
data:store.organizationList.join.map(item=>{
|
||||
return {
|
||||
name:item.name,
|
||||
value:item.id
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
if(store.organizationInfo?.photo) {
|
||||
desktop.setLogo(store.organizationInfo.photo)
|
||||
}
|
||||
if(sessionStorage.getItem("organizationId")) {
|
||||
desktop.setMenu(arr,sessionStorage.getItem("organizationId"))
|
||||
} else {
|
||||
desktop.setMenu(arr)
|
||||
}
|
||||
}
|
||||
iconManager.setList(store.appList)
|
||||
nextTick(()=>{
|
||||
iconManager.sort()
|
||||
})
|
||||
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
30
code/client/src/business/controller/desktop/icon/calendar.ts
Normal file
30
code/client/src/business/controller/desktop/icon/calendar.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import {Icon} from "../../../../teamOS/icon/icon";
|
||||
import {v4} from "uuid"
|
||||
import {ETeamOS_Window_Type, Window} from "../../../../teamOS/window/window";
|
||||
import Setting from "../../app/setting/setting.vue";
|
||||
import {Message} from "@arco-design/web-vue";
|
||||
import {windowManager} from "../../../../teamOS/window/windowManager";
|
||||
import {markRaw} from "vue";
|
||||
|
||||
export const iconCalendar=new Icon("calendar","calendar")
|
||||
iconCalendar.addEventListener("dbClick",item => {
|
||||
if(!sessionStorage.getItem("organizationId")) {
|
||||
Message.error("you must choose organization")
|
||||
return;
|
||||
}
|
||||
const win=new Window("setting",ETeamOS_Window_Type.SIMPLE, "calendar",true,[
|
||||
{
|
||||
id:v4(),
|
||||
meta:{
|
||||
title:"calendar"
|
||||
},
|
||||
components:{
|
||||
setting:markRaw(Setting)
|
||||
},
|
||||
default:{
|
||||
name:"setting"
|
||||
}
|
||||
}
|
||||
],"calendar");
|
||||
windowManager.open(win);
|
||||
})
|
30
code/client/src/business/controller/desktop/icon/im.ts
Normal file
30
code/client/src/business/controller/desktop/icon/im.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import {Icon} from "../../../../teamOS/icon/icon";
|
||||
import {v4} from "uuid"
|
||||
import {ETeamOS_Window_Type, Window} from "../../../../teamOS/window/window";
|
||||
import Setting from "../../app/setting/setting.vue";
|
||||
import {Message} from "@arco-design/web-vue";
|
||||
import {windowManager} from "../../../../teamOS/window/windowManager";
|
||||
import {markRaw} from "vue";
|
||||
|
||||
export const iconIM=new Icon("im","message")
|
||||
iconIM.addEventListener("dbClick",item => {
|
||||
if(!sessionStorage.getItem("organizationId")) {
|
||||
Message.error("you must choose organization")
|
||||
return;
|
||||
}
|
||||
const win=new Window("im",ETeamOS_Window_Type.SIMPLE, "im",true,[
|
||||
{
|
||||
id:v4(),
|
||||
meta:{
|
||||
title:"im"
|
||||
},
|
||||
components:{
|
||||
setting:markRaw(Setting)
|
||||
},
|
||||
default:{
|
||||
name:"setting"
|
||||
}
|
||||
}
|
||||
],"im");
|
||||
windowManager.open(win);
|
||||
})
|
30
code/client/src/business/controller/desktop/icon/meeting.ts
Normal file
30
code/client/src/business/controller/desktop/icon/meeting.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import {Icon} from "../../../../teamOS/icon/icon";
|
||||
import {v4} from "uuid"
|
||||
import {ETeamOS_Window_Type, Window} from "../../../../teamOS/window/window";
|
||||
import Setting from "../../app/setting/setting.vue";
|
||||
import {Message} from "@arco-design/web-vue";
|
||||
import {windowManager} from "../../../../teamOS/window/windowManager";
|
||||
import {markRaw} from "vue";
|
||||
|
||||
export const iconMeeting=new Icon("meeting","video")
|
||||
iconMeeting.addEventListener("dbClick",item => {
|
||||
if(!sessionStorage.getItem("organizationId")) {
|
||||
Message.error("you must choose organization")
|
||||
return;
|
||||
}
|
||||
const win=new Window("meeting",ETeamOS_Window_Type.SIMPLE, "meeting",true,[
|
||||
{
|
||||
id:v4(),
|
||||
meta:{
|
||||
title:"meeting"
|
||||
},
|
||||
components:{
|
||||
setting:markRaw(Setting)
|
||||
},
|
||||
default:{
|
||||
name:"setting"
|
||||
}
|
||||
}
|
||||
],"meeting");
|
||||
windowManager.open(win);
|
||||
})
|
30
code/client/src/business/controller/desktop/icon/project.ts
Normal file
30
code/client/src/business/controller/desktop/icon/project.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import {Icon} from "../../../../teamOS/icon/icon";
|
||||
import {v4} from "uuid"
|
||||
import {ETeamOS_Window_Type, Window} from "../../../../teamOS/window/window";
|
||||
import Setting from "../../app/setting/setting.vue";
|
||||
import {Message} from "@arco-design/web-vue";
|
||||
import {windowManager} from "../../../../teamOS/window/windowManager";
|
||||
import {markRaw} from "vue";
|
||||
|
||||
export const iconProject=new Icon("project","project")
|
||||
iconProject.addEventListener("dbClick",item => {
|
||||
if(!sessionStorage.getItem("organizationId")) {
|
||||
Message.error("you must choose organization")
|
||||
return;
|
||||
}
|
||||
const win=new Window("project",ETeamOS_Window_Type.TAB, "project",true,[
|
||||
{
|
||||
id:v4(),
|
||||
meta:{
|
||||
title:"project"
|
||||
},
|
||||
components:{
|
||||
setting:markRaw(Setting)
|
||||
},
|
||||
default:{
|
||||
name:"setting"
|
||||
}
|
||||
}
|
||||
],"project");
|
||||
windowManager.open(win);
|
||||
})
|
30
code/client/src/business/controller/desktop/icon/setting.ts
Normal file
30
code/client/src/business/controller/desktop/icon/setting.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import {Icon} from "../../../../teamOS/icon/icon";
|
||||
import {v4} from "uuid"
|
||||
import {ETeamOS_Window_Type, Window} from "../../../../teamOS/window/window";
|
||||
import Setting from "../../app/setting/setting.vue";
|
||||
import {Message} from "@arco-design/web-vue";
|
||||
import {windowManager} from "../../../../teamOS/window/windowManager";
|
||||
import {markRaw} from "vue";
|
||||
|
||||
export const iconSetting=new Icon("setting","setting")
|
||||
iconSetting.addEventListener("dbClick",item => {
|
||||
if(!sessionStorage.getItem("organizationId")) {
|
||||
Message.error("you must choose organization")
|
||||
return;
|
||||
}
|
||||
const win=new Window("setting",ETeamOS_Window_Type.SIMPLE, "setting",true,[
|
||||
{
|
||||
id:v4(),
|
||||
meta:{
|
||||
title:"setting"
|
||||
},
|
||||
components:{
|
||||
setting:markRaw(Setting)
|
||||
},
|
||||
default:{
|
||||
name:"setting"
|
||||
}
|
||||
}
|
||||
],"setting");
|
||||
windowManager.open(win);
|
||||
})
|
30
code/client/src/business/controller/desktop/icon/team.ts
Normal file
30
code/client/src/business/controller/desktop/icon/team.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import {Icon} from "../../../../teamOS/icon/icon";
|
||||
import {v4} from "uuid"
|
||||
import {ETeamOS_Window_Type, Window} from "../../../../teamOS/window/window";
|
||||
import Setting from "../../app/setting/setting.vue";
|
||||
import {Message} from "@arco-design/web-vue";
|
||||
import {windowManager} from "../../../../teamOS/window/windowManager";
|
||||
import {markRaw} from "vue";
|
||||
|
||||
export const iconTeam=new Icon("team","team")
|
||||
iconTeam.addEventListener("dbClick",item => {
|
||||
if(!sessionStorage.getItem("organizationId")) {
|
||||
Message.error("you must choose organization")
|
||||
return;
|
||||
}
|
||||
const win=new Window("team",ETeamOS_Window_Type.TAB, "team",true,[
|
||||
{
|
||||
id:v4(),
|
||||
meta:{
|
||||
title:"team"
|
||||
},
|
||||
components:{
|
||||
setting:markRaw(Setting)
|
||||
},
|
||||
default:{
|
||||
name:"setting"
|
||||
}
|
||||
}
|
||||
],"team");
|
||||
windowManager.open(win);
|
||||
})
|
30
code/client/src/business/controller/desktop/icon/wiki.ts
Normal file
30
code/client/src/business/controller/desktop/icon/wiki.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import {Icon} from "../../../../teamOS/icon/icon";
|
||||
import {v4} from "uuid"
|
||||
import {ETeamOS_Window_Type, Window} from "../../../../teamOS/window/window";
|
||||
import Setting from "../../app/setting/setting.vue";
|
||||
import {Message} from "@arco-design/web-vue";
|
||||
import {windowManager} from "../../../../teamOS/window/windowManager";
|
||||
import {markRaw} from "vue";
|
||||
|
||||
export const iconWiki=new Icon("wiki","file-text")
|
||||
iconWiki.addEventListener("dbClick",item => {
|
||||
if(!sessionStorage.getItem("organizationId")) {
|
||||
Message.error("you must choose organization")
|
||||
return;
|
||||
}
|
||||
const win=new Window("wiki",ETeamOS_Window_Type.TAB, "wiki",true,[
|
||||
{
|
||||
id:v4(),
|
||||
meta:{
|
||||
title:"wiki"
|
||||
},
|
||||
components:{
|
||||
setting:markRaw(Setting)
|
||||
},
|
||||
default:{
|
||||
name:"setting"
|
||||
}
|
||||
}
|
||||
],"wiki");
|
||||
windowManager.open(win);
|
||||
})
|
70
code/client/src/business/controller/desktop/store/desktop.ts
Normal file
70
code/client/src/business/controller/desktop/store/desktop.ts
Normal file
@ -0,0 +1,70 @@
|
||||
import {defineStore} from "pinia";
|
||||
import {ICommon_Model_Organization} from "../../../../../../common/model/organization";
|
||||
import {apiOrganization, apiUser, DCSType} from "../../../common/request/request";
|
||||
import {ICommon_Route_Res_Organization_List} from "../../../../../../common/routes/response";
|
||||
import {Icon} from "../../../../teamOS/icon/icon";
|
||||
import {iconSetting} from "../icon/setting";
|
||||
import {iconCalendar} from "../icon/calendar";
|
||||
import {iconMeeting} from "../icon/meeting";
|
||||
import {iconProject} from "../icon/project";
|
||||
import {iconTeam} from "../icon/team";
|
||||
import {iconWiki} from "../icon/wiki";
|
||||
import {iconIM} from "../icon/im";
|
||||
|
||||
export const useDesktopStore=defineStore("desktop",{
|
||||
state:()=>({
|
||||
organizationInfo:null as DCSType<ICommon_Model_Organization>,
|
||||
organizationList:null as DCSType<ICommon_Route_Res_Organization_List>,
|
||||
appList:[
|
||||
iconSetting,
|
||||
iconCalendar,
|
||||
iconMeeting,
|
||||
iconProject,
|
||||
iconTeam,
|
||||
iconWiki,
|
||||
iconIM
|
||||
] as Icon[]
|
||||
}),
|
||||
actions:{
|
||||
async getOrganizationList() {
|
||||
let ret=await apiOrganization.list()
|
||||
if(ret && ret.code==0) {
|
||||
this.organizationList=ret.data;
|
||||
}
|
||||
},
|
||||
async enterOrganization (organizationId:string){
|
||||
let retEnter=await apiOrganization.enter({
|
||||
organizationId
|
||||
});
|
||||
if(retEnter?.code==0) {
|
||||
let retOrganization=await apiOrganization.info({
|
||||
organizationId
|
||||
})
|
||||
if(retOrganization?.code==0) {
|
||||
this.organizationInfo=retOrganization.data
|
||||
sessionStorage.setItem("organizationId",retOrganization.data.id);
|
||||
}
|
||||
}
|
||||
},
|
||||
async isAuth():Promise<boolean> {
|
||||
if(!sessionStorage.getItem("userToken")) {
|
||||
return false
|
||||
}
|
||||
let ret=await apiUser.refresh();
|
||||
if(!ret || ret.code!=0) {
|
||||
return false
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
},
|
||||
async $update() {
|
||||
await this.getOrganizationList();
|
||||
let retOrganization=await apiOrganization.info({
|
||||
organizationId:sessionStorage.getItem("organizationId")
|
||||
})
|
||||
if(retOrganization?.code==0) {
|
||||
this.organizationInfo=retOrganization.data
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
45
code/client/src/business/controller/login/login.vue
Normal file
45
code/client/src/business/controller/login/login.vue
Normal file
@ -0,0 +1,45 @@
|
||||
<template>
|
||||
<a-row align="center" justify="center" style="height: 100%">
|
||||
<a-form :model="form" style="width: 600px" @submit="onSubmit">
|
||||
<h3 style="text-align: center;margin-bottom: 50px">Teamlinker</h3>
|
||||
<a-form-item field="username" label="username" required>
|
||||
<a-input v-model="form.username" placeholder="please enter username"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item field="password" label="password" required>
|
||||
<a-input v-model="form.password" type="password" placeholder="please enter password"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-button html-type="submit">Submit</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-row>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {reactive} from "vue";
|
||||
import {apiUser} from "../../common/request/request";
|
||||
import {useRouter} from "vue-router";
|
||||
import {Message} from "@arco-design/web-vue";
|
||||
|
||||
let form=reactive({
|
||||
username:"",
|
||||
password:""
|
||||
})
|
||||
let router=useRouter();
|
||||
const onSubmit=async ()=>{
|
||||
let ret=await apiUser.login({
|
||||
username:form.username,
|
||||
password:form.password
|
||||
})
|
||||
if(ret.code==0) {
|
||||
sessionStorage.removeItem("organizationId")
|
||||
await router.push("desktop")
|
||||
} else {
|
||||
Message.error(ret.msg);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
8
code/client/src/icon/Ant.ts
Normal file
8
code/client/src/icon/Ant.ts
Normal file
File diff suppressed because one or more lines are too long
86
code/client/src/icon/sicon.vue
Normal file
86
code/client/src/icon/sicon.vue
Normal file
@ -0,0 +1,86 @@
|
||||
<template>
|
||||
<div ref="iconRef" style="display: inline-flex;" :style="{ fontSize: `${size}px`, color: color as any }">
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { Ant, AntJson, AntName } from './Ant';
|
||||
|
||||
const val = {
|
||||
[AntName]: AntJson,
|
||||
}
|
||||
|
||||
const props = defineProps<{
|
||||
name:keyof typeof val,
|
||||
type:Ant,
|
||||
size:string,
|
||||
color:string
|
||||
}>(
|
||||
// {
|
||||
// name: { default: 'Ant' as keyof typeof val },
|
||||
// type: { default: 'up' as Ant },
|
||||
// size: { default: '20' },
|
||||
// color: { default: undefined as any },
|
||||
// }
|
||||
)
|
||||
|
||||
|
||||
const iconRef = ref<HTMLDivElement>({} as any)
|
||||
const str = {
|
||||
svg: ['<svg style="width: 1em;height: 1em;vertical-align: middle;overflow: hidden;" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg">', '</svg>'],
|
||||
getPath: (param: { d: string, fill?: string }) => {
|
||||
return `<path d="${param.d}" fill="${param.fill ?? 'currentColor'}" />`
|
||||
}
|
||||
}
|
||||
onMounted(() => {
|
||||
//@ts-ignore
|
||||
let obj: any = val[props.name][props.type]
|
||||
if (!obj) {
|
||||
const color = ['color:black;', 'color:red;']
|
||||
console.info(`%c sicon组件传入name<%c${props.name}%c> => type<%c${props.type}%c> 不存在`, `${color[0]} font-size:12px`, color[1], color[0], color[1], color[0]);
|
||||
return
|
||||
}
|
||||
let res = ''
|
||||
if (typeof (obj) === 'string') {
|
||||
res = `${str.svg[0]}<path d="${obj}" fill="currentColor" />${str.svg[1]}`
|
||||
} else {
|
||||
res = str.svg[0]
|
||||
let objt: { d: string[]; fill: { [key: string]: number[] }; } = obj
|
||||
const getFill = (fill: { [key: string]: number[] }, index: number) => {
|
||||
const fillkeys = Object.keys(fill)
|
||||
const colors = props.color
|
||||
|
||||
for (let i = 0; i < fillkeys.length; i++) {
|
||||
const key = fillkeys[i];
|
||||
const nums = fill[key]
|
||||
if (nums.includes(index)) {
|
||||
|
||||
if (colors) {
|
||||
if (Array.isArray(colors))
|
||||
return colors[i] ?? colors[colors.length - 1]
|
||||
return colors
|
||||
} else {
|
||||
return key
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return 'currentColor'
|
||||
}
|
||||
|
||||
objt.d.forEach((item, index) => {
|
||||
res += str.getPath({
|
||||
d: item,
|
||||
fill: getFill(objt.fill, index)
|
||||
})
|
||||
})
|
||||
res += str.svg[1]
|
||||
}
|
||||
|
||||
iconRef.value.innerHTML = res
|
||||
})
|
||||
</script>
|
36
code/client/src/main.ts
Normal file
36
code/client/src/main.ts
Normal file
@ -0,0 +1,36 @@
|
||||
import {createApp} from 'vue'
|
||||
import './style.css'
|
||||
import App from './App.vue'
|
||||
import '@arco-design/web-vue/dist/arco.css';
|
||||
import ArcoVue, {Message} from '@arco-design/web-vue';
|
||||
import sicon from "./icon/sicon.vue"
|
||||
import ArcoVueIcon from '@arco-design/web-vue/es/icon';
|
||||
import {createPinia} from "pinia";
|
||||
import Login from "./business/controller/login/login.vue";
|
||||
import Desktop from "./business/controller/desktop/desktop.vue";
|
||||
import {createRouter, createWebHashHistory} from "vue-router";
|
||||
|
||||
const routes=[
|
||||
{
|
||||
name:"login",
|
||||
path:"/login",
|
||||
component:Login
|
||||
},
|
||||
{
|
||||
name:"desktop",
|
||||
path:"/desktop",
|
||||
component:Desktop
|
||||
}
|
||||
]
|
||||
const router=createRouter({
|
||||
history:createWebHashHistory(),
|
||||
routes
|
||||
});
|
||||
let app=createApp(App)
|
||||
Message._context=app._context
|
||||
app.use(ArcoVue)
|
||||
app.use(ArcoVueIcon)
|
||||
app.use(createPinia())
|
||||
app.use(router);
|
||||
app.component("sicon",sicon)
|
||||
app.mount('#app')
|
11
code/client/src/style.css
Normal file
11
code/client/src/style.css
Normal file
@ -0,0 +1,11 @@
|
||||
html,body {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
|
||||
#app {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
21
code/client/src/teamOS/common/component/contextMenu.vue
Normal file
21
code/client/src/teamOS/common/component/contextMenu.vue
Normal file
@ -0,0 +1,21 @@
|
||||
<template>
|
||||
<a-row class="row" style="font-size:14px;width: 120px;">
|
||||
<a-row class="row" v-for="(item,index) in data" style="height: 35px;line-height: 35px;overflow: hidden;text-overflow: ellipsis;width: 100%;padding-left: 10px;padding-right: 10px" @mouseenter="$event.currentTarget.style.backgroundColor='rgba(39,139,207,0.6)'" @mouseleave="$event.currentTarget.style.backgroundColor=''" @click="select(item)" justify="left" align="center" :style="{borderBottom:index!=data.length-1?'1px solid gray':''}">
|
||||
<sicon name="Ant" :type="item.icon" color="" size=""></sicon> {{item.title}}
|
||||
</a-row>
|
||||
</a-row>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
||||
import {ITeamOS_Menu} from "../type";
|
||||
|
||||
const props=defineProps<{
|
||||
data:ITeamOS_Menu[],
|
||||
}>()
|
||||
const select=(item:ITeamOS_Menu)=>{
|
||||
if(item.func) {
|
||||
item.func(item.value);
|
||||
}
|
||||
}
|
||||
</script>
|
219
code/client/src/teamOS/common/component/navigator/navigator.ts
Normal file
219
code/client/src/teamOS/common/component/navigator/navigator.ts
Normal file
@ -0,0 +1,219 @@
|
||||
import {Component, getCurrentInstance, h, inject, nextTick, reactive, ref, VNode} from "vue";
|
||||
import {NavigatorManager} from "./navigatorManager";
|
||||
import {Base} from "../../util/base";
|
||||
|
||||
export enum ETeamOS_Navigator_Action {
|
||||
PUSH,
|
||||
BACK,
|
||||
GO,
|
||||
REPLACE
|
||||
}
|
||||
|
||||
export function onNavigatorShow(func:(action:ETeamOS_Navigator_Action)=>void) {
|
||||
let navigator=getCurrentNavigator();
|
||||
let instance=getCurrentInstance().vnode
|
||||
navigator.setFunc(instance.type["__name"],func);
|
||||
}
|
||||
|
||||
export function getCurrentNavigator() {
|
||||
return inject("navigator",null) as Navigator
|
||||
}
|
||||
|
||||
export function getCurrentNavigatorManager() {
|
||||
return inject("navigatorManager",null) as NavigatorManager
|
||||
}
|
||||
|
||||
export function getCurrentNavigatorMeta<T>() {
|
||||
return inject("navigatorMeta",null) as {
|
||||
title?:string,
|
||||
data?:T
|
||||
}
|
||||
}
|
||||
|
||||
export class Navigator extends Base{
|
||||
private name:string=""
|
||||
private nodeShowFunc=new Map<string,(action:ETeamOS_Navigator_Action)=>void>()
|
||||
private manager:NavigatorManager
|
||||
private parent:Navigator;
|
||||
private router=reactive<VNode[]>([])
|
||||
private mapComponent:{
|
||||
[name:string]:Component
|
||||
}={}
|
||||
private index=ref(-1)
|
||||
private path=reactive([])
|
||||
constructor(name:string,mapComponent:{
|
||||
[name:string]:Component
|
||||
}) {
|
||||
super();
|
||||
this.name=name;
|
||||
for(let key in mapComponent) {
|
||||
this.mapComponent[key]=mapComponent[key]
|
||||
}
|
||||
}
|
||||
getName() {
|
||||
return this.name;
|
||||
}
|
||||
setFunc(name:string,func) {
|
||||
this.nodeShowFunc.set(name,func);
|
||||
}
|
||||
getIndex() {
|
||||
return this.index;
|
||||
}
|
||||
getId() {
|
||||
return this.id;
|
||||
}
|
||||
push(name:string,props?:object,title?:string) {
|
||||
if(this.mapComponent[name]) {
|
||||
let obj=this.mapComponent[name];
|
||||
if(this.index.value==this.router.length-1) {
|
||||
this.router.push(h(obj, {
|
||||
...props,
|
||||
key:Date.now()
|
||||
}))
|
||||
this.index.value++
|
||||
this.path.push(title??" ")
|
||||
} else {
|
||||
let vNode=h(obj, {
|
||||
...props,
|
||||
key:Date.now()
|
||||
})
|
||||
this.path.splice(this.index.value+1,this.router.length,title??" ");
|
||||
this.router.splice(this.index.value+1,this.router.length,vNode);
|
||||
this.index.value++
|
||||
|
||||
}
|
||||
|
||||
this.manager.setCurrentNavigator(this);
|
||||
(async ()=> {
|
||||
await nextTick();
|
||||
let objNew=this.router[this.index.value]
|
||||
let func=this.nodeShowFunc.get(objNew.type["__name"]);
|
||||
if(func) {
|
||||
func(ETeamOS_Navigator_Action.PUSH)
|
||||
}
|
||||
})()
|
||||
|
||||
} else {
|
||||
if(this.parent) {
|
||||
this.parent.push(name,props)
|
||||
}
|
||||
}
|
||||
}
|
||||
register(name:string,component:Component) {
|
||||
this.mapComponent[name]=component;
|
||||
}
|
||||
back() {
|
||||
if(this.index.value>0) {
|
||||
this.index.value--
|
||||
this.manager.setCurrentNavigator(this);
|
||||
let objNew=this.router[this.index.value]
|
||||
let func=this.nodeShowFunc.get(objNew.type["__name"]);
|
||||
if(func) {
|
||||
func(ETeamOS_Navigator_Action.BACK)
|
||||
}
|
||||
} else {
|
||||
if(this.parent) {
|
||||
this.parent.back();
|
||||
}
|
||||
}
|
||||
// if(this.index.value>0) {
|
||||
// this.router.splice(this.index.value)
|
||||
// this.index.value--
|
||||
// this.manager.setCurrentNavigator(this);
|
||||
// let objNew=this.router[this.index.value]
|
||||
// let func=this.nodeShowFunc.get(objNew.type["__name"]);
|
||||
// if(func) {
|
||||
// func(ETeamOS_Navigator_Action.BACK)
|
||||
// }
|
||||
// } else {
|
||||
// if(this.parent) {
|
||||
// this.parent.back();
|
||||
// }
|
||||
// }
|
||||
}
|
||||
replace(name:string,props?:object,title?:string) {
|
||||
if(this.mapComponent[name]) {
|
||||
let obj=this.mapComponent[name];
|
||||
this.router.splice(this.index.value,1,h(obj, {
|
||||
...props,
|
||||
key:Date.now()
|
||||
}));
|
||||
this.path.splice(this.index.value,1,title??" ")
|
||||
this.manager.setCurrentNavigator(this);
|
||||
let objNew=this.router[this.index.value]
|
||||
let func=this.nodeShowFunc.get(objNew.type["__name"]);
|
||||
if(func) {
|
||||
func(ETeamOS_Navigator_Action.REPLACE)
|
||||
}
|
||||
} else {
|
||||
if(this.parent) {
|
||||
this.parent.replace(name,props)
|
||||
}
|
||||
}
|
||||
}
|
||||
replaceRoot(name:string,props:object,title?:string) {
|
||||
if(this.mapComponent[name]) {
|
||||
let obj=this.mapComponent[name];
|
||||
this.router.splice(0,this.router.length,h(obj,{
|
||||
...props,
|
||||
key:Date.now()
|
||||
}))
|
||||
this.path.splice(0,this.path.length,title??" ");
|
||||
this.index.value=0;
|
||||
this.manager.setCurrentNavigator(this);
|
||||
let objNew=this.router[this.index.value]
|
||||
let func=this.nodeShowFunc.get(objNew.type["__name"]);
|
||||
if(func) {
|
||||
func(ETeamOS_Navigator_Action.REPLACE)
|
||||
}
|
||||
} else {
|
||||
if(this.parent) {
|
||||
this.parent.replaceRoot(name, props);
|
||||
}
|
||||
}
|
||||
}
|
||||
canBack(){
|
||||
if(this.index.value>0) {
|
||||
return true;
|
||||
} else {
|
||||
if(!this.parent) {
|
||||
return false;
|
||||
} else {
|
||||
return this.parent.canBack();
|
||||
}
|
||||
}
|
||||
}
|
||||
canGo() {
|
||||
if(this.index.value<this.router.length-1) {
|
||||
return true
|
||||
}
|
||||
return false;
|
||||
}
|
||||
go() {
|
||||
if(this.index.value<this.router.length-1) {
|
||||
this.index.value++
|
||||
this.manager.setCurrentNavigator(this);
|
||||
let objNew=this.router[this.index.value]
|
||||
let func=this.nodeShowFunc.get(objNew.type["__name"]);
|
||||
if(func) {
|
||||
func(ETeamOS_Navigator_Action.GO)
|
||||
}
|
||||
}
|
||||
}
|
||||
list() {
|
||||
return this.router
|
||||
}
|
||||
getParent () {
|
||||
return this.parent
|
||||
}
|
||||
setParent (parent:Navigator) {
|
||||
this.parent=parent;
|
||||
}
|
||||
setManager(manager:NavigatorManager) {
|
||||
this.manager=manager
|
||||
manager.getNavigators()[this.name]=this;
|
||||
}
|
||||
getPath(){
|
||||
return this.path
|
||||
}
|
||||
}
|
@ -0,0 +1,86 @@
|
||||
<template>
|
||||
<div style="width: 100%;height: 100%" ref="root">
|
||||
<template v-for="(item,index) in list">
|
||||
<component :is="item" v-show="index==currentIndex"></component>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {inject, onMounted, onUnmounted, provide, ref, watchEffect} from "vue";
|
||||
import {Navigator} from "./navigator";
|
||||
import {NavigatorManager} from "./navigatorManager";
|
||||
|
||||
let props=defineProps<{
|
||||
id?:string,
|
||||
name?:string,
|
||||
routes:{
|
||||
[name:string]:any
|
||||
}
|
||||
default?:{
|
||||
name:string,
|
||||
props?:object,
|
||||
title?:string
|
||||
},
|
||||
meta?:{
|
||||
title?:string,
|
||||
data?:any
|
||||
},
|
||||
path?:{
|
||||
[name:string]:{
|
||||
name:string,
|
||||
props?:object
|
||||
}
|
||||
}
|
||||
}>()
|
||||
let root=ref(null);
|
||||
let parent=inject("navigator",null) as Navigator;
|
||||
const objNavigator=new Navigator(props.name,props.routes)
|
||||
objNavigator.setParent(parent);
|
||||
let manager:NavigatorManager;
|
||||
if(!parent) {
|
||||
manager=new NavigatorManager()
|
||||
objNavigator.setManager(manager)
|
||||
if(props.path) {
|
||||
manager.locate(JSON.parse(JSON.stringify(props.path)))
|
||||
}
|
||||
provide("navigatorManager",manager)
|
||||
} else {
|
||||
manager=inject("navigatorManager",null) as NavigatorManager
|
||||
objNavigator.setManager(manager);
|
||||
}
|
||||
const list=objNavigator.list();
|
||||
const currentIndex=objNavigator.getIndex();
|
||||
provide("navigator",objNavigator)
|
||||
if(props.default) {
|
||||
objNavigator.push(props.default.name,props.default.props,props.default.title);
|
||||
}
|
||||
if(props.meta) {
|
||||
provide("navigatorMeta",props.meta)
|
||||
}
|
||||
watchEffect(()=>{
|
||||
let objPath=manager.getObjPath();
|
||||
let obj=objPath[props.name]
|
||||
if(obj) {
|
||||
objNavigator.replaceRoot(obj.name,obj.props);
|
||||
delete objPath[props.name];
|
||||
}
|
||||
})
|
||||
onUnmounted(()=>{
|
||||
delete manager.getNavigators()[objNavigator.getName()]
|
||||
})
|
||||
defineExpose({
|
||||
navigator:objNavigator,
|
||||
navigatorManager:manager,
|
||||
id:props.id
|
||||
})
|
||||
onMounted(()=>{
|
||||
if(!parent) {
|
||||
manager.setRootElement(root.value);
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
@ -0,0 +1,54 @@
|
||||
import {Navigator} from "./navigator";
|
||||
import {reactive, ref} from "vue";
|
||||
import {Base} from "../../util/base";
|
||||
|
||||
export class NavigatorManager extends Base {
|
||||
canGo=ref(false)
|
||||
canBack=ref(false)
|
||||
private currentNavigator:Navigator
|
||||
private navigators:{
|
||||
[name:string]:Navigator
|
||||
}={}
|
||||
private objPath=reactive<{
|
||||
[name:string]:{
|
||||
name:string,
|
||||
props?:object
|
||||
}
|
||||
}>({})
|
||||
private rootElement:HTMLElement
|
||||
setRootElement(ele:HTMLElement) {
|
||||
this.rootElement=ele;
|
||||
}
|
||||
getRootElement() {
|
||||
return this.rootElement;
|
||||
}
|
||||
getObjPath() {
|
||||
return this.objPath;
|
||||
}
|
||||
getCurrentNavigator() {
|
||||
return this.currentNavigator
|
||||
}
|
||||
setCurrentNavigator(navigator:Navigator) {
|
||||
this.currentNavigator=navigator;
|
||||
this.canGo.value=navigator.canGo();
|
||||
this.canBack.value=navigator.canBack()
|
||||
}
|
||||
go() {
|
||||
if(this.currentNavigator) {
|
||||
this.currentNavigator.go()
|
||||
}
|
||||
}
|
||||
back() {
|
||||
if(this.currentNavigator) {
|
||||
this.currentNavigator.back()
|
||||
}
|
||||
}
|
||||
getNavigators(){
|
||||
return this.navigators;
|
||||
}
|
||||
locate(objPath:typeof this.objPath) {
|
||||
for(let key in objPath) {
|
||||
this.objPath[key]=objPath[key];
|
||||
}
|
||||
}
|
||||
}
|
66
code/client/src/teamOS/common/directive/drag.ts
Normal file
66
code/client/src/teamOS/common/directive/drag.ts
Normal file
@ -0,0 +1,66 @@
|
||||
import {ITeamOS_Point} from "../type";
|
||||
|
||||
export const vDrag={
|
||||
mounted(el:HTMLElement, binding, vnode, prevVnode){
|
||||
el.draggable=true;
|
||||
let point:ITeamOS_Point=binding.value;
|
||||
let left:any,top:any,parentWidth:number,parentHeight:number,startLeft:number,startTop:number,parentElement:HTMLElement
|
||||
const onDragStart=(ev: DragEvent)=>{
|
||||
let target=ev.currentTarget as HTMLElement
|
||||
if(binding.arg) {
|
||||
target=document.getElementById(binding.arg)
|
||||
}
|
||||
left=ev.pageX - target.offsetLeft
|
||||
top=ev.pageY - target.offsetTop
|
||||
let ele:HTMLElement;
|
||||
startLeft=(ev.pageX-left)/parentWidth*100
|
||||
startTop=(ev.pageY-top)/parentHeight*100;
|
||||
if(!parentElement) {
|
||||
while(ele=target.parentElement) {
|
||||
if(ele.tagName.toLowerCase()=="body" || ele.style.position=="absolute" || ele.style.position=="relative") {
|
||||
parentHeight=ele.clientHeight;
|
||||
parentWidth=ele.clientWidth;
|
||||
parentElement=ele;
|
||||
break
|
||||
}
|
||||
target=ele;
|
||||
}
|
||||
} else {
|
||||
parentHeight=parentElement.clientHeight;
|
||||
parentWidth=parentElement.clientWidth;
|
||||
}
|
||||
let img = new Image();
|
||||
img.src = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7';
|
||||
img.width=0;
|
||||
img.height=0;
|
||||
img.style.opacity="0"
|
||||
ev.dataTransfer.setDragImage(img, 0, 0);
|
||||
}
|
||||
const onDrag=(ev: any)=>{
|
||||
let calcLeft:number,calcTop:number
|
||||
if(ev.pageX==0) {
|
||||
calcLeft=startLeft
|
||||
} else if(ev.pageX<left) {
|
||||
calcLeft=(ev.pageX-left)/parentWidth*100
|
||||
} else {
|
||||
calcLeft=(ev.pageX-left)/parentWidth*100
|
||||
}
|
||||
if(ev.pageY==0) {
|
||||
calcTop=startTop;
|
||||
} else if(ev.pageY<top) {
|
||||
calcTop=0
|
||||
} else {
|
||||
calcTop=(ev.pageY-top)/parentHeight*100;
|
||||
}
|
||||
point.left = `${calcLeft}%`;
|
||||
point.top=`${calcTop}%`
|
||||
}
|
||||
const onDragOver=(ev: any)=>{
|
||||
ev.stopPropagation()
|
||||
ev.preventDefault()
|
||||
}
|
||||
el.addEventListener("dragstart",onDragStart)
|
||||
el.addEventListener("drag",onDrag)
|
||||
el.addEventListener("dragover",onDragOver)
|
||||
},
|
||||
}
|
49
code/client/src/teamOS/common/directive/menu.ts
Normal file
49
code/client/src/teamOS/common/directive/menu.ts
Normal file
@ -0,0 +1,49 @@
|
||||
import {renderComponent} from "../util/component";
|
||||
import ContextMenu from "../component/contextMenu.vue";
|
||||
import {VNode} from "vue";
|
||||
|
||||
export const vMenu={
|
||||
mounted(el:HTMLElement, binding, vnode:VNode, prevVnode){
|
||||
const onContextMenu=async (event:MouseEvent)=>{
|
||||
let value=binding.value
|
||||
let modifiers=binding.modifiers;
|
||||
if(!value) {
|
||||
return;
|
||||
}
|
||||
let appContext=(<any>vnode).ctx.appContext;
|
||||
if(modifiers.self) {
|
||||
if(event.target!==event.currentTarget) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
if(typeof(value)=="function") {
|
||||
value=await value()
|
||||
}
|
||||
let ele=document.createElement("div")
|
||||
ele.style.position="absolute"
|
||||
ele.style.left=event.pageX+5+"px";
|
||||
ele.style.top=event.pageY+5+"px"
|
||||
ele.style.borderRadius="3px";
|
||||
ele.style.boxShadow="0px 0px 2px 2px rgba(169, 169, 169, 0.2)"
|
||||
ele.style.backgroundColor=`rgb(249,249,249)`
|
||||
ele.style.zIndex="1000"
|
||||
let destroyFunc=renderComponent(ele,ContextMenu,appContext,{
|
||||
data:value
|
||||
});
|
||||
ele.style.color="rgb(93,93,93)"
|
||||
ele.tabIndex=1000;
|
||||
ele.onblur=()=>{
|
||||
destroyFunc()
|
||||
document.body.removeChild(ele);
|
||||
}
|
||||
ele.onclick=()=>{
|
||||
destroyFunc()
|
||||
}
|
||||
document.body.appendChild(ele);
|
||||
ele.focus();
|
||||
}
|
||||
el.addEventListener("contextmenu",onContextMenu)
|
||||
},
|
||||
}
|
23
code/client/src/teamOS/common/directive/navigator.ts
Normal file
23
code/client/src/teamOS/common/directive/navigator.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import {Navigator} from "../component/navigator/navigator";
|
||||
|
||||
export const vNavigator={
|
||||
mounted(el:HTMLElement, binding, vnode, prevVnode){
|
||||
let objRouter=binding.value as {
|
||||
name:string,
|
||||
props?:object
|
||||
};
|
||||
let navigator=binding.arg as Navigator
|
||||
let modifiers=binding.modifiers;
|
||||
el.onclick=(ev:Event)=>{
|
||||
ev.stopPropagation()
|
||||
ev.preventDefault()
|
||||
if(modifiers.replace) {
|
||||
navigator.replace(objRouter.name,objRouter.props)
|
||||
} else if(modifiers.root) {
|
||||
navigator.replaceRoot(objRouter.name,objRouter.props)
|
||||
} else {
|
||||
navigator.push(objRouter.name,objRouter.props)
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
39
code/client/src/teamOS/common/directive/resize.ts
Normal file
39
code/client/src/teamOS/common/directive/resize.ts
Normal file
@ -0,0 +1,39 @@
|
||||
import {ITeamOS_Rect} from "../type";
|
||||
import {getDesktopInstance} from "../../teamOS";
|
||||
|
||||
let g_parentElement:{
|
||||
[id:string]:HTMLElement
|
||||
}={
|
||||
|
||||
}
|
||||
const resizeObserver=new ResizeObserver((entries, observer)=>{
|
||||
for(let entry of entries) {
|
||||
let id=entry.target.id
|
||||
let win=getDesktopInstance().windowManager.getById(id);
|
||||
if(win && g_parentElement[id]) {
|
||||
let ele=g_parentElement[id]
|
||||
win.rect.width= `${entry.contentRect.width/ele.clientWidth*100}%`
|
||||
win.rect.height= `${entry.contentRect.height/ele.clientHeight*100}%`
|
||||
}
|
||||
}
|
||||
})
|
||||
export const vResize={
|
||||
mounted(el:HTMLElement, binding, vnode, prevVnode){
|
||||
el.style.overflow="hidden"
|
||||
el.style.resize="both";
|
||||
let rect:ITeamOS_Rect=binding.value;
|
||||
resizeObserver.observe(el);
|
||||
let ele:HTMLElement
|
||||
while(ele=el.parentElement) {
|
||||
if(ele.tagName.toLowerCase()=="body" || ele.style.position=="absolute" || ele.style.position=="relative") {
|
||||
g_parentElement[el.id]=ele;
|
||||
break
|
||||
}
|
||||
el=ele;
|
||||
}
|
||||
},
|
||||
unmounted(el:HTMLElement) {
|
||||
resizeObserver.unobserve(el);
|
||||
delete g_parentElement[el.id];
|
||||
}
|
||||
}
|
18
code/client/src/teamOS/common/type.ts
Normal file
18
code/client/src/teamOS/common/type.ts
Normal file
@ -0,0 +1,18 @@
|
||||
export interface ITeamOS_Point {
|
||||
left:`${number}%`,
|
||||
top:`${number}%`
|
||||
}
|
||||
|
||||
export interface ITeamOS_Rect {
|
||||
left:`${number}%`,
|
||||
top:`${number}%`,
|
||||
width:`${number}%`,
|
||||
height:`${number}%`,
|
||||
}
|
||||
|
||||
export interface ITeamOS_Menu {
|
||||
icon:string,
|
||||
title:string,
|
||||
value:any,
|
||||
func:(value:any)=>void
|
||||
}
|
5
code/client/src/teamOS/common/util/base.ts
Normal file
5
code/client/src/teamOS/common/util/base.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import {v4} from "uuid";
|
||||
|
||||
export class Base {
|
||||
id=v4()
|
||||
}
|
42
code/client/src/teamOS/common/util/color.ts
Normal file
42
code/client/src/teamOS/common/util/color.ts
Normal file
@ -0,0 +1,42 @@
|
||||
export function getBaseColor(imgSrc:string) {
|
||||
return new Promise((resolve, reject)=>{
|
||||
let img = document.createElement("img");
|
||||
img.crossOrigin = "Anonymous"
|
||||
img.src=imgSrc
|
||||
let canvas = document.createElement('canvas')
|
||||
let context = canvas.getContext("2d");
|
||||
img.onload=function() {
|
||||
let width=canvas.width = img.width || img.offsetWidth || img.clientWidth;
|
||||
let height=canvas.height = img.height || img.offsetHeight || img.clientHeight;
|
||||
context.drawImage(img, 0, 0, canvas.width, canvas.height);
|
||||
let data = context.getImageData(0, 0, img.width, img.height).data;
|
||||
let r = 1,
|
||||
g = 1,
|
||||
b = 1;
|
||||
for (let row = 0; row < img.height; row++) {
|
||||
for (let col = 0; col < img.width; col++) {
|
||||
if (row == 0) {
|
||||
r += data[((img.width * row) + col)];
|
||||
g += data[((img.width * row) + col) + 1];
|
||||
b += data[((img.width * row) + col) + 2];
|
||||
} else {
|
||||
r += data[((img.width * row) + col) * 4];
|
||||
g += data[((img.width * row) + col) * 4 + 1];
|
||||
b += data[((img.width * row) + col) * 4 + 2];
|
||||
}
|
||||
}
|
||||
}
|
||||
r /= (img.width * img.height);
|
||||
g /= (img.width * img.height);
|
||||
b /= (img.width * img.height);
|
||||
|
||||
r = Math.round(r);
|
||||
g = Math.round(g);
|
||||
b = Math.round(b);
|
||||
resolve([r, g, b])
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
|
||||
}
|
11
code/client/src/teamOS/common/util/component.ts
Normal file
11
code/client/src/teamOS/common/util/component.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import {AppContext, Component, h, render} from "vue";
|
||||
|
||||
export function renderComponent(el:HTMLElement,component:Component,context:AppContext,props?:object):()=>void {
|
||||
let vNode=h(component,props)
|
||||
vNode.appContext={...context}
|
||||
render(vNode,el)
|
||||
return ()=>{
|
||||
render(null,el)
|
||||
vNode=null
|
||||
}
|
||||
}
|
73
code/client/src/teamOS/desktop/desktop.ts
Normal file
73
code/client/src/teamOS/desktop/desktop.ts
Normal file
@ -0,0 +1,73 @@
|
||||
import {reactive, ref} from "vue";
|
||||
import {getBaseColor} from "../common/util/color";
|
||||
import {Base} from "../common/util/base";
|
||||
|
||||
export interface ITeamOS_Desktop_Event {
|
||||
"menuClick":(value:string)=>void
|
||||
}
|
||||
|
||||
export interface ITeamOS_Desktop_Menu {
|
||||
group:string,
|
||||
data:{
|
||||
name:string,
|
||||
value:string
|
||||
}[]
|
||||
}
|
||||
class Desktop extends Base{
|
||||
private backgroundImage=ref("")
|
||||
private logo=ref("")
|
||||
private menu=reactive<ITeamOS_Desktop_Menu[]>([])
|
||||
private baseColor:{
|
||||
r:number,
|
||||
g:number,
|
||||
b:number
|
||||
}=reactive({
|
||||
r:0,
|
||||
g:0,
|
||||
b:0
|
||||
});
|
||||
private menuValue=ref("")
|
||||
onMenuClick:(value:string)=>void
|
||||
setLogo(logo:string) {
|
||||
this.logo.value=logo
|
||||
}
|
||||
setMenu(menu:ITeamOS_Desktop_Menu[],defaultValue?:string) {
|
||||
if(this.menu.length>0) {
|
||||
this.menu.splice(0)
|
||||
}
|
||||
this.menu.push(...menu);
|
||||
if(defaultValue) {
|
||||
this.menuValue.value=defaultValue
|
||||
}
|
||||
}
|
||||
getMenuValue() {
|
||||
return this.menuValue
|
||||
}
|
||||
getLogo() {
|
||||
return this.logo
|
||||
}
|
||||
getMenu() {
|
||||
return this.menu
|
||||
}
|
||||
getBackgroundImage() {
|
||||
return this.backgroundImage
|
||||
}
|
||||
setBackgroundImage(url:string) {
|
||||
this.backgroundImage.value=url
|
||||
getBaseColor(url).then(([r,g,b])=>{
|
||||
this.baseColor.r=r;
|
||||
this.baseColor.g=g;
|
||||
this.baseColor.b=b;
|
||||
})
|
||||
}
|
||||
getBaseColor() {
|
||||
return this.baseColor;
|
||||
}
|
||||
addEventListener<T extends keyof ITeamOS_Desktop_Event>(eventType:T,func:ITeamOS_Desktop_Event[T]) {
|
||||
if(eventType=="menuClick") {
|
||||
this.onMenuClick=func
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const desktop=new Desktop();
|
46
code/client/src/teamOS/desktop/desktopBar.vue
Normal file
46
code/client/src/teamOS/desktop/desktopBar.vue
Normal file
@ -0,0 +1,46 @@
|
||||
<template>
|
||||
<a-row>
|
||||
<a-col flex="80px" style="height: 100%">
|
||||
<a-row align="center" justify="center" style="height: 100%">
|
||||
<img :src="logo" style="max-height: 100%;max-width: 100%;height: auto;width: auto"/>
|
||||
</a-row>
|
||||
</a-col>
|
||||
<a-col flex="auto" style="height: 100%;padding-left: 50px">
|
||||
<a-row style="height: 100%" align="center" justify="start" :wrap="false">
|
||||
<a-col v-for="item in list" :key="item.id" flex="auto" style="height: 80%;line-height: 32px;max-width: 200px;text-align: center;box-shadow: 0px 2px 2px rgba(93, 93, 93, 0.2);margin-left: 5px;cursor: pointer;color: rgb(249,249,249);border-radius: 3px;color: rgb(249,249,249)" :style="{backgroundColor:item.isFocus?`rgb(255,255,255,0.3)`:`rgb(255,255,255,0.1)`}" @click="item.status==ETeamOS_Window_Status.MIN?windowManager.show(item.id):windowManager.setFocus(item.id)">
|
||||
{{item.title}}
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-col>
|
||||
<a-col flex="150px" style="height: 100%;" id="teamOS-Desktop-Menu">
|
||||
<a-row align="center" justify="center" style="height: 100%;">
|
||||
<a-select style="width: 100%;" placeholder="organization" :bordered="false" @change="onMenuClick" v-model:model-value="menuValue">
|
||||
<a-optgroup v-for="(item,index) in menu" :label="item.group">
|
||||
<a-option v-for="(item1,index1) in item.data" :label="item1.name" :value="item1.value"></a-option>
|
||||
</a-optgroup>
|
||||
</a-select>
|
||||
</a-row>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {desktop} from "./desktop";
|
||||
import {windowManager} from "../window/windowManager";
|
||||
import {ETeamOS_Window_Status} from "../window/window.js";
|
||||
|
||||
const logo=desktop.getLogo();
|
||||
const menu=desktop.getMenu();
|
||||
const list=windowManager.getList();
|
||||
const color=desktop.getBaseColor();
|
||||
const menuValue=desktop.getMenuValue()
|
||||
const onMenuClick=(value:string)=>{
|
||||
if(desktop.onMenuClick) {
|
||||
desktop.onMenuClick(value)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
28
code/client/src/teamOS/desktop/desktopContainer.vue
Normal file
28
code/client/src/teamOS/desktop/desktopContainer.vue
Normal file
@ -0,0 +1,28 @@
|
||||
<template>
|
||||
<div id="teamOS" :style="{backgroundImage:'url('+backImag+')'}">
|
||||
<DesktopBar style="height: 40px;background: rgba(255,255,255,0.3);">
|
||||
</DesktopBar>
|
||||
<IconContainer style="height: calc(100% - 40px)">
|
||||
<WindowContainer></WindowContainer>
|
||||
</IconContainer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
||||
import DesktopBar from "./desktopBar.vue";
|
||||
import IconContainer from "../icon/iconContainer.vue";
|
||||
import {desktop} from "./desktop";
|
||||
import WindowContainer from "../window/windowContainer.vue";
|
||||
|
||||
let backImag=desktop.getBackgroundImage()
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
#teamOS {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
background-size: 100% 100%;
|
||||
}
|
||||
</style>
|
36
code/client/src/teamOS/icon/icon.ts
Normal file
36
code/client/src/teamOS/icon/icon.ts
Normal file
@ -0,0 +1,36 @@
|
||||
import {ITeamOS_Menu, ITeamOS_Point} from "../common/type";
|
||||
import {Base} from "../common/util/base";
|
||||
|
||||
export interface ITeamOS_Icon_Event {
|
||||
"move":(item:Icon)=>void
|
||||
"dbClick":(item:Icon)=>void
|
||||
"contextmenu":((item:Icon)=>ITeamOS_Menu[]) | ((item:Icon)=>Promise<ITeamOS_Menu[]>)
|
||||
}
|
||||
|
||||
export class Icon extends Base{
|
||||
static height=80;
|
||||
static width=60;
|
||||
name=""
|
||||
icon=""
|
||||
meta:any
|
||||
point:ITeamOS_Point
|
||||
onDBClick:(item:Icon)=>void
|
||||
onMove:(item:Icon)=>void
|
||||
onContextMenu:((item:Icon)=>ITeamOS_Menu[]) | ((item:Icon)=>Promise<ITeamOS_Menu[]>)
|
||||
constructor(name:string,icon:string,point:ITeamOS_Point={left:"0%",top:"0%"},meta?:any) {
|
||||
super()
|
||||
this.name=name;
|
||||
this.icon=icon;
|
||||
this.point=point
|
||||
this.meta=meta
|
||||
}
|
||||
addEventListener<T extends keyof ITeamOS_Icon_Event>(eventType:T,func:ITeamOS_Icon_Event[T]) {
|
||||
if(eventType=="move") {
|
||||
this.onMove=func.bind(null,this);
|
||||
} else if(eventType=="dbClick") {
|
||||
this.onDBClick=func.bind(null,this);
|
||||
} else if(eventType=="contextmenu") {
|
||||
this.onContextMenu=func.bind(null,this) as ITeamOS_Icon_Event["contextmenu"];
|
||||
}
|
||||
}
|
||||
}
|
30
code/client/src/teamOS/icon/iconContainer.vue
Normal file
30
code/client/src/teamOS/icon/iconContainer.vue
Normal file
@ -0,0 +1,30 @@
|
||||
<template>
|
||||
<div id="teamOS-iconWindow" style="position: relative;overflow: hidden" v-menu.self="menu">
|
||||
<IconItem v-for="(item,index) in iconList" :index="index" :item="item" :key="item.id"></IconItem>
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {iconManager} from "./iconManager";
|
||||
import IconItem from "./iconItem.vue";
|
||||
import {vMenu} from "../common/directive/menu";
|
||||
import {ITeamOS_Menu} from "../common/type";
|
||||
|
||||
vMenu;
|
||||
let iconList=iconManager.getList();
|
||||
let menu:ITeamOS_Menu[]=[
|
||||
{
|
||||
icon:"sort-ascending",
|
||||
title:"sort",
|
||||
value:null,
|
||||
func:(value:any)=>{
|
||||
iconManager.sort();
|
||||
}
|
||||
}
|
||||
]
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
35
code/client/src/teamOS/icon/iconItem.vue
Normal file
35
code/client/src/teamOS/icon/iconItem.vue
Normal file
@ -0,0 +1,35 @@
|
||||
<template>
|
||||
<div :style="{width:Icon.width+'px',height:Icon.height+'px',left:point.left,top:point.top,position: 'absolute'}" @focus="onFocus" @blur="onBlur" :tabindex="index" v-drag="point" v-menu="item.onContextMenu" @dblclick="item.onDBClick" @dragend="item.onMove">
|
||||
<a-row :style="{width: Icon.width+'px',height:Icon.width+'px',textAlign: 'center'}" style="justify-content: center;align-items: center">
|
||||
<sicon name="Ant" :type="item.icon" size="40" color=""></sicon>
|
||||
</a-row>
|
||||
<div :style="{width: '100%',height:Icon.height-Icon.width+'px'}" style="overflow: hidden;text-overflow:ellipsis;textAlign:center;vertical-align:middle;white-space: nowrap;user-select: none;color: rgb(249,249,249)">
|
||||
{{item.name}}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {Icon} from "./icon"
|
||||
import {vDrag} from "../common/directive/drag";
|
||||
import {vMenu} from "../common/directive/menu";
|
||||
|
||||
vMenu;
|
||||
vDrag;
|
||||
const props=defineProps<{
|
||||
item:Icon,
|
||||
index:number,
|
||||
}>();
|
||||
let point=props.item.point;
|
||||
const onFocus=(ev)=> {
|
||||
ev.target.style.backgroundColor="rgba(0,0,0,0.1)"
|
||||
}
|
||||
|
||||
const onBlur=(ev)=>{
|
||||
ev.target.style.backgroundColor="rgba(111,111,11,0)"
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
49
code/client/src/teamOS/icon/iconManager.ts
Normal file
49
code/client/src/teamOS/icon/iconManager.ts
Normal file
@ -0,0 +1,49 @@
|
||||
import {Icon} from "./icon";
|
||||
import {reactive} from "vue";
|
||||
import {Base} from "../common/util/base";
|
||||
|
||||
class IconManager extends Base {
|
||||
private iconList=reactive<Icon[]>([])
|
||||
add(item:Icon) {
|
||||
this.iconList.push(item)
|
||||
}
|
||||
remove(name:string) {
|
||||
for(let i=0;i<this.iconList.length;i++) {
|
||||
let obj=this.iconList[i]
|
||||
if(obj.name==name) {
|
||||
this.iconList.splice(i,1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
setList(items:Icon[]) {
|
||||
this.iconList.splice(0,this.iconList.length,...items)
|
||||
}
|
||||
getList() {
|
||||
return this.iconList;
|
||||
}
|
||||
sort() {
|
||||
let ele=document.getElementById("teamOS-iconWindow")
|
||||
let height=ele.clientHeight
|
||||
let width=ele.clientWidth
|
||||
let count=Math.floor((height-40)/(Icon.height+20))
|
||||
this.iconList.sort((a,b)=>{
|
||||
if(a.name[0]>b.name[0]) {
|
||||
return 1
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
})
|
||||
for(let i=0;i<this.iconList.length;i+=count) {
|
||||
for(let j=0;j<count;j++) {
|
||||
let obj=this.iconList[i*count+j]
|
||||
if(obj) {
|
||||
obj.point.left=`${(i+1)*20/width*100}%`;
|
||||
obj.point.top=`${(20+((Icon.height+20)*j))/height*100}%`
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const iconManager=new IconManager();
|
10
code/client/src/teamOS/index.vue
Normal file
10
code/client/src/teamOS/index.vue
Normal file
@ -0,0 +1,10 @@
|
||||
<template>
|
||||
<DesktopContainer></DesktopContainer>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import DesktopContainer from "./desktop/desktopContainer.vue";</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
11
code/client/src/teamOS/teamOS.ts
Normal file
11
code/client/src/teamOS/teamOS.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import {desktop} from "./desktop/desktop";
|
||||
import {iconManager} from "./icon/iconManager";
|
||||
import {windowManager} from "./window/windowManager";
|
||||
|
||||
export function getDesktopInstance() {
|
||||
return {
|
||||
desktop: desktop,
|
||||
iconManager:iconManager,
|
||||
windowManager:windowManager
|
||||
}
|
||||
}
|
93
code/client/src/teamOS/window/window.ts
Normal file
93
code/client/src/teamOS/window/window.ts
Normal file
@ -0,0 +1,93 @@
|
||||
import {ITeamOS_Rect} from "../common/type";
|
||||
import {Component} from "vue";
|
||||
import {Base} from "../common/util/base";
|
||||
import {v4} from "uuid";
|
||||
|
||||
export enum ETeamOS_Window_Type {
|
||||
SIMPLE,
|
||||
TAB
|
||||
}
|
||||
|
||||
export enum ETeamOS_Window_Status {
|
||||
NORMAL,
|
||||
MAX,
|
||||
MIN
|
||||
}
|
||||
|
||||
export interface ITeamOS_Window_Node {
|
||||
id?:string
|
||||
components:{
|
||||
[name:string]:Component
|
||||
}
|
||||
default:{
|
||||
name:string,
|
||||
props?:object
|
||||
},
|
||||
meta?:{
|
||||
title?:string,
|
||||
data?:any
|
||||
},
|
||||
path?:{
|
||||
[name:string]:{
|
||||
name:string,
|
||||
props?:object
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export interface ITeamOS_Window_Event {
|
||||
"open":(item:Window)=>void,
|
||||
"move":(item:Window)=>void,
|
||||
"newTab":(item:Window)=>Promise<ITeamOS_Window_Node>
|
||||
"removeTab":(item:Window)=>void
|
||||
}
|
||||
|
||||
export class Window extends Base{
|
||||
name=""
|
||||
type:ETeamOS_Window_Type
|
||||
title=""
|
||||
isFocus=false
|
||||
rect:ITeamOS_Rect={
|
||||
left:"10%",
|
||||
top:"10%",
|
||||
width:"80%",
|
||||
height:"80%"
|
||||
}
|
||||
group:string
|
||||
nodes:ITeamOS_Window_Node[]
|
||||
status=ETeamOS_Window_Status.NORMAL
|
||||
isControl=false
|
||||
activeKey=""
|
||||
onMove:(item:Window)=>void
|
||||
onOpen:(item:Window)=>void
|
||||
onNewTab:(item:Window)=>Promise<ITeamOS_Window_Node>
|
||||
onRemoveTab:(item:Window)=>void
|
||||
constructor(name:string,type:ETeamOS_Window_Type,group:string,isControl:boolean,nodes:ITeamOS_Window_Node[],title?:string) {
|
||||
super()
|
||||
this.name=name;
|
||||
this.type=type;
|
||||
this.group=group;
|
||||
this.isControl=isControl
|
||||
this.nodes=nodes;
|
||||
for(let obj of this.nodes) {
|
||||
if(!obj.id) {
|
||||
obj.id=v4()
|
||||
}
|
||||
}
|
||||
this.activeKey=this.nodes[0].id
|
||||
if(title) {
|
||||
this.title=title;
|
||||
}
|
||||
}
|
||||
addEventListener<T extends keyof ITeamOS_Window_Event>(eventType:T,func:ITeamOS_Window_Event[T]) {
|
||||
if(eventType=="move") {
|
||||
this.onMove=func.bind(null,this);
|
||||
} else if(eventType=="open") {
|
||||
this.onOpen=func
|
||||
} else if(eventType=="newTab") {
|
||||
this.onNewTab=func as ITeamOS_Window_Event["newTab"]
|
||||
} else if(eventType=="removeTab") {
|
||||
this.onRemoveTab=func.bind(null,this);
|
||||
}
|
||||
}
|
||||
}
|
18
code/client/src/teamOS/window/windowContainer.vue
Normal file
18
code/client/src/teamOS/window/windowContainer.vue
Normal file
@ -0,0 +1,18 @@
|
||||
<template>
|
||||
<WindowItem v-for="(item,index) in windowList" :item="item" :index="index"></WindowItem>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {windowManager} from "./windowManager";
|
||||
|
||||
import WindowItem from "./windowItem.vue";
|
||||
|
||||
|
||||
let windowList=windowManager.getList();
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
177
code/client/src/teamOS/window/windowItem.vue
Normal file
177
code/client/src/teamOS/window/windowItem.vue
Normal file
@ -0,0 +1,177 @@
|
||||
<template>
|
||||
<div :data-id="item.id" :id="item.id" :style="{zIndex:item.isFocus?101:100,left:item.rect.left,top:item.rect.top,width:item.rect.width,height:item.rect.height}" style="box-shadow:0px 0px 2px 2px rgba(169, 169, 169, 0.2);position:absolute;border-radius: 5px" :tabindex="index" @focus="onFocus(item.id)" v-resize="item.rect" v-show="item.status!=ETeamOS_Window_Status.MIN">
|
||||
<a-row style="height: 35px;" :style="{backgroundColor:`rgb(249,249,249)`}" v-drag:[item.id]="item.rect" @dragend="item.onMove">
|
||||
<a-col flex="80px" style="height: 100%">
|
||||
<a-row style="height: 100%" align="center" justify="left" :wrap="false" v-if="item.isControl">
|
||||
<a-col flex="auto">
|
||||
<a-button type="text" @click="back" v-show="action.canBack">
|
||||
<template #icon>
|
||||
<icon-arrow-left style="color: rgb(93,93,93)"></icon-arrow-left>
|
||||
</template>
|
||||
</a-button>
|
||||
</a-col>
|
||||
<a-col flex="auto">
|
||||
<a-button type="text" @click="go" v-show="action.canGo">
|
||||
<template #icon>
|
||||
<icon-arrow-right style="color: rgb(93,93,93)"></icon-arrow-right>
|
||||
</template>
|
||||
</a-button>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-col>
|
||||
<a-col flex="auto" style="height: 100%;text-align: center;line-height: 35px;color: rgb(93,93,93);font-size: larger;cursor: move">
|
||||
{{item.title}}
|
||||
</a-col>
|
||||
<a-col flex="80px" style="height: 100%">
|
||||
<a-row style="height: 100%" align="center" justify="center" :wrap="false">
|
||||
<a-col flex="auto">
|
||||
<a-button type="text" @click="onMin(item.id)">
|
||||
<template #icon>
|
||||
<icon-minus style="color: rgb(93,93,93)"></icon-minus>
|
||||
</template>
|
||||
</a-button>
|
||||
</a-col>
|
||||
<a-col flex="auto">
|
||||
<a-button type="text" @click="item.status==ETeamOS_Window_Status.NORMAL?onMax(item.id):onNormal(item.id)">
|
||||
<template #icon>
|
||||
<icon-expand v-if="item.status==ETeamOS_Window_Status.NORMAL" style="color: rgb(93,93,93)"></icon-expand>
|
||||
<icon-shrink v-else-if="item.status==ETeamOS_Window_Status.MAX" style="color: rgb(93,93,93)"></icon-shrink>
|
||||
</template>
|
||||
</a-button>
|
||||
</a-col>
|
||||
<a-col flex="auto">
|
||||
<a-button type="text" @click="onClose(item.id)">
|
||||
<template #icon>
|
||||
<icon-close style="color: rgb(93,93,93)"></icon-close>
|
||||
</template>
|
||||
</a-button>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row style="height: calc(100% - 35px);background-color: white;position: relative">
|
||||
<NavigatorContainer :routes="item.nodes[0].components" :id="item.nodes[0].id" :path="item.nodes[0].path" :default="item.nodes[0].default" ref="navigator" v-if="item.type==ETeamOS_Window_Type.SIMPLE" name="root"/>
|
||||
<a-tabs type="card-gutter" :editable="true" show-add-button auto-switch :justify="true" v-else-if="item.type==ETeamOS_Window_Type.TAB" @change="change" style="width: 100%" @add="addTab" @delete="removeTab" @tabClick="change" :active-key="item.activeKey">
|
||||
<a-tab-pane v-for="(node,index) in item.nodes" :key="node.id" :title="node.meta?.title" :tabindex="node.id" :closable="item.nodes.length>1">
|
||||
<NavigatorContainer :id="node.id" :routes="node.components" :default="node.default" :meta="node.meta" ref="navigatorList" name="root" :path="node.path"/>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</a-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {windowManager} from "./windowManager";
|
||||
import {onMounted, reactive, ref} from "vue";
|
||||
import NavigatorContainer from "../common/component/navigator/navigatorContainer.vue";
|
||||
import {ETeamOS_Window_Status, ETeamOS_Window_Type, Window} from "./window";
|
||||
import {vDrag} from "../common/directive/drag";
|
||||
import {vResize} from "../common/directive/resize";
|
||||
import {v4} from "uuid"
|
||||
vResize;
|
||||
vDrag;
|
||||
let props=defineProps<{
|
||||
item:Window,
|
||||
index:number
|
||||
}>()
|
||||
|
||||
const onFocus=(id:string)=>{
|
||||
windowManager.setFocus(id);
|
||||
}
|
||||
const onMax=(id:string)=>{
|
||||
windowManager.max(id)
|
||||
}
|
||||
const onNormal=(id:string)=>{
|
||||
windowManager.normal(id)
|
||||
}
|
||||
const onMin=(id:string)=>{
|
||||
windowManager.hide(id)
|
||||
}
|
||||
const onClose=(id:string)=>{
|
||||
windowManager.close(id)
|
||||
}
|
||||
const navigator=ref<InstanceType<typeof NavigatorContainer>>(null)
|
||||
const navigatorList=ref<InstanceType<typeof NavigatorContainer>[]>([])
|
||||
const back=()=>{
|
||||
if(props.item.type==ETeamOS_Window_Type.SIMPLE) {
|
||||
navigator.value.navigatorManager.back()
|
||||
} else {
|
||||
for(let o of navigatorList.value) {
|
||||
if(o.id==props.item.activeKey) {
|
||||
o.navigatorManager.back()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
const go=()=>{
|
||||
if(props.item.type==ETeamOS_Window_Type.SIMPLE) {
|
||||
navigator.value.navigatorManager.go()
|
||||
} else {
|
||||
for(let o of navigatorList.value) {
|
||||
if(o.id==props.item.activeKey) {
|
||||
o.navigatorManager.go()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
const change=(key)=>{
|
||||
props.item.activeKey=key;
|
||||
for(let o of navigatorList.value) {
|
||||
if(o.id==props.item.activeKey) {
|
||||
action.canBack=o.navigatorManager.canBack
|
||||
action.canGo=o.navigatorManager.canGo
|
||||
}
|
||||
}
|
||||
}
|
||||
let action=reactive({
|
||||
canBack:ref(false),
|
||||
canGo:ref(false)
|
||||
})
|
||||
onMounted(()=>{
|
||||
if(props.item.type==ETeamOS_Window_Type.SIMPLE) {
|
||||
action.canBack=navigator.value.navigatorManager.canBack
|
||||
action.canGo=navigator.value.navigatorManager.canGo
|
||||
} else {
|
||||
for(let o of navigatorList.value) {
|
||||
if(o.id==props.item.activeKey) {
|
||||
action.canBack=o.navigatorManager.canBack
|
||||
action.canGo=o.navigatorManager.canGo
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
})
|
||||
const addTab=async ()=>{
|
||||
if(props.item.onNewTab) {
|
||||
let ret=await props.item.onNewTab(props.item)
|
||||
if(ret) {
|
||||
if(!ret.id) {
|
||||
ret.id=v4();
|
||||
}
|
||||
props.item.nodes.push(ret);
|
||||
props.item.activeKey=ret.id;
|
||||
}
|
||||
}
|
||||
}
|
||||
const removeTab=async (id:string)=>{
|
||||
for(let i=0;i<props.item.nodes.length;i++) {
|
||||
let node=props.item.nodes[i]
|
||||
if(node.id==id) {
|
||||
props.item.nodes.splice(i,1);
|
||||
if(id==props.item.activeKey) {
|
||||
if(i>=props.item.nodes.length) {
|
||||
props.item.activeKey=props.item.nodes[props.item.nodes.length-1].id;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(props.item.onRemoveTab) {
|
||||
props.item.onRemoveTab(props.item);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
136
code/client/src/teamOS/window/windowManager.ts
Normal file
136
code/client/src/teamOS/window/windowManager.ts
Normal file
@ -0,0 +1,136 @@
|
||||
import {reactive} from "vue";
|
||||
import {ETeamOS_Window_Status, ETeamOS_Window_Type, Window} from "./window";
|
||||
import {Base} from "../common/util/base";
|
||||
|
||||
export class WindowManager extends Base{
|
||||
private windowList=reactive<Window[]>([])
|
||||
open(window:Window,isMulti:boolean=false) {
|
||||
let arr=this.getWindowsByGroup(window.group)
|
||||
if(!isMulti) {
|
||||
if(arr.length==0) {
|
||||
this.windowList.push(window)
|
||||
this.setFocus(window.id)
|
||||
if(window.onOpen) {
|
||||
window.onOpen(window);
|
||||
}
|
||||
} else {
|
||||
if(window.type==ETeamOS_Window_Type.SIMPLE) {
|
||||
this.setFocus(arr[0].id)
|
||||
} else {
|
||||
let index=0,size=this.windowList[0].nodes.length;
|
||||
for(let i=1;i<this.windowList.length;i++) {
|
||||
if(this.windowList[i].nodes.length<size) {
|
||||
size=this.windowList[i].nodes.length;
|
||||
index=i;
|
||||
}
|
||||
}
|
||||
this.windowList[index].nodes.push(...window.nodes)
|
||||
this.windowList[index].activeKey=window.nodes[0].id;
|
||||
this.setFocus(this.windowList[index].id)
|
||||
if(window.onOpen) {
|
||||
window.onOpen(window);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.windowList.push(window)
|
||||
this.setFocus(window.id)
|
||||
if(window.onOpen) {
|
||||
window.onOpen(window);
|
||||
}
|
||||
}
|
||||
}
|
||||
removeById(id:string) {
|
||||
for(let i=0;i<this.windowList.length;i++) {
|
||||
if(this.windowList[i].id==id) {
|
||||
this.windowList.splice(i,1)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
getById(id:string) {
|
||||
for(let i=0;i<this.windowList.length;i++) {
|
||||
if(this.windowList[i].id==id) {
|
||||
return this.windowList[i];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
getList() {
|
||||
return this.windowList
|
||||
}
|
||||
setFocus(id:string) {
|
||||
let obj:Window;
|
||||
for(let i=0;i<this.windowList.length;i++) {
|
||||
if(this.windowList[i].id==id) {
|
||||
this.windowList[i].isFocus=true
|
||||
obj=this.windowList[i];
|
||||
} else {
|
||||
this.windowList[i].isFocus=false
|
||||
}
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
getFocused() {
|
||||
let obj:Window;
|
||||
for(let i=0;i<this.windowList.length;i++) {
|
||||
if(this.windowList[i].isFocus) {
|
||||
obj=this.windowList[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
max(id:string){
|
||||
let obj=this.setFocus(id);
|
||||
if(obj) {
|
||||
obj.rect.left="0%"
|
||||
obj.rect.top="0%"
|
||||
obj.rect.width="100%"
|
||||
obj.rect.height="100%"
|
||||
obj.status=ETeamOS_Window_Status.MAX
|
||||
}
|
||||
}
|
||||
normal(id:string){
|
||||
let obj=this.setFocus(id);
|
||||
if(obj) {
|
||||
obj.rect.left="10%"
|
||||
obj.rect.top="10%"
|
||||
obj.rect.width="80%"
|
||||
obj.rect.height="80%"
|
||||
obj.status=ETeamOS_Window_Status.NORMAL
|
||||
}
|
||||
}
|
||||
hide(id:string) {
|
||||
let obj=this.getById(id);
|
||||
if(obj) {
|
||||
obj.status=ETeamOS_Window_Status.MIN
|
||||
if(obj.isFocus) {
|
||||
obj.isFocus=false
|
||||
}
|
||||
}
|
||||
}
|
||||
show(id:string) {
|
||||
let obj=this.getById(id);
|
||||
if(obj) {
|
||||
obj.status=ETeamOS_Window_Status.NORMAL
|
||||
obj.rect.width="80%"
|
||||
obj.rect.height="80%"
|
||||
}
|
||||
this.setFocus(id);
|
||||
}
|
||||
close(id:string) {
|
||||
this.removeById(id);
|
||||
}
|
||||
getWindowsByGroup(group:string):Window[] {
|
||||
let arr:Window[]=[];
|
||||
for(let i=0;i<this.windowList.length;i++) {
|
||||
if(this.windowList[i].group===group) {
|
||||
arr.push(this.windowList[i]);
|
||||
}
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
}
|
||||
|
||||
export const windowManager=new WindowManager()
|
41
code/client/src/test.vue
Normal file
41
code/client/src/test.vue
Normal file
@ -0,0 +1,41 @@
|
||||
<template>
|
||||
<div style="color: rebeccapurple">
|
||||
efewfwefew<h1>{{name}}</h1>{{aa}}
|
||||
<button @click="onClick" draggable="true">133</button>
|
||||
<button @click="go" draggable="true">go</button>
|
||||
<a-modal v-model:visible="visible" :render-to-body="false" style="position: absolute">
|
||||
<template #title>
|
||||
Title
|
||||
</template>
|
||||
<div>You can cusstomize modal body text by the current situation. This modal will be closed immediately once you press the OK button.</div>
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {reactive, ref} from "vue"
|
||||
import {getCurrentNavigator, getCurrentNavigatorManager} from "./teamOS/common/component/navigator/navigator";
|
||||
import {useStore} from "./business/common/store/store";
|
||||
import {testStore} from "./testStore";
|
||||
|
||||
defineProps<{
|
||||
name?:string
|
||||
}>()
|
||||
let visible=ref(false)
|
||||
const onClick=()=>{
|
||||
visible.value=true
|
||||
}
|
||||
let obj=getCurrentNavigator()
|
||||
const go=()=>{
|
||||
obj.push("test1")
|
||||
}
|
||||
let aa=reactive<any>({})
|
||||
aa.name=123
|
||||
aa.aaa="fdf"
|
||||
let store=useStore(getCurrentNavigatorManager().id,testStore);
|
||||
console.log(store.name);
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
44
code/client/src/test1.vue
Normal file
44
code/client/src/test1.vue
Normal file
@ -0,0 +1,44 @@
|
||||
<template>
|
||||
<div style="color: rebeccapurple">
|
||||
111111
|
||||
<button @click="onClick" draggable="true">back</button>
|
||||
<button @click="push" draggable="true">push</button>
|
||||
<button @click="go" draggable="true">go</button>
|
||||
<a-modal v-model:visible="visible" :render-to-body="false" style="position: absolute">
|
||||
<template #title>
|
||||
Title
|
||||
</template>
|
||||
<div>You can cusstomize modal body text by the current situation. This modal will be closed immediately once you press the OK button.</div>
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {inject, onMounted, ref} from "vue"
|
||||
import {getCurrentNavigatorMeta, Navigator} from "./teamOS/common/component/navigator/navigator";
|
||||
|
||||
let visible=ref(false)
|
||||
let obj=inject("navigator",null) as Navigator;
|
||||
const onClick=()=>{
|
||||
obj.back();
|
||||
}
|
||||
const go=()=>{
|
||||
obj.go()
|
||||
}
|
||||
const push=()=>{
|
||||
obj.push("test3",{
|
||||
time:Date.now()
|
||||
})
|
||||
}
|
||||
let meta=getCurrentNavigatorMeta<string>()
|
||||
if(meta) {
|
||||
meta.title="eee"
|
||||
}
|
||||
onMounted(()=>{
|
||||
console.log("mounted test1")
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
33
code/client/src/test3.vue
Normal file
33
code/client/src/test3.vue
Normal file
@ -0,0 +1,33 @@
|
||||
<template>
|
||||
<div>zzzzzzz{{time}}
|
||||
<button @click="onClick" draggable="true">home</button>
|
||||
<NavigatorContainer :routes="router" :default="{name:'test4'}" name="nav1"></NavigatorContainer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {inject, markRaw, onMounted} from "vue";
|
||||
import {Navigator} from "./teamOS/common/component/navigator/navigator";
|
||||
import NavigatorContainer from "./teamOS/common/component/navigator/navigatorContainer.vue";
|
||||
import Test4 from "./test4.vue";
|
||||
import Test5 from "./test5.vue";
|
||||
|
||||
defineProps<{
|
||||
time?:number
|
||||
}>()
|
||||
let obj=inject("navigator",null) as Navigator;
|
||||
const onClick=()=>{
|
||||
obj.replace("test")
|
||||
}
|
||||
let router={
|
||||
test4:markRaw(Test4),
|
||||
test5:markRaw(Test5)
|
||||
}
|
||||
onMounted(()=>{
|
||||
console.log("mounted test3")
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
19
code/client/src/test4.vue
Normal file
19
code/client/src/test4.vue
Normal file
@ -0,0 +1,19 @@
|
||||
<template>
|
||||
<div style="background-color: blanchedalmond">7777777 <button v-navigator:[navigator]="{name:'test5',props:{name:'xxxxxxxxx'}}">click</button></div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {inject} from "vue";
|
||||
import {getCurrentNavigator, Navigator, onNavigatorShow} from "./teamOS/common/component/navigator/navigator";
|
||||
import {vNavigator} from "./teamOS/common/directive/navigator";
|
||||
|
||||
vNavigator;
|
||||
let navigator=getCurrentNavigator();
|
||||
onNavigatorShow((action)=>{
|
||||
console.log(action)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
23
code/client/src/test5.vue
Normal file
23
code/client/src/test5.vue
Normal file
@ -0,0 +1,23 @@
|
||||
<template>
|
||||
<div style="background-color: cornflowerblue">55555555{{name}}</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {getCurrentNavigatorManager, getCurrentNavigatorMeta} from "./teamOS/common/component/navigator/navigator";
|
||||
import {useStore} from "./business/common/store/store";
|
||||
import {testStore} from "./testStore";
|
||||
|
||||
defineProps<{
|
||||
name?:string
|
||||
}>()
|
||||
let meta=getCurrentNavigatorMeta<string>()
|
||||
if(meta) {
|
||||
meta.title="11"
|
||||
}
|
||||
let store=useStore(getCurrentNavigatorManager().id,testStore)
|
||||
store.aaa();
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
12
code/client/src/testStore.ts
Normal file
12
code/client/src/testStore.ts
Normal file
@ -0,0 +1,12 @@
|
||||
export const testStore={
|
||||
state:()=>{
|
||||
return {
|
||||
name:"rty"
|
||||
}
|
||||
},
|
||||
actions:{
|
||||
aaa(){
|
||||
console.log("aaa")
|
||||
}
|
||||
}
|
||||
}
|
@ -1,5 +1,7 @@
|
||||
/// <reference types="vite/client" />
|
||||
|
||||
declare module '*.vue' {
|
||||
import { DefineComponent } from 'vue'
|
||||
import type { DefineComponent } from 'vue'
|
||||
const component: DefineComponent<{}, {}, any>
|
||||
export default component
|
||||
}
|
||||
}
|
26
code/client/tsconfig.json
Normal file
26
code/client/tsconfig.json
Normal file
@ -0,0 +1,26 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"experimentalDecorators": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"noImplicitUseStrict":true,
|
||||
"sourceMap": true,
|
||||
"target": "ESNext",
|
||||
"useDefineForClassFields": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Node",
|
||||
"jsx": "preserve",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"esModuleInterop": true,
|
||||
"lib": ["ESNext", "DOM"],
|
||||
"skipLibCheck": true,
|
||||
"noEmit": true,
|
||||
"noImplicitOverride": true,
|
||||
"noImplicitThis": true,
|
||||
"plugins": [
|
||||
{ "transform": "../common/transform/transformer.js" }
|
||||
]
|
||||
},
|
||||
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
|
||||
"references": [{ "path": "./tsconfig.node.json" }]
|
||||
}
|
9
code/client/tsconfig.node.json
Normal file
9
code/client/tsconfig.node.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Node",
|
||||
"allowSyntheticDefaultImports": true
|
||||
},
|
||||
"include": ["vite.config.ts"]
|
||||
}
|
32
code/client/vite.config.ts
Normal file
32
code/client/vite.config.ts
Normal file
@ -0,0 +1,32 @@
|
||||
import {defineConfig} from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
// https://vitejs.dev/config/
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
vue(),
|
||||
// typescript({
|
||||
// // @ts-ignore
|
||||
// typescript:ttypescript
|
||||
// })
|
||||
],
|
||||
server:{
|
||||
port: 3000,
|
||||
hmr:true,
|
||||
open: false, //自动打开
|
||||
base: "./ ", //生产环境路径
|
||||
proxy: { // 本地开发环境通过代理实现跨域,生产环境使用 nginx 转发
|
||||
// 正则表达式写法
|
||||
'^/api': {
|
||||
target: 'http://localhost:14000/api', // 后端服务实际地址
|
||||
changeOrigin: true, //开启代理
|
||||
rewrite: (path) => path.replace(/^\/api/, '')
|
||||
},
|
||||
'^/file': {
|
||||
target: 'http://localhost:14000/file', // 后端服务实际地址
|
||||
changeOrigin: true, //开启代理
|
||||
rewrite: (path) => path.replace(/^\/file/, '')
|
||||
}
|
||||
}
|
||||
},
|
||||
})
|
@ -1,14 +0,0 @@
|
||||
# http://editorconfig.org
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.md]
|
||||
insert_final_newline = false
|
||||
trim_trailing_whitespace = false
|
@ -1,6 +0,0 @@
|
||||
# just a flag
|
||||
ENV = 'development'
|
||||
|
||||
# base api
|
||||
VUE_APP_BASE_API = '/teamlinker'
|
||||
VUE_APP_BASE_DOMAIN = 'http://175.27.166.37:13000'
|
@ -1,7 +0,0 @@
|
||||
# just a flag
|
||||
ENV = 'production'
|
||||
|
||||
# base api
|
||||
VUE_APP_BASE_API = '/'
|
||||
VUE_APP_BASE_DOMAIN = 'http://175.27.166.37:13000'
|
||||
|
@ -1,8 +0,0 @@
|
||||
NODE_ENV = production
|
||||
|
||||
# just a flag
|
||||
ENV = 'staging'
|
||||
|
||||
# base api
|
||||
VUE_APP_BASE_API = '/teamlinker'
|
||||
|
@ -1,4 +0,0 @@
|
||||
build/*.js
|
||||
src/assets
|
||||
public
|
||||
dist
|
@ -1,198 +0,0 @@
|
||||
// module.exports = {
|
||||
// root: true,
|
||||
// parserOptions: {
|
||||
// parser: 'babel-eslint',
|
||||
// sourceType: 'module'
|
||||
// },
|
||||
// env: {
|
||||
// browser: true,
|
||||
// node: true,
|
||||
// es6: true,
|
||||
// },
|
||||
// extends: ['plugin:vue/recommended', 'eslint:recommended'],
|
||||
|
||||
// // add your custom rules here
|
||||
// //it is base on https://github.com/vuejs/eslint-config-vue
|
||||
// rules: {
|
||||
// "vue/max-attributes-per-line": [2, {
|
||||
// "singleline": 10,
|
||||
// "multiline": {
|
||||
// "max": 1,
|
||||
// "allowFirstLine": false
|
||||
// }
|
||||
// }],
|
||||
// "vue/singleline-html-element-content-newline": "off",
|
||||
// "vue/multiline-html-element-content-newline":"off",
|
||||
// "vue/name-property-casing": ["error", "PascalCase"],
|
||||
// "vue/no-v-html": "off",
|
||||
// 'accessor-pairs': 2,
|
||||
// 'arrow-spacing': [2, {
|
||||
// 'before': true,
|
||||
// 'after': true
|
||||
// }],
|
||||
// 'block-spacing': [2, 'always'],
|
||||
// 'brace-style': [2, '1tbs', {
|
||||
// 'allowSingleLine': true
|
||||
// }],
|
||||
// 'camelcase': [0, {
|
||||
// 'properties': 'always'
|
||||
// }],
|
||||
// 'comma-dangle': [2, 'never'],
|
||||
// 'comma-spacing': [2, {
|
||||
// 'before': false,
|
||||
// 'after': true
|
||||
// }],
|
||||
// 'comma-style': [2, 'last'],
|
||||
// 'constructor-super': 2,
|
||||
// 'curly': [2, 'multi-line'],
|
||||
// 'dot-location': [2, 'property'],
|
||||
// 'eol-last': 2,
|
||||
// 'eqeqeq': ["error", "always", {"null": "ignore"}],
|
||||
// 'generator-star-spacing': [2, {
|
||||
// 'before': true,
|
||||
// 'after': true
|
||||
// }],
|
||||
// 'handle-callback-err': [2, '^(err|error)$'],
|
||||
// 'indent': [2, 2, {
|
||||
// 'SwitchCase': 1
|
||||
// }],
|
||||
// 'jsx-quotes': [2, 'prefer-single'],
|
||||
// 'key-spacing': [2, {
|
||||
// 'beforeColon': false,
|
||||
// 'afterColon': true
|
||||
// }],
|
||||
// 'keyword-spacing': [2, {
|
||||
// 'before': true,
|
||||
// 'after': true
|
||||
// }],
|
||||
// 'new-cap': [2, {
|
||||
// 'newIsCap': true,
|
||||
// 'capIsNew': false
|
||||
// }],
|
||||
// 'new-parens': 2,
|
||||
// 'no-array-constructor': 2,
|
||||
// 'no-caller': 2,
|
||||
// 'no-console': 'off',
|
||||
// 'no-class-assign': 2,
|
||||
// 'no-cond-assign': 2,
|
||||
// 'no-const-assign': 2,
|
||||
// 'no-control-regex': 0,
|
||||
// 'no-delete-var': 2,
|
||||
// 'no-dupe-args': 2,
|
||||
// 'no-dupe-class-members': 2,
|
||||
// 'no-dupe-keys': 2,
|
||||
// 'no-duplicate-case': 2,
|
||||
// 'no-empty-character-class': 2,
|
||||
// 'no-empty-pattern': 2,
|
||||
// 'no-eval': 2,
|
||||
// 'no-ex-assign': 2,
|
||||
// 'no-extend-native': 2,
|
||||
// 'no-extra-bind': 2,
|
||||
// 'no-extra-boolean-cast': 2,
|
||||
// 'no-extra-parens': [2, 'functions'],
|
||||
// 'no-fallthrough': 2,
|
||||
// 'no-floating-decimal': 2,
|
||||
// 'no-func-assign': 2,
|
||||
// 'no-implied-eval': 2,
|
||||
// 'no-inner-declarations': [2, 'functions'],
|
||||
// 'no-invalid-regexp': 2,
|
||||
// 'no-irregular-whitespace': 2,
|
||||
// 'no-iterator': 2,
|
||||
// 'no-label-var': 2,
|
||||
// 'no-labels': [2, {
|
||||
// 'allowLoop': false,
|
||||
// 'allowSwitch': false
|
||||
// }],
|
||||
// 'no-lone-blocks': 2,
|
||||
// 'no-mixed-spaces-and-tabs': 2,
|
||||
// 'no-multi-spaces': 2,
|
||||
// 'no-multi-str': 2,
|
||||
// 'no-multiple-empty-lines': [2, {
|
||||
// 'max': 1
|
||||
// }],
|
||||
// 'no-native-reassign': 2,
|
||||
// 'no-negated-in-lhs': 2,
|
||||
// 'no-new-object': 2,
|
||||
// 'no-new-require': 2,
|
||||
// 'no-new-symbol': 2,
|
||||
// 'no-new-wrappers': 2,
|
||||
// 'no-obj-calls': 2,
|
||||
// 'no-octal': 2,
|
||||
// 'no-octal-escape': 2,
|
||||
// 'no-path-concat': 2,
|
||||
// 'no-proto': 2,
|
||||
// 'no-redeclare': 2,
|
||||
// 'no-regex-spaces': 2,
|
||||
// 'no-return-assign': [2, 'except-parens'],
|
||||
// 'no-self-assign': 2,
|
||||
// 'no-self-compare': 2,
|
||||
// 'no-sequences': 2,
|
||||
// 'no-shadow-restricted-names': 2,
|
||||
// 'no-spaced-func': 2,
|
||||
// 'no-sparse-arrays': 2,
|
||||
// 'no-this-before-super': 2,
|
||||
// 'no-throw-literal': 2,
|
||||
// 'no-trailing-spaces': 2,
|
||||
// 'no-undef': 2,
|
||||
// 'no-undef-init': 2,
|
||||
// 'no-unexpected-multiline': 2,
|
||||
// 'no-unmodified-loop-condition': 2,
|
||||
// 'no-unneeded-ternary': [2, {
|
||||
// 'defaultAssignment': false
|
||||
// }],
|
||||
// 'no-unreachable': 2,
|
||||
// 'no-unsafe-finally': 2,
|
||||
// 'no-unused-vars': [2, {
|
||||
// 'vars': 'all',
|
||||
// 'args': 'none'
|
||||
// }],
|
||||
// 'no-useless-call': 2,
|
||||
// 'no-useless-computed-key': 2,
|
||||
// 'no-useless-constructor': 2,
|
||||
// 'no-useless-escape': 0,
|
||||
// 'no-whitespace-before-property': 2,
|
||||
// 'no-with': 2,
|
||||
// 'one-var': [2, {
|
||||
// 'initialized': 'never'
|
||||
// }],
|
||||
// 'operator-linebreak': [2, 'after', {
|
||||
// 'overrides': {
|
||||
// '?': 'before',
|
||||
// ':': 'before'
|
||||
// }
|
||||
// }],
|
||||
// 'padded-blocks': [2, 'never'],
|
||||
// 'quotes': [2, 'single', {
|
||||
// 'avoidEscape': true,
|
||||
// 'allowTemplateLiterals': true
|
||||
// }],
|
||||
// 'semi': [2, 'never'],
|
||||
// 'semi-spacing': [2, {
|
||||
// 'before': false,
|
||||
// 'after': true
|
||||
// }],
|
||||
// 'space-before-blocks': [2, 'always'],
|
||||
// 'space-before-function-paren': [2, 'never'],
|
||||
// 'space-in-parens': [2, 'never'],
|
||||
// 'space-infix-ops': 2,
|
||||
// 'space-unary-ops': [2, {
|
||||
// 'words': true,
|
||||
// 'nonwords': false
|
||||
// }],
|
||||
// 'spaced-comment': [2, 'always', {
|
||||
// 'markers': ['global', 'globals', 'eslint', 'eslint-disable', '*package', '!', ',']
|
||||
// }],
|
||||
// 'template-curly-spacing': [2, 'never'],
|
||||
// 'use-isnan': 2,
|
||||
// 'valid-typeof': 2,
|
||||
// 'wrap-iife': [2, 'any'],
|
||||
// 'yield-star-spacing': [2, 'both'],
|
||||
// 'yoda': [2, 'never'],
|
||||
// 'prefer-const': 2,
|
||||
// 'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0,
|
||||
// 'object-curly-spacing': [2, 'always', {
|
||||
// objectsInObjects: false
|
||||
// }],
|
||||
// 'array-bracket-spacing': [2, 'never']
|
||||
// }
|
||||
// }
|
16
code/client/web_admin/.gitignore
vendored
16
code/client/web_admin/.gitignore
vendored
@ -1,16 +0,0 @@
|
||||
.DS_Store
|
||||
node_modules/
|
||||
dist/
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
package-lock.json
|
||||
tests/**/coverage/
|
||||
|
||||
# Editor directories and files
|
||||
.idea
|
||||
.vscode
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
@ -1,5 +0,0 @@
|
||||
language: node_js
|
||||
node_js: 10
|
||||
script: npm run test
|
||||
notifications:
|
||||
email: false
|
@ -1,21 +0,0 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2017-present PanJiaChen
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
@ -1,102 +0,0 @@
|
||||
# vue-admin-template
|
||||
|
||||
> 这是一个极简的 vue admin 管理后台。它只包含了 Element UI & axios & iconfont & permission control & lint,这些搭建后台必要的东西。
|
||||
|
||||
[线上地址](http://panjiachen.github.io/vue-admin-template)
|
||||
|
||||
[国内访问](https://panjiachen.gitee.io/vue-admin-template)
|
||||
|
||||
目前版本为 `v4.0+` 基于 `vue-cli` 进行构建,若你想使用旧版本,可以切换分支到[tag/3.11.0](https://github.com/PanJiaChen/vue-admin-template/tree/tag/3.11.0),它不依赖 `vue-cli`。
|
||||
|
||||
## Extra
|
||||
|
||||
如果你想要根据用户角色来动态生成侧边栏和 router,你可以使用该分支[permission-control](https://github.com/PanJiaChen/vue-admin-template/tree/permission-control)
|
||||
|
||||
## 相关项目
|
||||
|
||||
- [vue-element-admin](https://github.com/PanJiaChen/vue-element-admin)
|
||||
|
||||
- [electron-vue-admin](https://github.com/PanJiaChen/electron-vue-admin)
|
||||
|
||||
- [vue-typescript-admin-template](https://github.com/Armour/vue-typescript-admin-template)
|
||||
|
||||
- [awesome-project](https://github.com/PanJiaChen/vue-element-admin/issues/2312)
|
||||
|
||||
写了一个系列的教程配套文章,如何从零构建后一个完整的后台项目:
|
||||
|
||||
- [手摸手,带你用 vue 撸后台 系列一(基础篇)](https://juejin.im/post/59097cd7a22b9d0065fb61d2)
|
||||
- [手摸手,带你用 vue 撸后台 系列二(登录权限篇)](https://juejin.im/post/591aa14f570c35006961acac)
|
||||
- [手摸手,带你用 vue 撸后台 系列三 (实战篇)](https://juejin.im/post/593121aa0ce4630057f70d35)
|
||||
- [手摸手,带你用 vue 撸后台 系列四(vueAdmin 一个极简的后台基础模板,专门针对本项目的文章,算作是一篇文档)](https://juejin.im/post/595b4d776fb9a06bbe7dba56)
|
||||
- [手摸手,带你封装一个 vue component](https://segmentfault.com/a/1190000009090836)
|
||||
|
||||
## Build Setup
|
||||
|
||||
```bash
|
||||
# 克隆项目
|
||||
git clone https://github.com/PanJiaChen/vue-admin-template.git
|
||||
|
||||
# 进入项目目录
|
||||
cd vue-admin-template
|
||||
|
||||
# 安装依赖
|
||||
npm install
|
||||
|
||||
# 建议不要直接使用 cnpm 安装以来,会有各种诡异的 bug。可以通过如下操作解决 npm 下载速度慢的问题
|
||||
npm install --registry=https://registry.npm.taobao.org
|
||||
|
||||
# 启动服务
|
||||
npm run dev
|
||||
```
|
||||
|
||||
浏览器访问 [http://localhost:9528](http://localhost:9528)
|
||||
|
||||
## 发布
|
||||
|
||||
```bash
|
||||
# 构建测试环境
|
||||
npm run build:stage
|
||||
|
||||
# 构建生产环境
|
||||
npm run build:prod
|
||||
```
|
||||
|
||||
## 其它
|
||||
|
||||
```bash
|
||||
# 预览发布环境效果
|
||||
npm run preview
|
||||
|
||||
# 预览发布环境效果 + 静态资源分析
|
||||
npm run preview -- --report
|
||||
|
||||
# 代码格式检查
|
||||
npm run lint
|
||||
|
||||
# 代码格式检查并自动修复
|
||||
npm run lint -- --fix
|
||||
```
|
||||
|
||||
更多信息请参考 [使用文档](https://panjiachen.github.io/vue-element-admin-site/zh/)
|
||||
|
||||
## 购买贴纸
|
||||
|
||||
你也可以通过 购买[官方授权的贴纸](https://smallsticker.com/product/vue-element-admin) 的方式来支持 vue-element-admin - 每售出一张贴纸,我们将获得 2 元的捐赠。
|
||||
|
||||
## Demo
|
||||
|
||||

|
||||
|
||||
## Browsers support
|
||||
|
||||
Modern browsers and Internet Explorer 10+.
|
||||
|
||||
| [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt="IE / Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>IE / Edge | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png" alt="Firefox" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Firefox | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png" alt="Chrome" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Chrome | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png" alt="Safari" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Safari |
|
||||
| --------- | --------- | --------- | --------- |
|
||||
| IE10, IE11, Edge| last 2 versions| last 2 versions| last 2 versions
|
||||
|
||||
## License
|
||||
|
||||
[MIT](https://github.com/PanJiaChen/vue-admin-template/blob/master/LICENSE) license.
|
||||
|
||||
Copyright (c) 2017-present PanJiaChen
|
@ -1,90 +0,0 @@
|
||||
# vue-admin-template
|
||||
|
||||
English | [简体中文](./README-zh.md)
|
||||
|
||||
> A minimal vue admin template with Element UI & axios & iconfont & permission control & lint
|
||||
|
||||
**Live demo:** http://panjiachen.github.io/vue-admin-template
|
||||
|
||||
|
||||
**The current version is `v4.0+` build on `vue-cli`. If you want to use the old version , you can switch branch to [tag/3.11.0](https://github.com/PanJiaChen/vue-admin-template/tree/tag/3.11.0), it does not rely on `vue-cli`**
|
||||
|
||||
## Build Setup
|
||||
|
||||
```bash
|
||||
# clone the project
|
||||
git clone https://github.com/PanJiaChen/vue-admin-template.git
|
||||
|
||||
# enter the project directory
|
||||
cd vue-admin-template
|
||||
|
||||
# install dependency
|
||||
npm install
|
||||
|
||||
# develop
|
||||
npm run dev
|
||||
```
|
||||
|
||||
This will automatically open http://localhost:9528
|
||||
|
||||
## Build
|
||||
|
||||
```bash
|
||||
# build for test environment
|
||||
npm run build:stage
|
||||
|
||||
# build for production environment
|
||||
npm run build:prod
|
||||
```
|
||||
|
||||
## Advanced
|
||||
|
||||
```bash
|
||||
# preview the release environment effect
|
||||
npm run preview
|
||||
|
||||
# preview the release environment effect + static resource analysis
|
||||
npm run preview -- --report
|
||||
|
||||
# code format check
|
||||
npm run lint
|
||||
|
||||
# code format check and auto fix
|
||||
npm run lint -- --fix
|
||||
```
|
||||
|
||||
Refer to [Documentation](https://panjiachen.github.io/vue-element-admin-site/guide/essentials/deploy.html) for more information
|
||||
|
||||
## Demo
|
||||
|
||||

|
||||
|
||||
## Extra
|
||||
|
||||
If you want router permission && generate menu by user roles , you can use this branch [permission-control](https://github.com/PanJiaChen/vue-admin-template/tree/permission-control)
|
||||
|
||||
For `typescript` version, you can use [vue-typescript-admin-template](https://github.com/Armour/vue-typescript-admin-template) (Credits: [@Armour](https://github.com/Armour))
|
||||
|
||||
## Related Project
|
||||
|
||||
- [vue-element-admin](https://github.com/PanJiaChen/vue-element-admin)
|
||||
|
||||
- [electron-vue-admin](https://github.com/PanJiaChen/electron-vue-admin)
|
||||
|
||||
- [vue-typescript-admin-template](https://github.com/Armour/vue-typescript-admin-template)
|
||||
|
||||
- [awesome-project](https://github.com/PanJiaChen/vue-element-admin/issues/2312)
|
||||
|
||||
## Browsers support
|
||||
|
||||
Modern browsers and Internet Explorer 10+.
|
||||
|
||||
| [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt="IE / Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>IE / Edge | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png" alt="Firefox" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Firefox | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png" alt="Chrome" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Chrome | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png" alt="Safari" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Safari |
|
||||
| --------- | --------- | --------- | --------- |
|
||||
| IE10, IE11, Edge| last 2 versions| last 2 versions| last 2 versions
|
||||
|
||||
## License
|
||||
|
||||
[MIT](https://github.com/PanJiaChen/vue-admin-template/blob/master/LICENSE) license.
|
||||
|
||||
Copyright (c) 2017-present PanJiaChen
|
@ -1,14 +0,0 @@
|
||||
module.exports = {
|
||||
presets: [
|
||||
// https://github.com/vuejs/vue-cli/tree/master/packages/@vue/babel-preset-app
|
||||
'@vue/cli-plugin-babel/preset'
|
||||
],
|
||||
'env': {
|
||||
'development': {
|
||||
// babel-plugin-dynamic-import-node plugin only does one thing by converting all import() to require().
|
||||
// This plugin can significantly increase the speed of hot updates, when you have a large number of pages.
|
||||
// https://panjiachen.github.io/vue-element-admin-site/guide/advanced/lazy-loading.html
|
||||
'plugins': ['dynamic-import-node']
|
||||
}
|
||||
}
|
||||
}
|
@ -1,35 +0,0 @@
|
||||
const { run } = require('runjs')
|
||||
const chalk = require('chalk')
|
||||
const config = require('../vue.config.js')
|
||||
const rawArgv = process.argv.slice(2)
|
||||
const args = rawArgv.join(' ')
|
||||
|
||||
if (process.env.npm_config_preview || rawArgv.includes('--preview')) {
|
||||
const report = rawArgv.includes('--report')
|
||||
|
||||
run(`vue-cli-service build ${args}`)
|
||||
|
||||
const port = 9526
|
||||
const publicPath = config.publicPath
|
||||
|
||||
var connect = require('connect')
|
||||
var serveStatic = require('serve-static')
|
||||
const app = connect()
|
||||
|
||||
app.use(
|
||||
publicPath,
|
||||
serveStatic('./dist', {
|
||||
index: ['index.html', '/']
|
||||
})
|
||||
)
|
||||
|
||||
app.listen(port, function () {
|
||||
console.log(chalk.green(`> Preview at http://localhost:${port}${publicPath}`))
|
||||
if (report) {
|
||||
console.log(chalk.green(`> Report at http://localhost:${port}${publicPath}report.html`))
|
||||
}
|
||||
|
||||
})
|
||||
} else {
|
||||
run(`vue-cli-service build ${args}`)
|
||||
}
|
@ -1,24 +0,0 @@
|
||||
module.exports = {
|
||||
moduleFileExtensions: ['js', 'jsx', 'json', 'vue'],
|
||||
transform: {
|
||||
'^.+\\.vue$': 'vue-jest',
|
||||
'.+\\.(css|styl|less|sass|scss|svg|png|jpg|ttf|woff|woff2)$':
|
||||
'jest-transform-stub',
|
||||
'^.+\\.jsx?$': 'babel-jest'
|
||||
},
|
||||
moduleNameMapper: {
|
||||
'^@/(.*)$': '<rootDir>/src/$1'
|
||||
},
|
||||
snapshotSerializers: ['jest-serializer-vue'],
|
||||
testMatch: [
|
||||
'**/tests/unit/**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)'
|
||||
],
|
||||
collectCoverageFrom: ['src/utils/**/*.{js,vue}', '!src/utils/auth.js', '!src/utils/request.js', 'src/components/**/*.{js,vue}'],
|
||||
coverageDirectory: '<rootDir>/tests/unit/coverage',
|
||||
// 'collectCoverage': true,
|
||||
'coverageReporters': [
|
||||
'lcov',
|
||||
'text-summary'
|
||||
],
|
||||
testURL: 'http://localhost/'
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"baseUrl": "./",
|
||||
"paths": {
|
||||
"@/*": ["src/*"]
|
||||
}
|
||||
},
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
@ -1,57 +0,0 @@
|
||||
const Mock = require('mockjs')
|
||||
const { param2Obj } = require('./utils')
|
||||
|
||||
const user = require('./user')
|
||||
const table = require('./table')
|
||||
|
||||
const mocks = [
|
||||
...user,
|
||||
...table
|
||||
]
|
||||
|
||||
// for front mock
|
||||
// please use it cautiously, it will redefine XMLHttpRequest,
|
||||
// which will cause many of your third-party libraries to be invalidated(like progress event).
|
||||
function mockXHR() {
|
||||
// mock patch
|
||||
// https://github.com/nuysoft/Mock/issues/300
|
||||
Mock.XHR.prototype.proxy_send = Mock.XHR.prototype.send
|
||||
Mock.XHR.prototype.send = function() {
|
||||
if (this.custom.xhr) {
|
||||
this.custom.xhr.withCredentials = this.withCredentials || false
|
||||
|
||||
if (this.responseType) {
|
||||
this.custom.xhr.responseType = this.responseType
|
||||
}
|
||||
}
|
||||
this.proxy_send(...arguments)
|
||||
}
|
||||
|
||||
function XHR2ExpressReqWrap(respond) {
|
||||
return function(options) {
|
||||
let result = null
|
||||
if (respond instanceof Function) {
|
||||
const { body, type, url } = options
|
||||
// https://expressjs.com/en/4x/api.html#req
|
||||
result = respond({
|
||||
method: type,
|
||||
body: JSON.parse(body),
|
||||
query: param2Obj(url)
|
||||
})
|
||||
} else {
|
||||
result = respond
|
||||
}
|
||||
return Mock.mock(result)
|
||||
}
|
||||
}
|
||||
|
||||
for (const i of mocks) {
|
||||
Mock.mock(new RegExp(i.url), i.type || 'get', XHR2ExpressReqWrap(i.response))
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
mocks,
|
||||
mockXHR
|
||||
}
|
||||
|
@ -1,92 +0,0 @@
|
||||
/*
|
||||
* @Descripttion:
|
||||
* @version:
|
||||
* @Author: gqwu
|
||||
* @Date: 2021-10-09 11:08:42
|
||||
* @LastEditors: gqwu
|
||||
* @LastEditTime: 2021-10-09 11:21:29
|
||||
*/
|
||||
const chokidar = require('chokidar')
|
||||
const bodyParser = require('body-parser')
|
||||
const chalk = require('chalk')
|
||||
const path = require('path')
|
||||
const Mock = require('mockjs')
|
||||
|
||||
const mockDir = path.join(process.cwd(), 'mock')
|
||||
|
||||
function registerRoutes(app) {
|
||||
let mockLastIndex
|
||||
const { mocks } = require('./index.js')
|
||||
const mocksForServer = mocks.map(route => {
|
||||
return responseFake(route.url, route.type, route.response)
|
||||
})
|
||||
for (const mock of mocksForServer) {
|
||||
// app[mock.type](mock.url, mock.response)
|
||||
app[mock.type](mock.url, bodyParser.json(), bodyParser.urlencoded({ // 添加
|
||||
extended: true
|
||||
}), mock.response)
|
||||
mockLastIndex = app._router.stack.length
|
||||
}
|
||||
const mockRoutesLength = Object.keys(mocksForServer).length
|
||||
return {
|
||||
mockRoutesLength: mockRoutesLength,
|
||||
mockStartIndex: mockLastIndex - mockRoutesLength
|
||||
}
|
||||
}
|
||||
|
||||
function unregisterRoutes() {
|
||||
Object.keys(require.cache).forEach(i => {
|
||||
if (i.includes(mockDir)) {
|
||||
delete require.cache[require.resolve(i)]
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// for mock server
|
||||
const responseFake = (url, type, respond) => {
|
||||
return {
|
||||
url: new RegExp(`${process.env.VUE_APP_BASE_API}${url}`),
|
||||
type: type || 'get',
|
||||
response(req, res) {
|
||||
console.log('request invoke:' + req.path)
|
||||
res.json(Mock.mock(respond instanceof Function ? respond(req, res) : respond))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = app => {
|
||||
// parse app.body
|
||||
// https://expressjs.com/en/4x/api.html#req.body
|
||||
// app.use(bodyParser.json())
|
||||
// app.use(bodyParser.urlencoded({
|
||||
// extended: true
|
||||
// }))
|
||||
|
||||
const mockRoutes = registerRoutes(app)
|
||||
var mockRoutesLength = mockRoutes.mockRoutesLength
|
||||
var mockStartIndex = mockRoutes.mockStartIndex
|
||||
|
||||
// watch files, hot reload mock server
|
||||
chokidar.watch(mockDir, {
|
||||
ignored: /mock-server/,
|
||||
ignoreInitial: true
|
||||
}).on('all', (event, path) => {
|
||||
if (event === 'change' || event === 'add') {
|
||||
try {
|
||||
// remove mock routes stack
|
||||
app._router.stack.splice(mockStartIndex, mockRoutesLength)
|
||||
|
||||
// clear routes cache
|
||||
unregisterRoutes()
|
||||
|
||||
const mockRoutes = registerRoutes(app)
|
||||
mockRoutesLength = mockRoutes.mockRoutesLength
|
||||
mockStartIndex = mockRoutes.mockStartIndex
|
||||
|
||||
console.log(chalk.magentaBright(`\n > Mock Server hot reload success! changed ${path}`))
|
||||
} catch (error) {
|
||||
console.log(chalk.redBright(error))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
@ -1,29 +0,0 @@
|
||||
const Mock = require('mockjs')
|
||||
|
||||
const data = Mock.mock({
|
||||
'items|30': [{
|
||||
id: '@id',
|
||||
title: '@sentence(10, 20)',
|
||||
'status|1': ['published', 'draft', 'deleted'],
|
||||
author: 'name',
|
||||
display_time: '@datetime',
|
||||
pageviews: '@integer(300, 5000)'
|
||||
}]
|
||||
})
|
||||
|
||||
module.exports = [
|
||||
{
|
||||
url: '/vue-admin-template/table/list',
|
||||
type: 'get',
|
||||
response: config => {
|
||||
const items = data.items
|
||||
return {
|
||||
code: 20000,
|
||||
data: {
|
||||
total: items.length,
|
||||
items: items
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
@ -1,92 +0,0 @@
|
||||
/*
|
||||
* @Descripttion:
|
||||
* @version:
|
||||
* @Author: gqwu
|
||||
* @Date: 2021-10-09 11:08:42
|
||||
* @LastEditors: gqwu
|
||||
* @LastEditTime: 2021-10-09 11:15:50
|
||||
*/
|
||||
|
||||
const tokens = {
|
||||
admin: {
|
||||
token: 'admin-token'
|
||||
},
|
||||
editor: {
|
||||
token: 'editor-token'
|
||||
}
|
||||
}
|
||||
|
||||
const users = {
|
||||
'admin-token': {
|
||||
roles: ['admin'],
|
||||
introduction: 'I am a super administrator',
|
||||
avatar: 'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif',
|
||||
name: 'Super Admin'
|
||||
},
|
||||
'editor-token': {
|
||||
roles: ['editor'],
|
||||
introduction: 'I am an editor',
|
||||
avatar: 'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif',
|
||||
name: 'Normal Editor'
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = [
|
||||
// user login
|
||||
{
|
||||
url: '/api/user/login',
|
||||
type: 'post',
|
||||
response: config => {
|
||||
const { username } = config.body
|
||||
const token = tokens[username]
|
||||
|
||||
// mock error
|
||||
if (!token) {
|
||||
return {
|
||||
code: 60204,
|
||||
message: 'Account and password are incorrect.'
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
code: 20000,
|
||||
data: token
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// get user info
|
||||
{
|
||||
url: '/vue-admin-template/user/info\.*',
|
||||
type: 'get',
|
||||
response: config => {
|
||||
const { token } = config.query
|
||||
const info = users[token]
|
||||
|
||||
// mock error
|
||||
if (!info) {
|
||||
return {
|
||||
code: 50008,
|
||||
message: 'Login failed, unable to get user details.'
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
code: 20000,
|
||||
data: info
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// user logout
|
||||
{
|
||||
url: '/vue-admin-template/user/logout',
|
||||
type: 'post',
|
||||
response: _ => {
|
||||
return {
|
||||
code: 20000,
|
||||
data: 'success'
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
@ -1,25 +0,0 @@
|
||||
/**
|
||||
* @param {string} url
|
||||
* @returns {Object}
|
||||
*/
|
||||
function param2Obj(url) {
|
||||
const search = decodeURIComponent(url.split('?')[1]).replace(/\+/g, ' ')
|
||||
if (!search) {
|
||||
return {}
|
||||
}
|
||||
const obj = {}
|
||||
const searchArr = search.split('&')
|
||||
searchArr.forEach(v => {
|
||||
const index = v.indexOf('=')
|
||||
if (index !== -1) {
|
||||
const name = v.substring(0, index)
|
||||
const val = v.substring(index + 1, v.length)
|
||||
obj[name] = val
|
||||
}
|
||||
})
|
||||
return obj
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
param2Obj
|
||||
}
|
@ -1,73 +0,0 @@
|
||||
{
|
||||
"name": "vue-admin-template",
|
||||
"version": "4.4.0",
|
||||
"description": "A vue admin template with Element UI & axios & iconfont & permission control & lint",
|
||||
"author": "Pan <panfree23@gmail.com>",
|
||||
"scripts": {
|
||||
"dev": "vue-cli-service serve",
|
||||
"build:prod": "vue-cli-service build",
|
||||
"build:stage": "vue-cli-service build --mode staging",
|
||||
"preview": "node build/index.js --preview",
|
||||
"svgo": "svgo -f src/icons/svg --config=src/icons/svgo.yml",
|
||||
"lint": "eslint --ext .js,.vue src",
|
||||
"test:unit": "jest --clearCache && vue-cli-service test:unit",
|
||||
"test:ci": "npm run lint && npm run test:unit"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "0.18.1",
|
||||
"core-js": "3.21.1",
|
||||
"element-ui": "2.13.2",
|
||||
"js-cookie": "2.2.0",
|
||||
"jsplumb": "^2.15.6",
|
||||
"moment": "^2.29.1",
|
||||
"normalize.css": "7.0.0",
|
||||
"nprogress": "0.2.0",
|
||||
"path-to-regexp": "2.4.0",
|
||||
"qs": "^6.10.3",
|
||||
"vue": "2.6.10",
|
||||
"vue-router": "3.0.6",
|
||||
"vuedraggable": "^2.24.3",
|
||||
"vuex": "3.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vue/cli-plugin-babel": "4.4.4",
|
||||
"@vue/cli-plugin-eslint": "4.4.4",
|
||||
"@vue/cli-plugin-unit-jest": "4.4.4",
|
||||
"@vue/cli-service": "4.4.4",
|
||||
"@vue/test-utils": "1.0.0-beta.29",
|
||||
"autoprefixer": "9.5.1",
|
||||
"babel-eslint": "10.1.0",
|
||||
"babel-jest": "23.6.0",
|
||||
"babel-plugin-dynamic-import-node": "2.3.3",
|
||||
"chalk": "2.4.2",
|
||||
"connect": "3.6.6",
|
||||
"eslint": "6.7.2",
|
||||
"eslint-plugin-vue": "6.2.2",
|
||||
"html-webpack-plugin": "3.2.0",
|
||||
"mockjs": "1.0.1-beta3",
|
||||
"runjs": "4.3.2",
|
||||
"sass": "1.26.8",
|
||||
"sass-loader": "8.0.2",
|
||||
"script-ext-html-webpack-plugin": "2.1.3",
|
||||
"serve-static": "1.13.2",
|
||||
"svg-sprite-loader": "4.1.3",
|
||||
"svgo": "2.8.0",
|
||||
"vue-template-compiler": "2.6.10",
|
||||
"ts-loader": "^4.4.2",
|
||||
"tslint": "^6.1.3",
|
||||
"tslint-config-standard": "^9.0.0",
|
||||
"tslint-loader": "^3.5.4",
|
||||
"typescript": "^3.5.2",
|
||||
"vue-property-decorator": "^9.1.2",
|
||||
"vue-class-component": "^7.2.6"
|
||||
},
|
||||
"browserslist": [
|
||||
"> 1%",
|
||||
"last 2 versions"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=8.9",
|
||||
"npm": ">= 3.0.0"
|
||||
},
|
||||
"license": "MIT"
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
// https://github.com/michael-ciniawsky/postcss-load-config
|
||||
|
||||
module.exports = {
|
||||
'plugins': {
|
||||
// to edit target browsers: use "browserslist" field in package.json
|
||||
'autoprefixer': {}
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user