Переглянути джерело

🚧 修改闽南文化概况栏目ID

快乐的梦鱼 2 тижнів тому
батько
коміт
ffec4af375

+ 41 - 37
pages/about/index.vue

@@ -41,38 +41,44 @@
     <section v-if="mainTabActive == 0" class="main-section main-background main-background-type0">
       <div class="content">   
         <SimplePageContentLoader :loader="introdLoader">
-          <div class="d-flex justify-content-center">
-            <h2>{{ introdLoader.content.value?.introd1?.title || '无法找到数据' }}</h2>
-          </div>
-          <LeftRightBox 
-            class="mt-4"
-            :title="introdLoader.content.value?.introd1?.title"
-            :desc="introdLoader.content.value?.introd1?.content"
-            :image="introdLoader.content.value?.introd1?.image"
-            :rightItems="introdLoader.content.value?.list"
-            :moreLink="router.resolve({ path: '/introduction/about' }).href"
-          />
-          <div class="d-flex justify-content-center mt-5">
-            <h2>{{ introdLoader.content.value?.introd2?.title || '无法找到数据' }}</h2>
-          </div>
-          <LeftRightBox 
-            class="mt-4"
-            :title="introdLoader.content.value?.introd2?.title"
-            :desc="introdLoader.content.value?.introd2?.content"
-            :image="introdLoader.content.value?.introd2?.image"
-            :moreLink="router.resolve({ path: '/introduction/about' }).href"
-          />
-          <div class="d-flex justify-content-center mt-5">
-            <h2>{{ introdLoader.content.value?.introd3?.title || '无法找到数据' }}</h2>
-          </div>
-          <LeftRightBox 
-            class="mt-4"
-            :title="introdLoader.content.value?.introd3?.title"
-            :desc="introdLoader.content.value?.introd3?.content"
-            :image="introdLoader.content.value?.introd3?.image"
-            :showMore="false"
-            left
-          />
+          <template v-if="introdLoader.content.value?.introd1">
+            <div class="d-flex justify-content-center">
+              <h2>{{ introdLoader.content.value.introd1.title }}</h2>
+            </div>
+            <LeftRightBox 
+              class="mt-4"
+              :title="introdLoader.content.value.introd1.title"
+              :desc="introdLoader.content.value.introd1.content"
+              :image="introdLoader.content.value.introd1.image"
+              :rightItems="introdLoader.content.value?.list"
+              :moreLink="router.resolve({ path: '/introduction/about' }).href"
+            />
+          </template>
+          <template v-if="introdLoader.content.value?.introd2">
+            <div class="d-flex justify-content-center mt-5">
+              <h2>{{ introdLoader.content.value.introd2.title }}</h2>
+            </div>
+            <LeftRightBox 
+              class="mt-4"
+              :title="introdLoader.content.value.introd2.title"
+              :desc="introdLoader.content.value.introd2.content"
+              :image="introdLoader.content.value.introd2.image"
+              :moreLink="router.resolve({ path: '/introduction/about' }).href"
+            />
+          </template>
+          <template v-if="introdLoader.content.value?.introd3">
+            <div class="d-flex justify-content-center mt-5">
+              <h2>{{ introdLoader.content.value.introd3.title }}</h2>
+            </div>
+            <LeftRightBox 
+              class="mt-4"
+              :title="introdLoader.content.value.introd3.title"
+              :desc="introdLoader.content.value.introd3.content"
+              :image="introdLoader.content.value.introd3.image"
+              :showMore="false"
+              left
+            />
+          </template>
         </SimplePageContentLoader>
       </div> 
     </section>
@@ -179,15 +185,13 @@ const introdLoader = await useSSrSimpleDataLoader('introd', async () => {
         .setMainBodyColumnId([ 256, 283, 284 ])
       , 1, 10);
   const res2 = await CommonContent.getContentList(new GetContentListParams()
-        .setModelId(17)
-        .setMainBodyColumnId([ 256,283 ])
+        .setModelId(14)
+        .setMainBodyColumnId([ 233 ])
       , 1, 10);
 
-  const id1 = res2.list.find(item => item.title.includes('厦门'))?.id; 
+  const id1 = res2.list.find(item => item.title.includes('闽南文化生态保护区(厦门市)基本情况'))?.id; 
   const id2 = res2.list.find(item => item.title.includes('闽南') && !item.title.includes('厦门'))?.id; 
   const id3 = res2.list.find(item => item.title.includes('全国'))?.id; 
-
-  console.log(res2, id1, id2, id3);
   
   const introd1 = id1 ? (await NewsIndexContent.getContentDetail(id1)).toJSON() : undefined;
   const introd2 = id2 ? (await NewsIndexContent.getContentDetail(id2)).toJSON() : undefined;

+ 393 - 0
scripts/UpdateScript/app.mjs

@@ -0,0 +1,393 @@
+/**
+ * 更新发布工具
+ *
+ * Copyright © 2025 imengyu.top imengyu-update-server
+ */
+
+import { program } from 'commander';
+import { input, confirm, select } from '@inquirer/prompts';
+import { postAppUpdate, postWebUpdate } from './postUpdate.mjs';
+import Table from 'cli-table3';
+import { config } from './postConfig.mjs';
+import {
+  axiosInstance,
+  initAuth,
+  login,
+  logout,
+  checkLogged,
+} from './auth.mjs';
+
+// 版本相关
+//========================================
+
+export async function viewVersion(type) {
+  if (type === 'all' || !type) {
+    const data = await axiosInstance.get('/version/list');
+    const table = new Table({
+      head: ['ID', '版本'], 
+      colWidths: [10, 20 ]
+    });
+
+    data.data.forEach((d) => {
+      table.push([ d.id, d.version ]);
+    })
+    console.log(table.toString());
+  } else {
+    let data = null;
+    try {
+      if (Number.isNaN(Number(type)))
+        data = await axiosInstance.get('/version/get-by-name?name=' + type);
+      else
+        data = await axiosInstance.get('/version/' + type);
+    }
+    catch (e) {
+      console.error('Failed to load version info', e);
+      return;
+    }
+    const table = new Table({
+      head: ['key', 'data'], 
+      colWidths: [30, 60]
+    });
+    table.push(
+      [ 'ID', data.data.id ],
+      [ '状态', stateConstant[data.data.status] ],
+      [ '版本号', data.data.version ],
+      [ '创建时间', new Date(data.data.createAt).toString() ],
+      [ '设置', data.data.config ],
+      [ '激活的Web更新ID', data.data.webUpdateId ],
+      [ '激活的App更新ID', data.data.appUpdateId ],
+      [ '激活的下一个App更新ID', data.data.appUpdateNextId ],
+    );
+    console.log(table.toString());
+  }
+}
+export async function getVersion(action, type) {
+  switch(action) {
+    case 'view':
+      await viewVersion(type);
+      break;
+    case 'new': {
+      const version = await input({ message: 'Enter version name, like (1.0.0)' });
+      try {
+        await axiosInstance.post('/version', {
+          version: version,
+          status: 1,
+          config: "{}",
+        });
+        console.log('Add version success');
+      } catch (e) {
+        console.error('Failed to add version', e);
+      }
+      break;
+    }
+    case 'delete': {
+      const versionId = type ? type : await input({ message: '输入版本ID' });
+      if (!await confirm({ message: `确定删除版本 ${versionId}?`, default: false }))
+        return;
+      if (!await confirm({ message: '确认删除版本?此操作会删除所属版本的所有更新项目、存储等,无法恢复,是否确定删除?', default: false }))
+        return;
+      try {
+        await axiosInstance.delete('/version/' + versionId);
+        console.log('删除版本成功');
+      } catch (e) {
+        console.error('删除版本失败', e);
+      }
+      break;
+    }
+    case 'set-state': {
+      const versionId = type ? type : await input({ message: '输入版本ID' });
+      const state = await select({
+        message: '设置状态',
+        choices: [
+          {
+            name: 'NotEnable',
+            value: 0,
+          },
+          {
+            name: 'Normal',
+            value: 1,
+          },
+          {
+            name: 'Deprecated',
+            value: 2,
+          },
+        ],
+      });
+
+      try {
+        await axiosInstance.put('/version/' + versionId, {
+          status: state
+        });
+        console.log('设置状态成功');
+      } catch (e) {
+        console.error('设置状态失败', e);
+      }
+      break;
+    }
+    case 'set-config': {
+      const versionId = type ? type : await input({ message: '输入版本ID' });
+      const config = await input({ message: '输入配置Json' });
+      try {
+        await axiosInstance.put('/version/' + versionId, {
+          config: config,
+        });
+        console.log('设置配置成功');
+      } catch (e) {
+        console.error('设置配置失败', e);
+      }
+      break;
+    }
+    case 'set-active-app-update': {
+      const versionId = await input({ message: '输入版本ID' });
+      const updateId = await input({ message: '输入更新ID' });
+      const isNext = await confirm({ message: 'Set as next active?', default: false });
+
+      try {
+        await axiosInstance.post('/update/active/app', { versionId, updateId, isNext });
+        console.log('成功');
+      } catch (e) {
+        console.error('失败', e);
+      }
+      break;
+    }
+    case 'set-active-web-update': {
+      const versionId = await input({ message: '输入版本ID' });
+      const updateId = await input({ message: '输入更新ID' });
+
+      try {
+        await axiosInstance.post('/update/active/web', { versionId, updateId });
+        console.log('成功');
+      } catch (e) {
+        console.error('失败', e);
+      }
+      break;
+    }
+    default:
+      console.error('未知参数', action);
+      break;
+  }
+}
+
+//选择方法
+//========================================
+
+export async function selectVersion(defaultVersionId = null) {
+  const data = (await axiosInstance.get('/version/list?full=true&search=' + JSON.stringify({ 
+    appId: config.appId 
+  }))).data;
+  if (data.length === 0) {
+    console.error('没有版本');
+    return;
+  }
+  const versionId = (await select({
+    choices: data.map(p => ({
+      value: p.id,
+      name: p.version,
+    })),
+    default: defaultVersionId,
+    message: '选择一个版本',
+  }));
+  const versionName = data.find(p => p.id === versionId).version;
+  return { versionId, versionName };
+}
+export async function selectUpdate() {
+  const { versionId } = await selectVersion();
+  const data = (await axiosInstance.get('/version/update?search=' + JSON.stringify({ versionId }))).data;
+  if (data.length === 0) {
+    console.error('没有更新');
+    return;
+  }
+  const resultId = (await select({
+    choices: data.map(p => ({
+      value: p.id,
+      name: p.version,
+    })),
+    message: '选择一个更新',
+  }));
+  return resultId;
+}
+
+//更新相关
+//========================================
+
+const stateConstant = [ 'Deleted', 'Normal', 'Deprecated' ];
+const typeConstant = [ 'Unknow', 'Web', 'app' ];
+const storageTypeConstant = [ 'Unknow', 'LocalStorage', 'AliOSS' ];
+
+export async function postUpdate(type, options) {
+  switch (type) {
+    case 'web': {
+      await postWebUpdate(axiosInstance, options);
+      break;
+    }
+    case 'app': {
+      await postAppUpdate(axiosInstance, options);
+      break;
+    }
+    default:
+      console.error('Unknow type', type);
+      break;
+  }
+}
+export async function deprecateOrDeleteUpdate(updateId) {
+  if (!updateId)
+    updateId = await selectUpdate();
+
+  const { currentUpdateInfo, currentVersionInfo } = await viewUpdate(updateId);
+
+  const deprecate = (await select({
+    choices: [
+      {
+        name: 'Deprecate',
+        value: 0,
+      },
+      {
+        name: 'Delete',
+        value: 1,
+      },
+    ],
+    message: '删除或弃用?',
+  })) === 0;
+
+  if (deprecate && currentUpdateInfo.type !== 1) {
+    console.log('只有Web更新允许弃用');
+    return;
+  }
+  if (currentUpdateInfo.status === 0) {
+    console.log(`当前状态 ${stateConstant[currentUpdateInfo.status]} 无法弃用`);
+    return;
+  }
+
+  if (deprecate) {
+    if (!await confirm({ message: `确定弃用当前版本 ${currentUpdateInfo.versionCode} ?弃用会删除存储文件以及备份。此操作无法恢复!`, default: false }))
+      return;
+  } else {
+    if (!await confirm({ message: `确定删除当前版本 ${currentUpdateInfo.versionCode} ?此操作无法恢复!`, default: false }))
+      return;
+  }
+
+  try {
+    await axiosInstance.post(`/update/${deprecate ? 'deprecate' : 'delete'}`, { updateId });
+    console.log(`${deprecate ? '弃用' : '删除'} 成功`);
+  } catch (e) {
+    console.error(`操作失败`, e);
+  }
+}
+export async function viewUpdate(updateId) {
+  
+  let currentUpdateInfo = null
+  let currentVersionInfo = null
+
+  try {
+    currentUpdateInfo = (await axiosInstance.get('/update/' + updateId)).data;
+  } catch (e) {
+    console.error('加载更新信息失败', updateId);
+  }
+  try {
+    currentVersionInfo = (await axiosInstance.get('/version/' + currentUpdateInfo.versionId)).data;
+  } catch (e) {
+    console.error('加载版本信息失败', currentUpdateInfo.versionId);
+  }
+
+  const table = new Table({
+    head: ['key', 'data'], 
+    colWidths: [20, 40]
+  });
+  table.push(
+    [ 'ID', currentUpdateInfo.id ],
+    [ '所属应用', currentUpdateInfo.name ],
+    [ '更新信息', currentUpdateInfo.updateInfo ],
+    [ '版本号', currentUpdateInfo.versionCode ],
+    [ '创建时间', new Date(currentUpdateInfo.createAt).toString() ],
+    [ '类型', typeConstant[currentUpdateInfo.type] ],
+    [ '状态', stateConstant[currentUpdateInfo.status] ],
+    [ '强制更新', currentUpdateInfo.force ],
+    [ '公共访问路径', currentUpdateInfo.publicUrl ],
+    [ '存储类型', storageTypeConstant[currentUpdateInfo.storageType] ],
+    [ '存储路径', currentUpdateInfo.storagePath ],
+  );
+
+  console.log(table.toString());
+
+  if (currentUpdateInfo.activeWebVersionName) {
+    table.push(
+      [ '使用中的Web版本', currentUpdateInfo.activeWebVersionName ],
+    );
+  }
+  if (currentUpdateInfo.activeAppVersionName) {
+    table.push(
+      [ '使用中的App版本', currentUpdateInfo.activeAppVersionName ],
+    );
+  }
+
+  return {
+    currentUpdateInfo,
+    currentVersionInfo,
+  }
+}
+export async function getUpdate(action, type, all, options) {
+  const typeNotANumber = isNaN(new Number(type)); 
+
+  if (action === 'view' && (!type || typeNotANumber)) {
+
+    let hasSerch = false;
+    const search = {};
+    const sort = {
+      field: "createAt",
+      order: "descend"
+    }
+    if (typeNotANumber && type !== 'all') {
+      hasSerch = true;
+      search.version = type;
+    }
+
+    const data = await axiosInstance.get('/update/list?full=true' + (hasSerch ? ('&search=' + JSON.stringify(search)) : '') + '&sort=' + JSON.stringify(sort));
+    const table = new Table({
+      head: ['ID', '版本', '版本号', '类型', '状态'], 
+      colWidths: [10, 10, 20, 10, 15 ]
+    });
+
+    if (type !== 'all' && all !== 'all' && data.data.length > 10) {
+      data.data = data.data.slice(0, 10);
+      console.log('filter!', all);
+    }
+
+    data.data.forEach((d) => {
+      table.push([ 
+        d.id, 
+        (d.activeAppVersionName ? `${d.activeAppVersionName} (App)` : (
+          d.activeWebVersionName? `${d.activeWebVersionName} (Web)` : '无'
+        )),  
+        d.versionCode, typeConstant[d.type], stateConstant[d.status] 
+      ]);
+    })
+    
+    console.log(table.toString());
+  } else {
+    switch (action) {
+      case 'post': {
+        await postUpdate(type, options);
+        break;
+      }
+      case 'delete': {
+        await deprecateOrDeleteUpdate(type);
+        break;
+      }
+      case 'view': {
+        viewUpdate(type);
+        break;
+      }
+      default: 
+        console.error('Unknow action', action);
+        break;
+    }
+  }
+}
+export async function testVersion(version) {
+  try {
+    const data = await axiosInstance.get('/update-get-info?version=' + version);
+    console.log('版本信息', data.data);
+  } catch(e) {
+    console.log('获取失败', e);
+  }
+}

+ 148 - 0
scripts/UpdateScript/auth.mjs

@@ -0,0 +1,148 @@
+/**
+ * 更新发布工具 - 认证与服务器连接模块
+ *
+ * Copyright © 2025 imengyu.top imengyu-update-server
+ */
+
+import { readFile, writeFile } from 'node:fs/promises';
+import path from 'node:path';
+import { dirname } from 'node:path';
+import { fileURLToPath } from 'node:url';
+import axios from 'axios';
+import md5 from 'md5';
+import { password } from '@inquirer/prompts';
+import { config } from './postConfig.mjs';
+
+// 基础配置
+// ========================================
+
+const __filename = fileURLToPath(import.meta.url);
+const __dirname = dirname(__filename);
+
+export const constant = {
+  ServerUrl: config.server,
+  TokenSave: path.resolve(__dirname, './_token.json'),
+};
+
+let currentData = {
+  token: '',
+  identifier: '',
+};
+
+function initIdentifier() {
+  if (!currentData.identifier)
+    currentData.identifier = `commandClient${Math.floor(Math.random() * 1000)}`;
+}
+
+function getErrorMessage(e) {
+  return e instanceof Error ? e.message : (typeof e === 'object' ? e : '' + e);
+}
+
+// Axios 实例
+// ========================================
+
+export const axiosInstance = axios.create({
+  baseURL: constant.ServerUrl,
+  timeoutErrorMessage: '请求超时,请检查网络连接',
+  responseType: 'json',
+  withCredentials: false,
+  validateStatus: () => true,
+});
+
+axiosInstance.interceptors.request.use((value) => {
+  value.headers['authorization'] = JSON.stringify({
+    auth: currentData?.token?.authName,
+    validity: currentData?.token?.authKey,
+    nonce: 'aaaaaaaaaa',
+    identifier: currentData.identifier,
+    key: 'abc123',
+  });
+  value.url =
+    value.url +
+    (value.url.includes('?') ? '&' : '?') +
+    `identifier=${currentData.identifier}`;
+  return value;
+});
+
+axiosInstance.interceptors.response.use((value) => {
+  if (value.data.success) return value.data;
+  else return Promise.reject(value.data);
+});
+
+// 认证初始化
+// ========================================
+
+/**
+ * 初始化认证模块,从本地加载 token
+ * @returns {Promise<void>}
+ */
+export async function initAuth() {
+  try {
+    const res = await readFile(constant.TokenSave);
+    const parsed = JSON.parse(res);
+    if (parsed && typeof parsed === 'object') {
+      currentData = { ...currentData, ...parsed };
+    }
+  } catch {
+    // 无 token 文件时使用默认值
+  }
+  initIdentifier();
+}
+
+/**
+ * 获取当前认证数据(只读)
+ */
+export function getCurrentData() {
+  return { ...currentData };
+}
+
+// 登录相关
+// ========================================
+
+/**
+ * 检查登录状态
+ */
+export async function checkLogged() {
+  try {
+    await axiosInstance.get('/auth');
+    console.log('已登录');
+  } catch (e) {
+    console.error('获取状态失败:', getErrorMessage(e));
+  }
+}
+
+/**
+ * 登录
+ * @param {string} user 用户名
+ */
+export async function login(user) {
+  try {
+    const pass = await password({ message: '输入密码' });
+    const res = await axiosInstance.post('/auth?rember=true', {
+      method: 'key',
+      key: `${user}@${md5(pass)}`,
+    });
+    currentData.token = {
+      authName: res.data.authName,
+      authKey: res.data.authKey,
+    };
+    await writeFile(constant.TokenSave, JSON.stringify(currentData));
+    console.log('登录成功');
+  } catch (e) {
+    console.error('登录失败', getErrorMessage(e));
+  }
+}
+
+/**
+ * 退出登录
+ */
+export async function logout() {
+  currentData.token = '';
+  await writeFile(constant.TokenSave, JSON.stringify(currentData));
+  try {
+    await axiosInstance.delete('/auth');
+    console.log('退出登录成功');
+  } catch (e) {
+    console.error('退出登录失败', getErrorMessage(e));
+  }
+}

+ 5 - 484
scripts/UpdateScript/index.mjs

@@ -1,493 +1,12 @@
 /**
  * 更新发布工具
- * 
+ *
  * Copyright © 2025 imengyu.top imengyu-update-server
  */
 
 import { program } from 'commander';
-import { password, input, confirm, select } from '@inquirer/prompts';
-import { writeFile, readFile } from 'node:fs/promises';
-import { postAppUpdate, postWebUpdate } from './postUpdate.mjs';
-import Table from 'cli-table3';
-import md5 from 'md5';
-import axios from 'axios';
-import path from 'path';
-import { dirname } from 'node:path';
-import { fileURLToPath } from 'node:url';
-import { config } from './postConfig.mjs';
-
-//基础配置
-//========================================
-
-const __filename = fileURLToPath(import.meta.url);
-const __dirname = dirname(__filename);
-const constant = {
-  ServerUrl: config.server,
-  TokenSave: path.resolve(__dirname, './_token.json'),
-};
-let currentData = {
-  token: '',
-  identifier: '',
-};
-
-function initIdentifier() {
-  if (!currentData.identifier) 
-    currentData.identifier = `commandClient${Math.floor(Math.random() * 1000)}`;
-}
-
-readFile(constant.TokenSave).then((res) => {
-  currentData = JSON.parse(res);
-  initIdentifier();
-  start();
-}).catch(() => {
-  initIdentifier();
-  start();
-})
-
-const axiosInstance = axios.create({
-  baseURL: constant.ServerUrl,
-  timeoutErrorMessage: '请求超时,请检查网络连接',
-  responseType: 'json',
-  withCredentials: false,
-  validateStatus: () => true,
-});
-
-axiosInstance.interceptors.request.use((value) => {
-  value.headers['authorization'] = JSON.stringify({
-    auth: currentData?.token?.authName,
-    validity: currentData?.token?.authKey,
-    nonce: "aaaaaaaaaa",
-    identifier: currentData.identifier,
-    key: 'abc123',
-  });  
-  value.url = value.url + (value.url.includes('?') ? '&' : '?' ) + `identifier=${currentData.identifier}`
-  return value;
-});
-axiosInstance.interceptors.response.use((value) => {
-  if (value.data.success) 
-    return value.data;
-  else
-    return Promise.reject(value.data);
-});
-
-function getErrorMessage(e) {
-  return e instanceof Error ? e.message : (typeof e === 'object' ? JSON.stringify(e) : ('' + e));
-}
-
-//登录相关
-//========================================
-
-async function checkLogged() {
-  try {
-    await axiosInstance.get('/auth');
-    console.log('已登录');
-  } catch (e) {
-    console.error('获取状态失败:', getErrorMessage(e));
-  }
-}
-async function login(user) {
-  try {
-    const pass = await password({ message: '输入密码' });
-    const res = await axiosInstance.post('/auth?rember=true', {
-      method: 'key',
-      key: `${user}@${md5(pass)}`,
-    })
-    currentData.token = {
-      authName: res.data.authName,
-      authKey: res.data.authKey,
-    };
-    writeFile(constant.TokenSave, JSON.stringify(currentData));
-    console.log('登录成功');
-  } catch (e) {
-    console.error('登录失败', getErrorMessage(e));
-  }
-}
-async function logout() {
-  currentToken = '';
-  writeFile(constant.TokenSave, JSON.stringify(currentData));
-  try {
-    await axiosInstance.delete('/auth');
-    console.log('退出登录成功');
-  } catch(e) {
-    console.error('退出登录失败', getErrorMessage(e));
-  }
-}
-
-//版本相关
-//========================================
-
-async function viewVersion(type) {
-  if (type === 'all' || !type) {
-    const data = await axiosInstance.get('/version/list');
-    const table = new Table({
-      head: ['ID', '版本'], 
-      colWidths: [10, 20 ]
-    });
-
-    data.data.forEach((d) => {
-      table.push([ d.id, d.version ]);
-    })
-    console.log(table.toString());
-  } else {
-    let data = null;
-    try {
-      if (Number.isNaN(Number(type)))
-        data = await axiosInstance.get('/version/get-by-name?name=' + type);
-      else
-        data = await axiosInstance.get('/version/' + type);
-    }
-    catch (e) {
-      console.error('Failed to load version info', e);
-      return;
-    }
-    const table = new Table({
-      head: ['key', 'data'], 
-      colWidths: [30, 60]
-    });
-    table.push(
-      [ 'ID', data.data.id ],
-      [ '状态', stateConstant[data.data.status] ],
-      [ '版本号', data.data.version ],
-      [ '创建时间', new Date(data.data.createAt).toString() ],
-      [ '设置', data.data.config ],
-      [ '激活的Web更新ID', data.data.webUpdateId ],
-      [ '激活的App更新ID', data.data.appUpdateId ],
-      [ '激活的下一个App更新ID', data.data.appUpdateNextId ],
-    );
-    console.log(table.toString());
-  }
-}
-async function getVersion(action, type) {
-  switch(action) {
-    case 'view':
-      await viewVersion(type);
-      break;
-    case 'new': {
-      const version = await input({ message: 'Enter version name, like (1.0.0)' });
-      try {
-        await axiosInstance.post('/version', {
-          version: version,
-          status: 1,
-          config: "{}",
-        });
-        console.log('Add version success');
-      } catch (e) {
-        console.error('Failed to add version', e);
-      }
-      break;
-    }
-    case 'delete': {
-      const versionId = type ? type : await input({ message: '输入版本ID' });
-      if (!await confirm({ message: `确定删除版本 ${versionId}?`, default: false }))
-        return;
-      if (!await confirm({ message: '确认删除版本?此操作会删除所属版本的所有更新项目、存储等,无法恢复,是否确定删除?', default: false }))
-        return;
-      try {
-        await axiosInstance.delete('/version/' + versionId);
-        console.log('删除版本成功');
-      } catch (e) {
-        console.error('删除版本失败', e);
-      }
-      break;
-    }
-    case 'set-state': {
-      const versionId = type ? type : await input({ message: '输入版本ID' });
-      const state = await select({
-        message: '设置状态',
-        choices: [
-          {
-            name: 'NotEnable',
-            value: 0,
-          },
-          {
-            name: 'Normal',
-            value: 1,
-          },
-          {
-            name: 'Deprecated',
-            value: 2,
-          },
-        ],
-      });
-
-      try {
-        await axiosInstance.put('/version/' + versionId, {
-          status: state
-        });
-        console.log('设置状态成功');
-      } catch (e) {
-        console.error('设置状态失败', e);
-      }
-      break;
-    }
-    case 'set-config': {
-      const versionId = type ? type : await input({ message: '输入版本ID' });
-      const config = await input({ message: '输入配置Json' });
-      try {
-        await axiosInstance.put('/version/' + versionId, {
-          config: config,
-        });
-        console.log('设置配置成功');
-      } catch (e) {
-        console.error('设置配置失败', e);
-      }
-      break;
-    }
-    case 'set-active-app-update': {
-      const versionId = await input({ message: '输入版本ID' });
-      const updateId = await input({ message: '输入更新ID' });
-      const isNext = await confirm({ message: 'Set as next active?', default: false });
-
-      try {
-        await axiosInstance.post('/update/active/app', { versionId, updateId, isNext });
-        console.log('成功');
-      } catch (e) {
-        console.error('失败', e);
-      }
-      break;
-    }
-    case 'set-active-web-update': {
-      const versionId = await input({ message: '输入版本ID' });
-      const updateId = await input({ message: '输入更新ID' });
-
-      try {
-        await axiosInstance.post('/update/active/web', { versionId, updateId });
-        console.log('成功');
-      } catch (e) {
-        console.error('失败', e);
-      }
-      break;
-    }
-    default:
-      console.error('未知参数', action);
-      break;
-  }
-}
-
-//选择方法
-//========================================
-
-export async function selectVersion(defaultVersionId = null) {
-  const data = (await axiosInstance.get('/version/list?full=true&search=' + JSON.stringify({ 
-    appId: config.appId 
-  }))).data;
-  if (data.length === 0) {
-    console.error('没有版本');
-    return;
-  }
-  const versionId = (await select({
-    choices: data.map(p => ({
-      value: p.id,
-      name: p.version,
-    })),
-    default: defaultVersionId,
-    message: '选择一个版本',
-  }));
-  const versionName = data.find(p => p.id === versionId).version;
-  return { versionId, versionName };
-}
-export async function selectUpdate() {
-  const { versionId } = await selectVersion();
-  const data = (await axiosInstance.get('/version/update?search=' + JSON.stringify({ versionId }))).data;
-  if (data.length === 0) {
-    console.error('没有更新');
-    return;
-  }
-  const resultId = (await select({
-    choices: data.map(p => ({
-      value: p.id,
-      name: p.version,
-    })),
-    default: defaultVersionId,
-    message: '选择一个更新',
-  }));
-  return resultId;
-}
-
-//更新相关
-//========================================
-
-const stateConstant = [ 'Deleted', 'Normal', 'Deprecated' ];
-const typeConstant = [ 'Unknow', 'Web', 'app' ];
-const storageTypeConstant = [ 'Unknow', 'LocalStorage', 'AliOSS' ];
-
-async function postUpdate(type, options) {
-  switch (type) {
-    case 'web': {
-      await postWebUpdate(axiosInstance, options);
-      break;
-    }
-    case 'app': {
-      await postAppUpdate(axiosInstance, options);
-      break;
-    }
-    default:
-      console.error('Unknow type', type);
-      break;
-  }
-}
-async function deprecateOrDeleteUpdate(updateId) {
-  if (!updateId)
-    updateId = await selectUpdate();
-
-  const { currentUpdateInfo, currentVersionInfo } = await viewUpdate(updateId);
-
-  const deprecate = (await select({
-    choices: [
-      {
-        name: 'Deprecate',
-        value: 0,
-      },
-      {
-        name: 'Delete',
-        value: 1,
-      },
-    ],
-    message: '删除或弃用?',
-  })) === 0;
-
-  if (deprecate && currentUpdateInfo.type !== 1) {
-    console.log('只有Web更新允许弃用');
-    return;
-  }
-  if (currentUpdateInfo.status === 0) {
-    console.log(`当前状态 ${stateConstant[currentUpdateInfo.status]} 无法弃用`);
-    return;
-  }
-
-  if (deprecate) {
-    if (!await confirm({ message: `确定弃用当前版本 ${currentUpdateInfo.versionCode} ?弃用会删除存储文件以及备份。此操作无法恢复!`, default: false }))
-      return;
-  } else {
-    if (!await confirm({ message: `确定删除当前版本 ${currentUpdateInfo.versionCode} ?此操作无法恢复!`, default: false }))
-      return;
-  }
-
-  try {
-    await axiosInstance.post(`/update/${deprecate ? 'deprecate' : 'delete'}`, { updateId });
-    console.log(`${deprecate ? '弃用' : '删除'} 成功`);
-  } catch (e) {
-    console.error(`操作失败`, e);
-  }
-}
-async function viewUpdate(updateId) {
-  
-  let currentUpdateInfo = null
-  let currentVersionInfo = null
-
-  try {
-    currentUpdateInfo = (await axiosInstance.get('/update/' + updateId)).data;
-  } catch (e) {
-    console.error('加载更新信息失败', updateId);
-  }
-  try {
-    currentVersionInfo = (await axiosInstance.get('/version/' + currentUpdateInfo.versionId)).data;
-  } catch (e) {
-    console.error('加载版本信息失败', currentUpdateInfo.versionId);
-  }
-
-  const table = new Table({
-    head: ['key', 'data'], 
-    colWidths: [20, 40]
-  });
-  table.push(
-    [ 'ID', currentUpdateInfo.id ],
-    [ '所属应用', currentUpdateInfo.name ],
-    [ '更新信息', currentUpdateInfo.updateInfo ],
-    [ '版本号', currentUpdateInfo.versionCode ],
-    [ '创建时间', new Date(currentUpdateInfo.createAt).toString() ],
-    [ '类型', typeConstant[currentUpdateInfo.type] ],
-    [ '状态', stateConstant[currentUpdateInfo.status] ],
-    [ '强制更新', currentUpdateInfo.force ],
-    [ '公共访问路径', currentUpdateInfo.publicUrl ],
-    [ '存储类型', storageTypeConstant[currentUpdateInfo.storageType] ],
-    [ '存储路径', currentUpdateInfo.storagePath ],
-  );
-
-  console.log(table.toString());
-
-  if (currentUpdateInfo.activeWebVersionName) {
-    table.push(
-      [ '使用中的Web版本', currentUpdateInfo.activeWebVersionName ],
-    );
-  }
-  if (currentUpdateInfo.activeAppVersionName) {
-    table.push(
-      [ '使用中的App版本', currentUpdateInfo.activeAppVersionName ],
-    );
-  }
-
-  return {
-    currentUpdateInfo,
-    currentVersionInfo,
-  }
-}
-async function getUpdate(action, type, all, options) {
-  const typeNotANumber = isNaN(new Number(type)); 
-
-  if (action === 'view' && (!type || typeNotANumber)) {
-
-    let hasSerch = false;
-    const search = {};
-    const sort = {
-      field: "createAt",
-      order: "descend"
-    }
-    if (typeNotANumber && type !== 'all') {
-      hasSerch = true;
-      search.version = type;
-    }
-
-    const data = await axiosInstance.get('/update/list?full=true' + (hasSerch ? ('&search=' + JSON.stringify(search)) : '') + '&sort=' + JSON.stringify(sort));
-    const table = new Table({
-      head: ['ID', '版本', '版本号', '类型', '状态'], 
-      colWidths: [10, 10, 20, 10, 15 ]
-    });
-
-    if (type !== 'all' && all !== 'all' && data.data.length > 10) {
-      data.data = data.data.slice(0, 10);
-      console.log('filter!', all);
-    }
-
-    data.data.forEach((d) => {
-      table.push([ 
-        d.id, 
-        (d.activeAppVersionName ? `${d.activeAppVersionName} (App)` : (
-          d.activeWebVersionName? `${d.activeWebVersionName} (Web)` : '无'
-        )),  
-        d.versionCode, typeConstant[d.type], stateConstant[d.status] 
-      ]);
-    })
-    
-    console.log(table.toString());
-  } else {
-    switch (action) {
-      case 'post': {
-        await postUpdate(type, options);
-        break;
-      }
-      case 'delete': {
-        await deprecateOrDeleteUpdate(type);
-        break;
-      }
-      case 'view': {
-        viewUpdate(type);
-        break;
-      }
-      default: 
-        console.error('Unknow action', action);
-        break;
-    }
-  }
-}
-async function testVersion(version) {
-  try {
-    const data = await axiosInstance.get('/update-get-info?version=' + version);
-    console.log('版本信息', data.data);
-  } catch(e) {
-    console.log('获取失败', e);
-  }
-}
+import { login, checkLogged, logout, initAuth } from './auth.mjs';
+import { testVersion, getVersion, getUpdate } from './app.mjs';
 
 //程序入口
 //============================================
@@ -525,6 +44,8 @@ function start() {
   program.parse(process.argv);
 }
 
+initAuth().then(start);
+
 process.on('unhandledRejection', (reason, p) => {
   console.error('Promise: ', p, 'Reason: ', reason)
 })

+ 12 - 12
scripts/UpdateScript/postUpdate.mjs

@@ -9,7 +9,7 @@ import { writeFile, readFile, access, unlink, stat, constants } from 'node:fs/pr
 import path from 'node:path';
 import OSS from 'ali-oss';
 import cliProgress from 'cli-progress';
-import { selectVersion } from './index.mjs';
+import { selectVersion } from './app.mjs';
 import { config } from './postConfig.mjs';
 import { readFileRange, compressZip, execAsync } from './utils.mjs';
 import { dirname } from 'node:path';
@@ -54,11 +54,11 @@ async function getUpdateInfo(postConfig) {
   }
   return updateInfo;
 }
-async function uploadMulitPartLarge(axiosInstance, fileInfo, filePath) {
+export async function uploadMulitPartLarge(axiosInstance, fileInfo, filePath) {
 
   console.log('开始分片上传');
 
-  const multuploadInfo = (await axiosInstance.post('/update-large-token', {
+  const multuploadInfo = (await axiosInstance.post('/update/update-large-token', {
     fileSize: fileInfo.size,
     fileName: path.basename(filePath)
   })).data;
@@ -76,7 +76,7 @@ async function uploadMulitPartLarge(axiosInstance, fileInfo, filePath) {
     subFormData.append("key", multuploadInfo.key);
     subFormData.append("info", JSON.stringify({}));
     
-    (await axiosInstance.post('/update-large', subFormData, {
+    (await axiosInstance.post('/update/update-large', subFormData, {
       headers: { 'Content-Type': 'multipart/form-data' }
     })).data;
     
@@ -148,7 +148,7 @@ export async function postAppUpdate(axiosInstance, param) {
   const { versionId, versionName } = await selectVersion(postConfig.lastVersion);
   const updateInfo = await getUpdateInfo(postConfig);
 
-  const serverConfig = await axiosInstance.post('/update-post', { 
+  const serverConfig = await axiosInstance.post('/update/update-post', { 
     config: { 
       type: 2, 
       test: true, 
@@ -188,7 +188,7 @@ export async function postAppUpdate(axiosInstance, param) {
   else if (updateSource === 'uploaded-alioss') {
     const fileName = await input({ message: '输入已上传的阿里OSS文件路径' });
     try {
-      const result = (await axiosInstance.post('/update-post', {
+      const result = (await axiosInstance.post('/update/update-post', {
         type: 2,
         ossConfig: {
           ossPath: fileName,
@@ -243,7 +243,7 @@ export async function postAppUpdate(axiosInstance, param) {
     }));
 
     try {
-      const result = (await axiosInstance.post('/update-post', formData, {
+      const result = (await axiosInstance.post('/update/update-post', formData, {
         headers: { 'Content-Type': 'multipart/form-data' }
       })).data;
       console.log('上传成功');
@@ -262,7 +262,7 @@ export async function postAppUpdate(axiosInstance, param) {
     } else if (serverConfig.uploadAppType === UPLOAD_APP_TYPE_ALI_OSS) {
       //阿里OSS上传
       //请求STS进行临时授权
-      const stsToken = (await axiosInstance.post('/update-ali-oss-sts')).data;
+      const stsToken = (await axiosInstance.post('/update/update-ali-oss-sts')).data;
       const client = new OSS({
         region: stsToken.Region,
         accessKeyId: stsToken.AccessKeyId,
@@ -270,7 +270,7 @@ export async function postAppUpdate(axiosInstance, param) {
         stsToken: stsToken.SecurityToken,
         bucket: stsToken.Bucket,
         refreshSTSToken: async () => {
-          const refreshToken = (await axiosInstance.get("/update-ali-oss-sts")).data;
+          const refreshToken = (await axiosInstance.get("/update/update-ali-oss-sts")).data;
           return {
             accessKeyId: refreshToken.AccessKeyId,
             accessKeySecret: refreshToken.AccessKeySecret,
@@ -308,7 +308,7 @@ export async function postAppUpdate(axiosInstance, param) {
     }
   
     try {
-      const result = (await axiosInstance.post('/update-post', {
+      const result = (await axiosInstance.post('/update/update-post', {
         config: {
           type: 2,
           ossConfig,
@@ -346,7 +346,7 @@ export async function postWebUpdate(axiosInstance, param) {
   postConfig.lastUpdateInfo = updateInfo;
   postConfig.lastTodaySubVersion++;
 
-  await axiosInstance.post('/update-post', { config: { type: 1, test: true, versionId, uploadWebConfig: config.uploadWebConfig, submitKey: config.submitKey } });
+  await axiosInstance.post('/update/update-post', { config: { type: 1, test: true, versionId, uploadWebConfig: config.uploadWebConfig, submitKey: config.submitKey } });
 
   const now = new Date();
   const versionCode = await config.buildWebVersionGenerateCommand(now, postConfig.lastTodaySubVersion);
@@ -404,7 +404,7 @@ export async function postWebUpdate(axiosInstance, param) {
 
   try {
     formData.append("config", JSON.stringify(submitConfig));
-    const result = (await axiosInstance.post('/update-post', formData, {
+    const result = (await axiosInstance.post('/update/update-post', formData, {
       headers: { 'Content-Type': 'multipart/form-data' }
     })).data;
     console.log('上传成功');

+ 11 - 1
scripts/UpdateScript/utils.mjs

@@ -49,6 +49,13 @@ export function execAsync(command) {
     });
   })
 }
+/**
+ * 压缩zip
+ * @param {string} dir 压缩目录
+ * @param {string} targetFilePath 压缩文件路径
+ * @param {string[]} skipFiles 压缩忽略文件 支持按开头匹配,和文件类型通配符匹配,如:'*.js', '*.css', '*.html'
+ * @returns {Promise<void>}
+ */
 export async function compressZip(dir, targetFilePath, skipFiles) {
   const output = fs.createWriteStream(targetFilePath);
   const archive = archiver('zip', {
@@ -63,7 +70,10 @@ export async function compressZip(dir, targetFilePath, skipFiles) {
       return true;
     if (!skipFiles || skipFiles.length === 0)
       return;
-    return skipFiles.find((item) => pathString.startsWith(item) || pathString.startsWith(`/${item}`));
+    return skipFiles.find((item) => (
+      pathString.startsWith(item) || pathString.startsWith(`/${item}`)
+      || (item.includes('*.') && pathString.endsWith(item.split('*')[1]))
+    ));
   }
 
   async function loopDir(path, subPrefix) {