Compare commits

...

10 Commits

Author SHA1 Message Date
wangxuefeng
99015ecbd2 chore: 更新底层引擎 2025-03-07 21:01:56 +08:00
wangxuefeng
db10bb6a6c feat: 设计器可获取天梯 token 2025-03-07 14:46:22 +08:00
wangxuefeng
8d0c890061 chore: 剔除跟登陆相关的无用逻辑 2025-03-07 10:08:16 +08:00
wangxuefeng
9d25cddcb3 feat: 用户通过天梯登陆 2025-03-07 10:06:58 +08:00
wangxuefeng
0852dd98f0 chore: 完成天梯登陆与登出 2025-03-06 17:58:47 +08:00
wangxuefeng
9b2c728d4c chore: 悦码v1增加环境配置 2025-03-06 10:28:14 +08:00
wangxuefeng
eecc2b1893 chore: 规范化常量配置 2025-03-05 18:30:41 +08:00
wangxuefeng
261dd5bb67 chore: 增加环境配置 2025-03-05 18:03:59 +08:00
wangxuefeng
47c9ec1aba feat: platform 接入天梯登陆的 依赖 2025-03-05 17:55:51 +08:00
wangxuefeng
dd69823a00 feat: 可通过配置入参来初始化编辑器 2025-03-05 14:34:40 +08:00
66 changed files with 1375 additions and 1856 deletions

View File

@ -8,4 +8,10 @@ VITE_BASE_URL = /
VITE_PORT = 10011
# VITE_BASE_API_URL = 'https://custom-chart-pre-api.shiyue.com/'
VITE_BASE_API_URL = 'https://custom-chart-api.shiyuegame.com/'
VITE_DEBUG_MODE = true
VITE_DEBUG_MODE = true
Y_CODE_PLATFORM_URL = 'https://localhost:10010/'
Y_CODE_DESIGNER_URL = 'https://localhost:10011/'
Y_CODE_RENDERER_URL = 'https://localhost:10012/'

View File

@ -5,3 +5,8 @@ VITE_NODE_ENV = 'production'
VITE_BASE_URL = /
VITE_BASE_API_URL = 'https://custom-chart-api.shiyuegame.com/'
Y_CODE_PLATFORM_URL = 'https://y-code-platform.shiyuegame.com/'
Y_CODE_DESIGNER_URL = 'https://y-code-designer.shiyuegame.com/'
Y_CODE_RENDERER_URL = 'https://y-code-renderer.shiyuegame.com/'

View File

@ -5,4 +5,8 @@ VITE_NODE_ENV = 'staging'
VITE_BASE_URL = /
# base api url
VITE_BASE_API_URL = 'https://custom-chart-pre-api.shiyue.com/'
VITE_BASE_API_URL = 'https://custom-chart-pre-api.shiyue.com/'
Y_CODE_PLATFORM_URL = 'https://y-code-platform-pre.shiyue.com/'
Y_CODE_DESIGNER_URL = 'https://y-code-designer-pre.shiyue.com/'
Y_CODE_RENDERER_URL = 'https://y-code-renderer-pre.shiyue.com/'

View File

@ -13,22 +13,23 @@
"clean": "rimraf node_modules"
},
"dependencies": {
"@vtj/core": "^0.10.9",
"@vtj/designer": "0.10.9",
"@vtj/icons": "0.10.9",
"@vtj/local": "^0.10.9",
"@vtj/materials": "^0.10.9",
"@vtj/core": "^0.10.10",
"@vtj/designer": "0.10.10",
"@vtj/icons": "0.10.10",
"@vtj/local": "^0.10.10",
"@vtj/materials": "^0.10.10",
"@vtj/node": "0.10.2",
"@vtj/pro": "^0.10.9",
"@vtj/renderer": "^0.10.9",
"@vtj/ui": "^0.10.9",
"@vtj/utils": "0.10.9",
"@vtj/web": "^0.10.9",
"@vtj/pro": "^0.10.10",
"@vtj/renderer": "^0.10.10",
"@vtj/ui": "^0.10.10",
"@vtj/utils": "0.10.10",
"@vtj/web": "^0.10.10",
"axios": "^1.8.1",
"element-plus": "^2.9.4",
"licia-es": "^1.46.0",
"pinia": "^3.0.1",
"pinia-plugin-persistedstate": "^4.2.0",
"postmate": "^1.5.2",
"unplugin-auto-import": "^19.1.1",
"vue": "~3.5.13",
"vue-router": "~4.5.0"

View File

@ -1,38 +0,0 @@
<script setup lang="ts">
import { ref } from 'vue'
defineProps<{ msg: string }>()
const count = ref(0)
</script>
<template>
<h1>{{ msg }}</h1>
<div class="card">
<button type="button" @click="count++">count is {{ count }}</button>
<p>
Edit
<code>components/HelloWorld.vue</code> to test HMR
</p>
</div>
<p>
Check out
<a href="https://vuejs.org/guide/quick-start.html#local" target="_blank"
>create-vue</a
>, the official Vue + Vite starter
</p>
<p>
Install
<a href="https://github.com/vuejs/language-tools" target="_blank">Volar</a>
in your IDE for a better DX
</p>
<p class="read-the-docs">Click on the Vite and Vue logos to learn more</p>
</template>
<style scoped>
.read-the-docs {
color: #888;
}
</style>

View File

@ -0,0 +1,2 @@
// @ts-ignore
export const currentEnv = __APP_ENV__;

View File

@ -0,0 +1 @@
export * from './env';

View File

@ -1,7 +1,6 @@
import axios from 'axios';
const apiBase = import.meta.env.VITE_BASE_API_URL;
console.log('apiBase', apiBase);
// 创建独立实例
const instance = axios.create({

View File

@ -8,7 +8,7 @@ export const pinia = createPinia();
// 用户模块 store
export const useUserStore = defineStore('user', () => {
// 状态定义
const token = ref<string>(localStorage.getItem('token') || '');
const token = ref<string>(localStorage.getItem('y-code-access-token') || '');
const userProfile = ref<null>(null);
// getter 计算属性
@ -17,20 +17,14 @@ export const useUserStore = defineStore('user', () => {
// 同步 action
const setToken = (newToken: string) => {
token.value = newToken;
localStorage.setItem('token', newToken);
};
// 异步 action
const fetchProfile = async () => {
const { data } = await axios.get('/api/user/profile');
userProfile.value = data;
localStorage.setItem('y-code-access-token', newToken);
};
// 清理方法
const logout = () => {
token.value = '';
userProfile.value = null;
localStorage.removeItem('token');
localStorage.removeItem('y-code-access-token');
};
return {
@ -38,7 +32,6 @@ export const useUserStore = defineStore('user', () => {
userProfile,
isLoggedIn,
setToken,
fetchProfile,
logout
};
});

View File

@ -1,40 +1,71 @@
<template>
<div class="designer-container" ref="container"></div>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import {
Engine,
widgetManager
// type ProjectModel
} from '@vtj/pro';
import Postmate from 'postmate';
import { Engine, widgetManager } from '@vtj/pro';
import { request, jsonp } from '@vtj/utils';
import { useUserStore } from '@/store';
import { LowCodeService } from '@/service';
const container = ref();
const service = new LowCodeService();
const userStore = useUserStore();
const engine = new Engine({
container,
service,
project: {
// @ts-ignore
id: 4,
name: '低代码平台'
}
});
onMounted(async () => {
//
const model = {
name: '',
url: '',
applicationId: -1,
projectId: -1,
accessToken: ''
};
widgetManager.set('Previewer', {
props: {
path: (block: any) => {
const pathname = location.pathname;
return `${pathname}#/preview/${block.id}`;
}
}
const handshake = new Postmate.Model({});
await handshake.then((parent) => {
parent.emit('sync-context', 'y-code-designer is ready');
Object.assign(model, parent.model);
// console.log('get parent model', model);
userStore.setToken(model.accessToken);
request.useRequest((req) => {
req.headers.set('Authorization', `Bearer ${model.accessToken}`);
return req;
});
const engine = new Engine({
container,
service,
project: {
// @ts-ignore
id: model.projectId,
name: model.name
},
adapter: {
request,
jsonp
}
});
widgetManager.set('Previewer', {
props: {
path: (block: any) => {
const pathname = location.pathname;
return `${pathname}#/preview/${block.id}`;
}
}
});
widgetManager.set('Templates', {
invisible: true
});
});
});
</script>
<template>
<div
class="designer-container"
ref="container"
:token="userStore.token"></div>
</template>
<style scoped>
.designer-container {
height: 100%;

View File

@ -1,12 +1,20 @@
<template>
<component v-if="renderer" :is="renderer"></component>
<component v-if="renderer" :is="renderer" v-bind="$attrs"></component>
</template>
<script lang="ts" setup>
import { ref, getCurrentInstance } from 'vue';
import { useRoute } from 'vue-router';
import { createProvider, ContextMode } from '@vtj/pro';
import { LowCodeService } from '@/service';
import { request, jsonp } from '@vtj/utils';
import { useUserStore } from '@/store';
const userStore = useUserStore();
const service = new LowCodeService();
request.useRequest((req) => {
req.headers.set('Authorization', `Bearer ${userStore.token}`);
return req;
});
const { provider, onReady } = createProvider({
mode: ContextMode.Runtime,
service,
@ -14,6 +22,10 @@ const { provider, onReady } = createProvider({
// @ts-ignore
id: 4
},
adapter: {
request,
jsonp
},
dependencies: {
Vue: () => import('vue'),
VueRouter: () => import('vue-router'),

View File

@ -35,7 +35,7 @@ const config = createViteConfig({
export default defineConfig(({ mode }) => {
console.log('mode', mode);
// 加载环境变量(支持 .env.development/.env.production
const env = loadEnv(mode, process.cwd(), ['VITE_', 'VTJ_', 'SY_']);
const env = loadEnv(mode, process.cwd(), ['VITE_', 'VTJ_', 'SY_', 'Y_CODE_']);
return {
...config,
server: {

View File

@ -6,6 +6,13 @@ VITE_BASE_URL = /
# 前端可见变量(必须以 VITE_ 开头)
VITE_PORT = 10010
# VITE_BASE_API_URL = 'https://custom-chart-pre-api.shiyue.com/'
VITE_BASE_API_URL = 'https://custom-chart-api.shiyuegame.com/'
VITE_DEBUG_MODE = true
VITE_BASE_API_URL = 'https://custom-chart-pre-api.shiyue.com/'
# VITE_BASE_API_URL = 'https://custom-chart-api.shiyuegame.com/'
VITE_DEBUG_MODE = true
Y_CODE_PLATFORM_URL = 'https://localhost:10010/'
Y_CODE_DESIGNER_URL = 'https://localhost:10011/'
Y_CODE_RENDERER_URL = 'https://localhost:10012/'
Y_CODE_V1_URL = 'https://localhost:10013/'

View File

@ -5,3 +5,8 @@ VITE_NODE_ENV = 'production'
VITE_BASE_URL = /
VITE_BASE_API_URL = 'https://custom-chart-api.shiyuegame.com/'
Y_CODE_PLATFORM_URL = 'https://y-code-platform.shiyuegame.com/'
Y_CODE_DESIGNER_URL = 'https://y-code-designer.shiyuegame.com/'
Y_CODE_RENDERER_URL = 'https://y-code-renderer.shiyuegame.com/'
Y_CODE_V1_URL = 'https://custom-chart.shiyuegame.com/'

View File

@ -5,4 +5,9 @@ VITE_NODE_ENV = 'staging'
VITE_BASE_URL = /
# base api url
VITE_BASE_API_URL = 'https://custom-chart-pre-api.shiyue.com/'
VITE_BASE_API_URL = 'https://custom-chart-pre-api.shiyue.com/'
Y_CODE_PLATFORM_URL = 'https://y-code-platform-pre.shiyue.com/'
Y_CODE_DESIGNER_URL = 'https://y-code-designer-pre.shiyue.com/'
Y_CODE_RENDERER_URL = 'https://y-code-renderer-pre.shiyue.com/'
Y_CODE_V1_URL = 'https://custom-chart.shiyue.com/'

View File

@ -1,42 +1,3 @@
## 安装使用
# 因为项目接入了天梯登陆,使用前需要申请天梯的悦码权限
- 安装依赖
```bash
pnpm install
```
- 运行
```bash
pnpm dev
```
- 打包
```bash
pnpm build
```
## vscode 配置
安装项目根目录 `.vscode` 推荐的插件,再安装 `Volar`,并禁用 `Vetur`,重启 vscode 即可。
## Git 贡献提交规范
- 参考 [vue](https://github.com/vuejs/vue/blob/dev/.github/COMMIT_CONVENTION.md) 规范 ([Angular](https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-angular))
- `feat` 增加新功能
- `fix` 修复问题/BUG
- `style` 代码风格相关无影响运行结果的
- `perf` 优化/性能提升
- `refactor` 重构
- `revert` 撤销修改
- `test` 测试相关
- `docs` 文档/注释
- `chore` 依赖更新/脚手架配置修改等
- `workflow` 工作流改进
- `ci` 持续集成
- `types` 类型定义文件更改
- `wip` 开发中
天梯地址 https://auth-pro.shiyue.com/

View File

@ -29,9 +29,11 @@
"@ant-design/icons-vue": "~7.0.1",
"@iconify/vue": "^4.3.0",
"@iframe-resizer/parent": "^5.3.3",
"@sy/unified-login": "1.0.29",
"@sy/y-code-renderer-adapter": "workspace:*",
"@tanstack/query-core": "^5.66.4",
"@tanstack/vue-query": "^5.66.9",
"@vue/runtime-core": "^3.5.13",
"@vueuse/core": "~11.1.0",
"ant-design-vue": "~4.2.6",
"axios": "~1.8.1",
@ -51,9 +53,9 @@
"vue": "~3.5.13",
"vue-i18n": "^11.1.1",
"vue-router": "~4.4.5",
"vue-types": "~5.1.3",
"vue-types": "~6.0.0",
"vue-virtual-scroller": "2.0.0-beta.8",
"wujie": "^1.0.25",
"wujie-vue3": "^1.0.25",
"xlsx": "~0.18.5"
},
"devDependencies": {
@ -62,9 +64,9 @@
"@iconify-json/ant-design": "^1.2.5",
"@iconify-json/ep": "^1.2.2",
"@iconify/json": "^2.2.307",
"@sy/y-code-designer": "workspace:*",
"@sy/low-code-shared": "workspace:*",
"@sy/vite-plugin-http2-proxy": "workspace:*",
"@sy/y-code-designer": "workspace:*",
"@types/crypto-js": "^4.2.2",
"@types/lodash-es": "~4.17.12",
"@types/node": "~22.7.9",
@ -74,8 +76,8 @@
"@typescript-eslint/parser": "~8.11.0",
"@umijs/openapi": "^1.13.0",
"@vitejs/plugin-vue": "~5.1.5",
"@vitejs/plugin-vue-jsx": "~4.0.1",
"@vue/tsconfig": "^0.5.1",
"@vitejs/plugin-vue-jsx": "~4.1.1",
"@vue/tsconfig": "^0.7.0",
"commitizen": "~4.3.1",
"conventional-changelog-cli": "~4.1.0",
"cross-env": "~7.0.3",
@ -90,9 +92,9 @@
"lint-staged": "~15.2.11",
"msw": "^2.7.0",
"postcss": "~8.4.49",
"postcss-html": "~1.7.0",
"postcss-html": "~1.8.0",
"postcss-less": "~6.0.0",
"prettier": "~3.3.3",
"prettier": "~3.5.3",
"rimraf": "~6.0.1",
"stylelint": "~16.10.0",
"stylelint-config-property-sort-order-smacss": "^10.0.0",
@ -101,7 +103,7 @@
"stylelint-config-standard": "~36.0.1",
"stylelint-order": "~6.0.4",
"stylelint-prettier": "^5.0.3",
"typescript": "~5.6.3",
"typescript": "~5.8.2",
"unocss": "^65.5.0",
"unplugin-vue-components": "~0.27.5",
"vite": "~6.2.0",
@ -110,8 +112,8 @@
"vite-plugin-mkcert": "^1.17.6",
"vite-plugin-svg-icons": "~2.0.1",
"vite-plugin-vue-inspector": "^5.3.1",
"vue-eslint-parser": "~9.4.3",
"vue-tsc": "~2.1.10"
"vue-eslint-parser": "~10.1.1",
"vue-tsc": "~2.2.8"
},
"keywords": [
"vue",

View File

@ -19,12 +19,17 @@
</template>
<script setup lang="ts">
import { useRoute } from 'vue-router';
import { theme } from 'ant-design-vue';
import { useUserStore } from '@/store/modules/user';
import { LockScreen } from '@/components/basic/lockscreen';
import { useRoute } from 'vue-router';
const route = useRoute();
const userStore = useUserStore();
userStore.login();
const { compactAlgorithm } = theme;
</script>

View File

@ -1,21 +1,24 @@
<template>
<!-- <div id="container" /> -->
<iframe
<!-- <iframe
ref="iframeRef"
width="100%"
style="border: none"
height="100%"
:src="route.meta?.app?.url"
/>
<!-- <component :is="WujieVue" v-bind="route.meta?.app" /> -->
/> -->
<WujieVue width="100%" height="100%" v-bind="route.meta?.app" />
</template>
<script setup>
import { onMounted } from 'vue';
import { setupApp, startApp } from 'wujie';
// import { setupApp, startApp } from 'wujie';
import { useRoute } from 'vue-router';
import WujieVue from 'wujie-vue3';
const route = useRoute();
console.log(route.meta?.app);
// console.log(route.meta?.app);
// onMounted(() => {
// setupApp({
// el: '#container',

View File

@ -2,32 +2,37 @@
import { ref, onMounted } from 'vue';
import { useRoute } from 'vue-router';
import Postmate from 'postmate';
import { Spin, Alert, Button } from 'ant-design-vue';
import { useUserStore } from '@/store/modules/user';
// import { Spin, Alert, Button } from 'ant-design-vue';
const userStore = useUserStore();
console.log('userStore', userStore);
const route = useRoute();
const MAX_RETRIES = 3;
// const MAX_RETRIES = 3;
const loading = ref(true);
const errorMessage = ref('');
const retryCount = ref(0);
// const retryCount = ref(0);
const initPostmate = async () => {
loading.value = true;
errorMessage.value = '';
const container = document.getElementById('low-code-adapter');
console.log('container', container);
if (!container) {
errorMessage.value = '容器元素未找到';
loading.value = false;
return;
}
const handle = new Postmate({
const connection = new Postmate({
container,
url: route.meta?.app?.url,
name: 'y-code-renderer',
classListArray: ['responsive-iframe'],
model: {
accessToken: userStore.token,
name: route.meta?.app?.name,
applicationId: route.meta?.app?.applicationId,
projectId: route.meta?.app?.projectId,
@ -36,23 +41,27 @@
},
});
handle
.then((instance) => {
console.log('Postmate连接成功', instance);
retryCount.value = 0; //
})
.catch((err) => {
retryCount.value++;
errorMessage.value = `连接失败: ${err.message}`;
if (retryCount.value < MAX_RETRIES) {
initPostmate(); //
} else {
errorMessage.value = '已达到最大重试次数,请检查网络连接';
}
})
.finally(() => {
loading.value = false;
connection.then((child) => {
console.log('Postmate 连接成功', child);
child.on('some-event', (data) => console.log(data)); // Logs "Hello, World!"
child.call('sayHi', {
name: route.meta?.app?.name,
});
// retryCount.value = 0; //
});
// .catch((err) => {
// retryCount.value++;
// errorMessage.value = `: ${err.message}`;
// if (retryCount.value < MAX_RETRIES) {
// initPostmate(); //
// } else {
// errorMessage.value = '';
// }
// })
// .finally(() => {
// loading.value = false;
// });
};
onMounted(() => {
@ -61,18 +70,7 @@
</script>
<template>
<div class="iframe-container">
<div id="low-code-adapter" />
<!-- <div v-if="loading" class="loading-overlay">
<Spin :tip="`正在连接应用(${retryCount + 1}/${MAX_RETRIES}`" />
</div> -->
<!-- <div v-if="errorMessage" class="error-overlay">
<Alert type="error" :message="errorMessage" show-icon />
<Button v-if="retryCount < MAX_RETRIES" @click="initPostmate">重新连接</Button>
</div> -->
</div>
<div id="low-code-adapter" />
</template>
<style>
@ -85,39 +83,8 @@
</style>
<style lang="scss" scoped>
.iframe-container {
position: relative;
height: 100%;
}
#low-code-adapter {
width: 100%;
height: 100%;
}
.loading-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(255, 255, 255, 0.8);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
.error-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(255, 255, 255, 0.8);
display: flex;
align-items: center;
justify-content: center;
z-index: 1001;
height: 99%;
}
</style>

View File

@ -2,6 +2,16 @@ export const devMode = 'development';
export const prodMode = 'production';
export const isDevMode = import.meta.env.DEV;
export const stagingMode = 'staging';
export const isProdMode = import.meta.env.PROD;
// @ts-ignore
export const currentEnv = __APP_ENV__;
export const isDevMode = import.meta.env.MODE === devMode;
export const isProdMode = import.meta.env.MODE === prodMode;
export const isStagingMode = import.meta.env.MODE === stagingMode;
export const Y_CODE_RENDERER_URL = currentEnv.Y_CODE_RENDERER_URL;
export const Y_CODE_DESIGNER_URL = currentEnv.Y_CODE_DESIGNER_URL;
export const Y_CODE_PLATFORM_URL = currentEnv.Y_CODE_PLATFORM_URL;
export const Y_CODE_V1_URL = currentEnv.Y_CODE_V1_URL;

View File

@ -1 +1,3 @@
export * from '@sy/low-code-shared/constants';
// export * from '@sy/low-code-shared/constants';
export * from './env';
export * from './low-code';

View File

@ -0,0 +1,2 @@
export const LOW_CODE_APPLICATION_ID = 0;
export const LOW_CODE_PROJECT_ID = 4;

View File

@ -0,0 +1,16 @@
import instance from './instance';
export const uploadFile = async (data: FormData) => {
const response = await instance.post('/api/v1/files/upload', data);
return response.data;
};
export const deleteFile = async (id: string) => {
const response = await instance.delete(`/api/v1/files/upload/${id}`);
return response.data;
};
export const getFileList = async () => {
const response = await instance.get('/api/v1/files/upload');
return response.data;
};

View File

@ -1,41 +1,41 @@
import axios from 'axios';
// import { useUserStore } from '@/store/modules/user';
// import router from '@/router';
import appClient from '@/io/tianti';
import { useUserStore } from '@/store/modules/user';
import router from '@/router';
// axios拦截器
const { reqInterceptor, resInterceptor } = appClient.getInterceptor();
const baseApiUrl = import.meta.env.VITE_BASE_API_URL;
// 创建独立实例
const instance = axios.create({
baseURL: baseApiUrl, // 基础URL直接放在实例配置中
baseURL: baseApiUrl,
});
// 请求拦截器改为使用实例
instance.interceptors.request.use(
(config) => {
// 可在此处添加统一请求头等配置
return config;
},
(error) => {
return Promise.reject(error);
},
(config) => config,
(error) => Promise.reject(error),
);
instance.interceptors.request.use(reqInterceptor.fulfilled, reqInterceptor.rejected);
// 响应拦截器改为使用实例
// 响应拦截器
instance.interceptors.response.use(
(response) => {
// 如果响应数据中 code 为 -1清空登录状态并跳转到登录页
if (response?.data?.code === -1) {
// 检查是否为401未授权错误
if (response.data.code === 401) {
const userStore = useUserStore();
userStore.clearLoginStatus(); // 清空用户信息
router.push('/login'); // 直接使用路由实例
console.error('请求失败:', response.data.msg);
console.log('用户未授权或登录已过期,即将跳转...');
// userStore.logout();
// 返回一个永远不会resolve的Promise防止后续代码执行
return new Promise(() => {});
}
return response;
},
(error) => {
return Promise.reject(error);
},
(error) => Promise.reject(error),
);
instance.interceptors.response.use(resInterceptor.fulfilled, resInterceptor.rejected);
// 导出实例
export default instance;

View File

@ -0,0 +1,12 @@
// @ts-ignore
import AxiosAppClient from '@sy/unified-login/es/app-client/axios';
export type Env = 'dev' | 'test' | 'pre' | 'prod';
const appClient = new AxiosAppClient({
appKey: 'y-code', // 应用标识,不同项目不能一样
env: 'prod', // dev本地开发test测试pre预发布prod正式
devBaseUrl: `https://localhost:${import.meta.env.VITE_PORT}`, // 应用开发环境地址
});
export default appClient;

View File

@ -9,3 +9,102 @@ export const logout = async () => {
const response = await instance.post('/logout');
return response.data;
};
/**
*
*/
export interface UserInfo {
/** 用户ID */
UserID: number;
/** 用户名 */
Username: string;
/** 头像URL */
Avatar: string;
/** 用户别名/真实姓名 */
Alias: string;
/** 创建时间 */
CreatedAt: string;
/** 更新时间 */
UpdatedAt: string;
}
/**
*
*
* @returns {Promise<{code: number, data: UserInfo, message: string}>}
*
* @example
* ```typescript
* const userInfo = await getCurrentUser();
* console.log(userInfo.data.Username); // 输出当前用户名
* ```
*/
export const getCurrentUser = async () => {
const response = await instance.get('/api/v1/users/current');
return response.data;
};
/**
*
*
* @param {number|string} id ID
* @returns {Promise<{code: number, data: UserInfo, message: string}>}
*
* @example
* ```typescript
* // 获取ID为2594的用户信息
* const userInfo = await getUserById(2594);
* console.log(userInfo.data.Username); // 输出用户名
* ```
*/
export const getUserById = async (id: number | string) => {
const response = await instance.get(`/api/v1/users/${id}`);
return response.data;
};
/**
*
*/
export interface UserListItem {
/** 用户ID */
user_id: number;
/** 用户名 */
username: string;
/** 头像URL */
avatar: string;
/** 用户别名/真实姓名 */
alias: string;
/** 创建时间 */
created_at: string;
/** 更新时间 */
updated_at: string;
}
/**
*
*/
export interface UserListParams {
/** 页码默认1 */
page?: number | string;
/** 每页数量默认20 */
per_page?: number | string;
}
/**
*
*
* @param {UserListParams} params
* @returns {Promise<{code: number, data: {list: UserListItem[], total: number}, message: string}>}
*
* @example
* ```typescript
* // 获取第1页每页20条记录
* const result = await getUserList({ page: 1, per_page: 20 });
* console.log(`总用户数: ${result.data.total}`);
* console.log(`第一个用户: ${result.data.list[0].username}`);
* ```
*/
export const getUserList = async (params: UserListParams = {}) => {
const response = await instance.get('/api/v1/users', { params });
return response.data;
};

View File

@ -33,10 +33,15 @@
</Menu.Item> -->
<Menu.Divider />
<Menu.Item>
<div @click.prevent="doLogout">
<div @click.prevent="tianti.logout">
<poweroff-outlined /> {{ $t('layout.header.dropdownItemLoginOut') }}
</div>
</Menu.Item>
<Menu.Item v-if="isDevEnv">
<div @click.prevent="tianti.openDev">
{{ $t('layout.header.openDev') }}
</div>
</Menu.Item>
</Menu>
</template>
</Dropdown>
@ -49,6 +54,8 @@
<script lang="tsx" setup>
import { computed, type CSSProperties } from 'vue';
import { useRouter, useRoute } from 'vue-router';
import tianti from '@/io/tianti';
import {
QuestionCircleOutlined,
MenuFoldOutlined,
@ -71,6 +78,8 @@
import { LOGIN_NAME } from '@/router/constant';
import { useLayoutSettingStore } from '@/store/modules/layoutSetting';
const isDevEnv = import.meta.env.VITE_NODE_ENV === 'development';
defineProps({
collapsed: {
type: Boolean,

View File

@ -1,20 +1,20 @@
{
"footer": { "onlinePreview": "在线预览", "onlineDocument": "在线文档" },
"footer": {
"onlinePreview": "在线预览",
"onlineDocument": "在线文档"
},
"header": {
"dropdownItemDoc": "文档",
"dropdownItemLoginOut": "退出系统",
"openDev": "打开开发环境",
"tooltipErrorLog": "错误日志",
"tooltipLock": "锁定屏幕",
"tooltipNotify": "消息通知",
"tooltipEntryFull": "全屏",
"tooltipExitFull": "退出全屏",
"lockScreenPassword": "锁屏密码",
"lockScreen": "锁定屏幕",
"lockScreenBtn": "锁定",
"home": "首页"
},
"multipleTab": {
@ -38,20 +38,15 @@
"menuTypeMixSidebar": "左侧菜单混合模式",
"menuTypeMix": "顶部菜单混合模式",
"menuTypeTopMenu": "顶部菜单模式",
"on": "开",
"off": "关",
"minute": "分钟",
"operatingTitle": "操作成功",
"operatingContent": "复制成功,请到 src/settings/projectSetting.ts 中修改配置!",
"resetSuccess": "重置成功!",
"copyBtn": "拷贝",
"clearBtn": "清空缓存并返回登录页",
"drawerTitle": "项目配置",
"darkMode": "主题",
"navMode": "导航栏模式",
"interfaceFunction": "界面功能",
@ -59,11 +54,9 @@
"animation": "动画",
"splitMenu": "分割菜单",
"closeMixSidebarOnChange": "切换页面关闭菜单",
"sysTheme": "系统主题",
"headerTheme": "顶栏主题",
"sidebarTheme": "菜单主题",
"menuDrag": "侧边菜单拖拽",
"menuSearch": "菜单搜索",
"menuAccordion": "侧边菜单手风琴模式",
@ -73,7 +66,6 @@
"menuCollapseButton": "菜单折叠按钮",
"contentMode": "内容区域宽度",
"expandedMenuWidth": "菜单展开宽度",
"breadcrumb": "面包屑",
"breadcrumbIcon": "面包屑图标",
"tabs": "标签页",
@ -87,22 +79,17 @@
"fullContent": "全屏内容",
"grayMode": "灰色模式",
"colorWeak": "色弱模式",
"progress": "顶部进度条",
"switchLoading": "切换loading",
"switchAnimation": "切换动画",
"animationType": "动画类型",
"autoScreenLock": "自动锁屏",
"notAutoScreenLock": "不自动锁屏",
"fixedHeader": "固定header",
"fixedSideBar": "固定Sidebar",
"mixSidebarTrigger": "混合菜单触发方式",
"triggerHover": "悬停",
"triggerClick": "点击",
"mixSidebarFixed": "固定展开菜单"
}
}
}

View File

@ -20,6 +20,7 @@ dayjs.extend(timezone);
const app = createApp(App);
app.use(VueQueryPlugin);
function setupPlugins() {
// 安装图标
setupIcons();

View File

@ -0,0 +1,41 @@
import type { RouteRecordRaw } from 'vue-router';
import { Y_CODE_RENDERER_URL } from '@/constants';
import { LOW_CODE_APPLICATION_ID, LOW_CODE_PROJECT_ID } from '@/constants/low-code';
const moduleName = 'acl';
const routes: Array<RouteRecordRaw> = [
{
path: '/acl',
name: moduleName,
redirect: '/acl/list',
meta: {
title: '权限管理',
icon: 'ant-design:user-outlined',
},
children: [
{
path: 'list',
name: `${moduleName}-list`,
meta: {
title: '权限列表',
keepAlive: true,
icon: 'ant-design:list',
app: {
url: Y_CODE_RENDERER_URL,
name: 'y-code-platform-project-list',
// sync: true,
// alive: true,
// degrade: true,
applicationId: LOW_CODE_APPLICATION_ID,
projectId: LOW_CODE_PROJECT_ID,
fileId: '1hsd0407hf',
},
},
component: () => import('@/components/renderer-adapter/index.vue'),
},
],
},
];
export default routes;

View File

@ -1,4 +1,5 @@
import type { RouteRecordRaw } from 'vue-router';
import { Y_CODE_RENDERER_URL } from '@/constants';
// 微前端路由
const moduleName = 'application';
@ -20,11 +21,11 @@ const routes: Array<RouteRecordRaw> = [
keepAlive: true,
icon: 'ant-design:list',
app: {
url: 'https://localhost:10010',
url: Y_CODE_RENDERER_URL,
name: 'y-code-platform-application-list',
sync: true,
alive: true,
degrade: true,
// sync: true,
// alive: true,
// degrade: true,
applicationId: 0,
projectId: 4,
fileId: 'b91n1y9yr',

View File

@ -4,4 +4,5 @@ import micro from './micro';
import application from './application';
import project from './project';
import staticFile from './static-file';
export default [...dashboard, ...user, ...micro, ...application, ...project, ...staticFile];
import acl from './acl';
export default [...dashboard, ...user, ...micro, ...application, ...project, ...staticFile, ...acl];

View File

@ -1,6 +1,6 @@
import type { RouteRecordRaw } from 'vue-router';
import { Y_CODE_DESIGNER_URL, Y_CODE_V1_URL } from '@/constants';
// 微前端路由
const moduleName = 'micro';
const routes: Array<RouteRecordRaw> = [
@ -17,52 +17,36 @@ const routes: Array<RouteRecordRaw> = [
name: `${moduleName}-designer`,
meta: {
title: '低代码编辑器',
keepAlive: true,
icon: 'ant-design:edit-outlined',
app: {
url: 'https://localhost:10011',
url: Y_CODE_DESIGNER_URL,
name: 'y-code-designer',
// sync: true,
// alive: true,
// degrade: true,
},
},
component: () => import('@/components/micro-container/index.vue'),
},
{
path: 'renderer',
name: `${moduleName}-renderer`,
meta: {
title: '低代码渲染器',
keepAlive: true,
// hideInMenu: true,
icon: 'ant-design:eye-outlined',
app: {
url: 'https://localhost:10010',
name: 'y-code-renderer',
// sync: true,
// alive: true,
projectId: 4,
sync: true,
alive: true,
degrade: true,
},
},
component: () => import('@/components/micro-container/index.vue'),
component: () => import('@/components/renderer-adapter/index.vue'),
},
{
path: 'y-code-v1',
name: `${moduleName}-y-code-v1`,
meta: {
title: '悦码 1.0',
// keepAlive: true,
keepAlive: true,
// hideInMenu: true,
icon: 'ant-design:delete-outlined',
app: {
url: 'http://localhost:10012',
url: Y_CODE_V1_URL,
name: 'y-code-v1',
// sync: true,
// alive: true,
degrade: true,
},
},
component: () => import('@/components/micro-container/index.vue'),
component: () => import('@/components/renderer-adapter/index.vue'),
},
],
},

View File

@ -1,4 +1,6 @@
import type { RouteRecordRaw } from 'vue-router';
import { Y_CODE_RENDERER_URL } from '@/constants';
import { LOW_CODE_APPLICATION_ID, LOW_CODE_PROJECT_ID } from '@/constants/low-code';
// 微前端路由
const moduleName = 'project';
@ -20,18 +22,38 @@ const routes: Array<RouteRecordRaw> = [
keepAlive: true,
icon: 'ant-design:list',
app: {
url: 'https://localhost:10010',
url: Y_CODE_RENDERER_URL,
name: 'y-code-platform-project-list',
// sync: true,
// alive: true,
// degrade: true,
applicationId: 0,
projectId: 4,
applicationId: LOW_CODE_APPLICATION_ID,
projectId: LOW_CODE_PROJECT_ID,
fileId: '4g4mz6qi8u',
},
},
component: () => import('@/components/renderer-adapter/index.vue'),
},
{
path: 'file',
name: `${moduleName}-file`,
meta: {
title: 'dsl 文件列表',
keepAlive: true,
icon: 'ant-design:file',
app: {
url: Y_CODE_RENDERER_URL,
name: 'y-code-platform-project-file',
// sync: true,
// alive: true,
// degrade: true,
applicationId: LOW_CODE_APPLICATION_ID,
projectId: LOW_CODE_PROJECT_ID,
fileId: '7pftwojzu',
},
},
component: () => import('@/components/renderer-adapter/index.vue'),
},
],
},
];

View File

@ -1,4 +1,6 @@
import type { RouteRecordRaw } from 'vue-router';
import { Y_CODE_RENDERER_URL } from '@/constants';
import { LOW_CODE_APPLICATION_ID, LOW_CODE_PROJECT_ID } from '@/constants/low-code';
// 微前端路由
const moduleName = 'static-file';
@ -9,7 +11,7 @@ const routes: Array<RouteRecordRaw> = [
name: moduleName,
meta: {
title: '静态文件管理',
icon: 'ant-design:file-outlined',
icon: 'ant-design:file',
},
children: [
{
@ -18,13 +20,13 @@ const routes: Array<RouteRecordRaw> = [
meta: {
title: '静态文件列表',
keepAlive: true,
icon: 'ant-design:list',
icon: 'ant-design:file',
app: {
url: 'https://localhost:10010',
url: Y_CODE_RENDERER_URL,
name: 'y-code-platform-application-list',
sync: true,
alive: true,
degrade: true,
applicationId: LOW_CODE_APPLICATION_ID,
projectId: LOW_CODE_PROJECT_ID,
fileId: '7pfr394d6',
},
},
component: () => import('@/components/renderer-adapter/index.vue'),

View File

@ -1,4 +1,6 @@
import type { RouteRecordRaw } from 'vue-router';
import { Y_CODE_RENDERER_URL } from '@/constants';
import { LOW_CODE_APPLICATION_ID, LOW_CODE_PROJECT_ID } from '@/constants/low-code';
const moduleName = 'user';
@ -17,55 +19,20 @@ const routes: Array<RouteRecordRaw> = [
name: `${moduleName}-list`,
meta: {
title: '用户列表',
icon: 'ant-design:unordered-list-outlined',
keepAlive: true,
hideInMenu: false,
icon: 'ant-design:list',
app: {
url: Y_CODE_RENDERER_URL,
name: 'y-code-platform-project-list',
// sync: true,
// alive: true,
// degrade: true,
applicationId: LOW_CODE_APPLICATION_ID,
projectId: LOW_CODE_PROJECT_ID,
fileId: 'b91ra0ej4',
},
},
component: () => import('@/views/user/list.vue'),
},
{
path: 'add',
name: `${moduleName}-add`,
meta: {
title: '新增用户',
icon: 'ant-design:user-add-outlined',
hideInMenu: true,
activeMenu: `${moduleName}-list`,
},
component: () => import('@/views/user/add.vue'),
},
{
path: 'edit/:id',
name: `${moduleName}-edit`,
meta: {
title: '编辑用户',
icon: 'ant-design:edit-outlined',
hideInMenu: true,
activeMenu: `${moduleName}-list`,
},
component: () => import('@/views/user/edit.vue'),
},
{
path: 'detail/:id',
name: `${moduleName}-detail`,
meta: {
title: '用户详情',
icon: 'ant-design:profile-outlined',
hideInMenu: true,
activeMenu: `${moduleName}-list`,
},
component: () => import('@/views/user/detail.vue'),
},
{
path: 'change-password/:id',
name: `${moduleName}-change-password`,
meta: {
title: '修改密码',
icon: 'ant-design:lock-outlined',
hideInMenu: true,
activeMenu: `${moduleName}-list`,
},
component: () => import('@/views/user/change-password.vue'),
component: () => import('@/components/renderer-adapter/index.vue'),
},
],
},

View File

@ -1,16 +1,16 @@
import type { RouteRecordRaw } from 'vue-router';
import { LOGIN_NAME } from '@/router/constant';
// import type { RouteRecordRaw } from 'vue-router';
// import { LOGIN_NAME } from '@/router/constant';
/**
* layout布局之外的路由
*/
export const LoginRoute: RouteRecordRaw = {
path: '/login',
name: LOGIN_NAME,
component: () => import('@/views/login/index.vue'),
meta: {
title: '登录',
},
};
// export const LoginRoute: RouteRecordRaw = {
// path: '/login',
// name: LOGIN_NAME,
// component: () => import('@/views/login/index.vue'),
// meta: {
// title: '登录',
// },
// };
export default [LoginRoute];
export default [];

View File

@ -5,8 +5,7 @@ interface KeepAliveState {
list: string[];
}
export const useKeepAliveStore = defineStore({
id: 'keep-alive',
export const useKeepAliveStore = defineStore('keep-alive', {
state: (): KeepAliveState => ({
list: [],
}),

View File

@ -3,7 +3,8 @@ import { defineStore } from 'pinia';
import { useSSEStore } from './sse';
import { store } from '@/store';
import { resetRouter } from '@/router';
import { login, logout } from '@/io';
import tianti from '@/io/tianti';
import { getCurrentUser } from '@/io/user';
export const useUserStore = defineStore(
'user',
@ -13,6 +14,10 @@ export const useUserStore = defineStore(
const perms = ref<string[]>([]);
const userInfo = ref<Partial<API.UserEntity>>({});
const setToken = (_token: string) => {
token.value = _token;
};
/** 清空登录态(token、userInfo...) */
const clearLoginStatus = () => {
token.value = '';
@ -23,21 +28,24 @@ export const useUserStore = defineStore(
localStorage.clear();
});
};
/** 登录成功保存token */
const setToken = (_token: string) => {
token.value = _token;
};
/** 登录 */
const login = async (params: any) => {
const data = await login(params);
console.log('data', data);
// @ts-ignore
setToken(data.msg);
const login = async () => {
tianti.checkQuery();
const token = localStorage.getItem('y-code-access-token');
if (token) {
setToken(token);
setTimeout(() => {
getCurrentUser().then((res) => {
userInfo.value = res.data.data;
console.log('userInfo', userInfo.value);
});
}, 1000);
}
};
/** 登出 */
const logout = async () => {
sseStore.closeEventSource();
await logout();
await tianti.logout();
clearLoginStatus();
};

View File

@ -0,0 +1,28 @@
<script lang="jsx">
import { defineComponent } from 'vue';
import OneClickAuth from '@sy/unified-login/one-click-auth.vue';
export default defineComponent({
name: 'ForbiddenPage',
setup() {
return () => {
return (
<div class="forbidden-page">
<OneClickAuth env="dev" appMark={30} />
</div>
);
};
},
});
</script>
<style lang="less" scoped>
.forbidden-page {
display: flex;
position: relative;
align-items: center;
justify-content: center;
height: 100%;
}
</style>

View File

@ -1,44 +0,0 @@
<template>
<div>1</div>
</template>
<script setup lang="ts"></script>
<style lang="less" scoped>
.login-box {
display: flex;
flex-direction: column;
align-items: center;
width: 100vw;
height: 100vh;
padding-top: 240px;
background: url('@/assets/login.svg');
background-size: 100%;
.login-logo {
display: flex;
align-items: center;
margin-bottom: 30px;
.svg-icon {
font-size: 48px;
}
}
:deep(.ant-form) {
width: 400px;
.ant-col {
width: 100%;
}
.ant-form-item-label {
padding-right: 6px;
}
}
.login-type-switch {
text-align: center;
}
}
</style>

View File

@ -1,60 +0,0 @@
<template>
<div class="add-user">
<a-card :bordered="false">
<a-form layout="vertical" :model="userForm" @submit="handleAddUser">
<a-form-item label="用户名">
<a-input
v-model:value="userForm.username"
placeholder="请输入用户名"
allow-clear
style="max-width: 400px"
/>
</a-form-item>
<a-form-item label="密码">
<a-input
v-model:value="userForm.password"
placeholder="请输入密码"
type="password"
allow-clear
style="max-width: 400px"
/>
</a-form-item>
<a-form-item>
<a-space>
<a-button type="primary" html-type="submit">新增用户</a-button>
<a-button @click="goBack">取消</a-button>
</a-space>
</a-form-item>
</a-form>
</a-card>
</div>
</template>
<script lang="ts" setup>
import { reactive } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { message } from 'ant-design-vue';
import { useTabsViewStore } from '@/store/modules/tabsView';
const { closeCurrentTab } = useTabsViewStore();
const router = useRouter();
const route = useRoute();
//
const userForm = reactive({
username: '',
password: '',
});
//
const handleAddUser = async () => {
// TODO:
message.success('用户新增成功');
goBack();
};
const goBack = () => {
router.back();
closeCurrentTab(route);
};
</script>

View File

@ -1,66 +0,0 @@
<template>
<div class="change-password">
<a-card :bordered="false">
<a-form layout="vertical" :model="passwordForm" @submit="handleChangePassword">
<a-form-item label="新密码">
<a-input
v-model:value="passwordForm.newPassword"
placeholder="请输入新密码"
type="password"
allow-clear
style="max-width: 400px"
/>
</a-form-item>
<a-form-item label="确认密码">
<a-input
v-model:value="passwordForm.confirmPassword"
placeholder="请确认新密码"
type="password"
allow-clear
style="max-width: 400px"
/>
</a-form-item>
<a-form-item>
<a-space>
<a-button type="primary" html-type="submit">修改密码</a-button>
<a-button type="default" @click="goBack">取消</a-button>
</a-space>
</a-form-item>
</a-form>
</a-card>
</div>
</template>
<script lang="ts" setup>
import { reactive } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { message } from 'ant-design-vue';
import { useTabsViewStore } from '@/store/modules/tabsView';
const { closeCurrentTab } = useTabsViewStore();
const router = useRouter();
console.log('router', router);
const route = useRoute();
//
const passwordForm = reactive({
newPassword: '',
confirmPassword: '',
});
//
const handleChangePassword = async () => {
if (passwordForm.newPassword !== passwordForm.confirmPassword) {
message.error('两次输入的密码不一致');
return;
}
// TODO:
message.success('密码修改成功');
goBack();
};
const goBack = () => {
router.back();
closeCurrentTab(route);
};
</script>

View File

@ -1,94 +0,0 @@
<template>
<div class="user-detail">
<a-card title="用户详情" :bordered="false">
<!-- 基本信息 -->
<a-descriptions bordered>
<a-descriptions-item label="用户名">
{{ userInfo.username }}
</a-descriptions-item>
<a-descriptions-item label="昵称">
{{ userInfo.nickname }}
</a-descriptions-item>
<a-descriptions-item label="手机号">
{{ userInfo.phone }}
</a-descriptions-item>
<a-descriptions-item label="邮箱">
{{ userInfo.email }}
</a-descriptions-item>
<a-descriptions-item label="状态">
<a-tag :color="userInfo.status === 1 ? 'green' : 'red'">
{{ userInfo.status === 1 ? '启用' : '禁用' }}
</a-tag>
</a-descriptions-item>
<a-descriptions-item label="创建时间">
{{ userInfo.createTime }}
</a-descriptions-item>
</a-descriptions>
<!-- 操作按钮 -->
<div class="operation-buttons" style="margin-top: 24px">
<a-button type="primary" @click="handleEdit">编辑</a-button>
<a-button style="margin-left: 8px" @click="goBack">返回</a-button>
</div>
</a-card>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { useTabsViewStore } from '@/store/modules/tabsView';
const { closeCurrentTab } = useTabsViewStore();
const userInfo = ref({
username: 'mockUser',
nickname: 'mockNickname',
phone: '1234567890',
email: 'mock@example.com',
status: 1,
createTime: '2023-01-01 12:00:00',
});
const route = useRoute();
const router = useRouter();
onMounted(() => {
getUserDetail();
});
//
async function getUserDetail() {
try {
const userId = route.params.id;
// API
const response = await $api.user.getDetail(userId);
userInfo.value = response.data;
} catch (err) {
console.error('err', err);
$message.error('获取用户详情失败');
}
}
//
function handleEdit() {
const userId = route.params.id;
router.push(`/user/edit/${userId}`);
}
//
function goBack() {
router.back();
closeCurrentTab(route);
}
</script>
<style lang="less" scoped>
.user-detail {
padding: 24px;
background: #fff;
.operation-buttons {
text-align: center;
}
}
</style>

View File

@ -1,106 +0,0 @@
<template>
<div class="user-edit">
<a-card :bordered="false">
<a-form layout="vertical" :model="userForm" @submit="handleSave">
<a-form-item label="用户名">
<a-input
v-model:value="userForm.username"
placeholder="请输入用户名"
allow-clear
style="max-width: 400px"
/>
</a-form-item>
<a-form-item label="昵称">
<a-input
v-model:value="userForm.nickname"
placeholder="请输入昵称"
allow-clear
style="max-width: 400px"
/>
</a-form-item>
<a-form-item label="手机号">
<a-input
v-model:value="userForm.phone"
placeholder="请输入手机号"
allow-clear
style="max-width: 400px"
/>
</a-form-item>
<a-form-item label="邮箱">
<a-input
v-model:value="userForm.email"
placeholder="请输入邮箱"
allow-clear
style="max-width: 400px"
/>
</a-form-item>
<a-form-item label="状态">
<a-select v-model:value="userForm.status" style="max-width: 400px">
<a-select-option :value="1">启用</a-select-option>
<a-select-option :value="0">禁用</a-select-option>
</a-select>
</a-form-item>
<a-form-item>
<a-space>
<a-button type="primary" html-type="submit">保存</a-button>
<a-button @click="goBack">取消</a-button>
</a-space>
</a-form-item>
</a-form>
</a-card>
</div>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import { useRouter, useRoute } from 'vue-router';
import { message } from 'ant-design-vue';
import { useTabsViewStore } from '@/store/modules/tabsView';
const router = useRouter();
const route = useRoute();
const { closeCurrentTab } = useTabsViewStore();
//
const userForm = ref({
username: '',
nickname: '',
phone: '',
email: '',
status: 1,
});
//
const handleSave = async () => {
// TODO:
message.success('保存成功');
goBack();
};
//
const goBack = () => {
router.back(); //
closeCurrentTab(route); //
};
//
const initUserData = async (userId: string) => {
// TODO:
// const res = await getUserInfo(userId);
// mock
userForm.value = {
username: 'mockUser',
nickname: 'mockNickname',
phone: '1234567890',
email: 'mock@example.com',
status: 1,
};
};
// ID
const userId = route.params.id; // ID
if (userId) {
initUserData(userId);
}
</script>

View File

@ -1,219 +0,0 @@
<template>
<div class="user-list">
<a-card :bordered="false">
<!-- 搜索区域 -->
<a-form layout="inline" :model="searchForm" @submit="handleSearch">
<a-form-item label="用户名" :label-col="{ span: 6 }" :wrapper-col="{ span: 18 }">
<a-input v-model:value="searchForm.username" placeholder="请输入用户名" allow-clear />
</a-form-item>
<a-form-item label="手机号" :label-col="{ span: 6 }" :wrapper-col="{ span: 18 }">
<a-input v-model:value="searchForm.phone" placeholder="请输入手机号" allow-clear />
</a-form-item>
<a-form-item>
<a-space>
<a-button type="primary" html-type="submit">查询</a-button>
<a-button @click="resetSearch">重置</a-button>
</a-space>
</a-form-item>
</a-form>
<!-- 操作按钮区域 -->
<div class="table-operations">
<a-space>
<a-button type="primary" @click="handleAdd">
<template #icon>
<plus-outlined />
</template>
新增用户
</a-button>
</a-space>
</div>
<!-- 表格区域 -->
<a-table
:columns="columns"
:data-source="tableData"
:loading="loading"
:pagination="pagination"
:row-selection="{ selectedRowKeys, onChange: onSelectChange }"
row-key="id"
@change="handleTableChange"
>
<!-- 状态列 -->
<template #status="{ text }">
<a-tag :color="text ? 'success' : 'error'">
{{ text ? '启用' : '禁用' }}
</a-tag>
</template>
<!-- 操作列 -->
<template #action="{ record }">
<a-space>
<a-button type="link" @click="handleEdit(record)">编辑</a-button>
<a-button type="link" @click="handleDetail(record)">详情</a-button>
<a-button type="link" @click="handleChangePassword(record)">修改密码</a-button>
<a-popconfirm title="确定要删除这条记录吗?" @confirm="handleDelete(record)">
<a-button type="link" danger>删除</a-button>
</a-popconfirm>
</a-space>
</template>
</a-table>
</a-card>
</div>
</template>
<script lang="ts" setup>
import { ref, reactive } from 'vue';
import { useRouter } from 'vue-router';
import { PlusOutlined } from '@ant-design/icons-vue';
import { message } from 'ant-design-vue';
const router = useRouter();
//
const searchForm = reactive({
username: '',
phone: '',
});
//
const columns = [
{
title: '用户名',
dataIndex: 'username',
width: 120,
},
{
title: '昵称',
dataIndex: 'nickname',
width: 120,
},
{
title: '手机号',
dataIndex: 'phone',
width: 120,
},
{
title: '状态',
dataIndex: 'status',
width: 100,
slots: { customRender: 'status' },
},
{
title: '创建时间',
dataIndex: 'createTime',
width: 180,
},
{
title: '操作',
key: 'action',
fixed: 'right',
width: 280,
slots: { customRender: 'action' },
},
];
//
const loading = ref(false);
const tableData = ref<any[]>([]);
const selectedRowKeys = ref<string[]>([]);
const pagination = reactive({
current: 1,
pageSize: 10,
total: 0,
showSizeChanger: true,
showQuickJumper: true,
});
//
const getTableData = async () => {
loading.value = true;
try {
// TODO:
// const res = await getUserList({
// ...searchForm,
// page: pagination.current,
// pageSize: pagination.pageSize,
// });
// tableData.value = res.data.list;
// pagination.total = res.data.total;
//
tableData.value = Array.from({ length: 10 }).map((_, index) => ({
id: index + 1,
username: `user${index + 1}`,
nickname: `用户${index + 1}`,
phone: '13800138000',
email: `user${index + 1}@example.com`,
status: index % 2,
createTime: '2024-03-20 12:00:00',
}));
pagination.total = 100;
} finally {
loading.value = false;
}
};
//
const onSelectChange = (keys: string[]) => {
selectedRowKeys.value = keys;
};
//
const handleTableChange = (pag: any) => {
pagination.current = pag.current;
pagination.pageSize = pag.pageSize;
getTableData();
};
//
const handleSearch = () => {
pagination.current = 1;
getTableData();
};
//
const resetSearch = () => {
searchForm.username = '';
searchForm.phone = '';
handleSearch();
};
//
const handleAdd = () => {
router.push('/user/add');
};
//
const handleEdit = (record: any) => {
router.push(`/user/edit/${record.id}`);
};
//
const handleDetail = (record: any) => {
router.push(`/user/detail/${record.id}`);
};
//
const handleChangePassword = (record: any) => {
router.push(`/user/change-password/${record.id}`);
};
//
const handleDelete = async (record: any) => {
// TODO:
message.success('删除成功');
getTableData();
};
//
getTableData();
</script>
<style lang="less" scoped>
.user-list {
.table-operations {
margin: 16px 0;
}
}
</style>

View File

@ -32,7 +32,7 @@
"strictFunctionTypes": false,
"noImplicitAny": false,
"lib": ["dom", "esnext", "DOM.Iterable"],
"types": ["node", "vite/client"],
"types": ["node", "vite/client", "vue"],
"paths": {
"@/*": ["src/*"]
}

View File

@ -14,6 +14,7 @@ declare module 'vue' {
Reflect: Reflect;
suspenseStatus: '' | 'pending' | 'resolve' | 'fallback';
}
export * from '@vue/runtime-core';
}
declare type Nullable<T> = T | null;

View File

@ -26,7 +26,8 @@ const __APP_INFO__ = {
export default ({ command, mode }: ConfigEnv): UserConfig => {
console.log('mode', mode);
// 环境变量
const env = loadEnv(mode, process.cwd(), ['VITE_', 'VTJ_', 'SY_']);
const env = loadEnv(mode, process.cwd(), ['VITE_', 'VTJ_', 'SY_', 'Y_CODE_']);
console.log('env', env);
const isDev = command === 'serve';
return {
@ -46,6 +47,8 @@ export default ({ command, mode }: ConfigEnv): UserConfig => {
server: {
open: true,
host: true,
cors: true,
port: Number(env.VITE_PORT),
proxy: {
'/api': {
target: 'https://custom-chart-pre-api.shiyue.com',

View File

@ -5,9 +5,13 @@ VITE_NODE_ENV = 'development'
VITE_BASE_URL = /
# 前端可见变量(必须以 VITE_ 开头)
VITE_PORT = 10010
VITE_PORT = 10012
# VITE_BASE_API_URL = 'https://custom-chart-pre-api.shiyue.com/'
VITE_BASE_API_URL = 'https://custom-chart-api.shiyuegame.com/'
VITE_DEBUG_MODE = true
VITE_DEBUG_MODE = true
Y_CODE_PLATFORM_URL = 'https://localhost:10010/'
Y_CODE_DESIGNER_URL = 'https://localhost:10011/'
Y_CODE_RENDERER_URL = 'https://localhost:10012/'

View File

@ -5,3 +5,7 @@ VITE_NODE_ENV = 'production'
VITE_BASE_URL = /
VITE_BASE_API_URL = 'https://custom-chart-api.shiyuegame.com/'
Y_CODE_PLATFORM_URL = 'https://y-code-platform.shiyuegame.com/'
Y_CODE_DESIGNER_URL = 'https://y-code-designer.shiyuegame.com/'
Y_CODE_RENDERER_URL = 'https://y-code-renderer.shiyuegame.com/'

View File

@ -8,4 +8,8 @@ VITE_BASE_URL = /
VITE_PORT = 10010
# base api url
VITE_BASE_API_URL = 'https://custom-chart-pre-api.shiyue.com/'
VITE_BASE_API_URL = 'https://custom-chart-pre-api.shiyue.com/'
Y_CODE_PLATFORM_URL = 'https://y-code-platform-pre.shiyue.com/'
Y_CODE_DESIGNER_URL = 'https://y-code-designer-pre.shiyue.com/'
Y_CODE_RENDERER_URL = 'https://y-code-renderer-pre.shiyue.com/'

View File

@ -7,7 +7,7 @@ import path from "path";
// @ts-ignore
export default defineConfig(({ mode }) => {
console.log("mode", mode);
const env = loadEnv(mode, process.cwd(), ["VITE_"]);
const env = loadEnv(mode, process.cwd(), ["VITE_", "Y_CODE_"]);
return {
server: {

View File

@ -18,13 +18,14 @@
"@sy/low-code-shared": "workspace:*",
"@sy/web-vitals": "workspace:*",
"@tanstack/vue-query": "^5.66.9",
"@vtj/core": "^0.10.9",
"@vtj/icons": "0.10.9",
"@vtj/materials": "^0.10.9",
"@vtj/pro": "^0.10.9",
"@vtj/renderer": "^0.10.9",
"@vtj/ui": "^0.10.9",
"@vtj/web": "^0.10.9",
"@vtj/core": "^0.10.10",
"@vtj/icons": "0.10.10",
"@vtj/materials": "^0.10.10",
"@vtj/pro": "^0.10.10",
"@vtj/renderer": "^0.10.10",
"@vtj/ui": "^0.10.10",
"@vtj/utils": "^0.10.10",
"@vtj/web": "^0.10.10",
"axios": "^1.8.1",
"core-js": "^3.40.0",
"element-plus": "^2.9.4",
@ -35,10 +36,10 @@
},
"devDependencies": {
"@farmfe/cli": "^1.0.4",
"@vtj/cli": "^0.10.2",
"@farmfe/core": "^1.6.6",
"@sy/vite-plugin-http2-proxy": "workspace:*",
"@vitejs/plugin-vue": "^5.2.1",
"@vtj/cli": "^0.10.2",
"vite-plugin-mkcert": "^1.17.6"
}
}

View File

@ -6,6 +6,7 @@ import { createProvider } from '@vtj/web'
import { useQuery } from '@tanstack/vue-query'
import { LowCodeService } from './service'
import { getFile } from './io'
import { request, jsonp } from '@vtj/utils'
// import * as VtjUI from '@vtj/ui'
//
@ -13,7 +14,11 @@ const renderer = ref()
const lowCodeService = new LowCodeService()
// Postmate
const handshake = new Postmate.Model({})
const postmate = new Postmate.Model({
sayHi: (data: any) => {
console.log('sayHi',data)
}
})
//
const model = {
@ -21,28 +26,33 @@ const model = {
applicationId: -1,
projectId: -1,
fileId: '',
url: ''
url: '',
accessToken: ''
}
//
const { data: file, isFetching } = useQuery({
queryKey: ['getFile'],
queryFn: async () => {
await handshake.then((parent) => {
// parent.emit('sync-context', 'Hello, World!')
await postmate.then((parent) => {
parent.emit('some-event', 'y-code-renderer is ready')
Object.assign(model, parent.model)
// console.log('model', model)
localStorage.setItem('y-code-access-token', model.accessToken || '')
})
return getFile(model.fileId).then(() => {
request.useRequest((req) => {
req.headers.set('Authorization', `Bearer ${model.accessToken}`)
return req
})
const { provider, onReady } = createProvider({
nodeEnv: import.meta.env.NODE_ENV,
service: lowCodeService,
project: { id: model.projectId },
// components: {
// ...VtjUI,
// }
adapter: {
request,
jsonp
}
})
onReady(async () => {
const instance = getCurrentInstance()

View File

@ -0,0 +1,2 @@
// @ts-ignore
export const currentEnv = __APP_ENV__;

View File

@ -0,0 +1 @@
export * from "./env";

View File

@ -12,7 +12,6 @@ const app = createApp(App);
// 批量注册组件
Object.entries(VtjUI).forEach(([name, component]) => {
console.log("name", name, component);
app.component(name, component);
});
app

View File

@ -1,7 +1,7 @@
# .env.development
VITE_NODE_ENV = development
VITE_PORT = 10012
VITE_PORT = 10013
VITE_OA_BASEURL = https://oa-pre.shiyue.com

View File

@ -43,11 +43,11 @@
"eslint-plugin-vue": "^9.32.0",
"less": "^4.2.2",
"semantic-release": "^24.2.2",
"typescript": "~5.3.3",
"typescript": "~5.8.2",
"unplugin-vue-components": "^0.26.0",
"vite": "^6.2.0",
"vite-plugin-qiankun": "^1.0.15",
"vue-tsc": "^2.2.0",
"vue-tsc": "^2.2.8",
"yargs-parser": "^21.1.1"
},
"packageManager": "pnpm@10.4.1"

View File

@ -13,7 +13,7 @@
},
"scripts": {
"low-code:help": "node ./scripts/index.mjs",
"preinstall": "npx only-allow pnpm && pnpm i -g turbo rimraf",
"preinstall": "npx only-allow pnpm && pnpm i -g turbo rimraf cross-env",
"dev": "turbo run dev",
"build": "turbo run build",
"preview": "turbo run preview",
@ -38,7 +38,9 @@
"eslint-plugin-prettier": "~5.2.3",
"eslint-plugin-unused-imports": "^4.1.4",
"husky": "~9.1.7",
"inquirer": "^12.4.2",
"lint-staged": "~15.2.11",
"ora": "^8.2.0",
"postcss-html": "~1.7.0",
"prettier": "~3.3.3",
"stylelint": "~16.10.0",
@ -48,11 +50,8 @@
"stylelint-config-standard": "~36.0.1",
"stylelint-order": "~6.0.4",
"stylelint-prettier": "^5.0.3",
"typescript": "~5.6.3",
"vue-eslint-parser": "~9.4.3",
"vue-tsc": "~2.1.10",
"inquirer": "^12.4.2",
"ora": "^8.2.0"
},
"dependencies": {}
"typescript": "~5.8.2",
"vue-eslint-parser": "~10.1.1",
"vue-tsc": "~2.2.8"
}
}

View File

@ -16,6 +16,6 @@
"clean": "rimraf node_modules"
},
"devDependencies": {
"typescript": "^5.7.3"
"typescript": "^5.8.2"
}
}

1644
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff