提交格式验证修改

This commit is contained in:
2026-08-13 13:34:14 +08:00
parent 3e5157909c
commit 181b8902e1
27 changed files with 220 additions and 579 deletions
-6
View File
@@ -37,7 +37,6 @@
"fmt": "oxfmt",
"gen-route": "sa gen-route",
"lint": "oxlint --fix && eslint --fix .",
"prepare": "simple-git-hooks",
"preview": "vite preview",
"release": "sa release",
"typecheck": "vue-tsc --noEmit --skipLibCheck",
@@ -108,7 +107,6 @@
"oxlint": "^1.64.0",
"pro-naive-ui-resolver": "1.0.2",
"sass": "1.99.0",
"simple-git-hooks": "2.13.1",
"tsx": "4.21.0",
"typescript": "6.0.3",
"unocss": "^66.6.8",
@@ -122,10 +120,6 @@
"vue-eslint-parser": "10.4.0",
"vue-tsc": "3.2.8"
},
"simple-git-hooks": {
"commit-msg": "pnpm sa git-commit-verify",
"pre-commit": "pnpm typecheck && pnpm lint && pnpm fmt && git diff --exit-code"
},
"engines": {
"node": ">=20.19.0",
"pnpm": ">=10.5.0"
+22
View File
@@ -0,0 +1,22 @@
{
"name": "@sa/alova",
"version": "2.2.0",
"typesVersions": {
"*": {
"*": [
"./src/*"
]
}
},
"exports": {
".": "./src/index.ts",
"./fetch": "./src/fetch.ts",
"./client": "./src/client.ts",
"./mock": "./src/mock.ts"
},
"dependencies": {
"@alova/mock": "2.0.20",
"@sa/utils": "workspace:*",
"alova": "3.5.1"
}
}
+1
View File
@@ -0,0 +1 @@
export * from 'alova/client';
+2
View File
@@ -0,0 +1,2 @@
/** the backend error code key */
export const BACKEND_ERROR_CODE = 'BACKEND_ERROR';
+2
View File
@@ -0,0 +1,2 @@
import adapterFetch from 'alova/fetch';
export default adapterFetch;
+77
View File
@@ -0,0 +1,77 @@
import { createAlova } from 'alova';
import type { AlovaDefaultCacheAdapter, AlovaGenerics, AlovaGlobalCacheAdapter, AlovaRequestAdapter } from 'alova';
import VueHook from 'alova/vue';
import type { VueHookType } from 'alova/vue';
import adapterFetch from 'alova/fetch';
import { createServerTokenAuthentication } from 'alova/client';
import type { FetchRequestInit } from 'alova/fetch';
import { BACKEND_ERROR_CODE } from './constant';
import type { CustomAlovaConfig, RequestOptions } from './type';
export const createAlovaRequest = <
RequestConfig = FetchRequestInit,
ResponseType = Response,
ResponseHeader = Headers,
L1Cache extends AlovaGlobalCacheAdapter = AlovaDefaultCacheAdapter,
L2Cache extends AlovaGlobalCacheAdapter = AlovaDefaultCacheAdapter
>(
customConfig: CustomAlovaConfig<
AlovaGenerics<any, any, RequestConfig, ResponseType, ResponseHeader, L1Cache, L2Cache, any>
>,
options: RequestOptions<AlovaGenerics<any, any, RequestConfig, ResponseType, ResponseHeader, L1Cache, L2Cache, any>>
) => {
const { tokenRefresher } = options;
const { onAuthRequired, onResponseRefreshToken } = createServerTokenAuthentication<
VueHookType,
AlovaRequestAdapter<RequestConfig, ResponseType, ResponseHeader>
>({
refreshTokenOnSuccess: {
isExpired: (response, method) => tokenRefresher?.isExpired(response, method) || false,
handler: async (response, method) => tokenRefresher?.handler(response, method)
},
refreshTokenOnError: {
isExpired: (response, method) => tokenRefresher?.isExpired(response, method) || false,
handler: async (response, method) => tokenRefresher?.handler(response, method)
}
});
const instance = createAlova({
...customConfig,
timeout: customConfig.timeout ?? 10 * 1000,
requestAdapter: (customConfig.requestAdapter as any) ?? adapterFetch(),
statesHook: VueHook,
beforeRequest: onAuthRequired(options.onRequest as any),
responded: onResponseRefreshToken({
onSuccess: async (response, method) => {
// check if http status is success
let error: any = null;
let transformedData: any = null;
try {
if (await options.isBackendSuccess(response)) {
transformedData = await options.transformBackendResponse(response);
} else {
error = new Error('the backend request error');
error.code = BACKEND_ERROR_CODE;
}
} catch (err) {
error = err;
}
if (error) {
await options.onError?.(error, response, method);
throw error;
}
return transformedData;
},
onComplete: options.onComplete,
onError: (error, method) => options.onError?.(error, null, method)
})
});
return instance;
};
export { BACKEND_ERROR_CODE };
export type * from './type';
export type * from 'alova';
+1
View File
@@ -0,0 +1 @@
export * from '@alova/mock';
+52
View File
@@ -0,0 +1,52 @@
import type { AlovaGenerics, AlovaOptions, AlovaRequestAdapter, Method, ResponseCompleteHandler } from 'alova';
export type CustomAlovaConfig<AG extends AlovaGenerics> = Omit<
AlovaOptions<AG>,
'statesHook' | 'beforeRequest' | 'responded' | 'requestAdapter'
> & {
/** request adapter. all request of alova will be sent by it. */
requestAdapter?: AlovaRequestAdapter<AG['RequestConfig'], AG['Response'], AG['ResponseHeader']>;
};
export interface RequestOptions<AG extends AlovaGenerics> {
/**
* The hook before request
*
* For example: You can add header token in this hook
*
* @param method alova Method Instance
*/
onRequest?: AlovaOptions<AG>['beforeRequest'];
/**
* The hook to check backend response is success or not
*
* @param response alova response
*/
isBackendSuccess: (response: AG['Response']) => Promise<boolean>;
/** The config to refresh token */
tokenRefresher?: {
/** detect the token is expired */
isExpired(response: AG['Response'], Method: Method<AG>): Promise<boolean> | boolean;
/** refresh token handler */
handler(response: AG['Response'], Method: Method<AG>): Promise<void>;
};
/** The hook after backend request complete */
onComplete?: ResponseCompleteHandler<AG>;
/**
* The hook to handle error
*
* For example: You can show error message in this hook
*
* @param error
*/
onError?: (error: any, response: AG['Response'] | null, methodInstance: Method<AG>) => any | Promise<any>;
/**
* transform backend response when the responseType is json
*
* @param response alova response
*/
transformBackendResponse: (response: AG['Response']) => any;
}
+19
View File
@@ -0,0 +1,19 @@
{
"compilerOptions": {
"target": "ESNext",
"jsx": "preserve",
"lib": ["DOM", "ESNext"],
"module": "ESNext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"types": ["node"],
"strict": true,
"strictNullChecks": true,
"noUnusedLocals": true,
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
-9
View File
@@ -195,9 +195,6 @@ importers:
sass:
specifier: 1.99.0
version: 1.99.0
simple-git-hooks:
specifier: 2.13.1
version: 2.13.1
tsx:
specifier: 4.21.0
version: 4.21.0
@@ -4705,10 +4702,6 @@ packages:
resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==}
engines: {node: '>=14'}
simple-git-hooks@2.13.1:
resolution: {integrity: sha512-WszCLXwT4h2k1ufIXAgsbiTOazqqevFCIncOuUBZJ91DdvWcC5+OFkluWRQPrcuSYd8fjq+o2y1QfWqYMoAToQ==}
hasBin: true
simple-statistics@6.1.0:
resolution: {integrity: sha512-Vi/xPPuiEIizXOCDx3N8LjcIqC7Z8euiIfvmj5oYimm9/KmXNKeK4aHiVMeE9q7e9wfg1i9YRoWnUNURq54ZFg==}
@@ -10139,8 +10132,6 @@ snapshots:
signal-exit@4.1.0: {}
simple-git-hooks@2.13.1: {}
simple-statistics@6.1.0: {}
simple-statistics@7.8.9: {}
-4
View File
@@ -12,10 +12,6 @@ export const themeSchemaRecord: Record<UnionKey.ThemeScheme, App.I18n.I18nKey> =
export const themeSchemaOptions = transformRecordToOption(themeSchemaRecord);
export const loginModuleRecord: Record<UnionKey.LoginModule, App.I18n.I18nKey> = {
'pwd-login': 'page.login.pwdLogin.title',
};
export const themeLayoutModeRecord: Record<UnionKey.ThemeLayoutMode, App.I18n.I18nKey> = {
vertical: 'theme.layout.layoutMode.vertical',
'vertical-mix': 'theme.layout.layoutMode.vertical-mix',
-71
View File
@@ -1,71 +0,0 @@
import { computed } from 'vue';
import { useCountDown, useLoading } from '@sa/hooks';
import { REG_PHONE } from '@/constants/reg';
import { $t } from '@/locales';
export function useCaptcha() {
const { loading, startLoading, endLoading } = useLoading();
const { count, start, stop, isCounting } = useCountDown(10);
const label = computed(() => {
let text = $t('page.login.codeLogin.getCode');
const countingLabel = $t('page.login.codeLogin.reGetCode', { time: count.value });
if (loading.value) {
text = '';
}
if (isCounting.value) {
text = countingLabel;
}
return text;
});
function isPhoneValid(phone: string) {
if (phone.trim() === '') {
window.$message?.error?.($t('form.phone.required'));
return false;
}
if (!REG_PHONE.test(phone)) {
window.$message?.error?.($t('form.phone.invalid'));
return false;
}
return true;
}
async function getCaptcha(phone: string) {
const valid = isPhoneValid(phone);
if (!valid || loading.value) {
return;
}
startLoading();
// request
await new Promise(resolve => {
setTimeout(resolve, 500);
});
window.$message?.success?.($t('page.login.codeLogin.sendCodeSuccess'));
start();
endLoading();
}
return {
label,
start,
stop,
isCounting,
loading,
getCaptcha
};
}
@@ -4,7 +4,6 @@ import type { VNode } from 'vue';
import { useAuthStore } from '@/store/modules/auth';
import { useRouterPush } from '@/hooks/common/router';
import { useSvgIcon } from '@/hooks/common/icon';
import { $t } from '@/locales';
defineOptions({
name: 'UserAvatar'
@@ -34,7 +33,7 @@ type DropdownOption =
const options = computed(() => {
const opts: DropdownOption[] = [
{
label: $t('common.userCenter'),
label: '个人中心',
key: 'user-center',
icon: SvgIconVNode({ icon: 'ph:user-circle', fontSize: 18 })
},
@@ -43,7 +42,7 @@ const options = computed(() => {
key: 'divider'
},
{
label: $t('common.logout'),
label: '退出登录',
key: 'logout',
icon: SvgIconVNode({ icon: 'ph:sign-out', fontSize: 18 })
}
@@ -54,10 +53,10 @@ const options = computed(() => {
function logout() {
window.$dialog?.info({
title: $t('common.tip'),
content: $t('common.logoutConfirm'),
positiveText: $t('common.confirm'),
negativeText: $t('common.cancel'),
title: '提示',
content: '确定退出登录吗?',
positiveText: '确定',
negativeText: '取消',
onPositiveClick: () => {
authStore.resetStore();
}
@@ -69,14 +68,14 @@ function handleDropdown(key: DropdownKey) {
logout();
} else {
// If your other options are jumps from other routes, they will be directly supported here
routerPushByKey(key);
// routerPushByKey(key);
}
}
</script>
<template>
<NButton v-if="!authStore.isLogin" quaternary @click="loginOrRegister">
{{ $t('page.login.common.loginOrRegister') }}
登录/注册
</NButton>
<NDropdown v-else placement="bottom" trigger="click" :options="options" @select="handleDropdown">
<div>
+10 -76
View File
@@ -229,82 +229,16 @@ const local: App.I18n.Schema = {
404: 'Page Not Found',
500: 'Server Error',
home: 'Home',
'user-center': 'User Center',
},
page: {
login: {
common: {
loginOrRegister: 'Login / Register',
userNamePlaceholder: 'Please enter user name',
phonePlaceholder: 'Please enter phone number',
codePlaceholder: 'Please enter verification code',
passwordPlaceholder: 'Please enter password',
confirmPasswordPlaceholder: 'Please enter password again',
codeLogin: 'Verification code login',
confirm: 'Confirm',
back: 'Back',
validateSuccess: 'Verification passed',
loginSuccess: 'Login successfully',
welcomeBack: 'Welcome back, {userName} !'
},
pwdLogin: {
title: 'Password Login',
rememberMe: 'Remember me',
forgetPassword: 'Forget password?',
register: 'Register',
otherAccountLogin: 'Other Account Login',
otherLoginMode: 'Other Login Mode',
superAdmin: 'Super Admin',
admin: 'Admin',
user: 'User'
},
codeLogin: {
title: 'Verification Code Login',
getCode: 'Get verification code',
reGetCode: 'Reacquire after {time}s',
sendCodeSuccess: 'Verification code sent successfully',
imageCodePlaceholder: 'Please enter image verification code'
},
register: {
title: 'Register',
agreement: 'I have read and agree to',
protocol: '《User Agreement》',
policy: '《Privacy Policy》'
},
resetPwd: {
title: 'Reset Password'
},
bindWeChat: {
title: 'Bind WeChat'
}
},
home: {
branchDesc:
'For the convenience of everyone in developing and updating the merge, we have streamlined the code of the main branch, only retaining the homepage menu, and the rest of the content has been moved to the example branch for maintenance. The preview address displays the content of the example branch.',
projectCount: 'Project Count',
todo: 'Todo',
message: 'Message',
downloadCount: 'Download Count',
registerCount: 'Register Count',
schedule: 'Work and rest Schedule',
study: 'Study',
work: 'Work',
rest: 'Rest',
entertainment: 'Entertainment',
visitCount: 'Visit Count',
turnover: 'Turnover',
dealCount: 'Deal Count',
projectNews: {
title: 'Project News',
moreNews: 'More News',
desc1: 'Soybean created the open source project soybean-admin on May 28, 2021!',
desc2: 'Yanbowe submitted a bug to soybean-admin, the multi-tab bar will not adapt.',
desc3: 'Soybean is ready to do sufficient preparation for the release of soybean-admin!',
desc4: 'Soybean is busy writing project documentation for soybean-admin!',
desc5: 'Soybean just wrote some of the workbench pages casually, and it was enough to see!'
},
creativity: 'Creativity'
},
home_index: '工作台首页',
home_initiatechange: '发起变更',
home_mydraft: '我的草稿',
home_pending: '待我处理',
home_process: '流程演示',
home_processdetail: '流程演示详情',
changeledger: '变更台账',
changeledger_mychanges: '我发起的变更',
changeledger_workshopchanges: '本车间变更',
changestatistics: '本部门变更统计',
},
form: {
required: 'Cannot be empty',
-11
View File
@@ -235,17 +235,6 @@ const local: App.I18n.Schema = {
changeledger_mychanges: '我发起的变更',
changeledger_workshopchanges: '本车间变更',
changestatistics: '本部门变更统计',
'user-center': '个人中心',
},
page: {
login: {
common: {
confirm: '确定',
back: '返回',
loginSuccess: '登录成功',
welcomeBack: '欢迎回来,{userName} '
},
},
},
form: {
required: '不能为空',
-1
View File
@@ -28,5 +28,4 @@ export const views: Record<LastLevelRouteKey, RouteComponent | (() => Promise<Ro
home_process: () => import("@/views/home/process/index.vue"),
home_processdetail: () => import("@/views/home/processDetail/index.vue"),
login: () => import("@/views/login/index.vue"),
"user-center": () => import("@/views/user-center/index.vue"),
};
-10
View File
@@ -169,15 +169,5 @@ export const generatedRoutes: GeneratedRoute[] = [
constant: true,
hideInMenu: true
}
},
{
name: 'user-center',
path: '/user-center',
component: 'layout.base$view.user-center',
meta: {
title: 'user-center',
i18nKey: 'route.user-center',
hideInMenu: true
}
}
];
+1 -2
View File
@@ -177,8 +177,7 @@ const routeMap: RouteMap = {
"home_pending": "/home/pending",
"home_process": "/home/process",
"home_processdetail": "/home/processdetail",
"login": "/login/:module(pwd-login)?",
"user-center": "/user-center"
"login": "/login/:module(pwd-login)?"
};
/**
+1 -1
View File
@@ -1,4 +1,4 @@
import type { CustomRoute, ElegantConstRoute, ElegantRoute } from '@elegant-router/types';
import type { ElegantConstRoute, ElegantRoute } from '@elegant-router/types';
import { generatedRoutes } from '../elegant/routes';
import { layouts, views } from '../elegant/imports';
import { transformElegantRoutesToVueRoutes } from '../elegant/transform';
+2 -3
View File
@@ -6,7 +6,6 @@ import { fetchGetUserInfo, fetchLogin } from '@/service/api';
import { useRouterPush } from '@/hooks/common/router';
import { localStg } from '@/utils/storage';
import { SetupStoreId } from '@/enum';
import { $t } from '@/locales';
import { useRouteStore } from '../route';
import { useTabStore } from '../tab';
import { clearAuthStorage, getToken } from './shared';
@@ -116,8 +115,8 @@ export const useAuthStore = defineStore(SetupStoreId.Auth, () => {
await redirectFromLogin(needRedirect);
window.$notification?.success({
title: $t('page.login.common.loginSuccess'),
content: $t('page.login.common.welcomeBack', { userName: userInfo.userName }),
title: '登录成功',
content: `欢迎回来,${userInfo.userName} !`,
duration: 4500
});
}
-334
View File
@@ -480,340 +480,6 @@ declare namespace App {
};
};
route: Record<I18nRouteKey, string>;
page: {
login: {
common: {
loginOrRegister: string;
userNamePlaceholder: string;
phonePlaceholder: string;
codePlaceholder: string;
passwordPlaceholder: string;
confirmPasswordPlaceholder: string;
codeLogin: string;
confirm: string;
back: string;
validateSuccess: string;
loginSuccess: string;
welcomeBack: string;
};
pwdLogin: {
title: string;
rememberMe: string;
forgetPassword: string;
register: string;
otherAccountLogin: string;
otherLoginMode: string;
superAdmin: string;
admin: string;
user: string;
};
codeLogin: {
title: string;
getCode: string;
reGetCode: string;
sendCodeSuccess: string;
imageCodePlaceholder: string;
};
register: {
title: string;
agreement: string;
protocol: string;
policy: string;
};
resetPwd: {
title: string;
};
bindWeChat: {
title: string;
};
};
about: {
title: string;
introduction: string;
projectInfo: {
title: string;
version: string;
latestBuildTime: string;
githubLink: string;
previewLink: string;
};
prdDep: string;
devDep: string;
};
home: {
branchDesc: string;
projectCount: string;
todo: string;
message: string;
downloadCount: string;
registerCount: string;
schedule: string;
study: string;
work: string;
rest: string;
entertainment: string;
visitCount: string;
turnover: string;
dealCount: string;
projectNews: {
title: string;
moreNews: string;
desc1: string;
desc2: string;
desc3: string;
desc4: string;
desc5: string;
};
creativity: string;
};
function: {
tab: {
tabOperate: {
title: string;
addTab: string;
addTabDesc: string;
closeTab: string;
closeCurrentTab: string;
closeAboutTab: string;
addMultiTab: string;
addMultiTabDesc1: string;
addMultiTabDesc2: string;
};
tabTitle: {
title: string;
changeTitle: string;
change: string;
resetTitle: string;
reset: string;
};
};
multiTab: {
routeParam: string;
backTab: string;
};
toggleAuth: {
toggleAccount: string;
authHook: string;
superAdminVisible: string;
adminVisible: string;
adminOrUserVisible: string;
};
request: {
repeatedErrorOccurOnce: string;
repeatedError: string;
repeatedErrorMsg1: string;
repeatedErrorMsg2: string;
};
};
alova: {
scenes: {
captchaSend: string;
autoRequest: string;
visibilityRequestTips: string;
pollingRequestTips: string;
networkRequestTips: string;
refreshTime: string;
startRequest: string;
stopRequest: string;
requestCrossComponent: string;
triggerAllRequest: string;
};
};
proNaive: {
form: {
basic: {
title: string;
appName: string;
appStatus: string;
createTime: string;
responseDate: string;
specificationInfo: string;
specificate: string;
specificationName: string;
specificationValue: string;
specificationColorRed: string;
specificationColorOrange: string;
addSpecificateItem: string;
fillValue: string;
reset: string;
submit: string;
add: string;
delete: string;
color: string;
normal: string;
anomaly: string;
};
query: {
title1: string;
title2: string;
appName: string;
appStatus: string;
createTime: string;
responseDate: string;
endDate: string;
field: string;
};
step: {
title: string;
step1: {
title: string;
field: string;
nextStep: string;
};
step2: {
title: string;
field: string;
prevStep: string;
submit: string;
};
};
};
table: {
remote: {
filterCondition: string;
name: string;
createTime: string;
responseTime: string;
title: string;
replicableText: string;
tags: string;
dateFormatting: string;
image: string;
};
rowEdit: {
title: string;
reset: string;
submit: string;
edit: string;
delete: string;
save: string;
task: string;
score: string;
time: string;
name: string;
action: string;
};
};
};
manage: {
common: {
status: {
enable: string;
disable: string;
};
};
role: {
title: string;
roleName: string;
roleCode: string;
roleStatus: string;
roleDesc: string;
form: {
roleName: string;
roleCode: string;
roleStatus: string;
roleDesc: string;
};
addRole: string;
editRole: string;
menuAuth: string;
buttonAuth: string;
};
user: {
title: string;
userName: string;
userGender: string;
nickName: string;
userPhone: string;
userEmail: string;
userStatus: string;
userRole: string;
form: {
userName: string;
userGender: string;
nickName: string;
userPhone: string;
userEmail: string;
userStatus: string;
userRole: string;
};
addUser: string;
editUser: string;
gender: {
male: string;
female: string;
};
};
menu: {
home: string;
title: string;
id: string;
parentId: string;
menuType: string;
menuName: string;
routeName: string;
routePath: string;
pathParam: string;
layout: string;
page: string;
i18nKey: string;
icon: string;
localIcon: string;
iconTypeTitle: string;
order: string;
constant: string;
keepAlive: string;
href: string;
hideInMenu: string;
activeMenu: string;
multiTab: string;
fixedIndexInTab: string;
query: string;
button: string;
buttonCode: string;
buttonDesc: string;
menuStatus: string;
form: {
home: string;
menuType: string;
menuName: string;
routeName: string;
routePath: string;
pathParam: string;
layout: string;
page: string;
i18nKey: string;
icon: string;
localIcon: string;
order: string;
keepAlive: string;
href: string;
hideInMenu: string;
activeMenu: string;
multiTab: string;
fixedInTab: string;
fixedIndexInTab: string;
queryKey: string;
queryValue: string;
button: string;
buttonCode: string;
buttonDesc: string;
menuStatus: string;
};
addMenu: string;
editMenu: string;
addChildMenu: string;
type: {
directory: string;
menu: string;
};
iconType: {
iconify: string;
local: string;
};
};
};
};
form: {
required: string;
userName: FormMsg;
-3
View File
@@ -32,7 +32,6 @@ declare module "@elegant-router/types" {
"home_process": "/home/process";
"home_processdetail": "/home/processdetail";
"login": "/login/:module(pwd-login)?";
"user-center": "/user-center";
};
/**
@@ -71,7 +70,6 @@ declare module "@elegant-router/types" {
| "changestatistics"
| "home"
| "login"
| "user-center"
>;
/**
@@ -101,7 +99,6 @@ declare module "@elegant-router/types" {
| "home_process"
| "home_processdetail"
| "login"
| "user-center"
>;
/**
+2 -3
View File
@@ -33,14 +33,13 @@ const ROLES = [
{ id: "viewer", name: "非审批人员", person: "吴新", org: "相关部门", desc: "以信息查看为主,不参与变更流程" },
];
const mine = computed(() => CHANGES.filter((c) => c.applicant === ROLES[0].person));
interface MyRow { c: ChangeOrder | null; id: string; title: string; type: ChangeType; level: ChangeLevel; status: ChangeStatus; date: string; urgent: boolean; duration: "永久" | "临时" }
const myRows = computed(() => [
...mine.value.map((c:ChangeOrder) => ({ c, id: c.id, title: c.title, type: c.type, level: c.level, status: c.status, date: c.date, urgent: c.urgent, duration: c.duration })),
...MY_CLOSED.map((m) => ({ c: null, ...m, urgent: false })),
]);
const BIG_STAGES = ["申请审批", "实施与关闭", "变更关闭"];
const bigStageOf = (s: ChangeStatus) => (s === "审批中" ? 0 : s === "已关闭" ? 2 : 1);
const openRow = (row: MyRow) => {
const bigStageOf = (s: any) => (s === "审批中" ? 0 : s === "已关闭" ? 2 : 1);
const openRow = (row: any) => {
if (row.c){
emit("openChange", row.c);
} else {
+17 -7
View File
@@ -6,6 +6,7 @@ import {
CHANGES, DOC_TODO, LEVEL_COLOR, STATUS_COLOR, TEMP_WATCH, URGE_RECORDS,
countOpenStatus, type ChangeLevel, type ChangeOrder, type ChangeStatus, type ChangeType,
} from "@/typings/model";
import { getColorWithOpacity } from "@/utils/common";
const themeStore = useThemeStore();
@@ -27,6 +28,7 @@ const form = ref<ApplyForm>({
name: "", dept: "", mainChanges: [""], related: "", purposes: [], effect: "",
type: "", level: "", duration: "", urgent: false, date: "", restoreDate: "",
});
const tab = ref<string>("pre");
const TABS = [
{ id: "pre", name: "变更预识别", icon: 'lucide:sparkles' },
{ id: "form", name: "变更申请表", icon: 'akar-icons:file' },
@@ -36,8 +38,7 @@ const TABS = [
{ id: "accept", name: "验收评价", icon: 'material-symbols:check-circle-outline' },
{ id: "close", name: "变更关闭确认表", icon: 'quill:folder-open' },
];
const tab = ref<"pre" | "form" | "risk" | "train" | "pssr" | "accept" | "close">("pre");
const formGen = ref<"idle" | "generating" | "done">("idle");
const formGen = ref<string>("idle");
const confirmed = ref<Record<string, boolean>>({});
const blockedTabs = ref<string[]>([]);
@@ -47,10 +48,16 @@ const blockedTabs = ref<string[]>([]);
<NSpace vertical :size="16">
<!-- 发起变更 -->
<NCard :bordered="false" size="small" :style="{ borderRadius: themeStore.themeRadius + 'px', height: '100%' }">
<div class="sticky top-0 z-20 rounded-t-xl border-b bg-[#e8f2fd]">
<div
class="sticky top-0 z-20 border-b bg-[var(--bg-color)]"
:style="{
borderRadius: themeStore.themeRadius + 'px',
'--bg-color': getColorWithOpacity(themeStore.themeColor, 0.1)
}"
>
<div
class="flex flex-wrap items-center gap-x-3 gap-y-2 border-b px-4 py-2"
:style="{borderColor: themeStore.themeColor}"
class="flex flex-wrap items-center gap-x-3 gap-y-2 border-b px-4 py-2 border-[var(--border-color)]"
:style="{'--border-color': getColorWithOpacity(themeStore.themeColor, 0.2)}"
>
<span class="whitespace-nowrap text-sm font-medium text-slate-600">变更名称 <span class="text-red-500">*</span></span>
<NInput v-model:value="form.name" maxlength="28" show-count placeholder="AI 自动总结(28 字以内),可手动修改" class="!w-420px" />
@@ -60,8 +67,11 @@ const blockedTabs = ref<string[]>([]);
<div class="flex items-center gap-1 overflow-x-auto px-3 pt-2">
<div v-for="t in TABS" :key="t.id" @click="tab = t.id"
class="flex items-center gap-1.5 whitespace-nowrap px-3 py-2 text-sm transition cursor-pointer border hover:bg-white/60 rounded-t-lg"
:class="tab === t.id ? 'border-b-transparent border-[#ACC6E5] bg-white text-[var(--active-color)]':'border-transparent text-slate-500'"
:style="{'--active-color': themeStore.themeColor}"
:class="tab === t.id ? 'border-b-transparent border-[var(--border-color)] bg-white text-[var(--active-color)]':'border-transparent text-slate-500'"
:style="{
'--border-color': getColorWithOpacity(themeStore.themeColor, 0.3),
'--active-color': themeStore.themeColor
}"
>
<Icon :icon="t.icon" class="size-14px" />
{{ t.name }}
+2 -3
View File
@@ -1,8 +1,7 @@
<script setup lang="ts">
import type { ChangeStatus } from "../demo/model";
const props = defineProps<{ status: ChangeStatus }>();
const props = defineProps<{ status: any }>();
const BIG_STAGES = ["申请审批", "实施与关闭", "变更关闭"];
const bigStageOf = (s: ChangeStatus) => (s === "审批中" ? 0 : s === "已关闭" ? 2 : 1);
const bigStageOf = (s: any) => (s === "审批中" ? 0 : s === "已关闭" ? 2 : 1);
const cur = bigStageOf(props.status);
const pct = ((cur + 1) / BIG_STAGES.length) * 100;
const size = 34, stroke = 4, r = (size - stroke) / 2, c = 2 * Math.PI * r;
+1 -19
View File
@@ -2,32 +2,14 @@
import { computed } from 'vue';
import type { Component } from 'vue';
import { getPaletteColorByNumber, mixColor } from '@sa/color';
import { loginModuleRecord } from '@/constants/app';
import { useAppStore } from '@/store/modules/app';
import { useThemeStore } from '@/store/modules/theme';
import PwdLogin from './modules/pwd-login.vue';
interface Props {
/** The login module */
module?: UnionKey.LoginModule;
}
const props = defineProps<Props>();
const appStore = useAppStore();
const themeStore = useThemeStore();
interface LoginModule {
label: App.I18n.I18nKey;
component: Component;
}
const moduleMap: Record<UnionKey.LoginModule, LoginModule> = {
'pwd-login': { label: loginModuleRecord['pwd-login'], component: PwdLogin },
};
const activeModule = computed(() => moduleMap[props.module || 'pwd-login']);
const activeModule = computed(() => ({ label: '密码登录', component: PwdLogin }));
const bgThemeColor = computed(() =>
themeStore.darkMode ? getPaletteColorByNumber(themeStore.themeColor, 600) : themeStore.themeColor
-7
View File
@@ -1,7 +0,0 @@
<script setup lang="ts"></script>
<template>
<LookForward />
</template>
<style scoped></style>