Commit 9d944558 by lijiabin

【需求 17679】 wip: 调试系统登录 以及 部署等 加入第三方包

parent 02392d7f
import cp from 'node:child_process'
import ssh from 'ssh2'
import archiver from 'archiver'
import fs from 'node:fs'
import path from 'node:path'
const unzipDirMode = {
spawn: 'pnpm run build:test',
// 解压文件名
unzipDir: 'oa-test/',
// 终端定位位置 cd
servicePath: '/usr/local/nginx/www/',
// 压缩包存放位置
serviceFilePath: '/usr/local/nginx/www/dist.tar.gz',
}
const __dirname = path.resolve()
// 文件所在地
const distPath = path.resolve(__dirname, 'dist')
// 打包后位置
const zipPath = path.resolve(__dirname, 'dist.tar.gz')
// 远程服务器存放位置
const { spawn, servicePath, serviceFilePath, unzipDir } = unzipDirMode
// 服务器连接信息
const connectInfo = {
host: '47.119.149.50',
port: '22',
username: 'root',
password: 'Qdt20250205',
}
//链接服务器
let conn = new ssh.Client()
async function start() {
try {
// 1. 打包
await build()
// 2. 压缩zip
await startZip()
// 3. 将zip文件传输至远程服务器
await connect()
// 4. 部署解压
await shellCmd(conn)
console.log('部署完成')
} catch (error) {
console.error('Error:', error.message)
} finally {
// 5. 断开ssh,并删除本地压缩包
conn.end()
delZip()
}
}
start()
/**
* 1. 本地构建项目
*/
function build() {
return new Promise((resolve, reject) => {
//对项目进行打包,然后生成压缩文件
let pro = cp.spawn(spawn, {
shell: true,
stdio: 'inherit',
})
pro.on('exit', (code) => {
//打包完成后 开始链接目标服务器,并自动部署
if (code === 0) {
console.log('---构建成功---')
resolve()
} else {
reject(new Error('构建失败'))
}
})
})
}
/**
* 2. 将打包后文件压缩zip
* @returns
*/
function startZip() {
return new Promise((resolve, reject) => {
console.log('开始打包tar')
//定义打包格式和相关配置
const archive = archiver('tar', {
gzip: true, // 如果需要压缩,可以使用 gzip
gzipOptions: { level: 9 }, // gzip 压缩级别
}).on('error', (err) => reject(err))
const output = fs.createWriteStream(zipPath)
//监听流的打包
output.on('close', (err) => {
console.log('目标打包完成')
resolve(true)
})
//开始压缩
archive.pipe(output)
// 文件夹压缩
archive.directory(distPath, false)
archive.finalize()
})
}
/**
* 3. 将zip文件传输至远程服务器
*/
function connect() {
return new Promise((resolve, reject) => {
conn
.on('ready', () => {
conn.sftp((err, sftp) => {
if (err) {
return reject(err)
}
sftp.fastPut(zipPath, serviceFilePath, {}, (err, result) => {
if (err) {
return reject(err)
}
//开始上传
console.log('文件上传成功')
resolve()
})
})
})
.on('error', (err) => reject(err))
.connect(connectInfo)
})
}
/**
* 4. 解压部署操作
* @param {*} conn
*/
async function shellCmd(conn) {
return new Promise((resolve, reject) => {
conn.shell((err, stream) => {
if (err) {
return reject(err)
}
//进入服务器暂存地址
//解压上传的压缩包
//移动解压后的文件到发布目录
//删除压缩包
//退出
const commands = `
cd ${servicePath} &&
mkdir -p ${unzipDir} &&
tar -xzf dist.tar.gz -C ${unzipDir} &&
rm -rf dist.tar.gz &&
exit
`
console.log('终端执行命令:', commands)
stream
.on('close', () => resolve())
.on('data', (data) => console.log(data.toString()))
.stderr.on('data', (data) => console.error('Error:', data.toString()))
stream.end(commands)
})
})
}
/**
* 5. 删除本地的dist.zip
*/
function delZip() {
fs.unlink(zipPath, function (err) {
if (err) {
console.error('删除文件失败:', err.message)
}
console.log('文件:' + zipPath + '删除成功!')
})
}
...@@ -13,16 +13,20 @@ ...@@ -13,16 +13,20 @@
"build-only": "vite build", "build-only": "vite build",
"type-check": "vue-tsc --build", "type-check": "vue-tsc --build",
"lint": "eslint . --fix --cache", "lint": "eslint . --fix --cache",
"format": "prettier --write src/" "format": "prettier --write src/",
"deploy:test": "node deploy/deploytest.js",
"build:test": "nvm use 20 && vite build --mode test"
}, },
"dependencies": { "dependencies": {
"@element-plus/icons-vue": "^2.3.2", "@element-plus/icons-vue": "^2.3.2",
"@vueuse/components": "^14.0.0", "@vueuse/components": "^14.0.0",
"@vueuse/core": "^14.0.0", "@vueuse/core": "^14.0.0",
"archiver": "^7.0.1",
"axios": "^1.13.0", "axios": "^1.13.0",
"dayjs": "^1.11.19", "dayjs": "^1.11.19",
"element-plus": "^2.11.5", "element-plus": "^2.11.5",
"pinia": "^3.0.3", "pinia": "^3.0.3",
"ssh2": "^1.17.0",
"vue": "^3.5.22", "vue": "^3.5.22",
"vue-router": "^4.6.3" "vue-router": "^4.6.3"
}, },
......
import { createRouter, createWebHistory } from 'vue-router' import { createRouter, createWebHistory } from 'vue-router'
import layoutCulture from '@/layoutCulture/index.vue' import layoutCulture from '@/layoutCulture/index.vue'
import { scrollBehavior } from './scrollStorage'
import { registerRouterGuard } from './regards'
const routes = [ const routes = [
{ {
path: '/', path: '/',
...@@ -44,9 +46,9 @@ const routes = [ ...@@ -44,9 +46,9 @@ const routes = [
component: () => import('@/views/pointsStore/index.vue'), component: () => import('@/views/pointsStore/index.vue'),
}, },
{ {
path: 'postDetail/:articleId', path: 'articleDetail/:articleId',
name: 'CulturePostDetail', name: 'CultureArticleDetail',
component: () => import('@/views/postDetail/index.vue'), component: () => import('@/views/articleDetail/index.vue'),
}, },
// 发布视频 // 发布视频
{ {
...@@ -72,6 +74,12 @@ const routes = [ ...@@ -72,6 +74,12 @@ const routes = [
name: 'CultureSearchPage', name: 'CultureSearchPage',
component: () => import('@/views/searchPage/index.vue'), component: () => import('@/views/searchPage/index.vue'),
}, },
// 审核
{
path: 'auditArticle/:id',
name: 'CultureAuditArticle',
component: () => import('@/views/auditArticle/index.vue'),
},
// 发布文章 // 发布文章
// { // {
// { // {
...@@ -145,6 +153,12 @@ const routes = [ ...@@ -145,6 +153,12 @@ const routes = [
meta: { title: '专访——栏目管理' }, meta: { title: '专访——栏目管理' },
}, },
{ {
path: 'videoSettings',
name: 'VideoSettingsManagement',
component: () => import('@/views/backend/videoSettings/index.vue'),
meta: { title: '视频——栏目管理' },
},
{
path: 'videoManage', path: 'videoManage',
name: 'VideoManageManagement', name: 'VideoManageManagement',
component: () => import('@/views/backend/videoManage/index.vue'), component: () => import('@/views/backend/videoManage/index.vue'),
...@@ -172,45 +186,44 @@ const routes = [ ...@@ -172,45 +186,44 @@ const routes = [
}, },
] ]
const scrollPositionMap = new Map<string, number>()
const router = createRouter({ const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL), history: createWebHistory(import.meta.env.BASE_URL),
routes, routes,
scrollBehavior(to, from, savedPosition) { // scrollBehavior(to, from, savedPosition) {
return new Promise((resolve) => { // return new Promise((resolve) => {
// console.log(to, from, savedPosition) // // console.log(to, from, savedPosition)
// 如果有浏览器保存的位置(前进/后退),优先使用 // // 如果有浏览器保存的位置(前进/后退),优先使用
if (savedPosition) { // if (savedPosition) {
resolve(savedPosition) // resolve(savedPosition)
return // return
} // }
// 如果有锚点,滚动到锚点 // // 如果有锚点,滚动到锚点
if (to.hash) { // if (to.hash) {
resolve({ el: to.hash }) // resolve({ el: to.hash })
return // return
} // }
// 检查是否有保存的滚动位置 // // 检查是否有保存的滚动位置
const savedScrollY = scrollPositionMap.get(to.fullPath) // const savedScrollY = scrollPositionMap.get(to.fullPath)
if (savedScrollY !== undefined) { // if (savedScrollY !== undefined) {
resolve({ top: savedScrollY }) // resolve({ top: savedScrollY })
return // return
} // }
// 默认滚动到顶部 // // 默认滚动到顶部
resolve({ top: 0 }) // resolve({ top: 0 })
}) // })
}, // },
scrollBehavior,
}) })
// 在路由离开前保存当前滚动位置 // 在路由离开前保存当前滚动位置
router.beforeEach((to, from, next) => { // router.beforeEach((to, from, next) => {
// 保存当前页面的滚动位置 // // 保存当前页面的滚动位置
if (from.fullPath) { // if (from.fullPath) {
scrollPositionMap.set(from.fullPath, window.scrollY) // scrollPositionMap.set(from.fullPath, window.scrollY)
} // }
next() // next()
}) // })
registerRouterGuard(router)
export default router export default router
import type { Router } from 'vue-router'
import { saveScrollPosition } from './scrollStorage'
import { parseCode, parseIsCodeLogin, parseIsCutEmail } from '@/utils/wxUtil'
import { useUserStore } from '@/stores'
// 白名单
const WHITE_LIST: string[] = []
export function registerRouterGuard(router: Router) {
router.beforeEach(async (to, from, next) => {
// 保存当前页面的滚动位置
if (from.fullPath) {
saveScrollPosition(from.fullPath, window.scrollY)
}
if (WHITE_LIST.includes(to.path)) {
next()
return
}
const code = parseCode()
// code是否来自企业微信 1 不是 0 是 2 开发人员登录方式
const isCodeLogin = parseIsCodeLogin()
const cutEmail = parseIsCutEmail()
console.log(code, isCodeLogin, cutEmail)
const userStore = useUserStore()
if (code) {
await userStore.getUserInfoByCode(code)
return next()
} else {
return next()
}
})
}
import type { RouterScrollBehavior } from 'vue-router'
// 滚动位置存储 Map
const scrollPositionMap = new Map<string, number>()
/**
* 保存滚动位置
* @param path 路由路径
* @param position 滚动位置
*/
export function saveScrollPosition(path: string, position: number): void {
scrollPositionMap.set(path, position)
}
/**
* 获取滚动位置
* @param path 路由路径
*/
export function getScrollPosition(path: string): number | undefined {
return scrollPositionMap.get(path)
}
/**
* 清除滚动位置
* @param path 路由路径(可选,不传则清除所有)
*/
export function clearScrollPosition(path?: string): void {
if (path) {
scrollPositionMap.delete(path)
} else {
scrollPositionMap.clear()
}
}
/**
* 路由滚动行为配置
*/
export const scrollBehavior: RouterScrollBehavior = (to, from, savedPosition) => {
return new Promise((resolve) => {
// 1. 如果有浏览器保存的位置(前进/后退),优先使用
if (savedPosition) {
resolve(savedPosition)
return
}
// 2. 如果有锚点,滚动到锚点
if (to.hash) {
resolve({
el: to.hash,
behavior: 'smooth', // 平滑滚动
})
return
}
// 3. 检查是否有保存的滚动位置
const savedScrollY = scrollPositionMap.get(to.fullPath)
if (savedScrollY !== undefined) {
resolve({
top: savedScrollY,
behavior: 'smooth',
})
return
}
// 4. 默认滚动到顶部
resolve({ top: 0 })
})
}
// 导出默认配置
export default scrollBehavior
import { defineStore } from 'pinia' import { defineStore } from 'pinia'
import { loginByEmail } from '@/api/login' import { loginByCode, loginByEmail } from '@/api/login'
import type { LoginResponseDto } from '@/api/login/types' import type { LoginResponseDto } from '@/api/login/types'
/** /**
* 关于用户的store * 关于用户的store
*/ */
export const useUserStore = defineStore('user', () => { export const useUserStore = defineStore('user', () => {
const userInfo = ref({} as LoginResponseDto) const userInfo = ref(JSON.parse(sessionStorage.getItem('userInfo') || '{}') as LoginResponseDto)
const token = ref(localStorage.getItem('token') || '') // const token = ref(localStorage.getItem('token') || '')
const token = ref(sessionStorage.getItem('token') || '')
// 获取用户信息 // 获取用户信息
const fetchUserInfo = async () => { const fetchUserInfo = async () => {
// { // {
...@@ -21,14 +22,31 @@ export const useUserStore = defineStore('user', () => { ...@@ -21,14 +22,31 @@ export const useUserStore = defineStore('user', () => {
email: 'lijiabin@yswg.com.cn', email: 'lijiabin@yswg.com.cn',
password: 'Lijiabin123.', password: 'Lijiabin123.',
}) })
// const { data } = await loginByEmail({
// email: 'wangshouyong@yswg.com.cn',
// password: 'Wsy123456!',
// })
setUserInfo(data)
}
// 根据code 获取用户信息
const getUserInfoByCode = async (code: string) => {
const { data } = await loginByCode({ code, isCodeLogin: 0 })
console.log(data)
setUserInfo(data) setUserInfo(data)
setToken(data.token)
} }
const setUserInfo = (info: LoginResponseDto) => { const setUserInfo = (info: LoginResponseDto) => {
userInfo.value = info userInfo.value = info
// session存一份
sessionStorage.setItem('userInfo', JSON.stringify(info))
} }
const setToken = (str: string) => { const setToken = (str: string) => {
token.value = str token.value = str
// session存一份
sessionStorage.setItem('token', str)
} }
return { userInfo, token, fetchUserInfo, setUserInfo, setToken } return { userInfo, token, fetchUserInfo, setUserInfo, setToken, getUserInfoByCode }
}) })
...@@ -22,6 +22,7 @@ export default class DhRequest { ...@@ -22,6 +22,7 @@ export default class DhRequest {
this.instance.interceptors.request.use( this.instance.interceptors.request.use(
async (config) => { async (config) => {
const userStore = useUserStore() const userStore = useUserStore()
console.log(userStore.token)
const token = userStore.token const token = userStore.token
if (token) { if (token) {
config.headers.Authorization = token config.headers.Authorization = token
......
import { app_config } from '@/config' import { app_config } from '@/config'
import Axois from './axios' import Axois from './axios'
// 'http://192.168.2.168:8089'
const baseUrl = app_config[import.meta.env.MODE]?.baseUrl const baseUrl = app_config[import.meta.env.MODE]?.baseUrl
export default new Axois({ export default new Axois({
baseURL: baseUrl, baseURL: 'http://192.168.2.168:8089',
timeout: 1000 * 60, timeout: 1000 * 60,
}) })
export * from './wxLogin' export * from './wxLogin'
export * from './initWXConfig' export * from './initWXConfig'
import wx from 'weixin-js-sdk' // import wx from 'weixin-js-sdk'
/** /**
* 分享 * 分享
...@@ -51,13 +51,13 @@ export function selectDepOrUser(wxOption = {}): Promise<ISelectDepOrUser> { ...@@ -51,13 +51,13 @@ export function selectDepOrUser(wxOption = {}): Promise<ISelectDepOrUser> {
const defaultOption = { const defaultOption = {
fromDepartmentId: -1, fromDepartmentId: -1,
mode: 'single', mode: 'single',
type: ['user', 'department'] type: ['user', 'department'],
// "selectedDepartmentIds": [], // "selectedDepartmentIds": [],
// "selectedUserIds": checkPerson // "selectedUserIds": checkPerson
} }
const option = { const option = {
...defaultOption, ...defaultOption,
...wxOption ...wxOption,
} }
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
wx.invoke('selectEnterpriseContact', option, function (res: ISelectDepOrUser) { wx.invoke('selectEnterpriseContact', option, function (res: ISelectDepOrUser) {
......
/* 企业微信js配置 */ /* 企业微信js配置 */
//注意:如果要在页面调用企业微信内部方法,请现在下面的jsApiList数组中添加方法 //注意:如果要在页面调用企业微信内部方法,请现在下面的jsApiList数组中添加方法
import { getWxSignature } from '@/api' // import { getWxSignature } from '@/api'
import wx from 'weixin-js-sdk' // import wx from 'weixin-js-sdk'
export async function initWxConfig() { export async function initWxConfig() {
const url = location.href.split('#')[0] const url = location.href.split('#')[0]
...@@ -20,8 +20,8 @@ export async function initWxConfig() { ...@@ -20,8 +20,8 @@ export async function initWxConfig() {
jsApiList: [ jsApiList: [
// 所有要调用的 API 都要加到这个列表中 // 所有要调用的 API 都要加到这个列表中
'shareAppMessage', 'shareAppMessage',
'selectEnterpriseContact' 'selectEnterpriseContact',
] ],
// success: function (result) { // success: function (result) {
// // 回调 // // 回调
// } // }
......
...@@ -17,11 +17,13 @@ export default defineConfig({ ...@@ -17,11 +17,13 @@ export default defineConfig({
// 'inline-block cursor-pointer select-none opacity-75 transition duration-200 ease-in-out hover:opacity-100 hover:text-teal-600', // 'inline-block cursor-pointer select-none opacity-75 transition duration-200 ease-in-out hover:opacity-100 hover:text-teal-600',
// ], // ],
// 放大的效果 // 放大的效果
['scale-bounce', '!-translate-y-1 !scale-105 transition-all duration-200'], // ['scale-bounce', '!-translate-y-1 !scale-105 transition-all duration-300'],
// 闪烁 // 闪烁
['flash', 'transition-all duration-300 opacity-30'], ['flash', 'transition-all duration-300 opacity-30'],
// 水平滑动 // 水平滑动
['shake-x', 'transition-all duration-150 -translate-x-1'], // ['shake-x', 'transition-all duration-300 -translate-x-1'],
// 垂直滑动 贝塞尔曲线
['shake-y', 'transition-all duration-300 -translate-y-5 ease-[cubic-bezier(0.33,1,0.68,1)]'],
// 垂直滑动 // 垂直滑动
['highlight', 'transition-all duration-300 shadow-[0_0_10px_rgba(255,200,0,0.7)]'], ['highlight', 'transition-all duration-300 shadow-[0_0_10px_rgba(255,200,0,0.7)]'],
], ],
......
...@@ -48,18 +48,24 @@ export default defineConfig({ ...@@ -48,18 +48,24 @@ export default defineConfig({
// 是否开启 https // 是否开启 https
// https: false as const, // https: false as const,
port: 3000, port: 3000,
proxy: { // proxy: {
'/api1': { // '/api1': {
target: 'http://192.168.2.168:8089', // 立鹏本地 // target: 'http://192.168.2.168:8089', // 立鹏本地
// target: 'http://192.168.2.55:8089', // 首拥本地 // // target: 'http://192.168.2.55:8089', // 首拥本地
changeOrigin: true, // changeOrigin: true,
rewrite: (path) => { // rewrite: (path) => {
return path.replace(/^\/api1/, '') // return path.replace(/^\/api1/, '')
}, // },
}, // },
}, // },
}, },
build: { build: {
minify: false, minify: false,
rollupOptions: {
output: {
assetFileNames: 'assets/[ext]/[name]-[hash][extname]',
chunkFileNames: 'assets/js/[name]-[hash].js',
},
},
}, },
}) })
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment