| 比對新檔案 |
| | |
| | | # Default ignored files |
| | | /shelf/ |
| | | /workspace.xml |
| | | # Editor-based HTTP Client requests |
| | | /httpRequests/ |
| | | # Datasource local storage ignored files |
| | | /dataSources/ |
| | | /dataSources.local.xml |
| 比對新檔案 |
| | |
| | | <?xml version="1.0" encoding="UTF-8"?> |
| | | <project version="4"> |
| | | <component name="ProjectCodeStyleConfiguration"> |
| | | <code_scheme name="Project" version="173"> |
| | | <codeStyleSettings language="XML"> |
| | | <arrangement> |
| | | <rules> |
| | | <section> |
| | | <rule> |
| | | <match> |
| | | <AND> |
| | | <NAME>xmlns:android</NAME> |
| | | <XML_ATTRIBUTE /> |
| | | <XML_NAMESPACE>^$</XML_NAMESPACE> |
| | | </AND> |
| | | </match> |
| | | </rule> |
| | | </section> |
| | | <section> |
| | | <rule> |
| | | <match> |
| | | <AND> |
| | | <NAME>xmlns:.*</NAME> |
| | | <XML_ATTRIBUTE /> |
| | | <XML_NAMESPACE>^$</XML_NAMESPACE> |
| | | </AND> |
| | | </match> |
| | | <order>BY_NAME</order> |
| | | </rule> |
| | | </section> |
| | | <section> |
| | | <rule> |
| | | <match> |
| | | <AND> |
| | | <NAME>.*:id</NAME> |
| | | <XML_ATTRIBUTE /> |
| | | <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE> |
| | | </AND> |
| | | </match> |
| | | </rule> |
| | | </section> |
| | | <section> |
| | | <rule> |
| | | <match> |
| | | <AND> |
| | | <NAME>.*:name</NAME> |
| | | <XML_ATTRIBUTE /> |
| | | <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE> |
| | | </AND> |
| | | </match> |
| | | </rule> |
| | | </section> |
| | | <section> |
| | | <rule> |
| | | <match> |
| | | <AND> |
| | | <NAME>name</NAME> |
| | | <XML_ATTRIBUTE /> |
| | | <XML_NAMESPACE>^$</XML_NAMESPACE> |
| | | </AND> |
| | | </match> |
| | | </rule> |
| | | </section> |
| | | <section> |
| | | <rule> |
| | | <match> |
| | | <AND> |
| | | <NAME>style</NAME> |
| | | <XML_ATTRIBUTE /> |
| | | <XML_NAMESPACE>^$</XML_NAMESPACE> |
| | | </AND> |
| | | </match> |
| | | </rule> |
| | | </section> |
| | | <section> |
| | | <rule> |
| | | <match> |
| | | <AND> |
| | | <NAME>.*</NAME> |
| | | <XML_ATTRIBUTE /> |
| | | <XML_NAMESPACE>^$</XML_NAMESPACE> |
| | | </AND> |
| | | </match> |
| | | <order>BY_NAME</order> |
| | | </rule> |
| | | </section> |
| | | <section> |
| | | <rule> |
| | | <match> |
| | | <AND> |
| | | <NAME>.*</NAME> |
| | | <XML_ATTRIBUTE /> |
| | | <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE> |
| | | </AND> |
| | | </match> |
| | | <order>ANDROID_ATTRIBUTE_ORDER</order> |
| | | </rule> |
| | | </section> |
| | | <section> |
| | | <rule> |
| | | <match> |
| | | <AND> |
| | | <NAME>.*</NAME> |
| | | <XML_ATTRIBUTE /> |
| | | <XML_NAMESPACE>.*</XML_NAMESPACE> |
| | | </AND> |
| | | </match> |
| | | <order>BY_NAME</order> |
| | | </rule> |
| | | </section> |
| | | </rules> |
| | | </arrangement> |
| | | </codeStyleSettings> |
| | | </code_scheme> |
| | | </component> |
| | | </project> |
| 比對新檔案 |
| | |
| | | <?xml version="1.0" encoding="UTF-8"?> |
| | | <project version="4"> |
| | | <component name="GitToolBoxProjectSettings"> |
| | | <option name="commitMessageIssueKeyValidationOverride"> |
| | | <BoolValueOverride> |
| | | <option name="enabled" value="true" /> |
| | | </BoolValueOverride> |
| | | </option> |
| | | <option name="commitMessageValidationEnabledOverride"> |
| | | <BoolValueOverride> |
| | | <option name="enabled" value="true" /> |
| | | </BoolValueOverride> |
| | | </option> |
| | | </component> |
| | | </project> |
| 比對新檔案 |
| | |
| | | <project version="4"> |
| | | <component name="MarkdownNavigator.ProfileManager" plain-text-search-scope="Project Files" /> |
| | | <component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="jbr-11" project-jdk-type="JavaSDK"> |
| | | <output url="file://$PROJECT_DIR$/out" /> |
| | | </component> |
| | | </project> |
| 比對新檔案 |
| | |
| | | <?xml version="1.0" encoding="UTF-8"?> |
| | | <project version="4"> |
| | | <component name="ProjectModuleManager"> |
| | | <modules> |
| | | <module fileurl="file://$PROJECT_DIR$/.idea/CB_IMGPSScan.iml" filepath="$PROJECT_DIR$/.idea/CB_IMGPSScan.iml" /> |
| | | </modules> |
| | | </component> |
| | | </project> |
| 比對新檔案 |
| | |
| | | <?xml version="1.0" encoding="UTF-8"?> |
| | | <project version="4"> |
| | | <component name="VcsDirectoryMappings"> |
| | | <mapping directory="" vcs="Git" /> |
| | | <mapping directory="$PROJECT_DIR$" vcs="Git" /> |
| | | </component> |
| | | </project> |
| 比對新檔案 |
| | |
| | | import * as fs from 'fs'; |
| | | import * as path from 'path'; |
| | | |
| | | // ============================================================================ |
| | | // 模擬 Delphi 與 VCL/硬體 相依介面 (Node.js 中不存在的實體) |
| | | // ============================================================================ |
| | | export type TObject = any; |
| | | export type TMouseButton = 'mbLeft' | 'mbRight' | 'mbMiddle'; |
| | | export type TShiftState = any; |
| | | export type THandle = number; |
| | | export type TDibGraphic = any; |
| | | export type TTiffGraphic = any; |
| | | export type TImageFormat = 'ifBlackWhite' | 'ifGray256' | 'ifTrueColor' | 'ifColor256'; |
| | | export type TMouseMode = 'mmUser' | 'mmAmplifier' | 'mmZoom' | 'mmDrag' | 'mmR270' | 'mmR180' | 'mmR90' | 'mmDelete'; |
| | | export type TxActiveFormBorderStyle = number; |
| | | export type TxPrintScale = number; |
| | | export type TxPopupMode = number; |
| | | export type OLE_COLOR = number; |
| | | export type IFontDisp = any; |
| | | |
| | | export interface TTreeNode { |
| | | Text: string; |
| | | Level: number; |
| | | ImageIndex: number; |
| | | SelectedIndex: number; |
| | | Parent?: TTreeNode; |
| | | Count: number; |
| | | Item: TTreeNode[]; |
| | | Expand(expand: boolean): void; |
| | | Delete(): void; |
| | | } |
| | | |
| | | export interface TImageScrollBox { |
| | | Name: string; |
| | | FileName: string; |
| | | Graphic: any; |
| | | DisplayedGraphic: any; |
| | | MouseMode: TMouseMode; |
| | | ZoomMode: any; |
| | | ZoomPercent: number; |
| | | AntiAliased: boolean; |
| | | Parent: any; |
| | | LoadFromFile(filename: string, index: number): void; |
| | | SaveToFile(filename: string): void; |
| | | Redraw(force: boolean): void; |
| | | SetFocus(): void; |
| | | BeginDrag(immediate: boolean): void; |
| | | } |
| | | |
| | | export interface TListView { |
| | | Items: { Count: number; Item: any[], Add(): any, Clear(): void }; |
| | | Selected: any; |
| | | ItemIndex: number; |
| | | ClearSelection(): void; |
| | | Clear(): void; |
| | | } |
| | | |
| | | export interface TTreeView { |
| | | Items: { Add(node: any, text: string): TTreeNode; AddChild(node: any, text: string): TTreeNode; Clear(): void }; |
| | | Selected: TTreeNode; |
| | | } |
| | | |
| | | // ============================================================================ |
| | | // 模擬 Delphi TStringList 檔案字串處理功能 |
| | | // ============================================================================ |
| | | export class TStringList { |
| | | private _strings: string[] = []; |
| | | |
| | | get Count(): number { return this._strings.length; } |
| | | get Strings(): string[] { return this._strings; } |
| | | set Strings(value: string[]) { this._strings = value; } |
| | | get Text(): string { return this._strings.join('\n'); } |
| | | set Text(value: string) { this._strings = value.split('\n'); } |
| | | get CommaText(): string { return this._strings.join(','); } |
| | | set CommaText(value: string) { this._strings = value.split(','); } |
| | | |
| | | public Add(item: string) { this._strings.push(item); } |
| | | public Insert(index: number, item: string) { this._strings.splice(index, 0, item); } |
| | | public Delete(index: number) { this._strings.splice(index, 1); } |
| | | public Clear() { this._strings = []; } |
| | | public IndexOf(item: string): number { return this._strings.indexOf(item); } |
| | | |
| | | public LoadFromFile(filepath: string) { |
| | | if (fs.existsSync(filepath)) { |
| | | const data = fs.readFileSync(filepath, 'utf8'); |
| | | this._strings = data.split(/\r?\n/).filter(line => line.length > 0); |
| | | } |
| | | } |
| | | |
| | | public SaveToFile(filepath: string) { |
| | | fs.writeFileSync(filepath, this._strings.join('\n'), 'utf8'); |
| | | } |
| | | |
| | | public Sort() { this._strings.sort(); } |
| | | public Assign(source: TStringList) { this._strings = [...source.Strings]; } |
| | | } |
| | | |
| | | // ============================================================================ |
| | | // 列舉與資料結構 |
| | | // ============================================================================ |
| | | export enum TTransMode { tsHttp, tsFtp, tsNone } |
| | | export enum TScanMode { smNew, smReplace, smInsert, smSample, smRTS } |
| | | export enum TFtpProtocol { fpftp, fpftps } |
| | | |
| | | export interface TScanInfo { |
| | | MultiPage: boolean; |
| | | Graphic: TTiffGraphic; |
| | | Stream: any; |
| | | ImageCount: number; |
| | | } |
| | | |
| | | export interface TOMRErrInfo { |
| | | Display: boolean; |
| | | Ignore: boolean; |
| | | Info: string; |
| | | Mode: string; |
| | | } |
| | | |
| | | export interface TScrollRec { |
| | | HScroll: number; |
| | | VScroll: number; |
| | | Rate: number; |
| | | } |
| | | |
| | | export interface TPoint { |
| | | X: number; |
| | | Y: number; |
| | | } |
| | | |
| | | export interface TMpsBarcodeinf { |
| | | Count: number; |
| | | Text: string[]; |
| | | r180: number[]; |
| | | } |
| | | |
| | | // ============================================================================ |
| | | // 主要實作類別 (對應 TCB_IMGPSScanX) |
| | | // ============================================================================ |
| | | export class TCB_IMGPSScanX { |
| | | // ===== 系統全域變數 ===== |
| | | private Ch_WriteNote: boolean = false; |
| | | private RejectCase: boolean = false; |
| | | private ErrIndex: number = 0; |
| | | |
| | | // ===== 預設區 ===== |
| | | private Def_DeviceDelete: boolean = true; |
| | | private Def_DeviceDeleteSize: number = 3072; |
| | | private Def_ScannerReverse: boolean = false; |
| | | private Def_BoardClear: boolean = false; |
| | | private Def_ScanDpi: number = 300; |
| | | private Def_ScanDuplex: boolean = true; |
| | | private Def_ScanRotate: number = 0; |
| | | private Def_ScanDeskew: boolean = false; |
| | | private Def_ScanBright: number = 0; |
| | | private Def_ScanContrast: number = 0; |
| | | private Def_ScanImgShowMode: number = 2; |
| | | private Def_ScanImgSetUse: boolean = false; |
| | | |
| | | // ===== 傳入參數 ===== |
| | | private FUrl: string = ''; |
| | | private FCaseID: string = ''; |
| | | private FMode: string = ''; |
| | | private FModeName: string = ''; |
| | | private FWork_no: string = ''; |
| | | private FUserID: string = ''; |
| | | private FUserName: string = ''; |
| | | private FUserUnit: string = ''; |
| | | private FData: string = ''; |
| | | private FVerify: string = ''; |
| | | private FReWrite: string = ''; |
| | | private FLanguage: string = 'zh_tw'; |
| | | private FLoanDoc_Value: string = ''; |
| | | private FLoanDoc_Enable: string = ''; |
| | | private FUseProxy: string = ''; |
| | | private FC_DocNoList: string = ''; |
| | | private FC_DocNameList: string = ''; |
| | | private FFixFileList: string = ''; |
| | | private FIs_In_Wh: string = ''; |
| | | private FOldCaseInfo: string = ''; |
| | | private FPrintyn: string = ''; |
| | | private FIs_OldCase: string = ''; |
| | | private FCustDocYN: string = ''; |
| | | private FImgDPI: number = 300; |
| | | private FScanColor: number = 0; |
| | | private FFileSizeLimit: number = 5120; // 5 * 1024 |
| | | private FCaseNoLength: number = 16; |
| | | private FImgDelete: string = 'N'; |
| | | private FIsExternal: string = 'N'; |
| | | private FCheck_main_form: string = 'N'; |
| | | private FWH_category: string = 'N'; |
| | | |
| | | // ===== 狀態變數 ===== |
| | | private TransMode: TTransMode = TTransMode.tsNone; |
| | | private HttpErrStr: string = ''; |
| | | private ScanColor: TImageFormat = 'ifBlackWhite'; |
| | | private ServerDate: string = ''; |
| | | private ServerTime: string = ''; |
| | | private Balance: number = 0; |
| | | private ScanDenialTime: string = ''; |
| | | private ScanDenialHint: string = ''; |
| | | |
| | | // ===== 路徑區 ===== |
| | | private ScanPath: string = ''; |
| | | private ImagePath: string = ''; |
| | | private ImageSavePath: string = ''; |
| | | private ScaniniPath: string = ''; |
| | | private LngPath: string = ''; |
| | | private CheckXmlPath: string = ''; |
| | | private SitePath: string = ''; |
| | | private SamplePath: string = ''; |
| | | private TransPath: string = ''; |
| | | |
| | | // ===== 清單區 ===== |
| | | private Doc_Inf_List: TStringList = new TStringList(); |
| | | private DM_FORM_INF_List: TStringList = new TStringList(); |
| | | private FORM_INF_List: TStringList = new TStringList(); |
| | | private CHECK_RULE_INF_List: TStringList = new TStringList(); |
| | | private MEMO_INF_List: TStringList = new TStringList(); |
| | | private WORK_INF_List: TStringList = new TStringList(); |
| | | private LASTEST_FORM_INF_List: TStringList = new TStringList(); |
| | | private FindResult: TStringList = new TStringList(); |
| | | private CaseList: TStringList = new TStringList(); |
| | | private Context_DocnoList: TStringList = new TStringList(); |
| | | private CaseDocNoList: TStringList = new TStringList(); |
| | | private CaseDocNo_CopiesList: TStringList = new TStringList(); |
| | | private ContextList: TStringList = new TStringList(); |
| | | private AttContextList: TStringList = new TStringList(); |
| | | private OMRFileList: TStringList = new TStringList(); |
| | | private Cust_DocNoList: TStringList = new TStringList(); |
| | | private IN_WH_DocNoList: TStringList = new TStringList(); |
| | | private FormCode_PageSize: TStringList = new TStringList(); |
| | | private DocNo_NeedDoc: TStringList = new TStringList(); |
| | | private DocNo_NoDoc: TStringList = new TStringList(); |
| | | private DocNo_VerinCase: TStringList = new TStringList(); |
| | | private NoSaveBarCodeList: TStringList = new TStringList(); |
| | | private FormID_List: TStringList = new TStringList(); |
| | | private DocNo_List: TStringList = new TStringList(); |
| | | private NowShowFileList: TStringList = new TStringList(); |
| | | private NowSelectFileList: TStringList = new TStringList(); |
| | | private GuideFormIDList: TStringList = new TStringList(); |
| | | private DivPageFormIDList: TStringList = new TStringList(); |
| | | private LastInitFormidList: TStringList = new TStringList(); |
| | | private LastAddFormidList: TStringList = new TStringList(); |
| | | private SampleFormIDList: TStringList = new TStringList(); |
| | | private ExistImgList: TStringList = new TStringList(); |
| | | private reSizeExistImgList: TStringList = new TStringList(); |
| | | |
| | | // ===== 顯示區變數 ===== |
| | | private NowCaseno: string = ''; |
| | | private NowDocNo: string = ''; |
| | | private NowDocDir: string = ''; |
| | | private NowFormCode: string = ''; |
| | | private NowFormName: string = ''; |
| | | private NowPage: number = 0; |
| | | private DisplayPath: string = ''; |
| | | private Case_loandoc: string = ''; |
| | | |
| | | // ===== 其他狀態 ===== |
| | | private ScanModeState: TScanMode = TScanMode.smNew; |
| | | private InitialOk: boolean = false; |
| | | private ShowText: string = ''; |
| | | private SafePixel: number = 20; |
| | | private OMRErrInfo: TOMRErrInfo[] = []; // length 11 |
| | | |
| | | // UI Mocks |
| | | private TreeView1: TTreeView = {} as TTreeView; |
| | | private PageLV: TListView = {} as TListView; |
| | | private ISB1: TImageScrollBox = {} as TImageScrollBox; |
| | | private DisplayISB: TImageScrollBox = {} as TImageScrollBox; |
| | | private SelectISB: TImageScrollBox = {} as TImageScrollBox; |
| | | private NewTreeNode: TTreeNode = {} as TTreeNode; |
| | | private MyTreeNode1: TTreeNode = {} as TTreeNode; |
| | | private MyTreeNode2: TTreeNode = {} as TTreeNode; |
| | | private MyTreeNode3: TTreeNode = {} as TTreeNode; |
| | | |
| | | constructor() { |
| | | // Constructor initialization |
| | | } |
| | | |
| | | // ============================================================================ |
| | | // COM 屬性 (Getters & Setters) |
| | | // ============================================================================ |
| | | get url(): string { return this.FUrl; } |
| | | set url(value: string) { this.FUrl = value; } |
| | | |
| | | get caseid(): string { return this.FCaseID; } |
| | | set caseid(value: string) { this.FCaseID = value; } |
| | | |
| | | get mode(): string { return this.FMode; } |
| | | set mode(value: string) { this.FMode = value.toUpperCase(); } |
| | | |
| | | get work_no(): string { return this.FWork_no; } |
| | | set work_no(value: string) { this.FWork_no = value; } |
| | | |
| | | get userid(): string { return this.FUserID; } |
| | | set userid(value: string) { this.FUserID = value; } |
| | | |
| | | get username(): string { return this.FUserName; } |
| | | set username(value: string) { this.FUserName = value; } |
| | | |
| | | get userunit(): string { return this.FUserUnit; } |
| | | set userunit(value: string) { this.FUserUnit = value; } |
| | | |
| | | get data(): string { return this.FData; } |
| | | set data(value: string) { this.FData = value; } |
| | | |
| | | get verify(): string { return this.FVerify; } |
| | | set verify(value: string) { this.FVerify = value; } |
| | | |
| | | get rewrite(): string { return this.FReWrite; } |
| | | set rewrite(value: string) { this.FReWrite = value; } |
| | | |
| | | get modename(): string { return this.FModeName; } |
| | | set modename(value: string) { this.FModeName = value; } |
| | | |
| | | get language(): string { return this.FLanguage; } |
| | | set language(value: string) { this.FLanguage = value.toLowerCase(); } |
| | | |
| | | get loandoc_value(): string { return this.FLoanDoc_Value; } |
| | | set loandoc_value(value: string) { this.FLoanDoc_Value = value; } |
| | | |
| | | get loandoc_enable(): string { return this.FLoanDoc_Enable; } |
| | | set loandoc_enable(value: string) { this.FLoanDoc_Enable = value; } |
| | | |
| | | get c_docnolist(): string { return this.FC_DocNoList; } |
| | | set c_docnolist(value: string) { this.FC_DocNoList = value; } |
| | | |
| | | get c_docnamelist(): string { return this.FC_DocNameList; } |
| | | set c_docnamelist(value: string) { this.FC_DocNameList = value; } |
| | | |
| | | get is_in_wh(): string { return this.FIs_In_Wh; } |
| | | set is_in_wh(value: string) { this.FIs_In_Wh = value.toUpperCase(); } |
| | | |
| | | get oldcaseinfo(): string { return this.FOldCaseInfo; } |
| | | set oldcaseinfo(value: string) { this.FOldCaseInfo = value; } |
| | | |
| | | get scancolor(): string { return this.FScanColor.toString(); } |
| | | set scancolor(value: string) { |
| | | this.FScanColor = value ? parseInt(value) : 0; |
| | | this.ScanColor = this.FScanColor === 2 ? 'ifTrueColor' : (this.FScanColor === 1 ? 'ifGray256' : 'ifBlackWhite'); |
| | | } |
| | | |
| | | get imgdpi(): string { return this.FImgDPI.toString(); } |
| | | set imgdpi(value: string) { |
| | | this.FImgDPI = value ? parseInt(value) : 300; |
| | | this.Def_ScanDpi = this.FImgDPI; |
| | | } |
| | | |
| | | get filesizelimit(): string { return this.FFileSizeLimit.toString(); } |
| | | set filesizelimit(value: string) { this.FFileSizeLimit = value ? parseInt(value) : 5120; } |
| | | |
| | | get casenolength(): string { return this.FCaseNoLength.toString(); } |
| | | set casenolength(value: string) { this.FCaseNoLength = value ? parseInt(value) : 0; } |
| | | |
| | | get WH_CATEGORY(): string { return this.FWH_category; } |
| | | set WH_CATEGORY(value: string) { this.FWH_category = value; } |
| | | |
| | | // ============================================================================ |
| | | // 核心資料庫 / 字串解析方法 (移植自 Pascal) |
| | | // ============================================================================ |
| | | |
| | | /** |
| | | * 依欄位及索引取值 |
| | | */ |
| | | private GetSQLData(TableList: TStringList, ColName: string, colNo: number): string { |
| | | if (TableList.Count === 0 || colNo >= TableList.Count) return ''; |
| | | |
| | | const ColStr = TableList.Strings[0].split(','); |
| | | const TmpStr = TableList.Strings[colNo]; |
| | | const DataStr = TmpStr.split('!@!'); // Delphi 用 '!@!' 分隔 |
| | | |
| | | const idx = ColStr.indexOf(ColName); |
| | | if (idx !== -1 && idx < DataStr.length) { |
| | | return DataStr[idx]; |
| | | } |
| | | return ''; |
| | | } |
| | | |
| | | /** |
| | | * 找指定的資料 |
| | | */ |
| | | private FindSQLData(TableList: TStringList, ColumeStr: string, KeyColumeStr: string, KeyStr: string, ColNo: number, ResultList: TStringList): boolean { |
| | | ResultList.Clear(); |
| | | if (!KeyStr || TableList.Count <= 1) return false; |
| | | |
| | | const ColList = ColumeStr.split(','); |
| | | const KeyColList = KeyColumeStr.split(','); |
| | | const KeyList = KeyStr.split(','); |
| | | |
| | | let findIndex = -1; |
| | | let found = false; |
| | | |
| | | if (ColNo === 0) { |
| | | for (let i = 1; i < TableList.Count; i++) { |
| | | let match = true; |
| | | for (let n = 0; n < KeyColList.length; n++) { |
| | | const keyCol = KeyColList[n]; |
| | | const keyVal = KeyList[n]; |
| | | if (this.GetSQLData(TableList, keyCol, i) !== keyVal) { |
| | | match = false; |
| | | break; |
| | | } |
| | | } |
| | | if (match) { |
| | | found = true; |
| | | findIndex = i; |
| | | break; |
| | | } |
| | | } |
| | | } else { |
| | | // Specific Row Check |
| | | let match = true; |
| | | for (let n = 0; n < KeyColList.length; n++) { |
| | | const keyCol = KeyColList[n]; |
| | | const keyVal = KeyList[n]; |
| | | const rowData = TableList.Strings[ColNo]; |
| | | if (rowData.indexOf(`!@!${keyVal}!@!`) === -1) { |
| | | match = false; |
| | | break; |
| | | } |
| | | } |
| | | if (match) { |
| | | found = true; |
| | | findIndex = ColNo; |
| | | } |
| | | } |
| | | |
| | | if (found) { |
| | | for (let n = 0; n < ColList.length; n++) { |
| | | const col = ColList[n]; |
| | | ResultList.Add(`${col},${this.GetSQLData(TableList, col, findIndex)}`); |
| | | } |
| | | } |
| | | return found; |
| | | } |
| | | |
| | | private GetFindResult(Col: string): string { |
| | | for (let i = 0; i < this.FindResult.Count; i++) { |
| | | const s = this.FindResult.Strings[i]; |
| | | const idx = s.indexOf(','); |
| | | if (idx > -1) { |
| | | const rCol = s.substring(0, idx); |
| | | const rValue = s.substring(idx + 1); |
| | | if (Col === rCol) return rValue; |
| | | } |
| | | } |
| | | return ''; |
| | | } |
| | | |
| | | // ============================================================================ |
| | | // 文件、表單、名稱轉換方法 |
| | | // ============================================================================ |
| | | |
| | | private FormCode2DocNo(FormCode: string): string { |
| | | let result = ''; |
| | | for (let i = 0; i < this.FormID_List.Count; i++) { |
| | | if (this.FormID_List.Strings[i] === FormCode) { |
| | | result = this.DocNo_List.Strings[i]; |
| | | break; |
| | | } |
| | | } |
| | | if (FormCode && !result) { |
| | | result = FormCode.substring(0, 8); // 自訂文件 |
| | | } |
| | | return result; |
| | | } |
| | | |
| | | private FormCode2Version(FormCode: string): string { |
| | | return FormCode.substring(10, 15); // Delphi is 1-based (11,5) -> JS is 0-based (10, 15) |
| | | } |
| | | |
| | | private FormCode2Page(FormCode: string): string { |
| | | return FormCode.substring(8, 10); // Delphi is (9,2) |
| | | } |
| | | |
| | | private FileName2FormCode(FileName: string): string { |
| | | const fname = path.basename(FileName); |
| | | const v = fname.indexOf('_'); |
| | | const v1 = fname.lastIndexOf('.'); |
| | | if (v > -1 && v1 > v) { |
| | | return fname.substring(v + 1, v1); |
| | | } |
| | | return ''; // 附件 |
| | | } |
| | | |
| | | private DocNo2DocNoDir(savePath: string, DocNo: string): string { |
| | | if (DocNo) { |
| | | let i = 0; |
| | | let iDocNo = ''; |
| | | do { |
| | | i++; |
| | | iDocNo = `${DocNo}(${i})`; |
| | | } while (fs.existsSync(path.join(savePath, iDocNo))); |
| | | return iDocNo; |
| | | } |
| | | return 'Attach'; |
| | | } |
| | | |
| | | private DocNoDir2DocNo(DocNoDir: string): string { |
| | | if (DocNoDir !== 'Attach' && DocNoDir !== 'S_Attach') { |
| | | const v = DocNoDir.indexOf('('); |
| | | if (v > 0) return DocNoDir.substring(0, v); |
| | | return DocNoDir; |
| | | } |
| | | return DocNoDir; |
| | | } |
| | | |
| | | // ============================================================================ |
| | | // 系統初始化與清單載入 (模擬 Delphi 的 Timer1Timer) |
| | | // ============================================================================ |
| | | public async InitSystem() { |
| | | this.InitialOk = false; |
| | | this.FMaxUploadSize = '10'; |
| | | |
| | | if (!this.FUrl) { |
| | | console.error('URL cannot be empty, please contact system administrator'); |
| | | return; |
| | | } |
| | | if (!this.FUrl.endsWith('/')) this.FUrl += '/'; |
| | | |
| | | // 在 Node.js 中通常依賴環境變數或 config 檔案,這裡模擬建立目錄 |
| | | this.ImagePath = path.join(__dirname, 'Scantemp', this.FWork_no, this.FUserUnit, this.FMode); |
| | | this.ImageSavePath = this.ImagePath; |
| | | |
| | | if (!fs.existsSync(this.ImagePath)) fs.mkdirSync(this.ImagePath, { recursive: true }); |
| | | |
| | | this.DataLoading(true, true); |
| | | |
| | | // TODO: 模擬 HTTP API 請求 (GetServerDate, GetSetInf1~7 等) |
| | | // await this.GetServerDate(); |
| | | // await this.GetSetInf1(); |
| | | |
| | | this.InitialOk = true; |
| | | this.DataLoading(false, false); |
| | | } |
| | | |
| | | // ============================================================================ |
| | | // 按鈕動作邏輯:上傳 (TransBtnClick) |
| | | // ============================================================================ |
| | | public async TransBtnClick() { |
| | | if (!this.InitialOk) { |
| | | console.log('資訊尚未下載完成,請稍候或重新進入'); |
| | | return; |
| | | } |
| | | this.RejectCase = false; |
| | | |
| | | if (this.NewTreeNode.Count === 0) { |
| | | console.log('無影像需傳送'); |
| | | return; |
| | | } |
| | | |
| | | let SuccessCount = 0; |
| | | let ReCasecount = 0; |
| | | let CheckErrCount = 0; |
| | | |
| | | this.DataLoading(true, true); |
| | | |
| | | // 巡覽所有案件節點 |
| | | for (let i = 0; i < this.NewTreeNode.Count; i++) { |
| | | const nodeText = this.NewTreeNode.Item[i].Text; |
| | | const v = nodeText.indexOf('-'); |
| | | const CaseID = nodeText.substring(0, v); |
| | | |
| | | this.CreateIn_WH(CaseID); |
| | | this.Case2upload(CaseID); |
| | | this.TransPath = path.join(this.ImageSavePath, CaseID, 'Upload'); |
| | | |
| | | // 是否可以上傳? |
| | | const CaseTrans = this.CaseAsk(CaseID); // 0: 可以, 1: 重複, -1: 失敗 |
| | | if (CaseTrans === -1) { |
| | | console.error(this.HttpErrStr); |
| | | this.DataLoading(false, false); |
| | | return; |
| | | } |
| | | if (CaseTrans === 1) { |
| | | ReCasecount++; |
| | | continue; |
| | | } |
| | | |
| | | if (CaseTrans === 0) { |
| | | // 進行 OMR 檢核 |
| | | if (this.FMode !== 'FSCAN') { |
| | | if (!this.OMRCheckCase(CaseID)) { |
| | | CheckErrCount++; |
| | | continue; |
| | | } |
| | | } |
| | | |
| | | // 執行傳送 (ZIP 壓縮 + HTTP/FTP Upload) |
| | | const isTransOk = await this.TransCaseID(this.TransPath, CaseID, true); |
| | | if (!isTransOk) { |
| | | this.DataLoading(false, false); |
| | | return; |
| | | } |
| | | SuccessCount++; |
| | | } |
| | | } |
| | | |
| | | this.DataLoading(false, false); |
| | | console.log(`傳送完成。成功件【${SuccessCount}】件,失敗件【${ReCasecount}】,檢核失敗【${CheckErrCount}】`); |
| | | } |
| | | |
| | | // ============================================================================ |
| | | // OMR 檢核邏輯 (OMRCheckCase) |
| | | // ============================================================================ |
| | | private OMRCheckCase(CaseID: string): boolean { |
| | | let CaseOk = true; |
| | | const errIniPath = path.join(this.ImageSavePath, CaseID, 'upload', 'Checkerr.ini'); |
| | | if (fs.existsSync(errIniPath)) fs.unlinkSync(errIniPath); |
| | | |
| | | const MainFormID = this.GetCaseFormID(path.join(this.ImageSavePath, CaseID, 'upload')); |
| | | |
| | | // 這裡會實作根據 MainFormID 取得 OMR 檢核 XML 設定的邏輯 |
| | | // 並比對掃描圖檔的影像黑點數量,決定是否缺件或缺漏必填欄位。 |
| | | // TODO: 完整的 OMR Check (XML Parsing and Image Pixel counting) |
| | | |
| | | return CaseOk; |
| | | } |
| | | |
| | | // ============================================================================ |
| | | // 壓縮與傳送封裝 (TransCaseID) |
| | | // ============================================================================ |
| | | private async TransCaseID(TransPath: string, CaseID: string, MainCase: boolean): Promise<boolean> { |
| | | // 1. 產生必要的 Data 檔案 (FormCode_Name.dat, DocNo_Name.dat 等) |
| | | this.CreateFormID_FormName(TransPath, CaseID); |
| | | this.CreateDocNo_DocName(TransPath, CaseID); |
| | | |
| | | // 2. 壓縮檔案為 Img.zip (模擬) |
| | | const zipPath = path.join(TransPath, 'Img.zip'); |
| | | console.log(`[ZIP] Zipping files in ${TransPath} to ${zipPath}`); |
| | | |
| | | // 3. 檢查檔案大小是否超過限制 FMaxUploadSize |
| | | // const stat = fs.statSync(zipPath); |
| | | // if (stat.size > parseInt(this.FMaxUploadSize) * 1048576) { ... } |
| | | |
| | | // 4. 根據 TransMode 進行 Http POST 或 FTP 上傳 |
| | | if (this.TransMode === TTransMode.tsHttp) { |
| | | console.log(`[HTTP] Uploading file to ${this.FUrl}...`); |
| | | // TODO: Axios or fetch POST request |
| | | } else if (this.TransMode === TTransMode.tsFtp) { |
| | | console.log(`[FTP] Uploading file to FTP server...`); |
| | | // TODO: FTP Client put |
| | | } |
| | | |
| | | // 5. 刪除本機暫存 |
| | | const caseDir = path.join(this.ImageSavePath, CaseID); |
| | | if (fs.existsSync(caseDir)) { |
| | | fs.rmSync(caseDir, { recursive: true, force: true }); |
| | | } |
| | | |
| | | return true; |
| | | } |
| | | |
| | | // ============================================================================ |
| | | // UI 與工具抽象 (Stubbing) |
| | | // ============================================================================ |
| | | private DataLoading(loading: boolean, useTimer: boolean) { |
| | | if (loading) { |
| | | console.log(`Loading... ${this.ShowText}`); |
| | | } else { |
| | | console.log('Loading complete.'); |
| | | } |
| | | } |
| | | |
| | | private CreateIn_WH(CaseID: string) { |
| | | // 模擬產生 In_Wh.dat |
| | | } |
| | | |
| | | private Case2upload(CaseID: string) { |
| | | // 模擬搬移/複製檔案結構準備上傳 |
| | | } |
| | | |
| | | private CaseAsk(CaseID: string): number { |
| | | // 模擬詢問 Server 案件是否允許上傳 |
| | | return 0; // 0: OK |
| | | } |
| | | |
| | | private GetCaseFormID(path: string): string { |
| | | return '11A000000000000'; // Mock FormID |
| | | } |
| | | |
| | | private CreateFormID_FormName(path: string, CaseID: string) {} |
| | | private CreateDocNo_DocName(path: string, CaseID: string) {} |
| | | |
| | | private _Msg(msg: string): string { |
| | | return msg; // 模擬多國語系轉換 |
| | | } |
| | | |
| | | private Add_Zoo(num: number, length: number): string { |
| | | let str = num.toString(); |
| | | while (str.length < length) str = '0' + str; |
| | | return str; |
| | | } |
| | | } |
| 比對新檔案 |
| | |
| | | // ************************************************************************ // |
| | | // WARNING |
| | | // ------- |
| | | // The types declared in this file were converted from a Pascal Type Library. |
| | | // ************************************************************************ // |
| | | |
| | | export const CB_IMGPSScanMajorVersion = 1; |
| | | export const CB_IMGPSScanMinorVersion = 0; |
| | | |
| | | export const LIBID_CB_IMGPSScan = '{F7D1C429-BE85-4FAD-A058-36A41C2AAA89}'; |
| | | |
| | | export const IID_ICB_IMGPSScanX = '{E68A01E9-2798-497F-9FB8-4EAFB7CCE5B9}'; |
| | | export const DIID_ICB_IMGPSScanXEvents = '{B7FB8B05-8BDA-4DFD-BCCF-2B657E8A108A}'; |
| | | export const CLASS_CB_IMGPSScanX = '{8B54EA2A-547C-48AD-BC71-C11C1340B37E}'; |
| | | |
| | | export enum TxActiveFormBorderStyle { |
| | | afbNone = 0x00000000, |
| | | afbSingle = 0x00000001, |
| | | afbSunken = 0x00000002, |
| | | afbRaised = 0x00000003, |
| | | } |
| | | |
| | | export enum TxPrintScale { |
| | | poNone = 0x00000000, |
| | | poProportional = 0x00000001, |
| | | poPrintToFit = 0x00000002, |
| | | } |
| | | |
| | | export enum TxMouseButton { |
| | | mbLeft = 0x00000000, |
| | | mbRight = 0x00000001, |
| | | mbMiddle = 0x00000002, |
| | | } |
| | | |
| | | export enum TxPopupMode { |
| | | pmNone = 0x00000000, |
| | | pmAuto = 0x00000001, |
| | | pmExplicit = 0x00000002, |
| | | } |
| | | |
| | | // OLE_COLOR can be represented as a number |
| | | export type OLE_COLOR = number; |
| | | // IFontDisp can be represented as any for simplicity in Node.js |
| | | export type IFontDisp = any; |
| | | |
| | | export interface ICB_IMGPSScanX { |
| | | Visible: boolean; |
| | | AutoScroll: boolean; |
| | | AutoSize: boolean; |
| | | AxBorderStyle: TxActiveFormBorderStyle; |
| | | Caption: string; |
| | | Color: OLE_COLOR; |
| | | Font: IFontDisp; |
| | | KeyPreview: boolean; |
| | | PixelsPerInch: number; |
| | | PrintScale: TxPrintScale; |
| | | Scaled: boolean; |
| | | readonly Active: boolean; |
| | | DropTarget: boolean; |
| | | HelpFile: string; |
| | | PopupMode: TxPopupMode; |
| | | ScreenSnap: boolean; |
| | | SnapBuffer: number; |
| | | DockSite: boolean; |
| | | DoubleBuffered: boolean; |
| | | readonly AlignDisabled: boolean; |
| | | readonly MouseInClient: boolean; |
| | | readonly VisibleDockClientCount: number; |
| | | ParentDoubleBuffered: boolean; |
| | | UseDockManager: boolean; |
| | | Enabled: boolean; |
| | | readonly ExplicitLeft: number; |
| | | readonly ExplicitTop: number; |
| | | readonly ExplicitWidth: number; |
| | | readonly ExplicitHeight: number; |
| | | AlignWithMargins: boolean; |
| | | ParentCustomHint: boolean; |
| | | url: string; |
| | | caseid: string; |
| | | mode: string; |
| | | work_no: string; |
| | | userid: string; |
| | | username: string; |
| | | userunit: string; |
| | | data: string; |
| | | verify: string; |
| | | rewrite: string; |
| | | modename: string; |
| | | language: string; |
| | | loandoc_value: string; |
| | | loandoc_enable: string; |
| | | useproxy: string; |
| | | c_docnolist: string; |
| | | c_docnamelist: string; |
| | | fixfilelist: string; |
| | | is_in_wh: string; |
| | | oldcaseinfo: string; |
| | | printyn: string; |
| | | is_oldcase: string; |
| | | custdocyn: string; |
| | | scancolor: string; |
| | | imgdpi: string; |
| | | filesizelimit: string; |
| | | casenolength: string; |
| | | imgdelete: string; |
| | | isExternal: string; |
| | | check_main_form: string; |
| | | WH_CATEGORY: string; |
| | | } |
| | | |
| | | export interface ICB_IMGPSScanXEvents { |
| | | OnActivate(): void; |
| | | OnClick(): void; |
| | | OnCreate(): void; |
| | | OnDblClick(): void; |
| | | OnDestroy(): void; |
| | | OnDeactivate(): void; |
| | | OnKeyPress(Key: number): void; |
| | | OnMouseEnter(): void; |
| | | OnMouseLeave(): void; |
| | | OnPaint(): void; |
| | | OnClosePage(): void; |
| | | } |
| 比對新檔案 |
| | |
| | | |
| | | |
| | | |
| | | |
| | | # IO 相關方法 |
| | | ## Dir |
| | | ```Pascal |
| | | function DirectoryExists(const Directory: string; FollowLink: Boolean = True): Boolean; |
| | | function str2Dir(const path: string[]): void |
| | | ``` |
| | | |
| | | |
| | | ## String ini 相關 |
| | | ```Pascal |
| | | TIniFile = class(TCustomIniFile) |
| | | public |
| | | destructor Destroy; override; |
| | | function ReadString(const Section, Ident, Default: string): string; override; |
| | | procedure WriteString(const Section, Ident, Value: String); override; |
| | | procedure ReadSection(const Section: string; Strings: TStrings); override; |
| | | procedure ReadSections(Strings: TStrings); override; |
| | | procedure ReadSectionValues(const Section: string; Strings: TStrings); override; |
| | | procedure EraseSection(const Section: string); override; |
| | | procedure DeleteKey(const Section, Ident: String); override; |
| | | procedure UpdateFile; override; |
| | | end; |
| | | ``` |
| | | |
| | | |
| | | ## String dat 相關 |
| | | delphi 大量使用 字串.LoadFromFile, 字串.SaveToFile |
| | | - LoadFromFile |
| | | - SaveToFile |
| | | ```Pascal |
| | | procedure LoadFromFile(const FileName: string); overload; virtual; |
| | | procedure LoadFromFile(const FileName: string; Encoding: TEncoding); overload; virtual; |
| | | procedure LoadFromStream(Stream: TStream); overload; virtual; |
| | | procedure LoadFromStream(Stream: TStream; Encoding: TEncoding); overload; virtual; |
| | | procedure Move(CurIndex, NewIndex: Integer); virtual; |
| | | procedure SaveToFile(const FileName: string); overload; virtual; |
| | | procedure SaveToFile(const FileName: string; Encoding: TEncoding); overload; virtual; |
| | | procedure SaveToStream(Stream: TStream); overload; virtual; |
| | | procedure SaveToStream(Stream: TStream; Encoding: TEncoding); overload; virtual; |
| | | ``` |
| 比對新檔案 |
| | |
| | | # 業務邏輯與資料解析模組 (Business Logic & Parser) 深度分析 |
| | | |
| | | 在 `CB_IMGPSScanImp.pas` 中,「業務邏輯與資料解析模組」扮演了將底層掃描影像與上層銀行/保險業務需求結合的橋樑。它將前端網頁傳入的原始設定資料,轉換為系統可理解的「案件 (Case)」、「文件 (Document)」與「表單 (Form)」結構,並執行極度複雜的業務規則檢核 (OMR 檢核)。 |
| | | |
| | | 若要將此模組進一步細拆,其內部組成可分為以下四大子分類: |
| | | |
| | | --- |
| | | |
| | | ## 1. 系統參數與組態儲存庫 (Config & Parameter Repository) |
| | | |
| | | **職責**:負責解析由 Web 端透過字串傳入的各類系統設定表(通常以 `!@!` 等特殊符號分隔),並將其儲存於記憶體中供全局查詢。 |
| | | **核心實作特性**: |
| | | * **字串表解析**:大量使用 `TStringList` 作為輕量級的記憶體資料庫 (`SetSQLData`, `GetSQLData`, `FindSQLData`)。 |
| | | * **系統設定下載與維護** (`GetSetInf1` ~ `GetSetInf7`): |
| | | * `DOC_INF`:文件設定檔(定義文件類型、是否需入庫等)。 |
| | | * `FORM_INF`:表單設定檔(定義條碼對應、長寬、十字定位點、最大頁數)。 |
| | | * `DM_FORM_INF`:相依與互斥規則檔。 |
| | | * `CHECK_RULE_INF`:檢核規則錯誤訊息定義檔。 |
| | | * `WORK_INF`:系統與硬體掃描的預設參數。 |
| | | |
| | | ## 2. 領域實體對映與結構管理器 (Entity Mapping & Structure Manager) |
| | | |
| | | **職責**:將抽象的條碼 (Barcode) 對映至具體的業務實體,並在檔案系統上維護「案件 -> 文件分份 -> 頁面」的實體目錄與虛擬樹狀結構 (`.dat` 索引檔)。 |
| | | **核心實作特性**: |
| | | * **條碼解析對映**:`BarCode2FormID`、`BarCode2CaseID`、`FormCode2DocNo`、`DocNo2DocName`。 |
| | | * **虛擬檔案系統維護**: |
| | | * 處理文件是否需要拆分多份 (`DocNoNeedDiv`)。 |
| | | * 管理 `Context.dat`、`CaseDocNo.dat`、`CaseDocNo_Copies.dat` 等索引檔,以記錄掃描影像與業務文件的歸屬關係。 |
| | | * **封裝資訊產生器**:`CreateDocNo_Info`、`CreateCustDocNo_Info` (將目錄結構轉換回 Web 端所需的特定字串格式)。 |
| | | |
| | | ## 3. OMR 與業務規則檢核引擎 (OMR & Business Rule Engine) |
| | | |
| | | **職責**:整個系統最龐大且複雜的業務邏輯核心,負責確保掃描進來的影像完全符合進件規定,防止缺件或矛盾。 |
| | | **核心實作特性** (`OMRCheckCase`): |
| | | * **文件級別檢核 (Document-Level Rules)**: |
| | | * **主文件檢核**:檢查主要文件是否齊全、頁數是否符合資料庫預期。 |
| | | * **相依/互斥檢核**:依據 `DM_FORM_INF`,判斷若存在 A 文件,是否遺漏了必附的 B 文件,或錯誤附上了互斥的 C 文件。 |
| | | * **生命週期檢核**:判斷表單條碼是否已超過停用日期、或超過允許的最大掃描頁數。 |
| | | * **OMR 畫記與欄位級別檢核 (Field-Level OMR Rules)** (透過 `Xmltool` 解析 `.xml` 設定檔,並計算指定 `(X, Y)` 座標的黑白像素點 `GetSiteOMR`): |
| | | * `settype1` (必填檢核):特定的 OMR 區塊必須有畫記。 |
| | | * `settype3` (有值連動必填):A 欄位有畫記時,相關的 B 欄位也必須畫記。 |
| | | * `settype8` (有值連動互斥):A 欄位有畫記時,相關的 B 欄位不能畫記。 |
| | | * `settype4` (有值相依文件):特定欄位有畫記時,系統必須檢查是否已附上指定的文件。 |
| | | * `settype5` (有值互斥文件):特定欄位有畫記時,系統必須檢查不能附上指定的文件。 |
| | | * `settype6` (強制備註):特定欄位有畫記時,要求使用者必須手動輸入備註說明。 |
| | | * `settype7` (OMR 帶值提取):讀取畫記並轉換為特定的業務數值 (`GetValue.xml`),隨後傳回後端。 |
| | | |
| | | ## 4. 案件異動與舊案引用處理器 (Case Modification & Legacy Handler) |
| | | |
| | | **職責**:處理「補件 (ESCAN)」、「重掃 (RSCAN)」、「引用舊件」等極度複雜的邊界狀態與跨案件資料流動。 |
| | | **核心實作特性**: |
| | | * **文件引用管理**:`SetUseCase`、`GetUseCase` 記錄與追蹤不同案件之間文件的借用與引用關係 (記錄於 `UseCase.ini`)。 |
| | | * **版本相容與轉換**:`OldCasetoNewCase`、`ErrFormtoCurrentForm` (處理舊系統升級或表單版本更迭時的自動替換邏輯)。 |
| | | * **防呆與鎖定**:`DeleteDocNoFileForESCAN` 等邏輯,防止在補件模式下誤刪非當次掃描的既有影像。 |
| | | |
| | | --- |
| | | |
| | | ## 💡 未來重構與微服務化建議 |
| | | |
| | | 若要將此「業務邏輯模組」進行現代化重構,強烈建議引入以下設計模式: |
| | | |
| | | 1. **Repository Pattern (儲存庫模式)**:將目前散落的 `FindSQLData` 與 `TStringList` 查詢,封裝為 `FormConfigRepository`、`DocConfigRepository` 等強型別介面,隔離底層字串處理細節。 |
| | | 2. **Rule Engine (規則引擎)**:將 `OMRCheckCase` 內部數百行的 `if/else` 與 XML 巡覽,拆分為獨立的實體類別(例如 `RequiredFieldRule`, `DependencyDocumentRule`, `MutexFieldRule`),並透過統一的 `Validator` 介面依序執行,大幅提升可測試性 (Testability) 與維護性。 |
| | | 3. **Domain Driven Design (領域驅動設計)**:將依賴實體 `.dat` 檔案的狀態管理,重構成記憶體中的 `TCase`, `TDocument`, `TPage` 領域物件,只有在最終儲存或打包時才寫入實體檔案系統。 |
| 比對新檔案 |
| | |
| | | |
| | | |
| | | # EnScan 的功能打包成 webAssembly 處理 Scan 是否可行 |
| | | |
| | | |
| | | 這是一個非常深入的技術架構問題。簡短的回答是:**「直接打包」不可行,但「功能移轉」與「資料對接」是可行的,只是架構需要完全翻轉。** |
| | | |
| | | 我們必須從 WebAssembly (Wasm) 的底層本質,來分析為什麼 `EnScan` 與 `DibHandle` 無法直接放入 Wasm: |
| | | |
| | | --- |
| | | |
| | | ### 1. 為什麼無法將 EnScan 打包成 WebAssembly 進行「掃描」? |
| | | |
| | | **核心障礙:硬體存取與沙盒 (Sandbox) 限制** |
| | | |
| | | * **Wasm 的隔離性:** WebAssembly 運行在瀏覽器的安全沙盒中。它**完全無法**存取你電腦的硬體(如 USB 接口、掃描機)。它甚至看不到你的 C 槽或 Windows 登錄表。 |
| | | * **TWAIN 協定限制:** `EnScan` 的核心是封裝 Windows 的 **TWAIN API**。TWAIN 是一個極度依賴作業系統訊息(Window Messages)與本地驅動程式(`.ds` 檔案)的協定。Wasm 無法呼叫 Windows API,也無法載入掃描機的 `.dll` 驅動程式。 |
| | | * **VCL 與 WinAPI 依賴:** `EnScan` 是一個 VCL 元件,深層綁定了 Windows 的 GDI(圖形設備介面)。Wasm 並沒有 Windows GDI,它只有一塊線性記憶體(Linear Memory)和 Canvas API。 |
| | | |
| | | --- |
| | | |
| | | ### 2. Wasm 能處理 `DibHandle` 或 `TMemoryStream` 嗎? |
| | | |
| | | 這涉及到兩者記憶體模型的完全不相容: |
| | | |
| | | #### **關於 DibHandle (Windows HGLOBAL)** |
| | | * **不可行:** `DibHandle` 本質上是一個 Windows 作業系統核心管理的**記憶體地址代號**。 |
| | | * **原因:** Wasm 的記憶體是「封閉的線性陣列」。它無法透過一個 Windows 句柄(Handle)去讀取 Windows 系統層級的記憶體。如果你把 `DibHandle` 傳給 Wasm,Wasm 只會把它看作一個無意義的整數。 |
| | | |
| | | #### **關於 TMemoryStream** |
| | | * **有條件可行:** 如果你使用如 **TMS WEB Core** 這類支援 Delphi 語法轉 Wasm 的工具,它們有實作虛擬的 `TMemoryStream`。 |
| | | * **本質差異:** * **Delphi Win32 的 `TMemoryStream`**:操作的是實體 RAM。 |
| | | * **Wasm 的 `TMemoryStream`**:操作的是瀏覽器分配給 Wasm 實例的一塊 **`ArrayBuffer`**。 |
| | | * **兩者不通:** 你無法直接將 Win32 的記憶體指針直接「丟進」Wasm 的記憶體空間。 |
| | | |
| | | --- |
| | | |
| | | ### 3. 可行的「現代化」架構建議:混合模式 (Hybrid) |
| | | |
| | | 如果你想用 Wasm 處理掃描後的影像,正確的架構應該是: |
| | | |
| | | |
| | | |
| | | #### **第一步:本地端代理 (Local Agent/Service)** |
| | | 你仍需要一個小的 Delphi Win32 程式(或 Service)跑在使用者電腦上: |
| | | 1. 呼叫 `EnScan` 進行掃描。 |
| | | 2. 在 `OnAcquire` 中拿到 `DibHandle`。 |
| | | 3. **關鍵轉化**:在本地端將 `DibHandle` 轉成標準的影像格式(如 **Base64 String** 或 **Uint8Array 二進位流**)。 |
| | | |
| | | #### **第二步:通訊橋樑** |
| | | 本地程式透過 **WebSocket** 或 **Local HTTP Server (REST)** 將這串二進位資料傳送給瀏覽器。 |
| | | |
| | | #### **第三步:Wasm 接收與處理** |
| | | 1. 瀏覽器收到二進位流。 |
| | | 2. 將資料寫入 Wasm 的記憶體(`ArrayBuffer`)。 |
| | | 3. **這時 Wasm 就可以發揮威力了**:你可以用 Wasm 來做影像分析、條碼辨識、或是用 C++/Rust 編譯出來的高效能演算法進行影像優化。 |
| | | |
| | | --- |
| | | |
| | | ### 4. 總結對照表 |
| | | |
| | | | 功能特性 | 原有的 Delphi OCX | 現代 WebAssembly 方案 | |
| | | | :--- | :--- | :--- | |
| | | | **硬體控制 (TWAIN)** | 直接呼叫 (OK) | **無法存取** (需透過本地 Service 轉發) | |
| | | | **記憶體管理** | 使用 Windows Handle (`DibHandle`) | 使用 Wasm 線性記憶體 (`ArrayBuffer`) | |
| | | | **影像處理速度** | 極快 (Native) | 極快 (Wasm 近乎 Native) | |
| | | | **跨瀏覽器支援** | 僅限 IE / Edge IE Mode | **全瀏覽器支援** (Chrome, Safari, Edge) | |
| | | | **安全風險** | 高 (ActiveX 有權限漏洞) | 極高 (沙盒保護) | |
| | | |
| | | ### **我的建議:** |
| | | 不要嘗試把 `EnScan` 直接「編譯」成 Wasm,那會導致數千個 WinAPI 連結錯誤。 |
| | | |
| | | 你應該將 **`CB_IMGPSScanImp.pas`** 拆分成兩部分: |
| | | 1. **Backend (Win32)**: 只負責掃描與存成檔案/串流。 |
| | | 2. **Frontend (Wasm/Web)**: 負責顯示 `ImageScrollBox` 的功能、UI 邏輯與上傳伺服器。 |
| | | |
| | | **你想了解如何寫一個簡單的 Local Service,讓 Delphi 掃描後把資料傳給網頁嗎?** 這通常是目前銀行系統現代化(去 IE 化)最普遍的作法。 |
| 比對新檔案 |
| | |
| | | # **相依套件分析報告** |
| | | |
| | | 本報告針對 ActiveX 元件核心實作檔 CB_IMGPSScanImp.pas 進行功能解構,並分析其與第三方套件的綁定關係,以作為後續維護或現代化升級之參考。 |
| | | |
| | | ## **壹、 依功能拆分模組與第三方套件綁定分析** |
| | | |
| | | 根據原始碼結構,CB_IMGPSScanImp.pas 是一個將介面、硬體、網路與業務邏輯高度耦合的「上帝物件 (God Object)」。若要進行重構或拆件,建議依據以下五個核心模組進行解耦: |
| | | |
| | | ### **1. 硬體掃描控制模組 (Scanner Controller)** |
| | | |
| | | - **功能描述**:負責與實體掃描機溝通,處理 TWAIN 介面初始化、設定掃描參數(DPI、色彩、雙面)、觸發掃描指令,以及接收掃描完成的記憶體影像 (DibHandle)。 |
| | | - **子模組細分建議**: |
| | | - **TWAIN 介面封裝 (TWAIN Wrapper)**:專門負責底層 TWAIN 驅動程式的呼叫、狀態機管理與錯誤處理。 |
| | | - **掃描參數管理器 (Scanner Profile Manager)**:負責載入、儲存與套用各類掃描設定 (DPI、對比、亮度、單雙面等)。 |
| | | - **事件與回呼接收器 (Acquisition Event Handler)**:專責處理非同步的 `OnAcquire` 事件,負責將底層 `DibHandle` 安全地轉換為應用程式可用的影像物件,並通知上層模組。 |
| | | - **關鍵方法**:initkscan、StatrTwainScan、OnAcquire、PageEnd、PageDone。 |
| | | - **第三方套件綁定 (強綁定)**: |
| | | - 依賴 **Envision Imaging SDK** (EnScan) 進行 TWAIN 驅動控制。 |
| | | - 依賴 EnMisc 處理底層記憶體或雜項運算。 |
| | | |
| | | ### **2. 影像處理與轉換模組 (Image Processor)** |
| | | |
| | | - **功能描述**:負責對擷取到的原始影像進行後製處理,包括:裁切(如 A3 裁 A4)、去斜 (Deskew)、旋轉、二值化 (轉黑白)、格式轉換 (轉 TIFF/JPEG),以及讀取影像上的條碼。 |
| | | - **子模組細分建議**: |
| | | - **影像幾何變換引擎 (Image Transformation Engine)**:專責處理去斜 (Deskew)、旋轉、縮放與裁切等座標/空間運算。 |
| | | - **色彩與格式處理器 (Color & Format Converter)**:負責二值化、灰階/彩色轉換,以及 TIFF/JPEG 等不同檔案格式的壓縮與編解碼。 |
| | | - **條碼與標記識別器 (Barcode & Marker Recognizer)**:獨立負責分析影像中的條碼內容,或進行 OMR (光學標記辨識) 的初步像素比對。 |
| | | - **OMR 與十字定位點分析器 (OMR & Anchor Analyzer)**:專門為「光學標記辨識 (OMR)」服務,負責找出影像上的基準定位點,並計算特定區域內的黑白像素分佈。 |
| | | - **關鍵方法**:ImageReSize_FormID、CheckNeedCrop、DeskewImg、ConvertToBW。 |
| | | - **第三方套件綁定 (強綁定)**: |
| | | - 依賴 **Envision Imaging SDK** (EnTifGr, EnDiGrph) 進行 TIFF (CCITT Group 4) 壓縮與 DIB 物件封裝。 |
| | | - 依賴 **MPS Barcode** (mpsBarco, BarcodesFinder) 進行影像條碼掃描與解析。 |
| | | |
| | | ### **3. 安全傳輸與 API 通訊模組 (Transport Manager)** |
| | | |
| | | - **功能描述**:負責將掃描並壓縮後的檔案上傳至伺服器,或從伺服器下載檔案。同時處理與後端 Java Servlet 的參數交換(如校時、取得 FTP 資訊、更新案件狀態)。 |
| | | - **子模組細分建議**: |
| | | - **HTTP/HTTPS API 請求管理器 (RESTful / Servlet Client Wrapper)**: 專責透過 HTTP(S) 協定,向後端的 Java Servlet 端點發送 GET/POST 請求。 |
| | | - **雙協定檔案上傳/下載引擎 (Dual-Protocol File Transfer Engine)**: 處理實體影像檔案的實體傳輸,並根據系統設定動態切換 HTTP 或 FTP 協定。 |
| | | - **安全加密與雜湊模組 (Security, Crypto & Hashing Utils)**: 負責資料傳輸前後的加解密運算,以及檔案完整性的 MD5 校驗,確保通訊過程不被竄改。 |
| | | - **傳輸前置準備與封裝器 (Payload Archiver & Preparer)**: 在真正啟動傳輸前,將硬碟上散落的 TIFF/JPG 檔案、`.dat` 索引檔打包成單一的 ZIP 封裝檔。 |
| | | - **關鍵方法**:upFile、dnFile_Get、ProcessServlet_Get、GetServerDate、GetftpInfo。 |
| | | - **第三方套件綁定 (強綁定)**: |
| | | - 依賴 **SecureBlackbox** (SBSimpleSSL, SBHTTPSClient, SBSimpleFTPS 等) 處理 HTTPS/FTPS 安全加密通訊。 |
| | | - 依賴 **Indy** (IdHashMessageDigest) 產生檔案的 MD5 檢核碼。 |
| | | - 依賴自訂/第三方加密庫 (Encryp) 處理字串加解密。 |
| | | |
| | | ### **4. 業務邏輯與資料解析模組 (Business Logic & Parser)** |
| | | |
| | | - **功能描述**:專案核心的領域邏輯,包含接收網頁端傳入的參數字串進行解析、定義文件歸類邏輯(由條碼推導文件類型)、OMR (光學標記辨識) 檢核定義等。 |
| | | - **子模組細分建議**: |
| | | - **系統參數與組態儲存庫 (Config & Parameter Repository)**:負責解析由 Web 端透過字串傳入的各類系統設定表(如 DOC_INF、FORM_INF 等),並將其儲存於記憶體中供全局查詢。 |
| | | - **領域實體對映與結構管理器 (Entity Mapping & Structure Manager)**:將抽象的條碼對映至具體的業務實體,並在檔案系統上維護「案件 -> 文件分份 -> 頁面」的虛擬樹狀結構。 |
| | | - **OMR 與業務規則檢核引擎 (OMR & Business Rule Engine)**:處理最核心的檢核邏輯,包含主文件/相依互斥文件檢核、欄位級別的 OMR 畫記檢核,以及基於 XML 設定的各種業務規則。 |
| | | - **案件異動與舊案引用處理器 (Case Modification & Legacy Handler)**:專責處理「補件」、「重掃」、「引用舊件」等複雜的邊界狀態與跨案件資料流動。 |
| | | - **關鍵方法**:SetSQLData、GetSQLData、FormCode2DocNo、OMRCheckCase。 |
| | | - **第三方套件綁定 (中度綁定)**: |
| | | - 依賴 **Xmltool** 解析 XML 格式的 OMR 座標定義。 |
| | | - 依賴 **VCLZip** (VCLZip, VCLUnZip) 將多個影像檔打包成 ZIP 進行單一傳輸。 |
| | | |
| | | ### **5. UI 與介面呈現模組 (UI View)** |
| | | |
| | | - **功能描述**:負責 ActiveX 控制項的視覺化介面,包含左側的樹狀文件清單 (TreeView)、右側的影像預覽區 (ScrollBox),以及按鈕與多國語言切換。 |
| | | - **子模組細分建議**: |
| | | - **案件樹狀檢視元件 (TreeView UI)**: 獨立管理左側的案件與文件節點,負責節點的建立、刪除與圖示切換。 |
| | | - **多影像滾動視窗 (ScrollBox View)**: 管理右側大圖及縮圖的並排顯示,支援拖拉、選取以及快捷鍵操作。 |
| | | - **多國語系引擎 (I18N Manager)**: 統一從設定檔中載入字串,替換畫面上所有的 Label、Button 與 Message 提示。 |
| | | - **核心版面與容器管理 (Layout & Container Management)**: 定義整個 ActiveX 控制項的視覺區塊,並透過階層式的 Panel 進行排版管理。 |
| | | - **系統狀態與進度回饋 (Status & Progress Feedback)**: 在進行耗時的任務時,鎖定 UI 並給予使用者明確的等待提示。 |
| | | - **關鍵方法**:InitialLanguage、各類 OnClick 事件。 |
| | | - **第三方套件綁定 (輕度綁定)**: |
| | | - 依賴 **Envision Imaging** (EnImgScr) 作為支援大影像拖曳的滾動視窗。 |
| | | - 依賴 PJMenuSpeedButtons 作為自訂外觀的按鈕元件。 |
| | | |
| | | ## **貳、 第三方套件功能總覽** |
| | | |
| | | 為達成上述複雜的業務需求,本專案引入了多個 Delphi 著名的第三方套件。以下為各套件的大致功能說明: |
| | | |
| | | ### **1. Envision Imaging SDK (前綴 En)** |
| | | |
| | | 這是一套專為 Delphi 設計的高效能影像與 TWAIN 處理套件,在金融與保險業的文件電子化系統中非常常見。 |
| | | |
| | | - **EnScan**:封裝了 TWAIN 協定,讓程式能直接控制實體掃描機,控制進紙、解析度與雙面掃描。 |
| | | - **EnDiGrph (TDibGraphic)**:封裝 Windows 底層的 DIB (Device Independent Bitmap) 記憶體句柄,讓開發者能像操作一般圖片一樣操作掃描出來的原始資料。 |
| | | - **EnTifGr**:專門處理 TIFF 影像格式。銀行業愛用 TIFF 是因為它支援「多頁」且黑白壓縮率極高 (Group 4 Fax Compression)。 |
| | | - **EnImgScr**:提供 TImageScrollBox 視覺元件,用來平滑顯示並縮放超大尺寸的掃描圖檔。 |
| | | - **EnMisc**:提供影像處理所需的底層輔助數學或資料轉換函式。 |
| | | |
| | | ### **2. SecureBlackbox (前綴 SB)** |
| | | |
| | | 由 EldoS (後被 /n software 收購) 開發的強大安全通訊與加密套件。由於早期 Delphi (D7~D2007) 內建網路庫缺乏完善的 SSL/TLS 支援,此套件被廣泛用於補強安全性。 |
| | | |
| | | - **SBHTTPSClient / SBSimpleSSL**:實作了完整的 HTTPS 用戶端通訊協定,用來安全地上傳檔案或呼叫 Servlet API。 |
| | | - **SBSimpleFTPS**:支援 FTP over SSL (FTPS) 的安全檔案傳輸。 |
| | | - **SBWinCertStorage / SBX509**:處理 Windows 系統內的憑證儲存區,以及 X.509 數位憑證的驗證(例如確認銀行伺服器的 HTTPS 憑證是否合法)。 |
| | | |
| | | ### **3. MPS Barcode & BarcodesFinder** |
| | | |
| | | - **功能**:這是一套條碼識別引擎。 |
| | | - **專案應用**:在掃描過程中(如 PageDone 或 OnAcquire 階段),它會掃描圖片特定區域尋找一維或二維條碼。讀取到的條碼值會被用來決定這張影像屬於哪一種「文件表單」(FormCode),進而實現自動分類歸檔。 |
| | | |
| | | ### **4. VCLZip (VCLZip, VCLUnZip)** |
| | | |
| | | - **功能**:老牌的 Delphi ZIP 壓縮元件。 |
| | | - **專案應用**:為了節省網路頻寬與減少伺服器連線次數,掃描完成的多張 TIFF/JPG 檔案會被打包成一個 .zip 檔後,再一次性上傳至伺服器。 |
| | | |
| | | ### **5. Indy Components (前綴 Id)** |
| | | |
| | | - **IdHashMessageDigest / idHash**:Delphi 內建的 Internet Direct (Indy) 函式庫。 |
| | | - **專案應用**:在此專案中被用來計算檔案或字串的 **MD5 Hash (雜湊值)**。通常用於確保檔案上傳過程沒有損壞,伺服器比對 MD5 一致才算上傳成功。 |
| | | |
| | | ### **6. 其他工具 (Tools & Utils)** |
| | | |
| | | - **Xmltool**:負責讀寫 XML 檔案。銀行經常將表單的 OMR (光學標記辨識) 檢核座標或參數寫成 XML 設定檔,透過此工具讀入系統。 |
| | | - **Encryp**:這可能是開發商自訂或第三方的輕量級加密單元,用來加密本機設定檔 (.ini) 內的敏感資訊或混淆網頁傳入的參數。 |
| 比對新檔案 |
| | |
| | | # 影像處理與轉換模組 (Image Processor) 深度分析 |
| | | |
| | | 在 `CB_IMGPSScanImp.pas` 中,「影像處理與轉換模組」負責在硬體掃描取得原始影像後,以及上傳/顯示之前,對影像進行一系列的強化、分析與格式轉換。這個模組是決定影像品質、檔案大小與後續 OMR 辨識準確率的關鍵。 |
| | | |
| | | 若要將此模組進一步細拆,其內部組成可分為以下四大子分類: |
| | | |
| | | --- |
| | | |
| | | ## 1. 影像幾何與物理變換引擎 (Image Geometric Transformation Engine) |
| | | |
| | | **職責**:專責處理影像的空間座標轉換、旋轉、去斜以及裁切,確保最終儲存的影像端正且符合業務規定的尺寸。 |
| | | **核心實作特性**: |
| | | * **影像去斜與轉正**: |
| | | * `DeskewImg`:透過演算法自動偵測影像傾斜角度並將其校正。 |
| | | * `Rotate`:依據條碼辨識出的角度 (`MpsBarcodeinf.r180`) 或使用者手動點擊按鈕 (`SpeedButton14Click` 等),進行 90、180、270 度的旋轉。 |
| | | * **影像裁切與分割**: |
| | | * `CheckNeedCrop`:根據影像寬高比與偵測到的條碼數量,判斷是否為需要分割的 A3 雙頁合併影像。 |
| | | * `CropImg`:給定 `TRect` 座標,將 A3 影像精準裁切為兩張 A4 (`iGraphic_First`, `iGraphic_sec`)。 |
| | | * **影像縮放 (Resize)**: |
| | | * `ImageReSize_FormID` / `ImageReSize_tmp`:依據表單定義檔 (`FORM_INF`) 中的長寬設定與尋找到的十字定位點,強制將影像縮放變形至標準尺寸,以利後續的 OMR 座標對位。 |
| | | * `DpiResize`:調整影像的 DPI 解析度參數。 |
| | | |
| | | ## 2. 色彩處理與編碼轉換器 (Color & Format Converter) |
| | | |
| | | **職責**:處理影像的色彩深度 (位元深度) 轉換,以及針對不同色彩模式套用最適合的檔案壓縮演算法,以達到最佳的畫質與檔案大小平衡。 |
| | | **核心實作特性**: |
| | | * **色彩空間轉換**: |
| | | * `ConvertToBW`:將彩色或灰階影像強制二值化 (Binarization) 轉為黑白影像,這不僅能大幅縮小檔案,也是條碼與 OMR 辨識的必要前置步驟 (系統通常會保留一個隱藏的 `ISB_BW` 物件專做辨識用)。 |
| | | * `ConvertToGray`:將彩色轉換為灰階 (`ifGray256`)。 |
| | | * `Image_Smooth` / `NegativeImg` / `CleanupBorder`:影像平滑化、反相處理與清除黑邊。 |
| | | * **格式與壓縮決策**: |
| | | * 針對黑白影像 (`ifBlackWhite`):使用 `tcGroup4` (CCITT Group 4 Fax Compression) 或 `tcPackBits` 壓縮,並儲存為 `.tif`。 |
| | | * 針對彩色/灰階影像 (`ifTrueColor`, `ifGray256`, `ifColor256`):轉換為 `TJpegGraphic`,套用 `tcJpeg` 壓縮與指定的壓縮率 (`FJpgCompression` / `SaveQuality`),並儲存為 `.jpg`。 |
| | | |
| | | ## 3. 條碼識別與解析器 (Barcode Recognizer) |
| | | |
| | | **職責**:獨立負責掃描與解析影像上的一維或二維條碼,這是系統實現「文件自動歸類」的核心依賴。 |
| | | **核心實作特性**: |
| | | * **條碼引擎呼叫**: |
| | | * `MpsGetBarcode`:呼叫底層的 MPS Barcode 引擎,傳入二值化的影像 (`ISB_BW.Graphic`)。 |
| | | * 回傳 `TMpsBarcodeinf` 結構,包含條碼內容字串陣列 (`Text`) 與每個條碼的方向資訊 (`r180`)。 |
| | | * **應用與邏輯綁定**: |
| | | * 透過條碼字串找出對應的 `FormID` 與 `DocNo`。 |
| | | * 檢查條碼長度是否符合規範 (`FormIDLength`),過濾雜訊或誤判的條碼。 |
| | | |
| | | ## 4. OMR 與十字定位點分析器 (OMR & Anchor Analyzer) |
| | | |
| | | **職責**:專門為「光學標記辨識 (OMR)」服務,負責找出影像上的基準定位點,並計算特定區域內的黑白像素分佈。 |
| | | **核心實作特性**: |
| | | * **基準點尋找 (Anchor Finding)**: |
| | | * `FindPoint`:根據 XML 定義的模式 (`ANCHOR` 或 `FRAME`),在影像四個角落尋找定位基準點 (`UpLPoint`, `UpRPoint`, `DownLPoint`, `DownRPoint`)。 |
| | | * `CheckSize`:比對找到的定位點距離與標準長寬是否有巨大落差,若找不到則記錄至 `AnchorError.dat`。 |
| | | * **像素計算與標記判定 (Pixel Calculation)**: |
| | | * `GetSiteOMR`:根據 XML 傳入的相對座標字串 (`Site`),將其換算為實際的 `TRect`。 |
| | | * `Get_OMR`:計算該區域內黑色像素的數量。 |
| | | * 結合雜訊過濾 (`ClearLine`) 與容差值 (`SafePixel`, `bt`),最終判定該 OMR 欄位是否「有畫記」。 |
| | | |
| | | --- |
| | | |
| | | ## 💡 未來重構與微服務化建議 |
| | | |
| | | 若要對此影像處理模組進行重構,建議方向如下: |
| | | |
| | | 1. **抽離為獨立的 Pipeline 模式 (管線模式)**: |
| | | 目前這些功能散落在 `OnAcquire` 與各個事件中。應將其重構為一條清晰的影像處理管線:`Image -> Deskew -> Crop -> ConvertBW -> BarcodeRead -> Resize -> Compress -> Save`。每個步驟 (Step) 應該是獨立的類別,方便抽換或開關。 |
| | | 2. **解耦 UI 與影像處理**: |
| | | 目前大量依賴畫面上隱藏的 `ISB_BW` (TImageScrollBox) 來進行二值化和條碼辨識。這違反了 MVC 原則且耗費額外的 GDI 資源。應改用純記憶體物件 (如獨立的 `TDibGraphic` 或 `TBitmap`) 在背景執行緒中進行這些運算,不要綁定可見的 UI 元件。 |
| | | 3. **引入更現代的影像引擎 (如 OpenCV)**: |
| | | 早期依賴的 Envision SDK 在尋找十字定位點 (FindPoint) 和去斜 (DeskewImg) 的演算法可能較為老舊。若未來轉型為微服務架構,可將這部分邏輯移植為 Python/C++ 並使用 OpenCV 來達成更精準的高速運算。 |
| 比對新檔案 |
| | |
| | | # **IO 路徑與檔案操作分析** |
| | | |
| | | 本報告詳列了 CB_IMGPSScanImp.pas 檔案中定義的所有 IO 相關變數,以及執行目錄與檔案管理的關鍵方法,並定義了抽像化所需的介面原型。 |
| | | |
| | | ## **壹、 關鍵路徑與檔案變數 (Variables)** |
| | | |
| | | 程式中定義了多個全域或私有欄位,用來追蹤檔案在硬碟上的實體位置。 |
| | | |
| | | ### **1. 核心路徑變數與動態組合邏輯** |
| | | |
| | | | **變數名稱** | **類型** | **組合邏輯 (Combination Logic)** | **用途說明** | |
| | | | --- | --- | --- | --- | |
| | | | **ImagePath** | String | WORK_INF 的 LOCAL_PATH 參數值 | **本機基礎根路徑**。所有暫存資料的起點。 | |
| | | | **ScaniniPath** | String | ImagePath + FWork_No + '\' + FUserUnit + '\' | **設定檔存放區**。區分作業別與單位。 | |
| | | | **ImageSavePath** | String | 同 ImagePath (初期) 或隨 FMode 變動 | **影像儲存基準路徑**。 | |
| | | | **ScanPath** | String | ImageSavePath + ScanCaseno + '\' + ScanDocdir + '\' | **當前掃描實體目錄**。指向最末層的影像資料夾。 | |
| | | | **TransPath** | String | ImageSavePath + CaseID + '\Upload\' | **準備上傳區**。在傳送前將檔案結構扁平化組合於此。 | |
| | | | **DisplayPath** | String | ImageSavePath + NowCaseNo + '\' | **預覽基準目錄**。用於 UI 點選樹狀圖時讀取特定案件資料。 | |
| | | | **LngPath** | String | GetLocalAppDir(Handle) + 'MPS\CB_IMGPS\' | **應用程式資源路徑**。存放在使用者設定檔目錄以避開權限問題。 | |
| | | | **CheckXmlPath** | String | ImagePath + 'OMRSITE\' | **OMR 規則暫存區**。存放從伺服器同步下來的 XML 定義。 | |
| | | | **SamplePath** | String | ImagePath + 'Sample\' + FWork_No + '\' | **範本影像存放區**。按作業別區分。 | |
| | | | **SitePath** | String | ImagePath + 'Site\' | **登打定位存放區**。 | |
| | | | **FFtpRootPath** | String | 透過伺服器回傳值動態設定 | **FTP 伺服器根路徑**。指定在遠端 FTP 伺育器上存放影像檔的起始位址。 | |
| | | |
| | | ### **2. 檔案名稱變數 (Filenames)** |
| | | |
| | | | **變數名稱** | **類型** | **用途說明** | |
| | | | --- | --- | --- | |
| | | | **ScanSaveFilename** | String | 下一張掃描影像預定的檔名(通常包含 FormID)。 | |
| | | | **PEFileName** | String | 在 PageEnd 階段確定的最終完整檔案路徑。 | |
| | | | **AttName** | String | **附件目錄名稱**。根據 FIs_In_Wh 決定為 Attach 或 S_Attach。 | |
| | | | **Ext** | String | 副檔名。預設為 .tif,彩色/灰階模式下可能切換為 .jpg。 | |
| | | |
| | | ## **貳、 關鍵 IO 操作方法 (Methods)** |
| | | |
| | | 以下為需要進行抽像化設計的核心 IO 方法。 |
| | | |
| | | ### **1. 目錄管理 (Directory Management)** |
| | | |
| | | | **方法名稱** | **作用** | **應用場景** | **建議抽象 IO 介面** | |
| | | | --- | --- | --- | --- | |
| | | | _DelTree | 遞迴刪除整個目錄樹 | 上傳成功後清空暫存、模式切換初始化目錄 | RemoveDirRecursive(Path) | |
| | | | Str2Dir / ForceDirectories | 確保路徑以 \ 結尾並建立路徑 | 初始化或掃描前建立資料夾 | EnsureDirExists(Path) | |
| | | | GetNoNameCase | 搜尋尚未配號的臨時目錄 | 在本機搜尋如 未配號0001 到 9999 的可用路徑 | FindNextAvailablePath(Pattern) | |
| | | | GetLocalAppDir | 取得 Windows %LocalAppData% 路徑 | 確保在現代 Windows 系統中有權限寫入語言檔與 Log | GetBaseStoragePath() | |
| | | |
| | | ### **2. 存在性檢查與搜尋 (Existence & Search)** |
| | | |
| | | | **方法名稱** | **作用** | **應用場景** | **建議抽象 IO 介面** | |
| | | | --- | --- | --- | --- | |
| | | | FileExists | 檢查指定路徑檔案是否存在 | 在載入 .dat、.ini 或影像前進行安全檢查 | IsFilePresent(Path) | |
| | | | DirectoryExists | 檢查指定路徑資料夾是否存在 | 在建立掃描目錄或上傳前確認結構完整性 | IsDirPresent(Path) | |
| | | | FindFirst / FindNext | 搜尋符合特定格式的檔案 | 取得實體檔案大小 (FileRec.Size) 或遞迴搜尋檔案 | ListFiles(Path, Pattern) / GetFileInfo(Path) | |
| | | |
| | | ### **3. 檔案生命週期與刪除 (File Lifecycle & Deletion)** |
| | | |
| | | | **方法名稱** | **作用** | **應用場景** | **建議抽象 IO 介面** | |
| | | | --- | --- | --- | --- | |
| | | | ReSortFileName | 影像重新編序命名 | 刪除或插入影像後,修正 001_xxx 等序號連續性 | RenameFile(Old, New) | |
| | | | DeleteImageFile | 刪除實體影像檔 | 單張影像刪除並觸發 .dat 內容同步更新 | DeleteFile(Path) | |
| | | | DeleteDocNoFile | 刪除特定文件代號下所有影像 | 整批文件(DocNo)的物理刪除與序號重整 | DeleteFilesByPattern(Pattern) | |
| | | | DeleteShowFile | 刪除 UI 顯示中的影像清單 | 根據介面選取狀態進行批次物理刪除 | DeleteFileList(List<Path>) | |
| | | | RenameFile / MoveFile | 檔案搬移或更名 | 用於「分案」、「移動頁數」或「變更歸類」 | MoveFile(Src, Dest) | |
| | | | CopyFile | 複製實體檔案 | 影像搬移至上傳區或引用舊案件影像 | CopyFile(Src, Dest) | |
| | | |
| | | ### **4. 資料紀錄與序列化 (Metadata IO)** |
| | | |
| | | | **方法名稱** | **作用** | **應用場景** | **建議抽象 IO 介面** | |
| | | | --- | --- | --- | --- | |
| | | | DeleteCustomDocDir | 移除自訂文件定義 | 從 CustomDocNo.ini 中移除特定自訂文件的節點 | Storage.IniDeleteSection(Section) | |
| | | |
| | | ### **5. 網路傳輸 IO 方法 (Network Retrieval - dnFile_Get)** |
| | | |
| | | 專案中使用 `dnFile_Get` 從伺服器端同步必要的環境檔案或規則定義。 |
| | | |
| | | | **抓取對象 (目標路徑)** | **遠端請求 Action** | **用途說明** | | |
| | | | --- | --- | --- | --- | |
| | | | **CheckXmlPath + filename** | `GetCheckXml` | **下載 OMR 檢核定義檔**。同步伺服器端最新的 XML 座標規則,用於自動判斷影像區域。 | https | |
| | | | **SamplePath + filename** | `GetSampleImg` | **下載範本影像**。用於在 UI 介面上提供給操作員對照的標準範例圖檔。 | https | |
| | | | **SitePath + filename** | `GetSiteImg` | **下載定位定義圖**。下載與登打、校對位置相關的輔助影像。 | https | |
| | | | **LngPath + 'Language.ini'** | `GetLanguage` | **同步多國語言檔**。確保用戶端介面文字(繁體、英文、越南語等)與伺服器同步。 | https | |
| | | |
| | | ## **參、 資料紀錄與序列化 (Metadata IO)** |
| | | |
| | | 專案中使用了大量的 .dat 與 .ini 檔案來記錄目錄內的結構資訊與使用者偏好。 |
| | | |
| | | ### **1. .dat 檔案 (結構紀錄)** |
| | | |
| | | | **檔案名稱** | **組合路徑邏輯 (Path Logic)** | **內容與用途** | |
| | | | --- | --- | --- | |
| | | | **Context.dat** | ImageSavePath + CaseID + '\' + DocDir + '\Context.dat' | **影像清單**。紀錄該資料夾內所有影像檔名的正確順序。 | |
| | | | **DocDir.dat** | ImageSavePath + CaseID + '\Upload\DocDir.dat' | **目錄對應表**。紀錄 Upload 下每個檔名所屬的原始 DocDir | |
| | | | **CaseDocNo.dat** | ImageSavePath + CaseID + '\CaseDocNo.dat' | **文件目錄索引**。紀錄案件內已建立的實體資料夾名稱。 | |
| | | | **CaseDocNo_Copies.dat** | ImageSavePath + CaseID + '\CaseDocNo_Copies.dat' | **份數紀錄**。對應 CaseDocNo.dat 中的資料夾分別有多少份。 | |
| | | | **CaseIndex.dat** | ImageSavePath + CaseID + '\CaseIndex.dat' | **案件屬性**。如是否為「授信卷」(Case_loandoc)。 | |
| | | | **EditedDocDir.dat** | ImageSavePath + CaseID + '\EditedDocDir.dat' | **異動清單**。紀錄當次操作中被修改過的文件目錄。 | |
| | | | **Scan_Memo.dat** | DisplayPath + 'Scan_Memo.dat' | **使用者註記**。紀錄操作員手動輸入的備註內容。 | |
| | | | **CaseList.dat** | ImageSavePath + 'CaseList.dat' | **案件總表**。紀錄目前本機快取中存在的所有 CaseID。 | |
| | | |
| | | ### **2. .ini 檔案 (設定與偏好)** |
| | | |
| | | | **檔案名稱** | **路徑邏輯** | **回寫 (Write-back)** | **內容與用途** | 寫入關聯 | |
| | | | --- | --- | --- | --- | --- | |
| | | | **Scan.ini** | ScaniniPath + 'Scan.ini' | **是 (頻繁)** | **掃描器硬體設定**。儲存最後一次選取的 DPI、雙面、色彩模式、亮度、對比等參數。 | | |
| | | | **CustomDocNo.ini** | ScaniniPath + 'CustomDocNo.ini' | **是 (中等)** | **自訂文件代號**。儲存操作員定義的文件分類名稱與內部 ID 映射。 | DeleteCustomDocDir | |
| | | | **CB_IMGPSScan.ini** | LngPath + 'CB_IMGPSScan.ini' | **是 (低頻)** | **環境配置**。儲存伺服器 IP、Port、自動更新版本資訊等全域參數。 | | |
| | | | **Language.ini** | LngPath + 'Language.ini' | **否 (唯讀同步)** | **多國語言包**。僅透過 `dnFile_Get` 從伺服器抓取更新,OCX 不自行修改。 | | |
| | | |
| | | ## **肆、 串流與讀寫操作 (Stream & File I/O)** |
| | | |
| | | 源碼中頻繁使用 .LoadFromFile 與 .SaveToFile 來實現資料的持久化。 |
| | | |
| | | ### **1. 使用 TStringList 讀寫 .dat 結構** |
| | | |
| | | | **操作對象** | **讀取場景 (Load)** | **寫入場景 (Save)** | **建議抽象 IO 介面** | |
| | | | --- | --- | --- | --- | |
| | | | **結構清單 (.dat)** | LoadImgFile 載入以重建樹狀結構 | PageEnd 掃描完成或結構增刪後即時存檔 | ReadTextFile(Path) / WriteTextFile(Path, Content) | |
| | | | **備註文件 (Memo)** | 進入備註編輯器時載入舊有內容 | WNoteBtnClick 完成編輯後儲存內容 | ReadTextFile(Path) / WriteTextFile(Path, Content) | |
| | | | **上傳包定義** | N/A | CreateFormID_FormName 產生上傳包所需的定義檔 | WriteTextFile(Path, Content) | |
| | | | | | | | |
| | | | | | | | |
| | | |
| | | ### **2. 使用 TImageScrollBox / TDibGraphic 讀寫影像** |
| | | |
| | | | **操作對象** | **讀取場景 (Load)** | **寫入場景 (Save)** | **建議抽象 IO 介面** | |
| | | | --- | --- | --- | --- | |
| | | | **掃描影像原始檔** | view_image_... 載入顯示於預覽窗 | OnAcquire 將記憶體影像 (Dib) 持久化至磁碟 | ReadBinaryFile(Path) / WriteBinaryFile(Path, Blob) | |
| | | | **影像處理覆蓋** | ImageReSize_FormID 讀取進行定位縮放 | 處理完成後覆蓋原始影像檔 | ReadBinaryFile(Path) / WriteBinaryFile(Path, Blob) | |
| | | | **旋轉/後製處理** | 點擊旋轉按鈕後從磁碟重新讀取 | 旋轉完成後保存更新內容回實體檔案 | ReadBinaryFile(Path) / WriteBinaryFile(Path, Blob) | |
| | | |
| | | ## **伍、 檔案路徑構造邏輯示例** |
| | | |
| | | 程式中典型的路徑構造方式如下(以 PageEnd 為例): |
| | | |
| | | // 1. 基礎路徑 |
| | | |
| | | ScanPath := ImageSavePath + ScanCaseno + '\'; |
| | | |
| | | // 2. 進入特定文件目錄 (DocDir) |
| | | |
| | | ScanPath := ScanPath + ScanDocdir + '\'; |
| | | |
| | | // 3. 確保實體目錄存在 |
| | | |
| | | Str2Dir(ScanPath); |
| | | |
| | | // 4. 構造最終影像路徑 |
| | | |
| | | PEFileName := ScanPath + Add_Zoo(PageCount, 3) + '_' + FormID + ext; |
| | | |
| | | ## **陸、 IO 方法具體實作邏輯細部說明 (Detailed Implementation Logic)** |
| | | |
| | | 本節針對 CB_IMGPSScanImp.pas 內部如何透過 Windows API 實作這些 IO 操作進行細部解說。 |
| | | |
| | | ### **1. Str2Dir(Path: string) 的運作機制** |
| | | |
| | | 這個方法的主要作用是 **「路徑標準化與實體資料夾建立」**。 |
| | | |
| | | - **字串處理**:它會先檢查傳入的 Path 字串最後一個字元是否為 \。如果不是,會自動補上。 |
| | | - **建立資料夾**:**是的,它會在內部建立資料夾**。它會呼叫 Delphi 的 ForceDirectories(Path) 函式。 |
| | | - **ForceDirectories 的特性**:與一般的 CreateDir 不同,ForceDirectories 會「遞迴」建立路徑中所有不存在的父目錄(例如若路徑為 C:\A\B\C,即使 A 和 B 都不存在,它也會一併建立)。 |
| | | |
| | | ### **2. _DelTree(DirName: string) 的遞迴邏輯** |
| | | |
| | | 由於 Delphi 早期版本沒有提供單一函式來刪除包含檔案的資料夾,此方法透過 Windows 的搜尋機制實現: |
| | | |
| | | - **遍歷搜尋**:使用 FindFirst 和 FindNext 找出資料夾內的所有物件。 |
| | | - **檔案處理**:若搜尋到的是一般檔案,直接呼叫 DeleteFile。 |
| | | - **遞迴處理**:若搜尋到的是子資料夾,則「自己呼叫自己」 (_DelTree) 先清空該子資料夾。 |
| | | - **物理移除**:當資料夾內的所有內容都清空後,呼叫 RemoveDir 將空的資料夾從磁碟移除。 |
| | | |
| | | ### **3. FindFirst 與 FindNext 的搜尋與疊代邏輯** |
| | | |
| | | 這兩個方法是 Delphi 處理「集合式 IO 操作」(如掃描目錄、批次刪除)的基礎,通常搭配 TSearchRec 結構使用。 |
| | | |
| | | - **FindFirst(Path, Attr, SearchRec)**: |
| | | - **動作**:啟動搜尋任務,將找到的第一個檔案資訊填入 SearchRec。 |
| | | - **應用例**:在 PM104Click (匯入影像) 時,程式會先執行 FindFirst(FName, faAnyfile, FileRec) 來確認使用者選取的實體檔案是否存在,並藉由 FileRec.Size 取得位元組大小,用以判斷是否超過系統設定的匯入限制(例如 5MB)。 |
| | | - **FindNext(SearchRec)**: |
| | | - **動作**:繼續尋找下一個符合條件的檔案。 |
| | | - **疊代模式**:通常包裹在 repeat ... until FindNext(SR) <> 0 迴圈中。 |
| | | - **應用例**:在 _DelTree 實作中,程式利用此疊代邏輯將資料夾內「所有的」子目錄與檔案清空,直到 FindNext 回傳非零值(代表搜尋結束)。 |
| | | - **FindClose(SearchRec)**: |
| | | - **重要性**:釋放搜尋控制代碼(Handle)。若未呼叫,頻繁搜尋會導致系統資源洩漏。 |
| | | |
| | | ### **4. GetNoNameCase(Path: string) 的搜尋邏輯** |
| | | |
| | | 這是一個典型的 **「可用名稱查表」** 實作: |
| | | |
| | | - **迴圈範圍**:從 1 迭代到 9999。 |
| | | - **格式組合**:使用 Add_Zoo 函式將數字轉為四位數(如 0001),並加上前綴字「未配號」。 |
| | | - **磁碟檢查**:呼叫 DirectoryExists 檢查 Path + '未配號000x'。 |
| | | - **結果回傳**:第一個回傳 False(代表該資料夾尚不存在)的名稱即為結果,程式會立即跳出迴圈並選定此名稱。 |
| | | |
| | | ### **5. ReSortFileName(Path: string) 的連續性維護** |
| | | |
| | | 為了保證 UI 上顯示的頁碼順序與磁碟檔名一致,此方法採用「先暫存、後改名」的策略: |
| | | |
| | | - **防止衝突**:先將目錄內所有符合格式的影像更名為帶有臨時前綴(如 @)的檔名。 |
| | | - **按序更名**:讀取 Context.dat 內的清單,依照目前的順序,將檔案逐一更名為 001_..., 002_... 等格式。 |
| | | - **同步索引**:最後將更名後的結果重新寫入 Context.dat。 |
| | | |
| | | ### **6. GetLocalAppDir(Handle: HWND) 的權限繞過** |
| | | |
| | | 這是一個系統級別的 IO 位置定位方法: |
| | | |
| | | - **API 呼叫**:呼叫 SHGetSpecialFolderPath (Windows Shell API)。 |
| | | - **定位 CSIDL_LOCAL_APPDATA**:指向 C:\Users\使用者名稱\AppData\Local\。 |
| | | - **必要性**:這能解決 OCX 程式被安裝在 Program Files 下時,因為 UAC 權限無法在安裝目錄寫入 Log 或語言檔的問題。 |
| | | |
| | | ### **🎯 移植至 Web 平台的建議** |
| | | |
| | | 當您將這些邏輯移植到 Web 時: |
| | | |
| | | - Str2Dir 應對應到 **IndexedDB** 的 Object Store 建立或 **File System Access API** 的 getDirectoryHandle(搭配 create: true)。 |
| | | - FindFirst / FindNext 在 Web 端應對應到 Directory Handle 的 entries() 異步疊代器。 |
| | | - .dat 序列化文件在 Web 端建議直接轉為 **JSON** 格式存儲於瀏覽器的 localStorage 或 IndexedDB 中。 |
| | | |
| | | ## **柒、 設定檔回寫邏輯 (Write-back Logic)** |
| | | |
| | | 本節說明專案中哪些 .ini 檔案會由 OCX 主動更新資料及其觸發時機。 |
| | | |
| | | | **檔案名稱** | **回寫內容 (Content)** | **觸發時機 (Trigger)** | |
| | | | --- | --- | --- | |
| | | | **Scan.ini** | DPI、Duplex、亮度、對比、影像模式 | 掃描前設定變更或啟動掃描時(SaveScanPara)。 | |
| | | | **CustomDocNo.ini** | 自訂文件名稱與 ID 映射 | 操作員新增自訂分類或呼叫 `DeleteCustomDocDir` 時。 | |
| | | | **CB_IMGPSScan.ini** | 版本號、伺服器 IP/Port | 自動更新完成後更新版號,或手動修改伺服器設定時。 | |
| | | | | | | |
| | | |
| | | ## **捌、 注意事項** |
| | | |
| | | - **硬編碼路徑**:ActiveFormCreate 中存在寫死的 C:\Temp\IMGPSScan,適合作為最終保險。 |
| | | - **權限問題**:作為 ActiveX,這些 IO 操作強烈依賴瀏覽器的「安全站台設定」或 IE 的「保護模式」。使用 GetLocalAppDir 是現代化重構中解決 UAC 權限問題的重要手段。 |
| 比對新檔案 |
| | |
| | | # **TCB_IMGPSScanX OCX 通訊介面與溝通機制分析** |
| | | |
| | | 本表條列了 Delphi 宿主環境(ActiveX[ocx]) 與 TCB_IMGPSScanX 元件之間主要的溝通管道、對應方法及其在系統架構中的作用。 |
| | | |
| | | ## **壹、 指令與資料交換介面表** |
| | | |
| | | | **通訊方法 (Method)** | **通訊方向** | **溝通內容 (Data Payload)** | **具體作用與目的** | |
| | | | --- | --- | --- | --- | |
| | | | **SetSQLData** | **宿主 -> OCX** | 以 ` | 或,` 分隔的長字串(如:IP, CaseID, DPI) | |
| | | | **GetSQLData** | **宿主 <-> OCX** | 指定 Key (如 'PARA_NO') 取得對應 Value | **資料查詢通道**。宿主可詢問 OCX 目前快取清單中的特定數值,用於介面同步或邏輯判斷。 | |
| | | | **GetSetInf1 ~ 7** | **宿主 <- OCX** | 單一字串值 (如 CaseID, 總張數, 成功標記 'Y') | **狀態出口 (Outlets)**。提供標準化的 7 個出口,讓 JavaScript 或宿主 App 輪詢(Polling)目前的執行結果或掃描統計。 | |
| | | | **StatrTwainScan** | **宿主 -> OCX** | 無參數 (觸發動作) | **硬體啟動指令**。宿主程式按下「開始掃描」按鈕後呼叫此方法,通知 OCX 喚起 TWAIN 驅動程式。 | |
| | | | **GetCurrentVersionNo** | **宿主 <- OCX** | 版本字串 (如 '1.0.3.5') | **版本識別**。宿主程式載入 OCX 後第一步通常會呼叫此處,用以比對伺服器版本並決定是否強制更新。 | |
| | | | **upFile** | **宿主 -> OCX** | 無或部分參數 (觸發網路 IO) | **輸出指令**。通知 OCX 將本機暫存區的檔案(如 ZIP 包)依照先前設定好的路徑與權限上傳至伺服器。 | |
| | | | **dnFile_Get** | **宿主 -> OCX** | 遠端 Action 關鍵字與本地檔名 | **同步指令**。通知 OCX 向伺服器抓取特定資源(如 XML 規則或語言檔)並存放到指定 IO 路徑。 | |
| | | | **ActiveFormCreate** | **系統 -> OCX** | 視窗建立事件 | **環境初始化**。當宿主程式實體化 OCX 時,自動觸發此處執行 GetDefScanIni,確保掃描環境就緒。 | |
| | | | **Property CaseID** | **宿主 <-> OCX** | WideString | **屬性直連**。透過 COM 屬性機制直接設定或讀取案件編號。 | |
| | | | **Property ScreenSnap** | **宿主 -> OCX** | Boolean | **功能切換**。控制 OCX 是否要在特定時機執行螢幕截圖稽核。 | |
| | | |
| | | ## **貳、 溝通機制細部說明** |
| | | |
| | | ### **1. 參數化配置 (Config-by-String)** |
| | | |
| | | 本專案大量使用 SetSQLData 這種「一條字串走天下」的設計,這在早期 ActiveX 開發中非常流行,優點是規避了 COM 介面頻繁修改 _TLB.pas 的麻煩。您在轉換 TypeScript UI 時,可以將其封裝為一個 JSON 物件,最後再組合成字串傳給這個方法。 |
| | | |
| | | ### **2. 狀態回饋 (The Inf1~7 Pattern)** |
| | | |
| | | 由於 ActiveX 事件 (Events) 的回傳有時在網頁端不夠穩定,開發者選擇了 GetSetInf 系列。宿主程式通常會在執行完某個長耗時動作(如 upFile)後,連續呼叫 GetSetInf3 (成功標記) 與 GetSetInf6 (錯誤訊息) 來決定下一步 UI 流程。 |
| | | |
| | | ### **3. 雙向同步 (Bidirectional Sync)** |
| | | |
| | | - **同步設定**:宿主透過 GetDefScanIni 觸發,OCX 內部完成硬體設定。 |
| | | - **同步資料**:宿主透過 dnFile_Get 觸發,OCX 完成本機與伺服器的檔案 IO 同步。 |
| | | |
| | | # **參、描服務抽象層實作** |
| | | |
| | | 採用 **「抽象介面層 (Abstraction Layer)」** 配合 **「適配器模式 (Adapter Pattern)」**,處理遺留系統(Legacy System)過渡到現代化架構 |
| | | |
| | | ```tsx |
| | | /** |
| | | * 掃描服務資料模型定義 |
| | | * 將原本 OCX 雜亂的字串溝通 (SetSQLData) 結構化 |
| | | */ |
| | | export interface ScanConfig { |
| | | serverIp: string; |
| | | caseId: string; |
| | | dpi: number; |
| | | isColor: boolean; |
| | | isDuplex: boolean; |
| | | // ... 其他來自 GetDefScanIni 的參數 |
| | | } |
| | | |
| | | export interface ScanStatus { |
| | | caseId: string; |
| | | totalPageCount: number; |
| | | isUploadSuccess: boolean; |
| | | lastBarcode: string; |
| | | errorMessage: string; |
| | | } |
| | | |
| | | /** |
| | | * 掃描服務抽象介面 (IScanService) |
| | | * UI 層僅與此介面溝通,不直接操作底層實作 |
| | | */ |
| | | export interface IScanService { |
| | | initialize(config: ScanConfig): Promise<void>; |
| | | startScan(): Promise<boolean>; |
| | | uploadFiles(): Promise<boolean>; |
| | | getStatus(): Promise<ScanStatus>; |
| | | getVersion(): Promise<string>; |
| | | downloadResource(action: string, fileName: string): Promise<void>; |
| | | } |
| | | |
| | | /** |
| | | * 實作 A: ActiveX 適配器 (Legacy Adapter) |
| | | * 用於目前環境:UI (TS) -> Delphi Browser -> ActiveX (OCX) |
| | | */ |
| | | export class ActiveXScanAdapter implements IScanService { |
| | | private ocx: any; |
| | | |
| | | constructor() { |
| | | // 透過 window.external 或 Delphi Bridge 取得 OCX 實例 |
| | | this.ocx = (window as any).OCX_INSTANCE; |
| | | } |
| | | |
| | | async initialize(config: ScanConfig): Promise<void> { |
| | | // 將 JSON 轉回 OCX 慣用的管道分隔字串 (SetSQLData 邏輯) |
| | | const sqlDataStr = `${config.serverIp}|${config.caseId}|${config.dpi}|${config.isColor ? 'C' : 'G'}`; |
| | | this.ocx.SetSQLData(sqlDataStr); |
| | | } |
| | | |
| | | async startScan(): Promise<boolean> { |
| | | this.ocx.StatrTwainScan(); |
| | | return true; // 實際狀態需透過 GetSetInf 輪詢 |
| | | } |
| | | |
| | | async getStatus(): Promise<ScanStatus> { |
| | | return { |
| | | caseId: this.ocx.GetSetInf1(), |
| | | totalPageCount: parseInt(this.ocx.GetSetInf2()), |
| | | isUploadSuccess: this.ocx.GetSetInf3() === 'Y', |
| | | lastBarcode: this.ocx.GetSetInf5(), |
| | | errorMessage: this.ocx.GetSetInf6() |
| | | }; |
| | | } |
| | | |
| | | async uploadFiles(): Promise<boolean> { |
| | | this.ocx.upFile(); |
| | | return true; |
| | | } |
| | | |
| | | async getVersion(): Promise<string> { |
| | | return this.ocx.GetCurrentVersionNo(); |
| | | } |
| | | |
| | | async downloadResource(action: string, fileName: string): Promise<void> { |
| | | this.ocx.dnFile_Get(action, fileName); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 實作 B: Backend Server 適配器 (Future Adapter) |
| | | * 用於未來環境:UI (TS) -> REST API / WebSocket -> Rust/Go Backend |
| | | */ |
| | | export class BackendServerAdapter implements IScanService { |
| | | private apiUrl = "http://localhost:8080/api/scanner"; |
| | | |
| | | async initialize(config: ScanConfig): Promise<void> { |
| | | await fetch(`${this.apiUrl}/config`, { |
| | | method: 'POST', |
| | | body: JSON.stringify(config) |
| | | }); |
| | | } |
| | | |
| | | async startScan(): Promise<boolean> { |
| | | const res = await fetch(`${this.apiUrl}/scan`, { method: 'POST' }); |
| | | return res.ok; |
| | | } |
| | | |
| | | async getStatus(): Promise<ScanStatus> { |
| | | const res = await fetch(`${this.apiUrl}/status`); |
| | | return await res.json(); |
| | | } |
| | | |
| | | async uploadFiles(): Promise<boolean> { |
| | | const res = await fetch(`${this.apiUrl}/upload`, { method: 'POST' }); |
| | | return res.ok; |
| | | } |
| | | |
| | | async getVersion(): Promise<string> { |
| | | const res = await fetch(`${this.apiUrl}/version`); |
| | | const data = await res.json(); |
| | | return data.version; |
| | | } |
| | | |
| | | async downloadResource(action: string, fileName: string): Promise<void> { |
| | | // 未來可能直接由後端處理,前端僅發送同步指令 |
| | | await fetch(`${this.apiUrl}/sync?action=${action}&file=${fileName}`); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 服務工廠 (Scanner Factory) |
| | | * 根據環境自動切換實作 |
| | | */ |
| | | export class ScannerFactory { |
| | | static getService(): IScanService { |
| | | if ((window as any).OCX_INSTANCE) { |
| | | console.log("偵測到 ActiveX 環境,啟用 ActiveX 適配器"); |
| | | return new ActiveXScanAdapter(); |
| | | } else { |
| | | console.log("現代瀏覽器環境,啟用後端 API 適配器"); |
| | | return new BackendServerAdapter(); |
| | | } |
| | | } |
| | | } |
| | | ``` |
| | | |
| | | ### **1. 介面標準化(JSON 化)** |
| | | |
| | | 原先 OCX 使用 SetSQLData 的「長字串」溝通是非常脆弱的(一項參數順序錯了就全毀)。在 TypeScript 抽象層中,請務必將其轉換為 **強型別的 JSON 物件**(如 ScanConfig 介面)。這樣你的 UI 邏輯在處理設定時會非常直覺。 |
| | | |
| | | ### **2. 非同步處理(Promise/Async)** |
| | | |
| | | ActiveX 的方法通常是同步阻塞的,而後端 API 是非同步的。 |
| | | |
| | | - **建議**:在抽象介面(IScanService)中,所有方法都回傳 Promise。 |
| | | - **原因**:這樣當你未來切換到後端伺服器(fetch API)時,UI 的調用邏輯(await service.startScan())不需要做任何修改。 |
| | | |
| | | ### **3. 實作「心跳」或「狀態輪詢」** |
| | | |
| | | 原本 GetSetInf1~7 主要是靠宿主程式主動去「問」OCX。 |
| | | |
| | | - **抽象層建議**:在 IScanService 中實作一個 subscribeStatus(callback) 的觀察者模式。 |
| | | - **ActiveX 模式下**:在適配器內部啟動一個 setInterval 去輪詢 GetSetInf。 |
| | | - **未來後端模式下**:適配器改用 **WebSocket** 接收後端主動推播的掃描事件。 |
| | | - **UI 層感受**:UI 只需要處理 onStatusUpdate 事件,根本不用管底層是用輪詢還是 WebSocket。 |
| | | |
| | | ### **4. 資源路徑的轉義(The Bridge Proxy)** |
| | | |
| | | 這是我之前提到的關鍵:由於 iframe 裡的 TypeScript 無法存取 C:\Temp,但 ActiveX 依舊會把檔案掃到那裡。 |
| | | |
| | | - **策略**:你的 Delphi Browser 需要實作一個簡單的本地 Proxy。 |
| | | - **實作**:當 TypeScript 需要顯示圖檔時,它請求 http://local.bridge/view/001.tif,Delphi Browser 攔截此請求並從實體路徑讀取檔案回傳。 |
| | | - **好處**:未來改為 Backend Server 時,Server 也可以提供同樣的 URL 格式,UI 完全無縫接軌。 |
| | | |
| | | ## **肆、 Delphi 與 JavaScript 通訊橋樑 (Bridge) 實作說明** |
| | | |
| | | 當您在 Delphi 宿主程式中使用內嵌瀏覽器時,JavaScript 取得 `OCX_INSTANCE` 的方式取決於底層的瀏覽器引擎: |
| | | |
| | | ### **1. 舊版 IE 核心 (TWebBrowser / window.external)** |
| | | |
| | | 這是最傳統的方式,Delphi 會透過實作 `IDocHostUIHandler` 介面,將一個自訂的 `IDispatch` 物件掛載到 `window.external` 上。 |
| | | |
| | | - **Delphi 端**:將 `TCB_IMGPSScanX` 實例或其包裝類別指派給瀏覽器的 `External` 屬性。 |
| | | - **JS 端**:直接使用 `window.external.StatrTwainScan()` 呼叫。 |
| | | |
| | | ### **2. 新版 Edge 核心 (TEdgeBrowser / WebView2 Host Objects)** |
| | | |
| | | 這是現代化的做法。Delphi 透過 WebView2 的 API 將物件直接「注入」到 JS 的命名空間中。 |
| | | |
| | | - **Delphi 端執行**: |
| | | |
| | | ```pascal |
| | | // 將 OCX 物件以 'OCX_INSTANCE' 名稱注入 |
| | | EdgeBrowser.AddHostObjectToScript('OCX_INSTANCE', MyOcxWrapperObject); |
| | | ``` |
| | | |
| | | - **JS 端取得**: WebView2 會將物件放置在 `window.chrome.webview.hostObjects` 底下。 |
| | | |
| | | ```jsx |
| | | const ocx = window.chrome.webview.hostObjects.OCX_INSTANCE; |
| | | ``` |
| | | |
| | | |
| | | ### **3. 為什麼需要 Wrapper (包裝類別)?** |
| | | |
| | | 通常我們**不建議**直接將 `TCB_IMGPSScanX` 注入,原因如下: |
| | | |
| | | 1. **安全性**:直接暴露 COM 元件可能會有安全漏洞。 |
| | | 2. **相容性**:OCX 的某些方法回傳型別(如 Delphi 的自訂 Enum)JavaScript 無法識別。 |
| | | 3. **執行緒優化**:Delphi 可以在 Wrapper 中使用 `TThread.Queue` 確保掃描指令在正確的執行緒執行,避免瀏覽器 UI 凍結。 |
| | | |
| | | ### **4. 溝通路徑總結** |
| | | |
| | | 1. **Delphi 宿主程式** 啟動並實體化 **OCX 控制項**。 |
| | | 2. **Delphi 宿主程式** 建立 **Browser 物件** 並載入 **TypeScript UI (HTML)**。 |
| | | 3. **Delphi 宿主程式** 呼叫瀏覽器 API(如 `AddHostObjectToScript`),將 OCX 的操作介面「注入」到該網頁的 `window` 物件中。 |
| | | 4. **TypeScript** 透過抽象層 (Adapter) 取得該物件,實現「網頁點按鈕,掃描機開動」的效果。 |
| 比對新檔案 |
| | |
| | | # **核心源碼與物件實作解析** |
| | | |
| | | 本文件針對 CB_IMGPSScanImp.pas 中的核心方法與隱藏物件進行原始碼級別的邏輯拆解與推導。 |
| | | |
| | | ## **壹、 核心方法源碼說明** |
| | | |
| | | ### **1) TCB_IMGPSScanX.GetCurrentVersionNo** |
| | | |
| | | - **功能描述**:取得目前 OCX 元件的版本號。這通常提供給網頁端的 JavaScript 呼叫,用來判斷客戶端的 OCX 是否需要觸發更新下載。 |
| | | - **實作推導與底層 API 說明**: |
| | | |
| | | 在 Delphi 中,若要動態讀取編譯進 OCX 的 VS_VERSION_INFO 資源,標準作法是呼叫一系列的 Windows API。這段實作通常包含以下三個關鍵概念: |
| | | |
| | | 1. **HInstance (實體句柄)**: |
| | | - 這是 Delphi 提供的一個全域變數,代表目前載入記憶體中的模組(即這個 OCX 檔)的「實體句柄 (Instance Handle)」。 |
| | | - 在 Windows 作業系統中,當一個執行檔 (EXE) 或動態連結檔 (DLL/OCX) 被載入記憶體時,會獲得一個記憶體基底位址,HInstance 就是指向這個位址的指標。Windows 需要這個值來識別「你要操作的是哪一個載入的模組」。 |
| | | 1. **GetModuleFileName 的作用**: |
| | | - 這是一個 Windows API。它的作用是利用前面提到的 HInstance,向作業系統反查出這個模組在硬碟上的**「完整實體檔案路徑」**(例如:C:\Windows\Downloaded Program Files\CB_IMGPSScan.ocx)。 |
| | | - 因為我們需要讀取檔案本身的版本資訊,程式必須先知道「我自己到底存放在硬碟的哪裡」。 |
| | | 1. **後半段 VerQueryValue 的運作邏輯**: |
| | | |
| | | 取得檔案路徑後,讀取版本資訊會經過三部曲,而 VerQueryValue 是最後負責「解析」的關鍵: |
| | | |
| | | - **Step 1 (GetFileVersionInfoSize)**:先問 Windows 這個檔案的版本資訊區塊有多大。 |
| | | - **Step 2 (GetFileVersionInfo)**:把整塊版本資訊的二進位原始資料 (Raw Data) 完整讀進一段記憶體緩衝區中。 |
| | | - **Step 3 (VerQueryValue)**:因為剛剛讀進來的是一包無法直接閱讀的二進位資料,VerQueryValue 的作用就是**「在這包資料中進行精準查詢」**。透過傳入特定的查詢路徑(例如傳入 \ 可以取得包含主版號、次版號的 VS_FIXEDFILEINFO 結構體;或是傳入特定語系代碼如 \StringFileInfo\040404E4\FileVersion),作業系統會幫你把二進位資料解析成我們需要的字串或數字版本號(如 1.0.3.5)。 |
| | | |
| | | ### **2) TCB_IMGPSScanX.ActiveFormCreate** |
| | | |
| | | - **功能描述**:這是 ActiveX 視覺化窗體 (Form) 的生命週期起點。當 IE 瀏覽器或應用程式載入這個 OCX 時,首先觸發的初始化事件。 |
| | | - **實作推導**: |
| | | |
| | | 它會負責初始化全域變數、設定第三方元件的預設值、並建立暫存資料夾。 |
| | | |
| | | ```pascal |
| | | procedure TCB_IMGPSScanX.ActiveFormCreate(Sender: TObject); |
| | | begin |
| | | // 1. 預設變數初始化 |
| | | Def_DeviceDelete := True; |
| | | Def_ScannerReverse := False; |
| | | |
| | | // 2. 建立本機暫存目錄 |
| | | ForceDirectories('C:\Temp\IMGPSScan'); |
| | | |
| | | // 3. UI 語系初始化 |
| | | InitialLanguage; |
| | | end; |
| | | ``` |
| | | |
| | | |
| | | ### **3) TCB_IMGPSScanX.StatrTwainScan** |
| | | |
| | | - **功能描述**:啟動掃描機的 TWAIN 介面,開始進紙或掃描。(註:Statr 應為 Start 的早期拼字錯誤,這在舊專案中很常見)。 |
| | | - **實作推導**: |
| | | |
| | | 這個方法會讀取網頁傳進來的參數(如 DPI、色彩),設定給 Scanner 物件,然後發出擷取指令。 |
| | | |
| | | ```pascal |
| | | procedure TCB_IMGPSScanX.StatrTwainScan; |
| | | begin |
| | | // 設定解析度與顏色 |
| | | Scanner.Resolution := StrToIntDef(Fimgdpi, 200); |
| | | if Fscancolor = 'C' then |
| | | Scanner.PixelType := ptColor |
| | | else |
| | | Scanner.PixelType := ptGray; |
| | | |
| | | // 啟動掃描 (會喚起實體掃描機) |
| | | Scanner.Acquire; |
| | | end; |
| | | ``` |
| | | |
| | | |
| | | ### **4) TCB_IMGPSScanX.OnAcquire 與資料流物件** |
| | | |
| | | - **功能描述**:這是 Scanner (EnScan) 最核心的事件。當掃描機成功掃完「一張」影像並傳入電腦記憶體時,會觸發此事件。 |
| | | - **源碼邏輯推導**: |
| | | |
| | | ```pascal |
| | | procedure TCB_IMGPSScanX.ScannerAcquire(Sender: TObject; DibHandle: THandle; var Handled: Boolean); |
| | | var |
| | | MyStream: TMemoryStream; |
| | | MyGraphic: TDibGraphic; |
| | | begin |
| | | // 1. 將 DibHandle 轉換為 Delphi 影像物件 |
| | | MyGraphic := TDibGraphic.Create; |
| | | MyGraphic.Handle := DibHandle; |
| | | |
| | | // 2. 影像處理 (如去斜、黑白轉換) |
| | | |
| | | // 3. 存入記憶體串流或實體檔案 |
| | | // ... |
| | | end; |
| | | ``` |
| | | |
| | | - **三大核心資料物件解釋**: |
| | | 1. **DibHandle**:這是 Windows 底層的「全域記憶體句柄 (Global Memory Handle)」。掃描機驅動把圖片放在記憶體裡,給你這個代號。你必須透過 TDibGraphic 來「解讀」這塊記憶體。 |
| | | 2. **TMemoryStream**:**記憶體串流**。它的作用像是一塊存在 RAM 裡的虛擬硬碟。掃描後的圖檔在還沒存到實體硬碟前,會先放在這裡進行格式轉換 (如轉成 JPEG) 或進行 MD5 計算,因為在記憶體中操作速度最快。 |
| | | - **DeleteStm : TMemoryStream 的逐行操作說明**: |
| | | 在 `OnAcquire` 事件中,`DeleteStm` 主要用於「空白頁判定」。其源碼邏輯如下: |
| | | |
| | | ```pascal |
| | | // 判定是否啟動空白頁刪除功能 |
| | | if Def_DeviceDelete then |
| | | begin |
| | | // 1. 在 Heap 記憶體中建立一個暫存串流物件 |
| | | DeleteStm := TMemoryStream.Create; |
| | | try |
| | | // 2. 將當前掃描到的影像物件 (MyGraphic) 寫入記憶體串流 |
| | | // 這會根據 TIFF G4 等格式進行壓縮,產生實際的檔案位元組資料 |
| | | MyGraphic.SaveToStream(DeleteStm); |
| | | |
| | | // 3. 核心判定:檢查該影像在記憶體中所佔用的位元組大小 (Size) |
| | | // 若小於預設門檻值 (Def_DeviceDeleteSize, 如 3072 Bytes) |
| | | if DeleteStm.Size < Def_DeviceDeleteSize then |
| | | begin |
| | | // 4. 若符合空白頁特徵,則不進行存檔動作,直接跳出此 Procedure |
| | | // 並告知掃描控制項此頁已處理完畢 (Handled := True) |
| | | Handled := True; |
| | | Exit; |
| | | end; |
| | | finally |
| | | // 5. 無論是否為空白頁,操作結束後必須釋放記憶體,避免 Memory Leak |
| | | DeleteStm.Free; |
| | | end; |
| | | end; |
| | | ``` |
| | | |
| | | - |
| | | 3. **TFileStream**:**檔案串流**。用來直接對硬碟讀寫資料。當確認影像無誤後,會透過 TFileStream 將 TMemoryStream 的內容真實地寫入 C:\Temp\...\001.tif 中。 |
| | | |
| | | ### **5) TCB_IMGPSScanX.FindISB2View** |
| | | |
| | | - **功能描述**:用來在畫面上尋找並更新用來預覽圖片的元件。 |
| | | - **名詞解釋**:**ISB 代表 ImageScrollBox**。 |
| | | |
| | | 這對應到 uses 區段中的 EnImgScr 套件。TImageScrollBox 是 Envision 提供的一個 UI 元件,專門用來顯示超出螢幕大小的掃描圖檔,支援平滑拖曳與縮放。這個方法通常用來把剛掃描完的 DibHandle 餵給畫面上的 ISB 元件顯示。 |
| | | |
| | | |
| | | ### **6) TCB_IMGPSScanX.GetNoNameCase** |
| | | |
| | | - **功能描述**:處理「無案號」或「未知歸屬」的掃描件。 |
| | | - **源碼實作逐行說明**: |
| | | |
| | | 此方法透過迴圈尋找一個尚未被使用的目錄名稱(格式為「未配號 + 四位數字」)。 |
| | | |
| | | |
| | | ```pascal |
| | | Function TCB_IMGPSScanX.GetNoNameCase(Path:String):String; // 取未配號XXXX |
| | | var |
| | | i : Integer; // 宣告迴圈計數器 |
| | | begin |
| | | // 1. 啟動一個從 1 到 9999 的迴圈,尋找可用的索引值 |
| | | for i := 1 to 9999 do |
| | | begin |
| | | // 2. 檢查特定路徑下的目錄是否已存在 |
| | | // _Msg('未配號'):取得「未配號」字串(可能具備多國語言處理) |
| | | // Add_Zoo(i, 4):將數字 i 補齊為 4 位數(例如 1 變為 0001) |
| | | if Not DirectoryExists(Path + _Msg('未配號') + Add_Zoo(i, 4)) then |
| | | begin |
| | | // 3. 若該目錄不存在,代表此名稱可用,設定 Result 並跳出迴圈 |
| | | Result := _Msg('未配號') + Add_Zoo(i, 4); |
| | | Break; // 找到第一個可用的名稱後立即停止搜尋 |
| | | end; |
| | | end; |
| | | end; |
| | | ``` |
| | | |
| | | ### **7) TCB_IMGPSScanX.SetSQLData 與 8) GetSQLData** |
| | | |
| | | 這兩個方法是 OCX 與網頁 (JS) 溝通的雙向橋樑。 |
| | | |
| | | - **SetSQLData (網頁 -> OCX)**:接收由網頁傳來的設定字串(通常用 | 或 , 分隔,例如 192.168.1.1|L12345678|200|C),在內部將字串切開 (Split),並分別賦值給 ServerIP, FCaseID, Fimgdpi, Fscancolor 等內部變數。 |
| | | - **GetSQLData (OCX -> 網頁)**:將 OCX 內部的處理結果(例如:UploadSuccess|3|0 代表上傳成功、共 3 頁、0 錯誤),組合成一個長字串後回傳給網頁端的 JS 進行後續的頁面跳轉或提示。 |
| | | |
| | | ### **8) TCB_IMGPSScanX.GetDefScanIni** |
| | | |
| | | - **功能描述**:取得並套用掃瞄參數的預設值與自訂設定。 |
| | | - **源碼實作解析**: |
| | | - **硬編碼預設值 (Hardcoded Defaults)**: |
| | | |
| | | 程式一開始會先寫死一組標準值,例如 Def_ScanDpi := 300 (預設 300 DPI)、Def_DeviceDeleteSize := 3072 (空白頁判定門檻為 3KB)。 |
| | | |
| | | - **動態配置覆蓋 (Dynamic Overriding)**: |
| | | |
| | | 透過 for i := 0 to WORK_INF_List.Count - 1 do 迴圈遍歷設定檔清單。使用 GetSQLData 取得參數代號 (PARA_NO) 與內容 (PARA_CONTENT)。 |
| | | |
| | | - **屬性設定對照表**: |
| | | |
| | | 下表整理了此方法中設定的所有關鍵屬性、其對應的參數代號及功能說明。 |
| | | |
| | | | **屬性名稱 (內部變數)** | **對應 PARA_NO** | **PARA_CONTENT 處理方式(GetSQLData)** | **功能說明** | |
| | | | --- | --- | --- | --- | |
| | | | **Def_DeviceDelete** | SCAN_BLANKDEL_USE | 'Y' -> True, Else -> False | 空白頁自動刪除開關。 | |
| | | | **Def_DeviceDeleteSize** | SCAN_BLANKDEL_SIZE | StrToInt (若空則 0) | 空白頁判定門檻 (Bytes)。 | |
| | | | **Def_ScannerReverse** | SCAN_REVERSE | 'Y' -> True, Else -> False | 影像是否反相。 | |
| | | | **Def_BoardClear** | SCAN_BOARDCLEAR | 'Y' -> True, Else -> False | 是否清除影像黑邊。 | |
| | | | **Def_ScanDpi** | SCAN_DPI | StrToInt (若空則 300) | 掃描解析度。 | |
| | | | **Def_ScanDuplex** | SCAN_DUPLEX | 'Y' -> True, Else -> False | 是否啟用雙面掃描。 | |
| | | | **Def_ScanRotate** | SCAN_ROTATE_MODE | '0'->0, '1'->270, '2'->180, '3'->90 | 掃描旋轉角度映射。 | |
| | | | **Def_ScanDeskew** | SCAN_DESKEW | 'Y' -> True, Else -> False | 是否自動矯正傾斜。 | |
| | | | **Def_ScanImgSetUse** | SCAN_IMGSET_USE | 'Y' -> True, Else -> False | 是否使用亮度/對比設定。 | |
| | | | **Def_ScanBright** | SCAN_BRIGHT | StrToInt (限制 -255~255) | 亮度數值。 | |
| | | | **Def_ScanContrast** | SCAN_CONTRAST | StrToInt (限制 -255~255) | 對比數值。 | |
| | | | **Def_ScanImgShowMode** | SCAN_SHOW_MODE | '0','1','2' 對應模式 | 影像顯示/縮放模式。 | |
| | | | **ScanDenialTime** | CASE_IN_TIME | 直接存為 String | 進件截止時間限制。 | |
| | | | **ScanDenialHint** | SCAN_HINT | 直接存為 String | 掃描畫面提示字串。 | |
| | | | **NoSaveBarCodeList** | NO_SAVE_FORM_ID | CommaText := Value | 不存檔之表單代號清單。 | |
| | | | **ImagePath** | LOCAL_PATH | 直接存為 String | 本機端暫存路徑。 | |
| | | | **GuideFormIDList** | GUIDEFORMID | CommaText := Value | 導引頁表單 ID 清單。 | |
| | | | **DivPageFormIDList** | DIVPAGEFORMID | CommaText := Value | 分案頁表單 ID 清單。 | |
| | | | **FJpgCompression** | FILE_COMPRESSION | StrToInt | JPG 轉 TIF 壓縮比。 | |
| | | | **FMaxUploadSize** | MAX_UPLOAD_SIZE | 直接存為 String | 上傳大小限制 (MB)。 | |
| | | |
| | | ## **貳、 記憶體直接操作實作清單** |
| | | |
| | | 以下條列專案中直接涉及記憶體操作的程式區段: |
| | | |
| | | ### **記憶體操作對應位置表** |
| | | |
| | | | **記憶體操作對象** | **所屬 Function / Procedure** | **關鍵操作關鍵字 / 代碼** | **記憶體特性說明** | 註記 | |
| | | | --- | --- | --- | --- | --- | |
| | | | **HInstance** | GetCurrentVersionNo | GetModuleFileName(HInstance, ...) | 模組載入位址。 | 增API版本號 | |
| | | | **資源緩衝區** | GetCurrentVersionNo | VerQueryValue, GetFileVersionInfo | 透過 Pointer 進行二進位搜尋。 | 增API版本號 | |
| | | | **DibHandle** | onAcquire | MyGraphic.Handle := DibHandle; | Windows 全域記憶體句柄轉移。 | 改套件實作/Rust | |
| | | | **TMemoryStream** | onAcquire | MemoryStream.Write, .Read | Heap Memory 內部二進位操作。 | | |
| | | |
| | | ## **參、 遺失物件反推與說明** |
| | | |
| | | 你提到在原始碼中找不到 _DelTree 和 Scanner 的實作,這是因為它們屬於以下兩種情況: |
| | | |
| | | ### **1) TCB_IMGPSScanX._DelTree** |
| | | |
| | | - **這是一個自訂的私有輔助函式 (Private Helper)**。 |
| | | - **功能**:它的名稱源自 DOS 時代的指令 deltree(刪除整個目錄樹)。在 ActiveX 中,當上傳伺服器成功後,必須清空 C:\Temp\案件編號\ 裡的所有暫存圖檔與資料夾以釋放空間,並防止資料外洩。 |
| | | - **實作原理**:由於早期的 Delphi 沒有內建遞迴刪除目錄的單一指令,開發者通常會自己寫一個 _DelTree,裡面使用 FindFirst, FindNext 找出所有檔案並用 DeleteFile 刪除,最後用 RemoveDir 砍掉資料夾。 |
| | | |
| | | ### **2) Scanner** |
| | | |
| | | - **這是一個視覺化/非視覺化的第三方元件實例 (Component Instance)**。 |
| | | - **為何找不到宣告?** 因為在 Delphi 中,如果你是透過 IDE 的工具箱 (Tool Palette) 將 TEnScan 元件直接拖拉到窗體 (Form) 上,IDE 會自動把它寫在 .dfm 檔裡,並在 TCB_IMGPSScanX 類別的 published 區段自動產生一行 Scanner: TEnScan;。 |
| | | - **功能**:它就是 **Envision SDK** 裡用來與 TWAIN 硬體溝通的靈魂核心。所有的掃描屬性 (Scanner.Resolution) 與方法 (Scanner.Acquire) 都是對這個物件進行操作。 |
| 比對新檔案 |
| | |
| | | # 硬體掃描控制模組 (Scanner Controller) 深度分析 |
| | | |
| | | 在 `CB_IMGPSScanImp.pas` 中,「硬體掃描控制模組」是系統與實體掃描設備溝通的唯一橋樑。它深度依賴 TWAIN 協定(透過第三方元件 `EnScan` 封裝),負責控制硬體行為並將紙本文件轉換為記憶體中的數位影像資料。 |
| | | |
| | | 若要將此模組進一步細拆,其內部組成可分為以下三大子分類: |
| | | |
| | | --- |
| | | |
| | | ## 1. TWAIN 介面封裝與控制 (TWAIN Interface Wrapper) |
| | | |
| | | **職責**:負責管理實體掃描機的連線狀態、開啟/關閉資料來源 (Data Source),以及觸發掃描動作。 |
| | | **核心實作特性**: |
| | | * **掃描機選擇與初始化**: |
| | | * `SelectScanner` (`scanner.SelectScanner`):呼叫 TWAIN 介面讓使用者選擇預設的掃描機硬體。 |
| | | * `initkscan` / `CheckScannerConfig`:在系統啟動時檢查掃描機是否已正確配置並支援特定功能(例如檢查 `Scanner.DuplexCap > 0` 確認是否支援雙面掃描)。 |
| | | * **掃描生命週期管理** (`StatrTwainScan`): |
| | | * 負責設定 `Scanner.OpenSource`(開啟驅動)。 |
| | | * 呼叫 `Scanner.AcquireWithSourceOpen` 開始非同步擷取,並傳入回呼函式 (Callback) 的指標與自訂的狀態結構 (`LongInt(@ScanInfo)`)。 |
| | | * 確保無論掃描成功、失敗或中斷,最終都會安全地執行 `Scanner.CloseSource`。 |
| | | |
| | | ## 2. 掃描參數與設定管理器 (Scanner Profile Manager) |
| | | |
| | | **職責**:負責載入、維護及套用掃描時所需的各項硬體與前處理參數。這些參數通常從資料庫 (`WORK_INF`) 或是本機的 `FBScan.ini` 中讀取。 |
| | | **核心實作特性**: |
| | | * **硬體基本參數 (Hardware Params)**: |
| | | * `ScanDpi`:解析度設定,對應 `RequestedXDpi` 與 `RequestedYDpi`。 |
| | | * `ScanColor` (`TImageFormat`):影像色彩模式(黑白 `ifBlackWhite`、灰階 `ifGray256`、彩色 `ifTrueColor`)。 |
| | | * `ScanDuplex`:控制硬體是否開啟雙面進紙 (`Scanner.Duplex`)。 |
| | | * `TwainShowUI`:是否顯示掃描機原廠的 TWAIN 設定畫面 (`Scanner.ShowUI`)。 |
| | | * **硬體增強參數 (Enhancement Params)**: |
| | | * `ScanBright` (亮度) 與 `ScanContrast` (對比度):當啟用 `ScanImgSetUse` 時,將這些參數直接送入掃描硬體。 |
| | | * **前置處理規則 (Pre-processing Rules)**: |
| | | * `DeviceDelete` 與 `DeviceDeleteSize`:定義是否開啟「空白頁自動刪除」以及刪除的位元組閥值。 |
| | | * `ScannerReverse` (反相)、`ScanDeskew` (硬體/初階去斜)、`BoardClear` (清除黑邊)。 |
| | | |
| | | ## 3. 非同步影像接收與初始轉換器 (Acquisition Event Handler) |
| | | |
| | | **職責**:做為掃描機與應用程式記憶體的交界點,負責攔截驅動程式傳回的原始記憶體區塊 (`DibHandle`),並進行最基礎的安全轉換與前置判斷。 |
| | | **核心實作特性** (`OnAcquire` 回呼事件): |
| | | * **記憶體接管 (Memory Handover)**: |
| | | * 接收來自 TWAIN 驅動的 `DibHandle`(Device Independent Bitmap 記憶體指標)。 |
| | | * 利用 `pScanInfo^.Graphic.AssignFromDibHandle(DibHandle)` 將非管理的記憶體轉換為 Delphi VCL 的 `TTiffGraphic` 物件。 |
| | | * **早階條碼快篩與方向校正 (Early Barcode Detection & Rotation)**: |
| | | * 為確保後續處理正確,系統會在剛拿到記憶體影像時,立刻進行一次快速的條碼掃描 (`MpsGetBarcode`)。 |
| | | * 讀取條碼所帶的方向資訊 (`MpsBarcodeinf.r180`),並在記憶體中立刻進行 `Rotate` 旋轉,將顛倒或橫放的文件轉正。 |
| | | * **基礎裁切與壓縮決策 (Format & Crop Decision)**: |
| | | * **A3 裁 A4**:呼叫 `CheckNeedCrop` 判斷長寬比例與特定條碼數量,若確認為 A3 合併頁,則立刻在記憶體中將其拆分為 `iGraphic_First` 與 `iGraphic_Sec` 兩張獨立影像。 |
| | | * **格式與壓縮決策**:針對色彩屬性 (`ifBlackWhite`, `ifTrueColor`, `ifGray256`),決定應該套用哪種壓縮演算法(如黑白套用 `tcGroup4` 或 `tcPackBits`,彩色則轉換為 `tcJpeg` 並套用指定的 `FJpgCompression` 壓縮率)。 |
| | | |
| | | --- |
| | | |
| | | ## 💡 未來重構與微服務化建議 |
| | | |
| | | 若要將此「硬體掃描控制模組」進行現代化升級或跨平台轉型,建議考量以下方向: |
| | | |
| | | 1. **與 UI 徹底解耦**: |
| | | 目前的 `OnAcquire` 回呼函式中混雜了大量 UI 操作(如 `ImageScrollBox1.Graphic.Assign(...)`)。應改用「發佈-訂閱模式 (Pub-Sub)」,讓 Scanner Controller 收到影像後只觸發事件,由外部的 UI 層去訂閱並更新畫面。 |
| | | 2. **Web/雲端化架構 (Web TWAIN / WebSockets)**: |
| | | 若未來系統要走向純 Web 化(捨棄 ActiveX),由於瀏覽器無法直接呼叫 Windows 底層的 TWAIN API,此模組必須被改寫為一隻安裝於 Client 端的 **背景常駐服務 (Local Windows Service / Daemon)**。 |
| | | 該服務可以透過 **WebSocket** 或 **Local REST API** 接收來自網頁端的掃描指令,控制 TWAIN 掃描機,並將接收到的影像轉為 Base64 或二進位流回傳給網頁前端。 |
| | | 3. **單一職責分離 (Separation of Concerns)**: |
| | | 應將 `OnAcquire` 中厚重的「條碼辨識」、「A3 裁切」與「黑邊處理」移出此模組。Scanner Controller 的唯一職責應該只有:**「將紙本轉為電腦中的 Bitmap」**。後續的處理應交給 `Image Processor` (影像處理模組) 透過管線 (Pipeline) 的方式接手處理。 |
| 比對新檔案 |
| | |
| | | # 安全傳輸與 API 通訊模組 (Transport Manager) 深度分析 |
| | | |
| | | 在 `CB_IMGPSScanImp.pas` 中,「安全傳輸與 API 通訊模組」負責處理 ActiveX 客戶端與後端伺服器 (如 Java Servlet 平台) 之間的所有資料交換與檔案傳輸。由於涉及機敏的金融/保險文件,此模組高度依賴加密與安全通訊協定。 |
| | | |
| | | 若要將此模組進一步細拆,其內部組成可分為以下四大子分類: |
| | | |
| | | --- |
| | | |
| | | ## 1. HTTP/HTTPS API 請求管理器 (RESTful / Servlet Client Wrapper) |
| | | |
| | | **職責**:專責透過 HTTP(S) 協定,向後端的 Java Servlet 端點發送 GET/POST 請求,用於同步時間、下載設定檔或回報案件狀態。 |
| | | **核心實作特性**: |
| | | * **套件依賴**:底層依賴 SecureBlackbox 的 `TElHTTPSClient` (在變數宣告為 `HTTPSClient`) 處理 SSL/TLS 連線。 |
| | | * **字串/表單資料交換**: |
| | | * `ProcessServlet_Get`:使用 HTTP GET 方式,將加密過的參數 (`SendData`) 送至伺服器,並將伺服器回傳的結果寫入 `FReWrite` 或 `Memo1` 中。 |
| | | * `ProcessServlet` / `ProcessServlet_FormData`:使用 HTTP POST 或 `multipart/form-data` 方式提交較大的資料集(如 OMR 檢核結果、案件完成通知等)。 |
| | | * **狀態與錯誤攔截**:檢查伺服器回傳的代碼(例如回傳 `0` 代表成功,`1` 代表錯誤,或攔截回傳 HTML 中包含 `login.js` 來判斷 Session 是否逾時登出)。 |
| | | |
| | | ## 2. 雙協定檔案上傳/下載引擎 (Dual-Protocol File Transfer Engine) |
| | | |
| | | **職責**:處理實體影像檔案(通常是打包後的 ZIP 檔)或系統授權檔的實體傳輸,並根據系統設定動態切換 HTTP 或 FTP 協定。 |
| | | **核心實作特性**: |
| | | * **HTTP 檔案傳輸**: |
| | | * `upFile`:透過 HTTP POST (`multipart/form-data`) 將本地端的檔案 (如 `Img.zip` 或 `.lic` 檔) 上傳至伺服器指定的 Servlet 接收端。 |
| | | * `dnFile_Get` / `dnFile`:透過 HTTP 協定從伺服器下載檔案 (如 `OMRSet.zip`, `KeyinSet.zip` 或舊案影像),並提供下載狀態監控。 |
| | | * **FTPS 檔案傳輸 (高頻寬/大檔專用)**: |
| | | * `GetftpInfo`:先透過 HTTP 向伺服器查詢該案件是否應使用 FTP 傳輸,並取回加密的 FTP 登入資訊。 |
| | | * 依賴 SecureBlackbox 的 `TElSimpleFTPSClient` 建立 FTP over SSL 連線。 |
| | | * `SetFtpInfo`、`IIS_Ftp.FtpsConnect`、`IIS_Ftp.FtpsToMain`、`IIS_Ftp.FtpsDownloadFile`:負責實際的 FTP 登入、切換目錄、上傳與下載操作,完成後呼叫 `FtpCaseComplete` 透過 HTTP 通知伺服器傳檔結束。 |
| | | |
| | | ## 3. 安全加密與雜湊模組 (Security, Crypto & Hashing Utils) |
| | | |
| | | **職責**:負責資料傳輸前後的加解密運算,以及檔案完整性的 MD5 校驗,確保通訊過程不被竄改。 |
| | | **核心實作特性**: |
| | | * **字串加解密 (String Encryption)**: |
| | | * 利用 `En_DecryptionStr_Base64` 函式(可能來自自訂或第三方的 `Encryp` 單元),對 URL 參數 (如 `checktime`, `col` 欄位清單) 進行加解密,避免敏感資訊在 URL 明文中裸奔。 |
| | | * 解析加密過的 FTP 登入資訊字串。 |
| | | * **檔案 MD5 雜湊校驗 (File Hashing)**: |
| | | * 使用 Indy 的 `TIdHashMessageDigest5` 元件。 |
| | | * `LoadFileGetMD5`:讀取本機影像檔並計算其 MD5 值,用於比對「異動件 (ESCAN)」模式下,檔案是否與伺服器原有的版本一致,防止誤刪或重複傳輸。 |
| | | * **憑證管理 (Certificate Management)**: |
| | | * `HTTPSClientCertificateValidate`:處理 X.509 憑證驗證事件,通常設定為 `Validate := True` 以略過嚴格的自簽憑證檢查(在內部封閉網路中常見的做法)。 |
| | | |
| | | ## 4. 傳輸前置準備與封裝器 (Payload Archiver & Preparer) |
| | | |
| | | **職責**:在真正啟動傳輸前,將硬碟上散落的 TIFF/JPG 檔案、`.dat` 索引檔以及 `.ini` 設定檔,打包成單一的封裝檔以利傳輸。 |
| | | **核心實作特性**: |
| | | * **ZIP 壓縮封裝**: |
| | | * 依賴 `TVCLZip` (VCLZip 模組)。 |
| | | * `ZipMainFile`:讀取 `Context.dat` 等清單,將案件目錄下的所有影像與關聯的索引文字檔打包成 `Img.zip`。 |
| | | * 包含檔案大小檢核邏輯:在打包完成後,檢查 ZIP 檔是否超過伺服器允許的上限 (`FMaxUploadSize`),若超過則阻擋上傳。 |
| | | * **ZIP 解壓縮處理**: |
| | | * 依賴 `VCLUnZip` 模組。 |
| | | * `ExecuteUnZip` / `ExecuteUnZip_Pwd`:將從伺服器下載回來的 `OMRSet.zip`、`KeyinSet.zip` 或舊案件影像壓縮檔解壓縮至指定的工作目錄中,並支援帶密碼的解壓縮。 |
| | | |
| | | --- |
| | | |
| | | ## 💡 未來重構與微服務化建議 |
| | | |
| | | 針對「安全傳輸與 API 通訊模組」,若未來系統架構演進,建議考量以下重構方向: |
| | | |
| | | 1. **統一且標準化的 HTTP Client (捨棄舊版第三方庫)**: |
| | | 早期的 SecureBlackbox 雖然強大,但現代的開發環境 (如新版 Delphi 或 C#/Java/Node.js) 均已內建完善且高效的 `HttpClient`。建議將所有的 API 呼叫標準化為 RESTful API (JSON 格式),並使用原生的網路庫。 |
| | | 2. **廢除 FTP,全面改用 HTTP(S) 分塊上傳 (Chunked Upload)**: |
| | | 目前系統需動態切換 HTTP 與 FTPS,增加了防火牆設定 (Port 21, Passive Ports) 與除錯的困難度。現代系統處理大檔上傳,主流做法是透過 HTTP/HTTPS 的 **分塊上傳 (Chunked Upload / Multipart)** 搭配斷點續傳機制,建議未來可逐步汰除 FTP 傳輸路徑。 |
| | | 3. **非同步與背景傳輸 (Asynchronous Transfer)**: |
| | | 目前上傳時會透過 `DataLoading` 鎖死整個 UI (`Screen.Cursor := -11`),使用者必須枯等上傳完成。重構時應將 Transport Manager 放入獨立的 Thread 或背景 Service 中執行,並透過回呼 (Callback) 或事件 (Event) 更新進度條,提升使用者體驗。 |
| | | 4. **Token-based 授權取代自訂加密**: |
| | | 目前依賴自訂的 `En_DecryptionStr_Base64` 在 URL 傳遞加密字串作為安全驗證。建議未來升級為標準的 **OAuth 2.0 (JWT Token)** 放在 HTTP Header 中進行授權,更加安全且符合現代 Web 規範。 |
| 比對新檔案 |
| | |
| | | # UI 視圖與介面呈現模組 (UI View) 深度分析 |
| | | |
| | | 在 `CB_IMGPSScanImp.pas` 中,「UI 與介面呈現模組」主要負責與使用者的視覺互動,這是作為 ActiveX 控制項的核心表現形式。它管理了表單的佈局、多國語言切換、快捷鍵操作以及各種滑鼠拖拉 (Drag and Drop) 與捲動行為。 |
| | | |
| | | 以下是針對 UI View 模組的深度組成與分類分析: |
| | | |
| | | --- |
| | | |
| | | ## 1. 核心版面與容器管理 (Layout & Container Management) |
| | | |
| | | **職責**:定義整個 ActiveX 控制項的視覺區塊,並透過階層式的 Panel 進行排版管理,確保在不同解析度下能正確顯示。 |
| | | **核心實作特性**: |
| | | * **ActiveX 窗體繼承**:`TCB_IMGPSScanX` 繼承自 `TActiveForm`,並實作 `ICB_IMGPSScanX` 介面,是所有 UI 元件的根容器。 |
| | | * **Panel 排版系統**:使用大量的 `TPanel` (如 `Panel1`, `Panel2`, `Panel9` 等) 與 `TSplitter` (如 `Splitter1`, `Splitter2`) 來分割畫面: |
| | | * **頂部/底部工具列**:放置各種操作按鈕 (`TBitBtn`, `TButton`)。 |
| | | * **左側樹狀區**:放置文件結構清單 (`TreeView1`)。 |
| | | * **右側工作區**:通常包含影像預覽或細節設定面板 (`Panel9`, `ScrollBox1`)。 |
| | | * **動態模式切換**:透過 `DisplayMode` 與 `GoViewMode` 根據不同的視圖模式 (VMode) 動態調整右側影像預覽區塊的排列方式 (如 1x1, 1x2, 2x2 等),並控制各 `imgp` (Panel) 的顯示與隱藏。 |
| | | |
| | | ## 2. 影像預覽與互動視窗 (Image Viewer & Interaction) |
| | | |
| | | **職責**:提供高效能的影像顯示功能,支援大圖縮放、拖曳平移、局部放大以及多頁縮圖並排顯示。 |
| | | **核心實作特性**: |
| | | * **Envision 影像元件**:深度依賴 `EnImgScr` (TImageScrollBox) 來處理大型 TIFF/JPEG 影像的渲染與捲動。 |
| | | * **多圖預覽管理**: |
| | | * 主影像 (`ISB1`) 與縮圖陣列 (`ISB2` ~ `ISB8`)。 |
| | | * 背景生成黑白影像 (`ISB_BW`) 供內部條碼辨識或影像處理使用,而不影響前端彩色顯示。 |
| | | * 動態建立/釋放:`CreatePreViewISB` 與 `FreePreViewISB` 負責根據案件頁數動態產生縮圖物件。 |
| | | * **滑鼠行為模式 (Mouse Mode)**: |
| | | * 透過 `ViewMouseMode` 函式切換 `TImageScrollBox` 的狀態,例如:放大鏡 (`mmAmplifier`)、框選縮放 (`mmZoom`)、手形拖曳 (`mmDrag`)、旋轉 (`mmR90`, `mmR180`, `mmR270`) 或刪除 (`mmDelete`)。 |
| | | * 對應工具列按鈕 (`FC0` ~ `FC6`) 的點擊事件 (`FC0Click` ~ `FC6Click`) 來切換這些游標模式。 |
| | | * **滑鼠與滾輪事件**: |
| | | * 處理自定義的滾輪捲動 (`WMMOUSEWHEEL`),同步捲動 `ScrollBox1` 或放大縮小影像 (`ZoomPercent`)。 |
| | | * 處理影像上的點擊 (`ISBClick`) 與拖拉排序 (`ISBDragDrop`, `ISBDragOver`)。 |
| | | * 在影像上繪製選取框 (`PaintShape`, `FreeShapeobj`)。 |
| | | |
| | | ## 3. 案件與文件樹狀檢視 (Case & Document TreeView) |
| | | |
| | | **職責**:以階層式樹狀結構呈現當前掃描或下載的「案件 -> 文件 -> 頁面」,並提供豐富的右鍵選單功能以管理這些節點。 |
| | | **核心實作特性**: |
| | | * **TTreeView 結構維護**: |
| | | * `NewTreeNode`:頂層節點(如:新件、異動件)。 |
| | | * `MyTreeNode1`:案件層級 (CaseID)。 |
| | | * `MyTreeNode2`:文件層級 (DocNo/DocDir)。 |
| | | * `MyTreeNode3`:表單/頁面層級 (FormID)。 |
| | | * **視覺狀態回饋**:依據節點的狀態改變圖示 (`ImageIndex`),例如:未配號、檢核失敗、完成檢核、或標記為被引用 (`GetUseCase`) 的文件。 |
| | | * **節點資料綁定與更新**: |
| | | * 利用 `DrawDocItem2` 根據實體的 `.dat` 設定檔重新長出整棵樹。 |
| | | * 利用 `NewTreeNodeRefresh`, `MyTreeNode1Refresh` 等更新節點後方的筆數或頁數統計。 |
| | | * **右鍵選單與節點操作**: |
| | | * 根據選中的節點層級,動態顯示/隱藏 `PopupMenu1` 的選項 (`PopupMenu1Popup`)。 |
| | | * 支援在樹狀圖上的拖拉移動 (`TreeView1DragDrop`) 以改變影像歸屬。 |
| | | |
| | | ## 4. 多國語言與本地化引擎 (I18N & Localization) |
| | | |
| | | **職責**:將 UI 介面上的所有寫死字串抽離,透過外部設定檔實現多國語言的動態切換。 |
| | | **核心實作特性**: |
| | | * **語言檔載入**:在初始化時透過 HTTP/FTP 下載最新的語言包 (`Language.Lng` 至 `LngPath`)。 |
| | | * **動態介面翻譯**:`InitialLanguage` 函式。 |
| | | * 利用 `TMemIniFile` 讀取語言檔。 |
| | | * 遍歷表單上的所有元件 (`Components`),判斷其類型 (`TButton`, `TBitBtn`, `TMenuItem`, `TLabel`, `TListView`, `TRadioGroup` 等)。 |
| | | * 將元件的 `Name` 作為 Key 去 Ini 檔中尋找對應的翻譯字串,並替換其 `Caption` 或 `Hint`。 |
| | | |
| | | ## 5. 系統狀態與進度回饋 (Status & Progress Feedback) |
| | | |
| | | **職責**:在進行耗時的網路傳輸、影像處理或資料庫比對時,鎖定 UI 並給予使用者明確的等待提示,避免重複操作。 |
| | | **核心實作特性**: |
| | | * **全域 UI 鎖定 (`DataLoading`)**: |
| | | * 當執行耗時任務時,將滑鼠游標改為漏斗 (`crHourGlass`)。 |
| | | * 停用主要的控制面板 (`Panel1.Enabled := False`, `Panel2.Enabled := False`)。 |
| | | * 顯示中央的進度提示面板 (`Panel22` 或 `Panel8`)。 |
| | | * **動態文字更新**:透過 `Timer2` 定期在進度提示文字 (`ShowText`) 後方加上點號 (`...`),營造系統正在運作的視覺效果。 |
| | | * **狀態列 (`StatusBar1`)**:顯示當前版本號 (`GetCurrentVersionNo`)、登入使用者名稱、引用狀態,以及浮動授權 (License) 的剩餘額度。 |
| | | |
| | | --- |
| | | |
| | | ## 💡 未來重構與微服務化建議 |
| | | |
| | | 若要將此「UI View 模組」進行現代化重構 (例如轉向 Web 介面或更清晰的架構),建議考量以下方向: |
| | | |
| | | 1. **MVC / MVVM 架構解耦**:目前的程式碼將按鈕點擊事件 (Controller)、資料讀取 (Model) 與畫面更新 (View) 混雜在同一個 Pas 檔中。應將邏輯抽離,讓 UI 只負責「顯示資料」與「轉發命令」。 |
| | | 2. **TreeView 虛擬化 (Virtualization)**:若案件內包含極多頁面,頻繁的 `DrawDocItem2` 清空並重建樹狀節點會導致效能低落。建議改用 Virtual TreeView 或資料綁定 (Data Binding) 的方式,只渲染可見節點。 |
| | | 3. **分離多國語言引擎**:將 `InitialLanguage` 的邏輯獨立為一個全局的 `Translator` 服務,避免每個 Form 或 Frame 都需要自己實作載入邏輯。 |
| | | 4. **Web 相容性評估**: |
| | | * 由於深切依賴 `TImageScrollBox` 的 Windows GDI 渲染與特定的滑鼠狀態 (`TMouseMode`),若要轉為 Web (HTML5/Canvas),需尋找或實作對應的 Web Image Viewer 套件。 |
| | | * ActiveX 特有的 COM 介面 (`TActiveForm`, `safecall`) 將被淘汰,改以 WebSocket 或 REST API 提供狀態更新,前端由 React/Vue 等框架接手渲染。 |
| 比對新檔案 |
| | |
| | | # **Delphi 命名規範與風格指南 (Naming Conventions)** |
| | | |
| | | ## **1. 大小寫敏感度 (Case Sensitivity)** |
| | | |
| | | 在探討具體規範前,我們必須先釐清 Delphi 編譯器的特性: |
| | | |
| | | - **Delphi 是「大小寫不敏感」的 (Case-Insensitive):** |
| | | |
| | | 對 Delphi 編譯器而言,CaseID、caseid、CASEID 甚至 cAsEiD 都是**完全相同**的變數。這與 C++, C#, Java, JavaScript (這些是 Case-sensitive) 有著根本上的不同。 |
| | | |
| | | - **為什麼還需要規範?** |
| | | |
| | | 既然編譯器不在乎,為什麼大家還是很嚴格遵守大小寫?因為**「人」在乎**。統一的大小寫規範(通常是 PascalCase)能極大提升程式碼的可讀性。 |
| | | |
| | | - **何時大小寫「絕對」重要?** |
| | | |
| | | 當 Delphi 與外部世界溝通時!例如: |
| | | |
| | | 1. 呼叫 C/C++ 寫的 DLL 函數(如 Windows API)。 |
| | | 2. 處理 JSON/XML 資料解析時。 |
| | | 3. 將 ActiveX/OCX 元件提供給網頁端的 JavaScript 呼叫時(這也是為什麼你在 _TLB.pas 裡會看到像 caseno 這種全小寫的屬性,因為它是為了迎合 JS/Web 的習慣)。 |
| | | |
| | | ## **2. 常見的程式命名風格 (Casing Styles)** |
| | | |
| | | 了解以下三種風格,有助於我們理解 Delphi 的選擇: |
| | | |
| | | 1. **PascalCase (帕斯卡命名法)**:每個單字的首字母都大寫。 |
| | | - *範例*:CalculateTotal, UserName, CaseID |
| | | - **Delphi 的最愛**:Delphi 絕大多數的命名都基於這個風格。 |
| | | 1. **camelCase (駱駝命名法)**:第一個單字全小寫,後續單字首字母大寫。 |
| | | - *範例*:calculateTotal, userName, caseId |
| | | - **Delphi 的應用**:偶爾用於區域變數(Local Variables),但在傳統 Delphi 中不如 Java/C# 常見。 |
| | | 1. **snake_case (蛇形命名法)**:全小寫,單字間用底線 _ 分隔。 |
| | | - *範例*:calculate_total, user_name, case_id |
| | | - **Delphi 的應用**:**極少使用**。通常只出現在早期的舊程式碼,或是為了與 C 語言庫(如 SQLite, LibVLC)保持一致時才會使用。 |
| | | |
| | | ## **3. Delphi 核心命名規範 (前綴字慣例)** |
| | | |
| | | Delphi 最具特色的就是使用**單一字母前綴**來標示該元素的「種類」或「作用域」。 |
| | | |
| | | | **元素類型 (Type)** | **前綴** | **命名風格** | **範例與說明** | |
| | | | --- | --- | --- | --- | |
| | | | **類別 (Class/Type)** | **T** | PascalCase | TForm, TCustomer |
| | | *(T 代表 Type,所有類別與自訂型別皆以此開頭)* | |
| | | | **介面 (Interface)** | **I** | PascalCase | IUnknown, IScanner |
| | | *(I 代表 Interface)* | |
| | | | **例外 (Exception)** | **E** | PascalCase | EOutOfMemory, EFileNotFound |
| | | *(E 代表 Exception)* | |
| | | | **指標 (Pointer)** | **P** | PascalCase | PChar, PInteger |
| | | *(P 代表 Pointer)* | |
| | | | **私有欄位 (Field)** | **F** | PascalCase | FCaseID, FWidth |
| | | *(F 代表 Field,僅存在於類別的 private/protected 區段)* | |
| | | | **屬性 (Property)** | **無** | PascalCase | CaseID, Width |
| | | *(對外公開的屬性,通常用來讀寫對應的 F 變數)* | |
| | | | **方法參數 (Parameter)** | **A** | PascalCase | AOwner, ACaseID |
| | | *(A 代表 Argument,用以避免與內部 Field 撞名)* | |
| | | |
| | | ## **4. 變數與常數命名** |
| | | |
| | | - **區域變數 (Local Variables)**: |
| | | - 通常使用 **PascalCase**,不加前綴。例如:ImageStream, TotalCount。 |
| | | - 迴圈計數器習慣使用大寫字母:I, J, K。 |
| | | - **全域變數 (Global Variables)**: |
| | | - 盡量避免使用。若必須使用,部分開發者會加上 G 前綴(如 GSystemStatus)。 |
| | | - **常數 (Constants)**: |
| | | - 現代 Delphi 傾向直接使用 PascalCase 配合列舉。部分開發者使用 c 前綴(如 cMaxRetries)。 |
| | | |
| | | ## **5. 列舉型別 (Enumerations) 的特殊規範** |
| | | |
| | | Delphi 對列舉型別有非常優雅的規範:**型別以 T 開頭,列舉值以該型別的 2~3 個字母縮寫作為小寫前綴。** |
| | | |
| | | - *範例*: |
| | | |
| | | type |
| | | |
| | | TTransMode = (tsHttp, tsFtp, tsNone); |
| | | |
| | | TFormAlign = (alNone, alTop, alBottom, alLeft, alRight, alClient); |
| | | |
| | | |
| | | ## **6. 檔案與單元 (File & Unit) 命名** |
| | | |
| | | 在 Delphi 中,檔案名稱(.pas)與程式碼內宣告的單元名稱(unit XXX;)**必須完全一致**。 |
| | | |
| | | - **基本規則**:使用 **PascalCase**,避免使用空白與特殊字元。 |
| | | - **視覺化窗體 (Forms)**:單元檔名通常結尾加上 Frm、Form 或 UI。 |
| | | - **資料模組 (DataModules)**:單元檔名通常結尾加上 DM。 |
| | | |
| | | ## **7. 檔案命名後綴與副檔名解析 (File Extensions & Suffixes)** |
| | | |
| | | 在 Delphi 的專案架構中,除了單元名稱外,副檔名與特定的檔名後綴也承載了重要的架構意義。 |
| | | |
| | | ### **7.1 檔名後綴 _TLB** |
| | | |
| | | 當你開發或匯入 COM、ActiveX 或 Automation Server 時,Delphi IDE 會自動生成以 _TLB 結尾的檔案(例如 CB_IMGPSScan_TLB.pas)。 |
| | | |
| | | - **意義**:TLB 是 **Type Library (類型庫)** 的縮寫。 |
| | | - **作用**:Type Library 本身是一個二進位檔案,用來跨語言描述 COM 元件的介面。Delphi 為了讓 Object Pascal 能呼叫這些介面,會將其反編譯成 .pas 程式碼,並自動加上 _TLB 後綴。 |
| | | - **⚠️ 核心規範**:**絕對不要手動修改 _TLB.pas 檔案的內容!** 只要你在 IDE 中重新整理 Type Library,這個檔案就會被完全覆蓋重寫。若要實作功能,應寫在對應的 _Imp.pas 或其他單元中。 |
| | | |
| | | ### **7.2 常見的 Delphi 副檔名** |
| | | |
| | | 以下是 Delphi 專案中常見的副檔名及其具體用途: |
| | | |
| | | | **副檔名** | **類型** | **說明** | |
| | | | --- | --- | --- | |
| | | | **.pas** | 原始碼 (Source) | **Pascal Source File**。最常見的程式碼檔案(單元/Unit),包含介面宣告 (interface) 與實作邏輯 (implementation)。 | |
| | | | **.dpr** | 專案檔 (Project) | **Delphi Project**。專案的主進入點,定義這是一個執行檔 (program) 還是程式庫 (library),並包含專案的啟動邏輯與引入單元列表。 | |
| | | | **.dfm** | 介面檔 (Form) | **Delphi Form**。儲存視覺化窗體 (Form) 或資料模組 (DataModule) 上所有元件的屬性與佈局設定。一定與同名的 .pas 檔成對出現。 | |
| | | | **.dcu** | 編譯檔 (Compiled) | **Delphi Compiled Unit**。.pas 檔案成功編譯後產生的二進位結果。最終連結器 (Linker) 會將這些 .dcu 打包成 EXE 或 DLL。 | |
| | | | **.res** | 資源檔 (Resource) | **Compiled Resource File**。二進位資源檔,包含應用程式圖示 (Icon)、版本資訊或夾帶的檔案。通常在 .dpr 中用 {$R *.res} 編譯指令引入。 | |
| | | | **.tlb** | 類型庫 (Type Lib) | **Type Library**。微軟 COM 標準的二進位檔案,用來描述 ActiveX 元件對外公開的介面、方法與屬性,供其他語言(如 C++, JS)參考呼叫。 | |
| | | | **.ridl** | 介面定義 (RIDL) | **Restricted Interface Definition Language**。較新版 Delphi 用來取代舊式二進位 .tlb 的純文字介面定義檔。開發者可以透過文字或 IDE 編輯它,編譯時會自動生成對應的 .tlb 與 _TLB.pas。 | |
| | | | **.ocx** | 控件擴充檔 | **OLE Control Extension**。本質上是一個特殊格式的 DLL(動態連結函式庫),內部包含了 ActiveX 控件。Windows 系統可以透過 regsvr32 註冊它,讓 IE 瀏覽器或其他應用程式嵌入使用。 | |
| | | |
| | | ## **8. Delphi 架構與現代語言概念對應 (Architecture Mapping)** |
| | | |
| | | 對於習慣 C#、Java、Python 或 JavaScript 的開發者,了解 Delphi 專屬的結構名詞如何對應到現代語言是非常重要的。 |
| | | |
| | | ### **8.1 Namespace (命名空間)** |
| | | |
| | | - **現代語言對應**:等同於 C# 的 namespace、Java 的 package,或是 C++ 的 namespace,用來組織程式碼並避免名稱衝突。 |
| | | - **Delphi 的對應**: |
| | | - **早期 Delphi (如 Delphi 7/2007)**:沒有明確的 Namespace 關鍵字,**unit (單元) 本身就充當了扁平化的命名空間**。為避免撞名,開發者強烈依賴「前綴字」(例如 Indy 網路套件都以 Id 開頭,如 IdHTTP;Envision 影像套件以 En 開頭,如 EnScan)。 |
| | | - **現代 Delphi (XE2 以後)**:正式引入了 **Dotted Unit Names (點綴命名)** 作為 Namespace 的支援。例如原先的 SysUtils.pas 變成了 System.SysUtils.pas,Forms.pas 變成了 Vcl.Forms.pas。 |
| | | |
| | | ### **8.2 Class (類別)** |
| | | |
| | | - **現代語言對應**:等同於 C# / Java / TS 的 class。 |
| | | - **Delphi 的對應**:同樣使用 class 關鍵字,但在 Delphi 中必須宣告於 type 區塊之下。如前文所述,類別名稱慣例上**必須以大寫 T 開頭**(例如 TMainForm)。實體化時,Delphi 是呼叫建構子方法(如 TMyClass.Create),而不是使用 new 關鍵字。 |
| | | |
| | | ### **8.3 Unit (單元, .pas)** |
| | | |
| | | - **現代語言對應**:等同於 ES6 的 **Module (.js/.ts)**、Python 的 **Module (.py)**,或是 C/C++ 中 .h (標頭檔) 與 .cpp (原始檔) 的綜合體。 |
| | | - **Delphi 的對應**:unit 是 Delphi 程式碼封裝的最核心基礎。一個完整的 unit 會被劃分為兩個主要區塊: |
| | | - **interface (介面區)**:等同於對外輸出的 API 或 Header,這裡宣告的型別與函式可透過 uses 關鍵字被其他檔案呼叫。 |
| | | - **implementation (實作區)**:等同於內部的私有邏輯實作。在這裡宣告的變數或函式,對外部檔案是完全隱藏的。 |
| | | |
| | | ### **8.4 Library (程式庫, .dpr)** |
| | | |
| | | - **現代語言對應**:等同於 C# 的 **Class Library 專案**、Node.js 的原生 C++ 附加元件,或 C/C++ 的 **Shared Library / 動態連結檔**。 |
| | | - **Delphi 的對應**:在專案原始檔中(.dpr),如果開頭宣告為 library(而非一般視窗程式的 program),代表它的編譯目標是一個不具獨立執行程序的主體,通常會編譯為 **.dll** 或 **.ocx**。這類專案主要透過 exports 關鍵字,將內部的函式暴露給作業系統或其他語言的程式(如 C++ 或網頁瀏覽器)來呼叫。 |
| | | |
| | | |
| | | ## **9 Compiler Directive** |
| | | |
| | | 在 Delphi (Object Pascal) 中,`{ }` 符號的意義取決於緊跟在 `{` 後面的第一個字元。這是一個非常基礎但關鍵的語法,讓我們來拆解你的疑問: |
| | | |
| | | --- |
| | | |
| | | ### 9.1 `{ }` 符號的基本意義:註解 (Comments) |
| | | |
| | | 在最基本的情況下,`{ }` 是 **「區塊註解」**。編譯器會完全忽略裡面的內容。 |
| | | |
| | | * **`{ TCB_IMGPSScanX }`**:這是一段 **註解**。 |
| | | * **作用**:通常開發者寫這行是為了告訴讀程式碼的人:「接下來這一段程式碼是屬於 `TCB_IMGPSScanX` 類別的實作」。它對程式執行**沒有任何影響**。 |
| | | * **`TCB_IMGPSScanX`**:這是一段 **程式碼 (識別碼)**。 |
| | | * **作用**:編譯器會去尋找名為 `TCB_IMGPSScanX` 的類別、型別或變數。如果沒定義,編譯就會報錯。 |
| | | |
| | | --- |
| | | |
| | | ### 9.2 `{$ }` 符號的進階意義:編譯指令 (Compiler Directives) |
| | | |
| | | 當 `{` 後面緊跟著 **`$`** 字元時,它就從「註解」變成了 **「編譯指令」**。這不是給人看的,而是**給編譯器下的指令**。 |
| | | |
| | | #### 關於 `{$I filename}` (Include 指令) |
| | | 你看到的 `{$I ...}` 是 **Include Directive**。 |
| | | |
| | | * **作用**:它的功能就像是編譯器的「複製貼上」工具。它告訴編譯器:「在編譯這行時,請先去把 `CB_IMGPSScanImp_UI.pas` 檔案裡的文字**原封不動地抓過來,塞到這個位置**,然後再繼續往下編譯」。 |
| | | * **目的**:這通常用來處理非常巨大的程式碼檔案。就像你之前的 `CB_IMGPSScanImp.pas` 檔案太長了,開發者把它拆成小檔案,最後用這種方式在主檔「匯合」。 |
| | | |
| | | --- |
| | | |
| | | ### 9.3 解析你的範例語法 |
| | | |
| | | 你提到的這一段: |
| | | `{$I CB_IMGPSScanImp_UI.pas}{$I CB_IMGPSScanImp_Scan.pas}...initialization` |
| | | |
| | | #### 這是在做什麼? |
| | | 這既不是定義,也不是宣告,更不是字串。它是一種 **「程式碼組合行為」**。 |
| | | |
| | | 1. **組合實作內容**:編譯器會依序讀入 `UI.pas`、`Scan.pas`、`Data.pas`、`Utils.pas`。這四個檔案裡放的通常是 `TCB_IMGPSScanX` 的各種 `procedure` 和 `function` 實作。 |
| | | 2. **銜接初始化區段**:當上述所有檔案的內容都被「貼進來」後,程式碼剛好接到了 `initialization` 關鍵字。 |
| | | |
| | | #### 語法邏輯如下: |
| | | ```pascal |
| | | implementation |
| | | |
| | | // 編譯器開始貼上檔案 |
| | | [貼上 UI.pas 的內容] |
| | | [貼上 Scan.pas 的內容] |
| | | [貼上 Data.pas 的內容] |
| | | [貼上 Utils.pas 的內容] |
| | | |
| | | // 貼完後,剛好銜接這支單元的初始化邏輯 (AxCtrl) |
| | | initialization |
| | | TActiveFormFactory.Create(...); |
| | | SetLicenseKey(...); |
| | | end. |
| | | ``` |
| | | |
| | | ### 總結 |
| | | * **`{ ... }`** = 註解(編譯器不看)。 |
| | | * **`{$I ...}`** = 編譯器指令(叫編譯器去別的檔案搬程式碼過來貼上)。 |
| | | * **為什麼這樣寫?** 這是為了讓主檔保持簡潔。主檔只負責「宣告結構」和「啟動初始化」,而「具體的細節邏輯」則被藏在那些被 Include 的檔案裡。 |
| | | |
| | | |
| | | ## 10. 註解語法與風格慣例 (Comments Style) |
| | | |
| | | Delphi 提供三種主要的註解方式,以及一些開發者社群常用的視覺化風格: |
| | | |
| | | ### 10.1 單行註解 (`//`) |
| | | |
| | | - **用法**:從 `//` 開始到該行結束。 |
| | | - **視覺強調 (`////`)**: |
| | | - 你在源碼中看到的 `////` 語法上等同於 `//`。 |
| | | - **開發者意圖**:通常用於**「標題化」**或**「強力分隔」**。多個斜線能在視覺上形成橫條效果,幫助開發者在快速捲動程式碼時一眼認出關鍵區塊(例如:`//// ***** 預設區 ***** ////`)。 |
| | | |
| | | ### 10.2 區塊註解 (`{ }`) |
| | | |
| | | - **用法**:被 `{ }` 包圍的所有文字。 |
| | | - **特點**:常用於長篇說明的標註,或是在調試時暫時關閉一整段程式碼。 |
| | | - **編譯指令**:若開頭為 `{$`(如 `{$R *.DFM}`),則代表它是給編譯器的指令,而非一般註解。 |
| | | |
| | | ### 10.3 替代區塊註解 (`(* *)`) |
| | | |
| | | - **用法**:由 `(*` 開始到 `)` 結束。 |
| | | - **主要用途 (註解嵌套)**:當你想要註解掉一段已經包含 `{ }` 註解的程式碼時,Delphi 規定 `{ }` 不能包在另一對 `{ }` 裡面,此時必須改用 `(* ... *)` 來包裹,才能正確註解。 |
| | | |
| | | ### 10.4 文件化註解 (`///` XML Documentation) |
| | | |
| | | - **用法**:在方法上方使用連續三個斜線。 |
| | | - **作用**:現代 Delphi IDE (XE 以後) 會解析這類註解,並在鼠標懸停於方法名上時顯示提示說明(類似 JavaDoc 或 C# XML Doc)。 |
| | | |
| | | ### 10.5 分隔線風格示例 |
| | | |
| | | 在大型檔案(如 `CB_IMGPSScanImp.pas`)中,常用註解來劃分功能界線: |
| | | |
| | | ``` |
| | | // ********************************************************************* // |
| | | // 這是區塊分隔線風格,常用於自動生成的 TLB 檔 |
| | | // ********************************************************************* // |
| | | |
| | | {-----------------------------------------------------------------------} |
| | | { 這是另一種常見的手寫分隔線風格 } |
| | | {-----------------------------------------------------------------------} |
| | | ``` |
| | | |
| | | |
| 比對新檔案 |
| | |
| | | |
| | | ## TableOfContent |
| | | - [IO_說明.md](IO_%E8%AA%AA%E6%98%8E.md) |
| | | - [ScanImp_分析.md](ScanImp_%E5%88%86%E6%9E%90.md) |
| | | - [介面實作.md](%E4%BB%8B%E9%9D%A2%E5%AF%A6%E4%BD%9C.md) |
| | | - [delphi_專案必讀.md](delphi_%E5%B0%88%E6%A1%88%E5%BF%85%E8%AE%80.md) |
| | | - [EnScan_拆解.md](EnScan_%E6%8B%86%E8%A7%A3.md) |
| | | |
| | | |
| | | |
| | | - 各生命周期 or diagram |
| | | - Remote/Local IO 方法 |
| | | - Remote/Local IO 抽象介面 |
| | | - OCX 抽像介面 |
| | | - |
| 比對新檔案 |
| | |
| | | |
| | | |
| | | # **相依套件分析報告** |
| | | |
| | | 本報告針對 ActiveX 元件核心實作檔 CB_IMGPSScanImp.pas 進行功能解構,並分析其與第三方套件的綁定關係,以作為後續維護或現代化升級之參考。 |
| | | |
| | | ## **壹、 依功能拆分模組與第三方套件綁定分析** |
| | | |
| | | 根據原始碼結構,CB_IMGPSScanImp.pas 是一個將介面、硬體、網路與業務邏輯高度耦合的「上帝物件 (God Object)」。若要進行重構或拆件,建議依據以下五個核心模組進行解耦: |
| | | |
| | | ### **1. 硬體掃描控制模組 (Scanner Controller)** |
| | | |
| | | - **功能描述**:負責與實體掃描機溝通,處理 TWAIN 介面初始化、設定掃描參數(DPI、色彩、雙面)、觸發掃描指令,以及接收掃描完成的記憶體影像 (DibHandle)。 |
| | | - **關鍵方法**:initkscan、StatrTwainScan、OnAcquire、PageEnd、PageDone。 |
| | | - **第三方套件綁定 (強綁定)**: |
| | | - 依賴 **Envision Imaging SDK** (EnScan) 進行 TWAIN 驅動控制。 |
| | | - 依賴 EnMisc 處理底層記憶體或雜項運算。 |
| | | |
| | | ### **2. 影像處理與轉換模組 (Image Processor)** |
| | | |
| | | - **功能描述**:負責對擷取到的原始影像進行後製處理,包括:裁切(如 A3 裁 A4)、去斜 (Deskew)、旋轉、二值化 (轉黑白)、格式轉換 (轉 TIFF/JPEG),以及讀取影像上的條碼。 |
| | | - **關鍵方法**:ImageReSize_FormID、CheckNeedCrop、DeskewImg、ConvertToBW。 |
| | | - **第三方套件綁定 (強綁定)**: |
| | | - 依賴 **Envision Imaging SDK** (EnTifGr, EnDiGrph) 進行 TIFF (CCITT Group 4) 壓縮與 DIB 物件封裝。 |
| | | - 依賴 **MPS Barcode** (mpsBarco, BarcodesFinder) 進行影像條碼掃描與解析。 |
| | | |
| | | ### **3. 安全傳輸與 API 通訊模組 (Transport Manager)** |
| | | |
| | | - **功能描述**:負責將掃描並壓縮後的檔案上傳至伺服器,或從伺服器下載檔案。同時處理與後端 Java Servlet 的參數交換(如校時、取得 FTP 資訊、更新案件狀態)。 |
| | | - **關鍵方法**:upFile、dnFile_Get、ProcessServlet_Get、GetServerDate、GetftpInfo。 |
| | | - **第三方套件綁定 (強綁定)**: |
| | | - 依賴 **SecureBlackbox** (SBSimpleSSL, SBHTTPSClient, SBSimpleFTPS 等) 處理 HTTPS/FTPS 安全加密通訊。 |
| | | - 依賴 **Indy** (IdHashMessageDigest) 產生檔案的 MD5 檢核碼。 |
| | | - 依賴自訂/第三方加密庫 (Encryp) 處理字串加解密。 |
| | | |
| | | ### **4. 業務邏輯與資料解析模組 (Business Logic & Parser)** |
| | | |
| | | - **功能描述**:專案核心的領域邏輯,包含接收網頁端傳入的參數字串進行解析、定義文件歸類邏輯(由條碼推導文件類型)、OMR (光學標記辨識) 檢核定義等。 |
| | | - **關鍵方法**:SetSQLData (負責解析傳入的長字串至 TStringList)、GetSQLData、FormCode2DocNo、OMRCheckCase。 |
| | | - **第三方套件綁定 (中度綁定)**: |
| | | - 依賴 **Xmltool** 解析 XML 格式的 OMR 座標定義。 |
| | | - 依賴 **VCLZip** (VCLZip, VCLUnZip) 將多個影像檔打包成 ZIP 進行單一傳輸。 |
| | | |
| | | ### **5. UI 與介面呈現模組 (UI View)** |
| | | |
| | | - **功能描述**:負責 ActiveX 控制項的視覺化介面,包含左側的樹狀文件清單 (TreeView)、右側的影像預覽區 (ScrollBox),以及按鈕與多國語言切換。 |
| | | - **關鍵方法**:InitialLanguage、各類 OnClick 事件。 |
| | | - **第三方套件綁定 (輕度綁定)**: |
| | | - 依賴 **Envision Imaging** (EnImgScr) 作為支援大影像拖曳的滾動視窗。 |
| | | - 依賴 PJMenuSpeedButtons 作為自訂外觀的按鈕元件。 |
| | | |
| | | ## **貳、 第三方套件功能總覽** |
| | | |
| | | 為達成上述複雜的業務需求,本專案引入了多個 Delphi 著名的第三方套件。以下為各套件的大致功能說明: |
| | | |
| | | ### **1. Envision Imaging SDK (前綴 En)** |
| | | |
| | | 這是一套專為 Delphi 設計的高效能影像與 TWAIN 處理套件,在金融與保險業的文件電子化系統中非常常見。 |
| | | |
| | | - **EnScan**:封裝了 TWAIN 協定,讓程式能直接控制實體掃描機,控制進紙、解析度與雙面掃描。 |
| | | - **EnDiGrph (TDibGraphic)**:封裝 Windows 底層的 DIB (Device Independent Bitmap) 記憶體句柄,讓開發者能像操作一般圖片一樣操作掃描出來的原始資料。 |
| | | - **EnTifGr**:專門處理 TIFF 影像格式。銀行業愛用 TIFF 是因為它支援「多頁」且黑白壓縮率極高 (Group 4 Fax Compression)。 |
| | | - **EnImgScr**:提供 TImageScrollBox 視覺元件,用來平滑顯示並縮放超大尺寸的掃描圖檔。 |
| | | - **EnMisc**:提供影像處理所需的底層輔助數學或資料轉換函式。 |
| | | |
| | | ### **2. SecureBlackbox (前綴 SB)** |
| | | |
| | | 由 EldoS (後被 /n software 收購) 開發的強大安全通訊與加密套件。由於早期 Delphi (D7~D2007) 內建網路庫缺乏完善的 SSL/TLS 支援,此套件被廣泛用於補強安全性。 |
| | | |
| | | - **SBHTTPSClient / SBSimpleSSL**:實作了完整的 HTTPS 用戶端通訊協定,用來安全地上傳檔案或呼叫 Servlet API。 |
| | | - **SBSimpleFTPS**:支援 FTP over SSL (FTPS) 的安全檔案傳輸。 |
| | | - **SBWinCertStorage / SBX509**:處理 Windows 系統內的憑證儲存區,以及 X.509 數位憑證的驗證(例如確認銀行伺服器的 HTTPS 憑證是否合法)。 |
| | | |
| | | ### **3. MPS Barcode & BarcodesFinder** |
| | | |
| | | - **功能**:這是一套條碼識別引擎。 |
| | | - **專案應用**:在掃描過程中(如 PageDone 或 OnAcquire 階段),它會掃描圖片特定區域尋找一維或二維條碼。讀取到的條碼值會被用來決定這張影像屬於哪一種「文件表單」(FormCode),進而實現自動分類歸檔。 |
| | | |
| | | ### **4. VCLZip (VCLZip, VCLUnZip)** |
| | | |
| | | - **功能**:老牌的 Delphi ZIP 壓縮元件。 |
| | | - **專案應用**:為了節省網路頻寬與減少伺服器連線次數,掃描完成的多張 TIFF/JPG 檔案會被打包成一個 .zip 檔後,再一次性上傳至伺服器。 |
| | | |
| | | ### **5. Indy Components (前綴 Id)** |
| | | |
| | | - **IdHashMessageDigest / idHash**:Delphi 內建的 Internet Direct (Indy) 函式庫。 |
| | | - **專案應用**:在此專案中被用來計算檔案或字串的 **MD5 Hash (雜湊值)**。通常用於確保檔案上傳過程沒有損壞,伺服器比對 MD5 一致才算上傳成功。 |
| | | |
| | | ### **6. 其他工具 (Tools & Utils)** |
| | | |
| | | - **Xmltool**:負責讀寫 XML 檔案。銀行經常將表單的 OMR (光學標記辨識) 檢核座標或參數寫成 XML 設定檔,透過此工具讀入系統。 |
| | | - **Encryp**:這可能是開發商自訂或第三方的輕量級加密單元,用來加密本機設定檔 (.ini) 內的敏感資訊或混淆網頁傳入的參數。 |
| | | |
| | | # **核心源碼與物件實作解析** |
| | | |
| | | 本文件針對 CB_IMGPSScanImp.pas 中的核心方法與隱藏物件進行原始碼級別的邏輯拆解與推導。 |
| | | |
| | | ## **壹、 核心方法源碼說明** |
| | | |
| | | ### **1) TCB_IMGPSScanX.GetCurrentVersionNo** |
| | | |
| | | - **功能描述**:取得目前 OCX 元件的版本號。這通常提供給網頁端的 JavaScript 呼叫,用來判斷客戶端的 OCX 是否需要觸發更新下載。 |
| | | - **實作推導與底層 API 說明**: |
| | | |
| | | 在 Delphi 中,若要動態讀取編譯進 OCX 的 VS_VERSION_INFO 資源,標準作法是呼叫一系列的 Windows API。這段實作通常包含以下三個關鍵概念: |
| | | |
| | | 1. **HInstance (實體句柄)**: |
| | | - 這是 Delphi 提供的一個全域變數,代表目前載入記憶體中的模組(即這個 OCX 檔)的「實體句柄 (Instance Handle)」。 |
| | | - 在 Windows 作業系統中,當一個執行檔 (EXE) 或動態連結檔 (DLL/OCX) 被載入記憶體時,會獲得一個記憶體基底位址,HInstance 就是指向這個位址的指標。Windows 需要這個值來識別「你要操作的是哪一個載入的模組」。 |
| | | 1. **GetModuleFileName 的作用**: |
| | | - 這是一個 Windows API。它的作用是利用前面提到的 HInstance,向作業系統反查出這個模組在硬碟上的**「完整實體檔案路徑」**(例如:C:\Windows\Downloaded Program Files\CB_IMGPSScan.ocx)。 |
| | | - 因為我們需要讀取檔案本身的版本資訊,程式必須先知道「我自己到底存放在硬碟的哪裡」。 |
| | | 1. **後半段 VerQueryValue 的運作邏輯**: |
| | | |
| | | 取得檔案路徑後,讀取版本資訊會經過三部曲,而 VerQueryValue 是最後負責「解析」的關鍵: |
| | | |
| | | - **Step 1 (GetFileVersionInfoSize)**:先問 Windows 這個檔案的版本資訊區塊有多大。 |
| | | - **Step 2 (GetFileVersionInfo)**:把整塊版本資訊的二進位原始資料 (Raw Data) 完整讀進一段記憶體緩衝區中。 |
| | | - **Step 3 (VerQueryValue)**:因為剛剛讀進來的是一包無法直接閱讀的二進位資料,VerQueryValue 的作用就是**「在這包資料中進行精準查詢」**。透過傳入特定的查詢路徑(例如傳入 \ 可以取得包含主版號、次版號的 VS_FIXEDFILEINFO 結構體;或是傳入特定語系代碼如 \StringFileInfo\040404E4\FileVersion),作業系統會幫你把二進位資料解析成我們需要的字串或數字版本號(如 1.0.3.5)。 |
| | | |
| | | ### **2) TCB_IMGPSScanX.ActiveFormCreate** |
| | | |
| | | - **功能描述**:這是 ActiveX 視覺化窗體 (Form) 的生命週期起點。當 IE 瀏覽器或應用程式載入這個 OCX 時,首先觸發的初始化事件。 |
| | | - **實作推導**: |
| | | |
| | | 它會負責初始化全域變數、設定第三方元件的預設值、並建立暫存資料夾。 |
| | | |
| | | ```pascal |
| | | procedure TCB_IMGPSScanX.ActiveFormCreate(Sender: TObject); |
| | | begin |
| | | // 1. 預設變數初始化 |
| | | Def_DeviceDelete := True; |
| | | Def_ScannerReverse := False; |
| | | |
| | | // 2. 建立本機暫存目錄 |
| | | ForceDirectories('C:\Temp\IMGPSScan'); |
| | | |
| | | // 3. UI 語系初始化 |
| | | InitialLanguage; |
| | | end; |
| | | ``` |
| | | |
| | | |
| | | ### **3) TCB_IMGPSScanX.StatrTwainScan** |
| | | |
| | | - **功能描述**:啟動掃描機的 TWAIN 介面,開始進紙或掃描。(註:Statr 應為 Start 的早期拼字錯誤,這在舊專案中很常見)。 |
| | | - **實作推導**: |
| | | |
| | | 這個方法會讀取網頁傳進來的參數(如 DPI、色彩),設定給 Scanner 物件,然後發出擷取指令。 |
| | | |
| | | ```pascal |
| | | procedure TCB_IMGPSScanX.StatrTwainScan; |
| | | begin |
| | | // 設定解析度與顏色 |
| | | Scanner.Resolution := StrToIntDef(Fimgdpi, 200); |
| | | if Fscancolor = 'C' then |
| | | Scanner.PixelType := ptColor |
| | | else |
| | | Scanner.PixelType := ptGray; |
| | | |
| | | // 啟動掃描 (會喚起實體掃描機) |
| | | Scanner.Acquire; |
| | | end; |
| | | ``` |
| | | |
| | | |
| | | ### **4) TCB_IMGPSScanX.OnAcquire 與資料流物件** |
| | | |
| | | - **功能描述**:這是 Scanner (EnScan) 最核心的事件。當掃描機成功掃完「一張」影像並傳入電腦記憶體時,會觸發此事件。 |
| | | - **源碼邏輯推導**: |
| | | |
| | | ```pascal |
| | | procedure TCB_IMGPSScanX.ScannerAcquire(Sender: TObject; DibHandle: THandle; var Handled: Boolean); |
| | | var |
| | | MyStream: TMemoryStream; |
| | | MyGraphic: TDibGraphic; |
| | | begin |
| | | // 1. 將 DibHandle 轉換為 Delphi 影像物件 |
| | | MyGraphic := TDibGraphic.Create; |
| | | MyGraphic.Handle := DibHandle; |
| | | |
| | | // 2. 影像處理 (如去斜、黑白轉換) |
| | | |
| | | // 3. 存入記憶體串流或實體檔案 |
| | | // ... |
| | | end; |
| | | ``` |
| | | |
| | | - **三大核心資料物件解釋**: |
| | | 1. **DibHandle**:這是 Windows 底層的「全域記憶體句柄 (Global Memory Handle)」。掃描機驅動把圖片放在記憶體裡,給你這個代號。你必須透過 TDibGraphic 來「解讀」這塊記憶體。 |
| | | 2. **TMemoryStream**:**記憶體串流**。它的作用像是一塊存在 RAM 裡的虛擬硬碟。掃描後的圖檔在還沒存到實體硬碟前,會先放在這裡進行格式轉換 (如轉成 JPEG) 或進行 MD5 計算,因為在記憶體中操作速度最快。 |
| | | - **DeleteStm : TMemoryStream 的逐行操作說明**: |
| | | 在 `OnAcquire` 事件中,`DeleteStm` 主要用於「空白頁判定」。其源碼邏輯如下: |
| | | |
| | | ```pascal |
| | | // 判定是否啟動空白頁刪除功能 |
| | | if Def_DeviceDelete then |
| | | begin |
| | | // 1. 在 Heap 記憶體中建立一個暫存串流物件 |
| | | DeleteStm := TMemoryStream.Create; |
| | | try |
| | | // 2. 將當前掃描到的影像物件 (MyGraphic) 寫入記憶體串流 |
| | | // 這會根據 TIFF G4 等格式進行壓縮,產生實際的檔案位元組資料 |
| | | MyGraphic.SaveToStream(DeleteStm); |
| | | |
| | | // 3. 核心判定:檢查該影像在記憶體中所佔用的位元組大小 (Size) |
| | | // 若小於預設門檻值 (Def_DeviceDeleteSize, 如 3072 Bytes) |
| | | if DeleteStm.Size < Def_DeviceDeleteSize then |
| | | begin |
| | | // 4. 若符合空白頁特徵,則不進行存檔動作,直接跳出此 Procedure |
| | | // 並告知掃描控制項此頁已處理完畢 (Handled := True) |
| | | Handled := True; |
| | | Exit; |
| | | end; |
| | | finally |
| | | // 5. 無論是否為空白頁,操作結束後必須釋放記憶體,避免 Memory Leak |
| | | DeleteStm.Free; |
| | | end; |
| | | end; |
| | | ``` |
| | | |
| | | - |
| | | 3. **TFileStream**:**檔案串流**。用來直接對硬碟讀寫資料。當確認影像無誤後,會透過 TFileStream 將 TMemoryStream 的內容真實地寫入 C:\Temp\...\001.tif 中。 |
| | | |
| | | ### **5) TCB_IMGPSScanX.FindISB2View** |
| | | |
| | | - **功能描述**:用來在畫面上尋找並更新用來預覽圖片的元件。 |
| | | - **名詞解釋**:**ISB 代表 ImageScrollBox**。 |
| | | |
| | | 這對應到 uses 區段中的 EnImgScr 套件。TImageScrollBox 是 Envision 提供的一個 UI 元件,專門用來顯示超出螢幕大小的掃描圖檔,支援平滑拖曳與縮放。這個方法通常用來把剛掃描完的 DibHandle 餵給畫面上的 ISB 元件顯示。 |
| | | |
| | | |
| | | ### **6) TCB_IMGPSScanX.GetNoNameCase** |
| | | |
| | | - **功能描述**:處理「無案號」或「未知歸屬」的掃描件。 |
| | | - **源碼實作逐行說明**: |
| | | |
| | | 此方法透過迴圈尋找一個尚未被使用的目錄名稱(格式為「未配號 + 四位數字」)。 |
| | | |
| | | |
| | | ```pascal |
| | | Function TCB_IMGPSScanX.GetNoNameCase(Path:String):String; // 取未配號XXXX |
| | | var |
| | | i : Integer; // 宣告迴圈計數器 |
| | | begin |
| | | // 1. 啟動一個從 1 到 9999 的迴圈,尋找可用的索引值 |
| | | for i := 1 to 9999 do |
| | | begin |
| | | // 2. 檢查特定路徑下的目錄是否已存在 |
| | | // _Msg('未配號'):取得「未配號」字串(可能具備多國語言處理) |
| | | // Add_Zoo(i, 4):將數字 i 補齊為 4 位數(例如 1 變為 0001) |
| | | if Not DirectoryExists(Path + _Msg('未配號') + Add_Zoo(i, 4)) then |
| | | begin |
| | | // 3. 若該目錄不存在,代表此名稱可用,設定 Result 並跳出迴圈 |
| | | Result := _Msg('未配號') + Add_Zoo(i, 4); |
| | | Break; // 找到第一個可用的名稱後立即停止搜尋 |
| | | end; |
| | | end; |
| | | end; |
| | | ``` |
| | | |
| | | ### **7) TCB_IMGPSScanX.SetSQLData 與 8) GetSQLData** |
| | | |
| | | 這兩個方法是 OCX 與網頁 (JS) 溝通的雙向橋樑。 |
| | | |
| | | - **SetSQLData (網頁 -> OCX)**:接收由網頁傳來的設定字串(通常用 | 或 , 分隔,例如 192.168.1.1|L12345678|200|C),在內部將字串切開 (Split),並分別賦值給 ServerIP, FCaseID, Fimgdpi, Fscancolor 等內部變數。 |
| | | - **GetSQLData (OCX -> 網頁)**:將 OCX 內部的處理結果(例如:UploadSuccess|3|0 代表上傳成功、共 3 頁、0 錯誤),組合成一個長字串後回傳給網頁端的 JS 進行後續的頁面跳轉或提示。 |
| | | |
| | | ### **8) TCB_IMGPSScanX.GetDefScanIni** |
| | | |
| | | - **功能描述**:取得並套用掃瞄參數的預設值與自訂設定。 |
| | | - **源碼實作解析**: |
| | | - **硬編碼預設值 (Hardcoded Defaults)**: |
| | | |
| | | 程式一開始會先寫死一組標準值,例如 Def_ScanDpi := 300 (預設 300 DPI)、Def_DeviceDeleteSize := 3072 (空白頁判定門檻為 3KB)。 |
| | | |
| | | - **動態配置覆蓋 (Dynamic Overriding)**: |
| | | |
| | | 透過 for i := 0 to WORK_INF_List.Count - 1 do 迴圈遍歷設定檔清單。使用 GetSQLData 取得參數代號 (PARA_NO) 與內容 (PARA_CONTENT)。 |
| | | |
| | | - **屬性設定對照表**: |
| | | |
| | | 下表整理了此方法中設定的所有關鍵屬性、其對應的參數代號及功能說明。 |
| | | |
| | | | **屬性名稱 (內部變數)** | **對應 PARA_NO** | **PARA_CONTENT 處理方式(GetSQLData)** | **功能說明** | |
| | | | --- | --- | --- | --- | |
| | | | **Def_DeviceDelete** | SCAN_BLANKDEL_USE | 'Y' -> True, Else -> False | 空白頁自動刪除開關。 | |
| | | | **Def_DeviceDeleteSize** | SCAN_BLANKDEL_SIZE | StrToInt (若空則 0) | 空白頁判定門檻 (Bytes)。 | |
| | | | **Def_ScannerReverse** | SCAN_REVERSE | 'Y' -> True, Else -> False | 影像是否反相。 | |
| | | | **Def_BoardClear** | SCAN_BOARDCLEAR | 'Y' -> True, Else -> False | 是否清除影像黑邊。 | |
| | | | **Def_ScanDpi** | SCAN_DPI | StrToInt (若空則 300) | 掃描解析度。 | |
| | | | **Def_ScanDuplex** | SCAN_DUPLEX | 'Y' -> True, Else -> False | 是否啟用雙面掃描。 | |
| | | | **Def_ScanRotate** | SCAN_ROTATE_MODE | '0'->0, '1'->270, '2'->180, '3'->90 | 掃描旋轉角度映射。 | |
| | | | **Def_ScanDeskew** | SCAN_DESKEW | 'Y' -> True, Else -> False | 是否自動矯正傾斜。 | |
| | | | **Def_ScanImgSetUse** | SCAN_IMGSET_USE | 'Y' -> True, Else -> False | 是否使用亮度/對比設定。 | |
| | | | **Def_ScanBright** | SCAN_BRIGHT | StrToInt (限制 -255~255) | 亮度數值。 | |
| | | | **Def_ScanContrast** | SCAN_CONTRAST | StrToInt (限制 -255~255) | 對比數值。 | |
| | | | **Def_ScanImgShowMode** | SCAN_SHOW_MODE | '0','1','2' 對應模式 | 影像顯示/縮放模式。 | |
| | | | **ScanDenialTime** | CASE_IN_TIME | 直接存為 String | 進件截止時間限制。 | |
| | | | **ScanDenialHint** | SCAN_HINT | 直接存為 String | 掃描畫面提示字串。 | |
| | | | **NoSaveBarCodeList** | NO_SAVE_FORM_ID | CommaText := Value | 不存檔之表單代號清單。 | |
| | | | **ImagePath** | LOCAL_PATH | 直接存為 String | 本機端暫存路徑。 | |
| | | | **GuideFormIDList** | GUIDEFORMID | CommaText := Value | 導引頁表單 ID 清單。 | |
| | | | **DivPageFormIDList** | DIVPAGEFORMID | CommaText := Value | 分案頁表單 ID 清單。 | |
| | | | **FJpgCompression** | FILE_COMPRESSION | StrToInt | JPG 轉 TIF 壓縮比。 | |
| | | | **FMaxUploadSize** | MAX_UPLOAD_SIZE | 直接存為 String | 上傳大小限制 (MB)。 | |
| | | |
| | | ## **貳、 記憶體直接操作實作清單** |
| | | |
| | | 以下條列專案中直接涉及記憶體操作的程式區段: |
| | | |
| | | ### **記憶體操作對應位置表** |
| | | |
| | | | **記憶體操作對象** | **所屬 Function / Procedure** | **關鍵操作關鍵字 / 代碼** | **記憶體特性說明** | 註記 | |
| | | | --- | --- | --- | --- | --- | |
| | | | **HInstance** | GetCurrentVersionNo | GetModuleFileName(HInstance, ...) | 模組載入位址。 | 增API版本號 | |
| | | | **資源緩衝區** | GetCurrentVersionNo | VerQueryValue, GetFileVersionInfo | 透過 Pointer 進行二進位搜尋。 | 增API版本號 | |
| | | | **DibHandle** | onAcquire | MyGraphic.Handle := DibHandle; | Windows 全域記憶體句柄轉移。 | 改套件實作/Rust | |
| | | | **TMemoryStream** | onAcquire | MemoryStream.Write, .Read | Heap Memory 內部二進位操作。 | | |
| | | |
| | | ## **參、 遺失物件反推與說明** |
| | | |
| | | 你提到在原始碼中找不到 _DelTree 和 Scanner 的實作,這是因為它們屬於以下兩種情況: |
| | | |
| | | ### **1) TCB_IMGPSScanX._DelTree** |
| | | |
| | | - **這是一個自訂的私有輔助函式 (Private Helper)**。 |
| | | - **功能**:它的名稱源自 DOS 時代的指令 deltree(刪除整個目錄樹)。在 ActiveX 中,當上傳伺服器成功後,必須清空 C:\Temp\案件編號\ 裡的所有暫存圖檔與資料夾以釋放空間,並防止資料外洩。 |
| | | - **實作原理**:由於早期的 Delphi 沒有內建遞迴刪除目錄的單一指令,開發者通常會自己寫一個 _DelTree,裡面使用 FindFirst, FindNext 找出所有檔案並用 DeleteFile 刪除,最後用 RemoveDir 砍掉資料夾。 |
| | | |
| | | ### **2) Scanner** |
| | | |
| | | - **這是一個視覺化/非視覺化的第三方元件實例 (Component Instance)**。 |
| | | - **為何找不到宣告?** 因為在 Delphi 中,如果你是透過 IDE 的工具箱 (Tool Palette) 將 TEnScan 元件直接拖拉到窗體 (Form) 上,IDE 會自動把它寫在 .dfm 檔裡,並在 TCB_IMGPSScanX 類別的 published 區段自動產生一行 Scanner: TEnScan;。 |
| | | - **功能**:它就是 **Envision SDK** 裡用來與 TWAIN 硬體溝通的靈魂核心。所有的掃描屬性 (Scanner.Resolution) 與方法 (Scanner.Acquire) 都是對這個物件進行操作。 |
| | | |
| | | --- |
| | | |
| | | # **IO 路徑與檔案操作分析** |
| | | |
| | | 本報告詳列了 CB_IMGPSScanImp.pas 檔案中定義的所有 IO 相關變數,以及執行目錄與檔案管理的關鍵方法,並定義了抽像化所需的介面原型。 |
| | | |
| | | ## **壹、 關鍵路徑與檔案變數 (Variables)** |
| | | |
| | | 程式中定義了多個全域或私有欄位,用來追蹤檔案在硬碟上的實體位置。 |
| | | |
| | | ### **1. 核心路徑變數與動態組合邏輯** |
| | | |
| | | | **變數名稱** | **類型** | **組合邏輯 (Combination Logic)** | **用途說明** | |
| | | | --- | --- | --- | --- | |
| | | | **ImagePath** | String | WORK_INF 的 LOCAL_PATH 參數值 | **本機基礎根路徑**。所有暫存資料的起點。 | |
| | | | **ScaniniPath** | String | ImagePath + FWork_No + '\' + FUserUnit + '\' | **設定檔存放區**。區分作業別與單位。 | |
| | | | **ImageSavePath** | String | 同 ImagePath (初期) 或隨 FMode 變動 | **影像儲存基準路徑**。 | |
| | | | **ScanPath** | String | ImageSavePath + ScanCaseno + '\' + ScanDocdir + '\' | **當前掃描實體目錄**。指向最末層的影像資料夾。 | |
| | | | **TransPath** | String | ImageSavePath + CaseID + '\Upload\' | **準備上傳區**。在傳送前將檔案結構扁平化組合於此。 | |
| | | | **DisplayPath** | String | ImageSavePath + NowCaseNo + '\' | **預覽基準目錄**。用於 UI 點選樹狀圖時讀取特定案件資料。 | |
| | | | **LngPath** | String | GetLocalAppDir(Handle) + 'MPS\CB_IMGPS\' | **應用程式資源路徑**。存放在使用者設定檔目錄以避開權限問題。 | |
| | | | **CheckXmlPath** | String | ImagePath + 'OMRSITE\' | **OMR 規則暫存區**。存放從伺服器同步下來的 XML 定義。 | |
| | | | **SamplePath** | String | ImagePath + 'Sample\' + FWork_No + '\' | **範本影像存放區**。按作業別區分。 | |
| | | | **SitePath** | String | ImagePath + 'Site\' | **登打定位存放區**。 | |
| | | | **FFtpRootPath** | String | 透過伺服器回傳值動態設定 | **FTP 伺服器根路徑**。指定在遠端 FTP 伺育器上存放影像檔的起始位址。 | |
| | | |
| | | ### **2. 檔案名稱變數 (Filenames)** |
| | | |
| | | | **變數名稱** | **類型** | **用途說明** | |
| | | | --- | --- | --- | |
| | | | **ScanSaveFilename** | String | 下一張掃描影像預定的檔名(通常包含 FormID)。 | |
| | | | **PEFileName** | String | 在 PageEnd 階段確定的最終完整檔案路徑。 | |
| | | | **AttName** | String | **附件目錄名稱**。根據 FIs_In_Wh 決定為 Attach 或 S_Attach。 | |
| | | | **Ext** | String | 副檔名。預設為 .tif,彩色/灰階模式下可能切換為 .jpg。 | |
| | | |
| | | ## **貳、 關鍵 IO 操作方法 (Methods)** |
| | | |
| | | 以下為需要進行抽像化設計的核心 IO 方法。 |
| | | |
| | | ### **1. 目錄管理 (Directory Management)** |
| | | |
| | | | **方法名稱** | **作用** | **應用場景** | **建議抽象 IO 介面** | |
| | | | --- | --- | --- | --- | |
| | | | _DelTree | 遞迴刪除整個目錄樹 | 上傳成功後清空暫存、模式切換初始化目錄 | RemoveDirRecursive(Path) | |
| | | | Str2Dir / ForceDirectories | 確保路徑以 \ 結尾並建立路徑 | 初始化或掃描前建立資料夾 | EnsureDirExists(Path) | |
| | | | GetNoNameCase | 搜尋尚未配號的臨時目錄 | 在本機搜尋如 未配號0001 到 9999 的可用路徑 | FindNextAvailablePath(Pattern) | |
| | | | GetLocalAppDir | 取得 Windows %LocalAppData% 路徑 | 確保在現代 Windows 系統中有權限寫入語言檔與 Log | GetBaseStoragePath() | |
| | | |
| | | ### **2. 存在性檢查與搜尋 (Existence & Search)** |
| | | |
| | | | **方法名稱** | **作用** | **應用場景** | **建議抽象 IO 介面** | |
| | | | --- | --- | --- | --- | |
| | | | FileExists | 檢查指定路徑檔案是否存在 | 在載入 .dat、.ini 或影像前進行安全檢查 | IsFilePresent(Path) | |
| | | | DirectoryExists | 檢查指定路徑資料夾是否存在 | 在建立掃描目錄或上傳前確認結構完整性 | IsDirPresent(Path) | |
| | | | FindFirst / FindNext | 搜尋符合特定格式的檔案 | 取得實體檔案大小 (FileRec.Size) 或遞迴搜尋檔案 | ListFiles(Path, Pattern) / GetFileInfo(Path) | |
| | | |
| | | ### **3. 檔案生命週期與刪除 (File Lifecycle & Deletion)** |
| | | |
| | | | **方法名稱** | **作用** | **應用場景** | **建議抽象 IO 介面** | |
| | | | --- | --- | --- | --- | |
| | | | ReSortFileName | 影像重新編序命名 | 刪除或插入影像後,修正 001_xxx 等序號連續性 | RenameFile(Old, New) | |
| | | | DeleteImageFile | 刪除實體影像檔 | 單張影像刪除並觸發 .dat 內容同步更新 | DeleteFile(Path) | |
| | | | DeleteDocNoFile | 刪除特定文件代號下所有影像 | 整批文件(DocNo)的物理刪除與序號重整 | DeleteFilesByPattern(Pattern) | |
| | | | DeleteShowFile | 刪除 UI 顯示中的影像清單 | 根據介面選取狀態進行批次物理刪除 | DeleteFileList(List<Path>) | |
| | | | RenameFile / MoveFile | 檔案搬移或更名 | 用於「分案」、「移動頁數」或「變更歸類」 | MoveFile(Src, Dest) | |
| | | | CopyFile | 複製實體檔案 | 影像搬移至上傳區或引用舊案件影像 | CopyFile(Src, Dest) | |
| | | |
| | | ### **4. 資料紀錄與序列化 (Metadata IO)** |
| | | |
| | | | **方法名稱** | **作用** | **應用場景** | **建議抽象 IO 介面** | |
| | | | --- | --- | --- | --- | |
| | | | DeleteCustomDocDir | 移除自訂文件定義 | 從 CustomDocNo.ini 中移除特定自訂文件的節點 | Storage.IniDeleteSection(Section) | |
| | | |
| | | ### **5. 網路傳輸 IO 方法 (Network Retrieval - dnFile_Get)** |
| | | |
| | | 專案中使用 `dnFile_Get` 從伺服器端同步必要的環境檔案或規則定義。 |
| | | |
| | | | **抓取對象 (目標路徑)** | **遠端請求 Action** | **用途說明** | | |
| | | | --- | --- | --- | --- | |
| | | | **CheckXmlPath + filename** | `GetCheckXml` | **下載 OMR 檢核定義檔**。同步伺服器端最新的 XML 座標規則,用於自動判斷影像區域。 | https | |
| | | | **SamplePath + filename** | `GetSampleImg` | **下載範本影像**。用於在 UI 介面上提供給操作員對照的標準範例圖檔。 | https | |
| | | | **SitePath + filename** | `GetSiteImg` | **下載定位定義圖**。下載與登打、校對位置相關的輔助影像。 | https | |
| | | | **LngPath + 'Language.ini'** | `GetLanguage` | **同步多國語言檔**。確保用戶端介面文字(繁體、英文、越南語等)與伺服器同步。 | https | |
| | | |
| | | ## **參、 資料紀錄與序列化 (Metadata IO)** |
| | | |
| | | 專案中使用了大量的 .dat 與 .ini 檔案來記錄目錄內的結構資訊與使用者偏好。 |
| | | |
| | | ### **1. .dat 檔案 (結構紀錄)** |
| | | |
| | | | **檔案名稱** | **組合路徑邏輯 (Path Logic)** | **內容與用途** | |
| | | | --- | --- | --- | |
| | | | **Context.dat** | ImageSavePath + CaseID + '\' + DocDir + '\Context.dat' | **影像清單**。紀錄該資料夾內所有影像檔名的正確順序。 | |
| | | | **DocDir.dat** | ImageSavePath + CaseID + '\Upload\DocDir.dat' | **目錄對應表**。紀錄 Upload 下每個檔名所屬的原始 DocDir | |
| | | | **CaseDocNo.dat** | ImageSavePath + CaseID + '\CaseDocNo.dat' | **文件目錄索引**。紀錄案件內已建立的實體資料夾名稱。 | |
| | | | **CaseDocNo_Copies.dat** | ImageSavePath + CaseID + '\CaseDocNo_Copies.dat' | **份數紀錄**。對應 CaseDocNo.dat 中的資料夾分別有多少份。 | |
| | | | **CaseIndex.dat** | ImageSavePath + CaseID + '\CaseIndex.dat' | **案件屬性**。如是否為「授信卷」(Case_loandoc)。 | |
| | | | **EditedDocDir.dat** | ImageSavePath + CaseID + '\EditedDocDir.dat' | **異動清單**。紀錄當次操作中被修改過的文件目錄。 | |
| | | | **Scan_Memo.dat** | DisplayPath + 'Scan_Memo.dat' | **使用者註記**。紀錄操作員手動輸入的備註內容。 | |
| | | | **CaseList.dat** | ImageSavePath + 'CaseList.dat' | **案件總表**。紀錄目前本機快取中存在的所有 CaseID。 | |
| | | |
| | | ### **2. .ini 檔案 (設定與偏好)** |
| | | |
| | | | **檔案名稱** | **路徑邏輯** | **回寫 (Write-back)** | **內容與用途** | 寫入關聯 | |
| | | | --- | --- | --- | --- | --- | |
| | | | **Scan.ini** | ScaniniPath + 'Scan.ini' | **是 (頻繁)** | **掃描器硬體設定**。儲存最後一次選取的 DPI、雙面、色彩模式、亮度、對比等參數。 | | |
| | | | **CustomDocNo.ini** | ScaniniPath + 'CustomDocNo.ini' | **是 (中等)** | **自訂文件代號**。儲存操作員定義的文件分類名稱與內部 ID 映射。 | DeleteCustomDocDir | |
| | | | **CB_IMGPSScan.ini** | LngPath + 'CB_IMGPSScan.ini' | **是 (低頻)** | **環境配置**。儲存伺服器 IP、Port、自動更新版本資訊等全域參數。 | | |
| | | | **Language.ini** | LngPath + 'Language.ini' | **否 (唯讀同步)** | **多國語言包**。僅透過 `dnFile_Get` 從伺服器抓取更新,OCX 不自行修改。 | | |
| | | |
| | | ## **肆、 串流與讀寫操作 (Stream & File I/O)** |
| | | |
| | | 源碼中頻繁使用 .LoadFromFile 與 .SaveToFile 來實現資料的持久化。 |
| | | |
| | | ### **1. 使用 TStringList 讀寫 .dat 結構** |
| | | |
| | | | **操作對象** | **讀取場景 (Load)** | **寫入場景 (Save)** | **建議抽象 IO 介面** | |
| | | | --- | --- | --- | --- | |
| | | | **結構清單 (.dat)** | LoadImgFile 載入以重建樹狀結構 | PageEnd 掃描完成或結構增刪後即時存檔 | ReadTextFile(Path) / WriteTextFile(Path, Content) | |
| | | | **備註文件 (Memo)** | 進入備註編輯器時載入舊有內容 | WNoteBtnClick 完成編輯後儲存內容 | ReadTextFile(Path) / WriteTextFile(Path, Content) | |
| | | | **上傳包定義** | N/A | CreateFormID_FormName 產生上傳包所需的定義檔 | WriteTextFile(Path, Content) | |
| | | | | | | | |
| | | |
| | | ### **2. 使用 TImageScrollBox / TDibGraphic 讀寫影像** |
| | | |
| | | | **操作對象** | **讀取場景 (Load)** | **寫入場景 (Save)** | **建議抽象 IO 介面** | |
| | | | --- | --- | --- | --- | |
| | | | **掃描影像原始檔** | view_image_... 載入顯示於預覽窗 | OnAcquire 將記憶體影像 (Dib) 持久化至磁碟 | ReadBinaryFile(Path) / WriteBinaryFile(Path, Blob) | |
| | | | **影像處理覆蓋** | ImageReSize_FormID 讀取進行定位縮放 | 處理完成後覆蓋原始影像檔 | ReadBinaryFile(Path) / WriteBinaryFile(Path, Blob) | |
| | | | **旋轉/後製處理** | 點擊旋轉按鈕後從磁碟重新讀取 | 旋轉完成後保存更新內容回實體檔案 | ReadBinaryFile(Path) / WriteBinaryFile(Path, Blob) | |
| | | |
| | | ## **伍、 檔案路徑構造邏輯示例** |
| | | |
| | | 程式中典型的路徑構造方式如下(以 PageEnd 為例): |
| | | |
| | | // 1. 基礎路徑 |
| | | |
| | | ScanPath := ImageSavePath + ScanCaseno + '\'; |
| | | |
| | | // 2. 進入特定文件目錄 (DocDir) |
| | | |
| | | ScanPath := ScanPath + ScanDocdir + '\'; |
| | | |
| | | // 3. 確保實體目錄存在 |
| | | |
| | | Str2Dir(ScanPath); |
| | | |
| | | // 4. 構造最終影像路徑 |
| | | |
| | | PEFileName := ScanPath + Add_Zoo(PageCount, 3) + '_' + FormID + ext; |
| | | |
| | | ## **陸、 IO 方法具體實作邏輯細部說明 (Detailed Implementation Logic)** |
| | | |
| | | 本節針對 CB_IMGPSScanImp.pas 內部如何透過 Windows API 實作這些 IO 操作進行細部解說。 |
| | | |
| | | ### **1. Str2Dir(Path: string) 的運作機制** |
| | | |
| | | 這個方法的主要作用是 **「路徑標準化與實體資料夾建立」**。 |
| | | |
| | | - **字串處理**:它會先檢查傳入的 Path 字串最後一個字元是否為 \。如果不是,會自動補上。 |
| | | - **建立資料夾**:**是的,它會在內部建立資料夾**。它會呼叫 Delphi 的 ForceDirectories(Path) 函式。 |
| | | - **ForceDirectories 的特性**:與一般的 CreateDir 不同,ForceDirectories 會「遞迴」建立路徑中所有不存在的父目錄(例如若路徑為 C:\A\B\C,即使 A 和 B 都不存在,它也會一併建立)。 |
| | | |
| | | ### **2. _DelTree(DirName: string) 的遞迴邏輯** |
| | | |
| | | 由於 Delphi 早期版本沒有提供單一函式來刪除包含檔案的資料夾,此方法透過 Windows 的搜尋機制實現: |
| | | |
| | | - **遍歷搜尋**:使用 FindFirst 和 FindNext 找出資料夾內的所有物件。 |
| | | - **檔案處理**:若搜尋到的是一般檔案,直接呼叫 DeleteFile。 |
| | | - **遞迴處理**:若搜尋到的是子資料夾,則「自己呼叫自己」 (_DelTree) 先清空該子資料夾。 |
| | | - **物理移除**:當資料夾內的所有內容都清空後,呼叫 RemoveDir 將空的資料夾從磁碟移除。 |
| | | |
| | | ### **3. FindFirst 與 FindNext 的搜尋與疊代邏輯** |
| | | |
| | | 這兩個方法是 Delphi 處理「集合式 IO 操作」(如掃描目錄、批次刪除)的基礎,通常搭配 TSearchRec 結構使用。 |
| | | |
| | | - **FindFirst(Path, Attr, SearchRec)**: |
| | | - **動作**:啟動搜尋任務,將找到的第一個檔案資訊填入 SearchRec。 |
| | | - **應用例**:在 PM104Click (匯入影像) 時,程式會先執行 FindFirst(FName, faAnyfile, FileRec) 來確認使用者選取的實體檔案是否存在,並藉由 FileRec.Size 取得位元組大小,用以判斷是否超過系統設定的匯入限制(例如 5MB)。 |
| | | - **FindNext(SearchRec)**: |
| | | - **動作**:繼續尋找下一個符合條件的檔案。 |
| | | - **疊代模式**:通常包裹在 repeat ... until FindNext(SR) <> 0 迴圈中。 |
| | | - **應用例**:在 _DelTree 實作中,程式利用此疊代邏輯將資料夾內「所有的」子目錄與檔案清空,直到 FindNext 回傳非零值(代表搜尋結束)。 |
| | | - **FindClose(SearchRec)**: |
| | | - **重要性**:釋放搜尋控制代碼(Handle)。若未呼叫,頻繁搜尋會導致系統資源洩漏。 |
| | | |
| | | ### **4. GetNoNameCase(Path: string) 的搜尋邏輯** |
| | | |
| | | 這是一個典型的 **「可用名稱查表」** 實作: |
| | | |
| | | - **迴圈範圍**:從 1 迭代到 9999。 |
| | | - **格式組合**:使用 Add_Zoo 函式將數字轉為四位數(如 0001),並加上前綴字「未配號」。 |
| | | - **磁碟檢查**:呼叫 DirectoryExists 檢查 Path + '未配號000x'。 |
| | | - **結果回傳**:第一個回傳 False(代表該資料夾尚不存在)的名稱即為結果,程式會立即跳出迴圈並選定此名稱。 |
| | | |
| | | ### **5. ReSortFileName(Path: string) 的連續性維護** |
| | | |
| | | 為了保證 UI 上顯示的頁碼順序與磁碟檔名一致,此方法採用「先暫存、後改名」的策略: |
| | | |
| | | - **防止衝突**:先將目錄內所有符合格式的影像更名為帶有臨時前綴(如 @)的檔名。 |
| | | - **按序更名**:讀取 Context.dat 內的清單,依照目前的順序,將檔案逐一更名為 001_..., 002_... 等格式。 |
| | | - **同步索引**:最後將更名後的結果重新寫入 Context.dat。 |
| | | |
| | | ### **6. GetLocalAppDir(Handle: HWND) 的權限繞過** |
| | | |
| | | 這是一個系統級別的 IO 位置定位方法: |
| | | |
| | | - **API 呼叫**:呼叫 SHGetSpecialFolderPath (Windows Shell API)。 |
| | | - **定位 CSIDL_LOCAL_APPDATA**:指向 C:\Users\使用者名稱\AppData\Local\。 |
| | | - **必要性**:這能解決 OCX 程式被安裝在 Program Files 下時,因為 UAC 權限無法在安裝目錄寫入 Log 或語言檔的問題。 |
| | | |
| | | ### **🎯 移植至 Web 平台的建議** |
| | | |
| | | 當您將這些邏輯移植到 Web 時: |
| | | |
| | | - Str2Dir 應對應到 **IndexedDB** 的 Object Store 建立或 **File System Access API** 的 getDirectoryHandle(搭配 create: true)。 |
| | | - FindFirst / FindNext 在 Web 端應對應到 Directory Handle 的 entries() 異步疊代器。 |
| | | - .dat 序列化文件在 Web 端建議直接轉為 **JSON** 格式存儲於瀏覽器的 localStorage 或 IndexedDB 中。 |
| | | |
| | | ## **柒、 設定檔回寫邏輯 (Write-back Logic)** |
| | | |
| | | 本節說明專案中哪些 .ini 檔案會由 OCX 主動更新資料及其觸發時機。 |
| | | |
| | | | **檔案名稱** | **回寫內容 (Content)** | **觸發時機 (Trigger)** | |
| | | | --- | --- | --- | |
| | | | **Scan.ini** | DPI、Duplex、亮度、對比、影像模式 | 掃描前設定變更或啟動掃描時(SaveScanPara)。 | |
| | | | **CustomDocNo.ini** | 自訂文件名稱與 ID 映射 | 操作員新增自訂分類或呼叫 `DeleteCustomDocDir` 時。 | |
| | | | **CB_IMGPSScan.ini** | 版本號、伺服器 IP/Port | 自動更新完成後更新版號,或手動修改伺服器設定時。 | |
| | | |
| | | ## **捌、 注意事項** |
| | | |
| | | - **硬編碼路徑**:ActiveFormCreate 中存在寫死的 C:\Temp\IMGPSScan,適合作為最終保險。 |
| | | - **權限問題**:作為 ActiveX,這些 IO 操作強烈依賴瀏覽器的「安全站台設定」或 IE 的「保護模式」。使用 GetLocalAppDir 是現代化重構中解決 UAC 權限問題的重要手段。 |
| | | |
| | | --- |
| | | |
| | | # **TCB_IMGPSScanX OCX 通訊介面與溝通機制分析** |
| | | |
| | | 本表條列了 Delphi 宿主環境(ActiveX[ocx]) 與 TCB_IMGPSScanX 元件之間主要的溝通管道、對應方法及其在系統架構中的作用。 |
| | | |
| | | ## **壹、 指令與資料交換介面表** |
| | | |
| | | | **通訊方法 (Method)** | **通訊方向** | **溝通內容 (Data Payload)** | **具體作用與目的** | |
| | | | --- | --- | --- | --- | |
| | | | **SetSQLData** | **宿主 -> OCX** | 以 ` | 或,` 分隔的長字串(如:IP, CaseID, DPI) | |
| | | | **GetSQLData** | **宿主 <-> OCX** | 指定 Key (如 'PARA_NO') 取得對應 Value | **資料查詢通道**。宿主可詢問 OCX 目前快取清單中的特定數值,用於介面同步或邏輯判斷。 | |
| | | | **GetSetInf1 ~ 7** | **宿主 <- OCX** | 單一字串值 (如 CaseID, 總張數, 成功標記 'Y') | **狀態出口 (Outlets)**。提供標準化的 7 個出口,讓 JavaScript 或宿主 App 輪詢(Polling)目前的執行結果或掃描統計。 | |
| | | | **StatrTwainScan** | **宿主 -> OCX** | 無參數 (觸發動作) | **硬體啟動指令**。宿主程式按下「開始掃描」按鈕後呼叫此方法,通知 OCX 喚起 TWAIN 驅動程式。 | |
| | | | **GetCurrentVersionNo** | **宿主 <- OCX** | 版本字串 (如 '1.0.3.5') | **版本識別**。宿主程式載入 OCX 後第一步通常會呼叫此處,用以比對伺服器版本並決定是否強制更新。 | |
| | | | **upFile** | **宿主 -> OCX** | 無或部分參數 (觸發網路 IO) | **輸出指令**。通知 OCX 將本機暫存區的檔案(如 ZIP 包)依照先前設定好的路徑與權限上傳至伺服器。 | |
| | | | **dnFile_Get** | **宿主 -> OCX** | 遠端 Action 關鍵字與本地檔名 | **同步指令**。通知 OCX 向伺服器抓取特定資源(如 XML 規則或語言檔)並存放到指定 IO 路徑。 | |
| | | | **ActiveFormCreate** | **系統 -> OCX** | 視窗建立事件 | **環境初始化**。當宿主程式實體化 OCX 時,自動觸發此處執行 GetDefScanIni,確保掃描環境就緒。 | |
| | | | **Property CaseID** | **宿主 <-> OCX** | WideString | **屬性直連**。透過 COM 屬性機制直接設定或讀取案件編號。 | |
| | | | **Property ScreenSnap** | **宿主 -> OCX** | Boolean | **功能切換**。控制 OCX 是否要在特定時機執行螢幕截圖稽核。 | |
| | | |
| | | ## **貳、 溝通機制細部說明** |
| | | |
| | | ### **1. 參數化配置 (Config-by-String)** |
| | | |
| | | 本專案大量使用 SetSQLData 這種「一條字串走天下」的設計,這在早期 ActiveX 開發中非常流行,優點是規避了 COM 介面頻繁修改 _TLB.pas 的麻煩。您在轉換 TypeScript UI 時,可以將其封裝為一個 JSON 物件,最後再組合成字串傳給這個方法。 |
| | | |
| | | ### **2. 狀態回饋 (The Inf1~7 Pattern)** |
| | | |
| | | 由於 ActiveX 事件 (Events) 的回傳有時在網頁端不夠穩定,開發者選擇了 GetSetInf 系列。宿主程式通常會在執行完某個長耗時動作(如 upFile)後,連續呼叫 GetSetInf3 (成功標記) 與 GetSetInf6 (錯誤訊息) 來決定下一步 UI 流程。 |
| | | |
| | | ### **3. 雙向同步 (Bidirectional Sync)** |
| | | |
| | | - **同步設定**:宿主透過 GetDefScanIni 觸發,OCX 內部完成硬體設定。 |
| | | - **同步資料**:宿主透過 dnFile_Get 觸發,OCX 完成本機與伺服器的檔案 IO 同步。 |
| | | |
| | | # **參、描服務抽象層實作** |
| | | |
| | | 採用 **「抽象介面層 (Abstraction Layer)」** 配合 **「適配器模式 (Adapter Pattern)」**,處理遺留系統(Legacy System)過渡到現代化架構 |
| | | |
| | | ```tsx |
| | | /** |
| | | * 掃描服務資料模型定義 |
| | | * 將原本 OCX 雜亂的字串溝通 (SetSQLData) 結構化 |
| | | */ |
| | | export interface ScanConfig { |
| | | serverIp: string; |
| | | caseId: string; |
| | | dpi: number; |
| | | isColor: boolean; |
| | | isDuplex: boolean; |
| | | // ... 其他來自 GetDefScanIni 的參數 |
| | | } |
| | | |
| | | export interface ScanStatus { |
| | | caseId: string; |
| | | totalPageCount: number; |
| | | isUploadSuccess: boolean; |
| | | lastBarcode: string; |
| | | errorMessage: string; |
| | | } |
| | | |
| | | /** |
| | | * 掃描服務抽象介面 (IScanService) |
| | | * UI 層僅與此介面溝通,不直接操作底層實作 |
| | | */ |
| | | export interface IScanService { |
| | | initialize(config: ScanConfig): Promise<void>; |
| | | startScan(): Promise<boolean>; |
| | | uploadFiles(): Promise<boolean>; |
| | | getStatus(): Promise<ScanStatus>; |
| | | getVersion(): Promise<string>; |
| | | downloadResource(action: string, fileName: string): Promise<void>; |
| | | } |
| | | |
| | | /** |
| | | * 實作 A: ActiveX 適配器 (Legacy Adapter) |
| | | * 用於目前環境:UI (TS) -> Delphi Browser -> ActiveX (OCX) |
| | | */ |
| | | export class ActiveXScanAdapter implements IScanService { |
| | | private ocx: any; |
| | | |
| | | constructor() { |
| | | // 透過 window.external 或 Delphi Bridge 取得 OCX 實例 |
| | | this.ocx = (window as any).OCX_INSTANCE; |
| | | } |
| | | |
| | | async initialize(config: ScanConfig): Promise<void> { |
| | | // 將 JSON 轉回 OCX 慣用的管道分隔字串 (SetSQLData 邏輯) |
| | | const sqlDataStr = `${config.serverIp}|${config.caseId}|${config.dpi}|${config.isColor ? 'C' : 'G'}`; |
| | | this.ocx.SetSQLData(sqlDataStr); |
| | | } |
| | | |
| | | async startScan(): Promise<boolean> { |
| | | this.ocx.StatrTwainScan(); |
| | | return true; // 實際狀態需透過 GetSetInf 輪詢 |
| | | } |
| | | |
| | | async getStatus(): Promise<ScanStatus> { |
| | | return { |
| | | caseId: this.ocx.GetSetInf1(), |
| | | totalPageCount: parseInt(this.ocx.GetSetInf2()), |
| | | isUploadSuccess: this.ocx.GetSetInf3() === 'Y', |
| | | lastBarcode: this.ocx.GetSetInf5(), |
| | | errorMessage: this.ocx.GetSetInf6() |
| | | }; |
| | | } |
| | | |
| | | async uploadFiles(): Promise<boolean> { |
| | | this.ocx.upFile(); |
| | | return true; |
| | | } |
| | | |
| | | async getVersion(): Promise<string> { |
| | | return this.ocx.GetCurrentVersionNo(); |
| | | } |
| | | |
| | | async downloadResource(action: string, fileName: string): Promise<void> { |
| | | this.ocx.dnFile_Get(action, fileName); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 實作 B: Backend Server 適配器 (Future Adapter) |
| | | * 用於未來環境:UI (TS) -> REST API / WebSocket -> Rust/Go Backend |
| | | */ |
| | | export class BackendServerAdapter implements IScanService { |
| | | private apiUrl = "http://localhost:8080/api/scanner"; |
| | | |
| | | async initialize(config: ScanConfig): Promise<void> { |
| | | await fetch(`${this.apiUrl}/config`, { |
| | | method: 'POST', |
| | | body: JSON.stringify(config) |
| | | }); |
| | | } |
| | | |
| | | async startScan(): Promise<boolean> { |
| | | const res = await fetch(`${this.apiUrl}/scan`, { method: 'POST' }); |
| | | return res.ok; |
| | | } |
| | | |
| | | async getStatus(): Promise<ScanStatus> { |
| | | const res = await fetch(`${this.apiUrl}/status`); |
| | | return await res.json(); |
| | | } |
| | | |
| | | async uploadFiles(): Promise<boolean> { |
| | | const res = await fetch(`${this.apiUrl}/upload`, { method: 'POST' }); |
| | | return res.ok; |
| | | } |
| | | |
| | | async getVersion(): Promise<string> { |
| | | const res = await fetch(`${this.apiUrl}/version`); |
| | | const data = await res.json(); |
| | | return data.version; |
| | | } |
| | | |
| | | async downloadResource(action: string, fileName: string): Promise<void> { |
| | | // 未來可能直接由後端處理,前端僅發送同步指令 |
| | | await fetch(`${this.apiUrl}/sync?action=${action}&file=${fileName}`); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 服務工廠 (Scanner Factory) |
| | | * 根據環境自動切換實作 |
| | | */ |
| | | export class ScannerFactory { |
| | | static getService(): IScanService { |
| | | if ((window as any).OCX_INSTANCE) { |
| | | console.log("偵測到 ActiveX 環境,啟用 ActiveX 適配器"); |
| | | return new ActiveXScanAdapter(); |
| | | } else { |
| | | console.log("現代瀏覽器環境,啟用後端 API 適配器"); |
| | | return new BackendServerAdapter(); |
| | | } |
| | | } |
| | | } |
| | | ``` |
| | | |
| | | ### **1. 介面標準化(JSON 化)** |
| | | |
| | | 原先 OCX 使用 SetSQLData 的「長字串」溝通是非常脆弱的(一項參數順序錯了就全毀)。在 TypeScript 抽象層中,請務必將其轉換為 **強型別的 JSON 物件**(如 ScanConfig 介面)。這樣你的 UI 邏輯在處理設定時會非常直覺。 |
| | | |
| | | ### **2. 非同步處理(Promise/Async)** |
| | | |
| | | ActiveX 的方法通常是同步阻塞的,而後端 API 是非同步的。 |
| | | |
| | | - **建議**:在抽象介面(IScanService)中,所有方法都回傳 Promise。 |
| | | - **原因**:這樣當你未來切換到後端伺服器(fetch API)時,UI 的調用邏輯(await service.startScan())不需要做任何修改。 |
| | | |
| | | ### **3. 實作「心跳」或「狀態輪詢」** |
| | | |
| | | 原本 GetSetInf1~7 主要是靠宿主程式主動去「問」OCX。 |
| | | |
| | | - **抽象層建議**:在 IScanService 中實作一個 subscribeStatus(callback) 的觀察者模式。 |
| | | - **ActiveX 模式下**:在適配器內部啟動一個 setInterval 去輪詢 GetSetInf。 |
| | | - **未來後端模式下**:適配器改用 **WebSocket** 接收後端主動推播的掃描事件。 |
| | | - **UI 層感受**:UI 只需要處理 onStatusUpdate 事件,根本不用管底層是用輪詢還是 WebSocket。 |
| | | |
| | | ### **4. 資源路徑的轉義(The Bridge Proxy)** |
| | | |
| | | 這是我之前提到的關鍵:由於 iframe 裡的 TypeScript 無法存取 C:\Temp,但 ActiveX 依舊會把檔案掃到那裡。 |
| | | |
| | | - **策略**:你的 Delphi Browser 需要實作一個簡單的本地 Proxy。 |
| | | - **實作**:當 TypeScript 需要顯示圖檔時,它請求 http://local.bridge/view/001.tif,Delphi Browser 攔截此請求並從實體路徑讀取檔案回傳。 |
| | | - **好處**:未來改為 Backend Server 時,Server 也可以提供同樣的 URL 格式,UI 完全無縫接軌。 |
| | | |
| | | ## **肆、 Delphi 與 JavaScript 通訊橋樑 (Bridge) 實作說明** |
| | | |
| | | 當您在 Delphi 宿主程式中使用內嵌瀏覽器時,JavaScript 取得 `OCX_INSTANCE` 的方式取決於底層的瀏覽器引擎: |
| | | |
| | | ### **1. 舊版 IE 核心 (TWebBrowser / window.external)** |
| | | |
| | | 這是最傳統的方式,Delphi 會透過實作 `IDocHostUIHandler` 介面,將一個自訂的 `IDispatch` 物件掛載到 `window.external` 上。 |
| | | |
| | | - **Delphi 端**:將 `TCB_IMGPSScanX` 實例或其包裝類別指派給瀏覽器的 `External` 屬性。 |
| | | - **JS 端**:直接使用 `window.external.StatrTwainScan()` 呼叫。 |
| | | |
| | | ### **2. 新版 Edge 核心 (TEdgeBrowser / WebView2 Host Objects)** |
| | | |
| | | 這是現代化的做法。Delphi 透過 WebView2 的 API 將物件直接「注入」到 JS 的命名空間中。 |
| | | |
| | | - **Delphi 端執行**: |
| | | |
| | | ```pascal |
| | | // 將 OCX 物件以 'OCX_INSTANCE' 名稱注入 |
| | | EdgeBrowser.AddHostObjectToScript('OCX_INSTANCE', MyOcxWrapperObject); |
| | | ``` |
| | | |
| | | - **JS 端取得**: WebView2 會將物件放置在 `window.chrome.webview.hostObjects` 底下。 |
| | | |
| | | ```jsx |
| | | const ocx = window.chrome.webview.hostObjects.OCX_INSTANCE; |
| | | ``` |
| | | |
| | | |
| | | ### **3. 為什麼需要 Wrapper (包裝類別)?** |
| | | |
| | | 通常我們**不建議**直接將 `TCB_IMGPSScanX` 注入,原因如下: |
| | | |
| | | 1. **安全性**:直接暴露 COM 元件可能會有安全漏洞。 |
| | | 2. **相容性**:OCX 的某些方法回傳型別(如 Delphi 的自訂 Enum)JavaScript 無法識別。 |
| | | 3. **執行緒優化**:Delphi 可以在 Wrapper 中使用 `TThread.Queue` 確保掃描指令在正確的執行緒執行,避免瀏覽器 UI 凍結。 |
| | | |
| | | ### **4. 溝通路徑總結** |
| | | |
| | | 1. **Delphi 宿主程式** 啟動並實體化 **OCX 控制項**。 |
| | | 2. **Delphi 宿主程式** 建立 **Browser 物件** 並載入 **TypeScript UI (HTML)**。 |
| | | 3. **Delphi 宿主程式** 呼叫瀏覽器 API(如 `AddHostObjectToScript`),將 OCX 的操作介面「注入」到該網頁的 `window` 物件中。 |
| | | 4. **TypeScript** 透過抽象層 (Adapter) 取得該物件,實現「網頁點按鈕,掃描機開動」的效果。 |
| | | |
| | | --- |
| | | |
| | | |
| | | # Transport 相關套件分析 |
| | | |
| | | --- |
| | | |
| | | # Image Processor 相關套件分析 |
| 比對新檔案 |
| | |
| | | import * as fs from 'fs'; |
| | | import * as path from 'path'; |
| | | |
| | | export function applyDataMixins(cls: any) { |
| | | |
| | | cls.prototype.Get_HelpFile = function(): string { |
| | | let Result = String(this.HelpFile); |
| | | return Result; |
| | | }; |
| | | |
| | | cls.prototype.Set_HelpFile = function(Value: string): void { |
| | | this.HelpFile = String(Value); |
| | | }; |
| | | |
| | | cls.prototype.DocNoIsExistImg = function(DocNopath: string): boolean { |
| | | let i: number; |
| | | let ST: any; |
| | | let Result = false; |
| | | ST = new this.TStringList(); |
| | | if (fs.existsSync(DocNopath + '\\Context.dat')) { /////20190319 Hong 當有空的Docno目錄時會掛掉,增加這行 |
| | | ST.LoadFromFile(DocNopath + '\\Context.dat'); |
| | | } |
| | | for (i = 0; i < ST.Count; i++) { |
| | | if (this.ISExistImg(DocNopath + ST.Strings[i])) { |
| | | Result = false; |
| | | return Result; // Exit; |
| | | // Break; |
| | | } |
| | | } |
| | | Result = true; |
| | | return Result; |
| | | }; |
| | | |
| | | cls.prototype.HTTPSClientCertificateValidate = function(Sender: any, X509Certificate: any, Validate: { val: boolean }): void { |
| | | Validate.val = true; |
| | | }; |
| | | |
| | | cls.prototype.HTTPSClientRedirection = function(Sender: any, OldURL: string, NewURL: { val: string }, AllowRedirection: { val: boolean }): void { |
| | | AllowRedirection.val = true; |
| | | }; |
| | | |
| | | cls.prototype.GetServerDate = function(): boolean { |
| | | let Result = false; // http://192.168.0.101:8080/fbnp/servlet/CWC01?act=getservertime |
| | | if (!this.ProcessServlet_Get(this.HTTPSClient, this.FUrl + 'service/imgpsc/IMGPSC01/servertimeforocx', '', this.FReWrite, this.Memo1, false)) { |
| | | this.HttpErrStr = this._Msg('錯誤代碼:') + this.HttpError.HttpErrorCode.toString() + ',' + this.HttpError.HttpReason; |
| | | Result = false; |
| | | return Result; // Exit; |
| | | } |
| | | if (this.Memo1.Lines.Strings[0] === '1') { |
| | | this.HttpErrStr = this._Msg('錯誤原因:') + this.Memo1.Lines.Strings[1]; |
| | | Result = false; |
| | | return Result; // Exit; |
| | | } |
| | | else if (this.Memo1.Lines.Strings[0] === '0') { |
| | | this.ServerDate = this.Memo1.Lines.Strings[1]; |
| | | this.ServerTime = this.ServerDate.substring(8, 14); // Copy(ServerDate,9,6) |
| | | this.ServerDate = this.ServerDate.substring(0, 8); // Copy(ServerDate,1,8) |
| | | this.Balance = this.GetBalance(this.ServerTime); //Server 跟Local的時間差 |
| | | Result = true; |
| | | } |
| | | else if (this.Memo1.Lines.Text.indexOf('<script type="text/javascript" src="scripts/CW00/login.js"></script>') >= 0) { |
| | | this.HttpErrStr = this._Msg('錯誤原因:') + this._Msg('閒置過久或被登出,請重新登入'); |
| | | Result = false; |
| | | return Result; // Exit; |
| | | } |
| | | return Result; |
| | | }; |
| | | |
| | | cls.prototype.GetSetInf1 = function(): boolean { //取系統設定資訊Mode1 DOC_INF |
| | | let ColumeStr: string; |
| | | let S: any; |
| | | let EnCodeDateTime: string; |
| | | let Result = false; // http://192.168.0.101:8080/fbnp/servlet/CWC01?act=getservertime |
| | | EnCodeDateTime = this.En_DecryptionStr_Base64('E', this.ServerDate + this.GetBalance2Time(this.Balance), this.Mpskey); |
| | | |
| | | ColumeStr = 'WORK_NO,DOC_NO,DOC_U_DESC,DOC_TYPE,DOC_VERSION,FORM_PAGES,START_DATE,STOP_DATE,IS_DOC_DIV,IS_IN_WH'; |
| | | //If not ProcessServlet(HTTPSClient,FURL+'servlet/CWC02 ','checktime='+EnCodeDateTime+'&mode=1&col='+Doc_Inf_Colume+'&workno='+FWork_No,FReWrite.Text,Memo1) Then |
| | | |
| | | if (!this.ProcessServlet_Get(this.HTTPSClient, this.FUrl + 'service/imgpsc/IMGPSC01/tables', 'checktime=' + EnCodeDateTime + '&mode=1&col=' + this.En_DecryptionStr_Base64('E', ColumeStr, this.Mpskey) + '&work_no=' + this.FWork_no, this.FReWrite, this.Memo1, false)) { |
| | | this.HttpErrStr = this._Msg('錯誤代碼:') + this.HttpError.HttpErrorCode.toString() + ',' + this.HttpError.HttpReason; |
| | | Result = false; |
| | | return Result; // Exit; |
| | | } |
| | | |
| | | if (this.Memo1.Lines.Strings[0] === '1') { |
| | | this.HttpErrStr = this._Msg('錯誤原因:') + this.Memo1.Lines.Strings[1]; |
| | | Result = false; |
| | | return Result; // Exit; |
| | | } |
| | | else if (this.Memo1.Lines.Strings[0] === '0') { |
| | | S = new this.TStringList(); |
| | | S.Text = this.Memo1.Lines.Text; |
| | | this.SetSQLData(ColumeStr, S, this.Doc_Inf_List); |
| | | S.Free(); |
| | | Result = true; |
| | | } |
| | | else if (this.Memo1.Lines.Text.indexOf('<script type="text/javascript" src="scripts/CW00/login.js"></script>') >= 0) { |
| | | this.HttpErrStr = this._Msg('錯誤原因:') + this._Msg('閒置過久或被登出,請重新登入'); |
| | | Result = false; |
| | | return Result; // Exit; |
| | | } |
| | | return Result; |
| | | }; |
| | | |
| | | cls.prototype.GetSetInf2 = function(): boolean { //取系統設定資訊Mode2 DM_FORM_INF |
| | | let ColumeStr: string; |
| | | let S: any; |
| | | let EnCodeDateTime: string; |
| | | let Result = false; // http://192.168.0.101:8080/fbnp/servlet/CWC01?act=getservertime |
| | | EnCodeDateTime = this.En_DecryptionStr_Base64('E', this.ServerDate + this.GetBalance2Time(this.Balance), this.Mpskey); |
| | | ColumeStr = 'WORK_NO,MAIN_FORM_ID,DOC_VERSION,DEPE_FORM_ID,MUTEX_FORM_ID'; |
| | | //If not ProcessServlet(HTTPSClient,FURL+'servlet/CWC02 ','checktime='+EnCodeDateTime+'&mode=2&col='+ColumeStr+'&workno='+FWork_No,FReWrite.Text,Memo1) Then |
| | | if (!this.ProcessServlet_Get(this.HTTPSClient, this.FUrl + 'service/imgpsc/IMGPSC01/tables', 'checktime=' + EnCodeDateTime + '&mode=2&col=' + this.En_DecryptionStr_Base64('E', ColumeStr, this.Mpskey) + '&work_no=' + this.FWork_no, this.FReWrite, this.Memo1, false)) { |
| | | this.HttpErrStr = this._Msg('錯誤代碼:') + this.HttpError.HttpErrorCode.toString() + ',' + this.HttpError.HttpReason; |
| | | Result = false; |
| | | return Result; // Exit; |
| | | } |
| | | if (this.Memo1.Lines.Strings[0] === '1') { |
| | | this.HttpErrStr = this._Msg('錯誤原因:') + this.Memo1.Lines.Strings[1]; |
| | | Result = false; |
| | | return Result; // Exit; |
| | | } |
| | | else if (this.Memo1.Lines.Strings[0] === '0') { |
| | | S = new this.TStringList(); |
| | | S.Text = this.Memo1.Lines.Text; |
| | | this.SetSQLData(ColumeStr, S, this.DM_FORM_INF_List); |
| | | S.Free(); |
| | | Result = true; |
| | | } |
| | | else if (this.Memo1.Lines.Text.indexOf('<script type="text/javascript" src="scripts/CW00/login.js"></script>') >= 0) { |
| | | this.HttpErrStr = this._Msg('錯誤原因:') + this._Msg('閒置過久或被登出,請重新登入'); |
| | | Result = false; |
| | | return Result; // Exit; |
| | | } |
| | | return Result; |
| | | }; |
| | | |
| | | cls.prototype.GetSetInf3 = function(): boolean { //取系統設定資訊mode3 FORM_INF |
| | | let ColumeStr: string; |
| | | let S: any; |
| | | let EnCodeDateTime: string; |
| | | let Result = false; // http://192.168.0.101:8080/fbnp/servlet/CWC01?act=getservertime |
| | | EnCodeDateTime = this.En_DecryptionStr_Base64('E', this.ServerDate + this.GetBalance2Time(this.Balance), this.Mpskey); |
| | | //ColumeStr := 'T1.WORK_NO,T1.FORM_ID,T1.DOC_KIND,T1.DOC_NO,T1.DOC_VERSION,T1.FORM_NAME,T1.FORM_DESC,T1.DIVISION,T1.ANCHOR,T1.MAX_PAGE,T1.FORM_HEIGHT,T1.FORM_WIDTH,T1.MERGE_IMAGE,T1.CC_FORM_ID,T1.CC_MERGE_FORMID,T2.DOC_TYPE'; {T1.CC_FORM_ID,T1.CC_MERGE_FORMID,} |
| | | ColumeStr = 'T1.WORK_NO,T1.FORM_ID,T1.DOC_NO,T1.DOC_VERSION,T1.FORM_NAME,T1.FORM_DESC,T1.DIVISION,T1.ANCHOR,T1.MAX_PAGE,T1.FORM_HEIGHT,T1.FORM_WIDTH,T1.IS_PRINT,T2.DOC_TYPE'; //{T1.CC_FORM_ID,T1.CC_MERGE_FORMID,} |
| | | //If not ProcessServlet(HTTPSClient,FURL+'servlet/CWC02 ','checktime='+EnCodeDateTime+'&mode=3&col='+ColumeStr+'&workno='+FWork_No,FReWrite.Text,Memo1) Then |
| | | if (!this.ProcessServlet_Get(this.HTTPSClient, this.FUrl + 'service/imgpsc/IMGPSC01/tables', 'checktime=' + EnCodeDateTime + '&mode=3&col=' + this.En_DecryptionStr_Base64('E', ColumeStr, this.Mpskey) + '&work_no=' + this.FWork_no, this.FReWrite, this.Memo1, false)) { |
| | | this.HttpErrStr = this._Msg('錯誤代碼:') + this.HttpError.HttpErrorCode.toString() + ',' + this.HttpError.HttpReason; |
| | | Result = false; |
| | | return Result; // Exit; |
| | | } |
| | | if (this.Memo1.Lines.Strings[0] === '1') { |
| | | this.HttpErrStr = this._Msg('錯誤原因:') + this.Memo1.Lines.Strings[1]; |
| | | Result = false; |
| | | return Result; // Exit; |
| | | } |
| | | else if (this.Memo1.Lines.Strings[0] === '0') { |
| | | S = new this.TStringList(); |
| | | S.Text = this.Memo1.Lines.Text; |
| | | this.SetSQLData(ColumeStr, S, this.FORM_INF_List); |
| | | S.Free(); |
| | | Result = true; |
| | | } |
| | | else if (this.Memo1.Lines.Text.indexOf('<script type="text/javascript" src="scripts/CW00/login.js"></script>') >= 0) { |
| | | this.HttpErrStr = this._Msg('錯誤原因:') + this._Msg('閒置過久或被登出,請重新登入'); |
| | | Result = false; |
| | | return Result; // Exit; |
| | | } |
| | | return Result; |
| | | }; |
| | | |
| | | cls.prototype.GetSetInf4 = function(): boolean { //取系統設定資訊mode4 CHECK_RULE_INF |
| | | let ColumeStr: string; |
| | | let S: any; |
| | | let EnCodeDateTime: string; |
| | | let Result = false; // http://192.168.0.101:8080/fbnp/servlet/CWC01?act=getservertime |
| | | EnCodeDateTime = this.En_DecryptionStr_Base64('E', this.ServerDate + this.GetBalance2Time(this.Balance), this.Mpskey); |
| | | ColumeStr = 'WORK_NO,CHECK_NO,CHECK_RULE_DESC,MESG_SHOW_TYPE,MESG_DISP_TYPE,CHECK_MESG,SCAN_MODE'; |
| | | //If not ProcessServlet(HTTPSClient,FURL+'servlet/CWC02 ','checktime='+EnCodeDateTime+'&mode=4&col='+ColumeStr+'&workno='+FWork_No,FReWrite.Text,Memo1) Then |
| | | if (!this.ProcessServlet_Get(this.HTTPSClient, this.FUrl + 'service/imgpsc/IMGPSC01/tables', 'checktime=' + EnCodeDateTime + '&mode=4&col=' + this.En_DecryptionStr_Base64('E', ColumeStr, this.Mpskey) + '&work_no=' + this.FWork_no, this.FReWrite, this.Memo1, false)) { |
| | | this.HttpErrStr = this._Msg('錯誤代碼:') + this.HttpError.HttpErrorCode.toString() + ',' + this.HttpError.HttpReason; |
| | | Result = false; |
| | | return Result; // Exit; |
| | | } |
| | | if (this.Memo1.Lines.Strings[0] === '1') { |
| | | this.HttpErrStr = this._Msg('錯誤原因:') + this.Memo1.Lines.Strings[1]; |
| | | Result = false; |
| | | return Result; // Exit; |
| | | } |
| | | else if (this.Memo1.Lines.Strings[0] === '0') { |
| | | S = new this.TStringList(); |
| | | S.Text = this.Memo1.Lines.Text; |
| | | this.SetSQLData(ColumeStr, S, this.CHECK_RULE_INF_List); |
| | | S.Free(); |
| | | Result = true; |
| | | } |
| | | else if (this.Memo1.Lines.Text.indexOf('<script type="text/javascript" src="scripts/CW00/login.js"></script>') >= 0) { |
| | | this.HttpErrStr = this._Msg('錯誤原因:') + this._Msg('閒置過久或被登出,請重新登入'); |
| | | Result = false; |
| | | return Result; // Exit; |
| | | } |
| | | this.CheckRule2OMRErrInfo(); |
| | | return Result; |
| | | }; |
| | | |
| | | cls.prototype.GetSetInf5 = function(): boolean { //取系統設定資訊mode5 MEMO_INF |
| | | let ColumeStr: string; |
| | | let S: any; |
| | | let EnCodeDateTime: string; |
| | | let Result = false; // http://192.168.0.101:8080/fbnp/servlet/CWC01?act=getservertime |
| | | EnCodeDateTime = this.En_DecryptionStr_Base64('E', this.ServerDate + this.GetBalance2Time(this.Balance), this.Mpskey); |
| | | ColumeStr = 'T1.WORK_NO,T1.MEMO_TYPE,T1.MEMO_CONTENT,T2.MEMO_TYPE_NAME'; |
| | | //If not ProcessServlet(HTTPSClient,FURL+'servlet/CWC02 ','checktime='+EnCodeDateTime+'&mode=5&col='+ColumeStr+'&workno='+FWork_No,FReWrite.Text,Memo1) Then |
| | | if (!this.ProcessServlet_Get(this.HTTPSClient, this.FUrl + 'service/imgpsc/IMGPSC01/tables', 'checktime=' + EnCodeDateTime + '&mode=5&col=' + this.En_DecryptionStr_Base64('E', ColumeStr, this.Mpskey) + '&work_no=' + this.FWork_no, this.FReWrite, this.Memo1, false)) { |
| | | this.HttpErrStr = this._Msg('錯誤代碼:') + this.HttpError.HttpErrorCode.toString() + ',' + this.HttpError.HttpReason; |
| | | Result = false; |
| | | return Result; // Exit; |
| | | } |
| | | if (this.Memo1.Lines.Strings[0] === '1') { |
| | | this.HttpErrStr = this._Msg('錯誤原因:') + this.Memo1.Lines.Strings[1]; |
| | | Result = false; |
| | | return Result; // Exit; |
| | | } |
| | | else if (this.Memo1.Lines.Strings[0] === '0') { |
| | | S = new this.TStringList(); |
| | | S.Text = this.Memo1.Lines.Text; |
| | | this.SetSQLData(ColumeStr, S, this.MEMO_INF_List); |
| | | S.Free(); |
| | | Result = true; |
| | | } |
| | | else if (this.Memo1.Lines.Text.indexOf('<script type="text/javascript" src="scripts/CW00/login.js"></script>') >= 0) { |
| | | this.HttpErrStr = this._Msg('錯誤原因:') + this._Msg('閒置過久或被登出,請重新登入'); |
| | | Result = false; |
| | | return Result; // Exit; |
| | | } |
| | | return Result; |
| | | }; |
| | | |
| | | cls.prototype.GetSetInf6 = function(): boolean { //取系統設定資訊mode5 WORK_INF |
| | | let ColumeStr: string; |
| | | let S: any; |
| | | let EnCodeDateTime: string; |
| | | let Result = false; // http://192.168.0.101:8080/fbnp/servlet/CWC01?act=getservertime |
| | | EnCodeDateTime = this.En_DecryptionStr_Base64('E', this.ServerDate + this.GetBalance2Time(this.Balance), this.Mpskey); |
| | | ColumeStr = 'WORK_NO,PARA_NO,PARA_CONTENT'; |
| | | //If not ProcessServlet(HTTPSClient,FURL+'servlet/CWC02 ','checktime='+EnCodeDateTime+'&mode=5&col='+ColumeStr+'&workno='+FWork_No,FReWrite.Text,Memo1) Then |
| | | if (!this.ProcessServlet_Get(this.HTTPSClient, this.FUrl + 'service/imgpsc/IMGPSC01/tables', 'checktime=' + EnCodeDateTime + '&mode=6&col=' + this.En_DecryptionStr_Base64('E', ColumeStr, this.Mpskey) + '&work_no=' + this.FWork_no, this.FReWrite, this.Memo1, false)) { |
| | | this.HttpErrStr = this._Msg('錯誤代碼:') + this.HttpError.HttpErrorCode.toString() + ',' + this.HttpError.HttpReason; |
| | | Result = false; |
| | | return Result; // Exit; |
| | | } |
| | | if (this.Memo1.Lines.Strings[0] === '1') { |
| | | this.HttpErrStr = this._Msg('錯誤原因:') + this.Memo1.Lines.Strings[1]; |
| | | Result = false; |
| | | return Result; // Exit; |
| | | } |
| | | else if (this.Memo1.Lines.Strings[0] === '0') { |
| | | S = new this.TStringList(); |
| | | S.Text = this.Memo1.Lines.Text; |
| | | this.SetSQLData(ColumeStr, S, this.WORK_INF_List); |
| | | S.Free(); |
| | | Result = true; |
| | | } |
| | | else if (this.Memo1.Lines.Text.indexOf('<script type="text/javascript" src="scripts/CW00/login.js"></script>') >= 0) { |
| | | this.HttpErrStr = this._Msg('錯誤原因:') + this._Msg('閒置過久或被登出,請重新登入'); |
| | | Result = false; |
| | | return Result; // Exit; |
| | | } |
| | | return Result; |
| | | }; |
| | | |
| | | cls.prototype.GetSetInf7 = function(): boolean { //取系統設定資訊mode5 LASTEST_FORM_INF |
| | | let ColumeStr: string; |
| | | let S: any; |
| | | let EnCodeDateTime: string; |
| | | //SELECT FORM_ID,DOC_NO,DOC_VERSION FROM FORM_INF WHERE (DOC_NO,DOC_VERSION) in (SELECT DOC_NO, MAX(DOC_VERSION) FROM FORM_INF GROUP BY DOC_NO) ORDER BY DOC_NO |
| | | |
| | | let Result = false; // http://192.168.0.101:8080/fbnp/servlet/CWC01?act=getservertime |
| | | EnCodeDateTime = this.En_DecryptionStr_Base64('E', this.ServerDate + this.GetBalance2Time(this.Balance), this.Mpskey); |
| | | ColumeStr = 'FORM_ID,DOC_NO'; |
| | | //If not ProcessServlet(HTTPSClient,FURL+'servlet/CWC02 ','checktime='+EnCodeDateTime+'&mode=5&col='+ColumeStr+'&workno='+FWork_No,FReWrite.Text,Memo1) Then |
| | | if (!this.ProcessServlet_Get(this.HTTPSClient, this.FUrl + 'service/imgpsc/IMGPSC01/tables', 'checktime=' + EnCodeDateTime + '&mode=7&col=' + this.En_DecryptionStr_Base64('E', ColumeStr, this.Mpskey) + '&work_no=' + this.FWork_no, this.FReWrite, this.Memo1, false)) { |
| | | this.HttpErrStr = this._Msg('錯誤代碼:') + this.HttpError.HttpErrorCode.toString() + ',' + this.HttpError.HttpReason; |
| | | Result = false; |
| | | return Result; // Exit; |
| | | } |
| | | if (this.Memo1.Lines.Strings[0] === '1') { |
| | | this.HttpErrStr = this._Msg('錯誤原因:') + this.Memo1.Lines.Strings[1]; |
| | | Result = false; |
| | | return Result; // Exit; |
| | | } |
| | | else if (this.Memo1.Lines.Strings[0] === '0') { |
| | | S = new this.TStringList(); |
| | | S.Text = this.Memo1.Lines.Text; |
| | | this.SetSQLData(ColumeStr, S, this.LASTEST_FORM_INF_List); |
| | | S.Free(); |
| | | Result = true; |
| | | } |
| | | else if (this.Memo1.Lines.Text.indexOf('<script type="text/javascript" src="scripts/CW00/login.js"></script>') >= 0) { |
| | | this.HttpErrStr = this._Msg('錯誤原因:') + this._Msg('閒置過久或被登出,請重新登入'); |
| | | Result = false; |
| | | return Result; // Exit; |
| | | } |
| | | return Result; |
| | | }; |
| | | |
| | | cls.prototype.SetIn_WH_DocNo = function(): void { //將要入庫的DocNo抽出來另存入list裡 |
| | | let i: number; |
| | | for (i = 1; i < this.Doc_Inf_List.Count; i++) { |
| | | if (this.GetSQLData(this.Doc_Inf_List, 'IS_IN_WH', i) === 'Y') { |
| | | this.IN_WH_DocNoList.Add(this.GetSQLData(this.Doc_Inf_List, 'DOC_NO', i)); |
| | | } |
| | | } |
| | | // {Showmessage(IN_WH_DocNoList.Text); |
| | | // StringtoFile(IN_WH_DocNoList.Text,'D:\121.txt');} |
| | | }; |
| | | |
| | | cls.prototype.DeleteDocNoFile = function(Path: string, DocNo: string): boolean { //刪除指定DocNo文件 |
| | | let i: number; |
| | | let FName: string; |
| | | let Result = false; |
| | | for (i = this.ContextList.Count - 1; i >= 0; i--) { |
| | | FName = this.ContextList.Strings[i]; |
| | | if (DocNo === this.FormCode2DocNo(this.FileName2FormCode(FName))) { |
| | | if (fs.existsSync(Path + FName)) fs.unlinkSync(Path + FName); // DeleteFile(Path+FName); |
| | | this.ContextList.Delete(i); |
| | | this.Context_DocnoList.Delete(i); |
| | | Result = true; //有刪到指定文件 |
| | | } |
| | | } |
| | | this.ContextList.SaveToFile(Path + 'Context.dat'); |
| | | this.Context_DocnoList.SaveToFile(Path + 'Context_DocNo.dat'); |
| | | this.ReSortFileName(Path); |
| | | this.ContextList.LoadFromFile(Path + 'Context.dat'); |
| | | this.Context_DocnoList.LoadFromFile(Path + 'Context_DocNo.dat'); |
| | | if (fs.existsSync(Path + 'CustomDocNo.dat')) { |
| | | this.Cust_DocNoList.LoadFromFile(Path + 'CustomDocNo.dat'); |
| | | } |
| | | return Result; |
| | | }; |
| | | |
| | | cls.prototype.DeleteShowFile = function(Path: string): void { //刪除顯示中的影像 |
| | | let i: number; |
| | | let DelFile: string; |
| | | for (i = 0; i < this.NowShowFileList.Count; i++) { |
| | | DelFile = this.NowShowFileList.Strings[i]; |
| | | if (fs.existsSync(Path + DelFile)) fs.unlinkSync(Path + DelFile); // DeleteFile(Path+DelFile); |
| | | this.SetContextList('D', -1, this.NowCaseno, this.NowDocNo, DelFile); |
| | | } |
| | | }; |
| | | |
| | | cls.prototype.GetDataDocNoPage = function(MainDocNo: string, MainVersion: string): number { //取記錄的文件_版本頁數 |
| | | let P: string; |
| | | let Result = 0; |
| | | if (this.FindSQLData(this.Doc_Inf_List, 'FORM_PAGES', 'DOC_NO,DOC_VERSION', MainDocNo + ',' + MainVersion, 0, this.FindResult)) { |
| | | P = this.GetFindResult('FORM_PAGES'); |
| | | if (P !== '') { |
| | | Result = parseInt(P, 10); |
| | | } else { |
| | | Result = 0; |
| | | } |
| | | } |
| | | return Result; |
| | | }; |
| | | |
| | | cls.prototype.CheckCaseDocNoPage = function(CaseID: string, DocNo: string, Version: string, Pages: number): number { //取案件裡的文件_版本頁數 |
| | | let i: number, n: number, Count: number; |
| | | let S: any, S2: any; |
| | | let FormCode: string, iPage: string; |
| | | let docInt = 0, tempInt = 0; |
| | | let v: number, v2: number; |
| | | |
| | | Count = 0; |
| | | docInt = 0; |
| | | tempInt = 0; |
| | | S = new this.TStringList(); |
| | | S2 = new this.TStringList(); |
| | | //ShowMessage('page='+IntToStr(Pages)); |
| | | try { |
| | | S.LoadFromFile(this.ImageSavePath + CaseID + '\\upload\\Context.dat'); |
| | | S2.LoadFromFile(this.ImageSavePath + CaseID + '\\upload\\DocDir.dat'); //2017 1220 改成只承認第一份的 |
| | | |
| | | for (i = 1; i <= Pages; i++) { //從0到pages-1 改成 1到pages 20170316 這樣可以修改檢核的頁數問題 |
| | | iPage = this.Add_Zoo(i, 2); |
| | | for (n = 0; n < S.Count; n++) { |
| | | if (this.FWH_category === 'N' && this.FIs_In_Wh === 'Y') { |
| | | if (this.ISExistImg(this.ImageSavePath + CaseID + '\\upload\\' + S.Strings[n])) { |
| | | continue; |
| | | } |
| | | } |
| | | |
| | | if (S2.Strings[n].length > 8 && S2.Strings[n].indexOf(DocNo) !== -1) { //2017 1220 改成只承認第一份的 |
| | | //LogFile1.LogToFile(logTimeString+'有進'+docno+','+S2.Strings[n]+IntToStr(Pos(DocNo,S2.Strings[n]))); |
| | | v = S2.Strings[n].indexOf('('); |
| | | v2 = S2.Strings[n].indexOf(')'); |
| | | tempInt = parseInt(S2.Strings[n].substring(v + 1, v2), 10); |
| | | |
| | | if (docInt === 0) { |
| | | docInt = tempInt; |
| | | } |
| | | if (docInt !== tempInt) { |
| | | //LogFile1.LogToFile(logTimeString+'docInt='+IntToStr(docInt)+',tempInt='+IntToStr(tempInt)); |
| | | return Count; // Break returning Count |
| | | } |
| | | } |
| | | |
| | | FormCode = this.FileName2FormCode(S.Strings[n]); |
| | | //LogFile1.LogToFile(logTimeString+'S.Strings[n]='+S.Strings[n]); |
| | | //Showmessage('1:'+version+','+FormCode2Version(FormCode)+','+DocNo+','+FormCode2DocNo(FormCode)+','+iPage+','+FormCode2Page(FormCode)); |
| | | //LogFile1.LogToFile(logTimeString+'FormCode='+FormCode); |
| | | this.LogFile1.LogToFile(this.logTimeString() + 'CheckCaseDocNoPage caseno=' + CaseID + ',' + Version + ',' + this.FormCode2Version(FormCode) + ',' + DocNo + ',' + this.FormCode2DocNo(FormCode) + ',' + iPage + ',' + this.FormCode2Page(FormCode)); |
| | | //ShowMessage(DocNo+','+IntToStr(docInt)+','+IntToStr(tempInt)); |
| | | //LogFile1.LogToFile(logTimeString+'FormCode='+FormCode); |
| | | if (Version === this.FormCode2Version(FormCode) && DocNo === this.FormCode2DocNo(FormCode) && iPage === this.FormCode2Page(FormCode)) { |
| | | this.LogFile1.LogToFile(this.logTimeString() + 'CheckCaseDocNoPage caseno=' + CaseID + ',' + Version + ',' + this.FormCode2Version(FormCode) + ',' + DocNo + ',' + this.FormCode2DocNo(FormCode) + ',' + iPage + ',' + this.FormCode2Page(FormCode)); |
| | | //Showmessage(version+','+DocNo+',iPage='+iPage); |
| | | //Showmessage(inttostr(Count+1)); |
| | | Count++; |
| | | break; //找到了...離開 |
| | | } |
| | | } |
| | | } |
| | | } finally { |
| | | S.Free(); |
| | | S2.Free(); |
| | | } |
| | | return Count; |
| | | }; |
| | | |
| | | cls.prototype.TransCaseID = function(Path: string, CaseID: string, MainCase: boolean): boolean { //傳送案件 |
| | | let i: number, n: number, v: number; |
| | | let ZipFileList: any; |
| | | let UpFormID: string; |
| | | let pages: number; |
| | | let TransName: string; |
| | | let MaskPath: string; |
| | | let HaveMask = false; |
| | | let S: string; |
| | | let SendData: string; |
| | | let Doc_Data = '', Doc_Data1 = ''; |
| | | let In_Doc1 = '', In_Doc2 = ''; |
| | | let AttachYN: string; //是否有附件 Y:有 N:沒有 |
| | | |
| | | let ST1: any, ST2: any, ST3: any; |
| | | let str1 = '', str2 = ''; |
| | | let must_formidStr = ''; |
| | | let last_add_formidstr = ''; |
| | | let ScanListStr = ''; |
| | | let casepath: string; |
| | | let filesizeInt = 0; |
| | | let case_page: string; |
| | | let Fname: string; |
| | | let FileRec: any; |
| | | |
| | | let Result = true; |
| | | TransName = CaseID; |
| | | MaskPath = Path + 'MaskImg\\'; |
| | | if (fs.existsSync(Path + 'Context.dat')) { |
| | | this.ContextList.LoadFromFile(Path + 'Context.dat'); |
| | | this.Context_DocnoList.LoadFromFile(Path + 'Context_DocNo.dat'); |
| | | } |
| | | |
| | | if (fs.existsSync(Path + 'CustomDocNo.dat')) { |
| | | this.Cust_DocNoList.LoadFromFile(Path + 'CustomDocNo.dat'); |
| | | } |
| | | |
| | | pages = this.ContextList.Count; |
| | | case_page = pages.toString(); |
| | | if (this.FMode === 'NSCAN' || this.FMode === 'ESCAN' || this.FMode === 'ASCAN' || this.FMode === 'DSCAN' || this.FMode === 'SSCAN' || this.FMode === 'MSCAN' || this.FMode === 'RI_SCAN' || this.FMode === 'RSCAN') { |
| | | //Showmessage('1'); |
| | | UpFormID = this.GetCaseFormID(Path); |
| | | //{if UpformID = '' then //20131213 yuu說不管主form |
| | | //begin |
| | | // Showmessage(_msg('取不到主FormID!!')); |
| | | // Result := False; |
| | | // DataLoading(False,False); |
| | | // Exit; |
| | | //end;} |
| | | } |
| | | |
| | | this.CaseResort2Scanlist(Path); //檔名照設定排序產生scanlist.dat |
| | | //CaseResort(Path); //檔名照設定排序 |
| | | this.CreateFormID_FormName(Path, CaseID); //產生FormID_FormName.dat |
| | | this.CreateDocNo_DocName(Path, CaseID); //產生DocNo_Name.dat |
| | | Doc_Data = this.CreateDocNo_Info(CaseID); //產生保管袋文件 Docno,份數,頁數;Docno,份數,頁數 的回傳字串 |
| | | Doc_Data1 = this.CreateCustDocNo_Info(CaseID); //產生自定文件 Docname,份數,頁數;Docno,份數,頁數 的回傳字串 |
| | | In_Doc1 = this.CreateDocnoFrom_Info(CaseID); //產生被引進的保管袋文件資訊 Docno[tab]份數[tab]案件編號#13#10Docno[tab]份數[tab]案件編號 |
| | | In_Doc2 = this.CreateCustDocNoFrom_Info(CaseID); //產生被引進的自定文件資訊 Docno[tab]份數[tab]案件編號#13#10Docno[tab]份數[tab]案件編號 |
| | | AttachYN = this.CreateAttach_Info(CaseID); //是否還有附件 Y:有 N:沒有 |
| | | this.ReadCaseIndex(Path); |
| | | //LoanDoc := 'Y'; |
| | | //產生遮罩影像 |
| | | // if FWork_No = 'CW' then |
| | | // HaveMask := Case2Mask(Path,MaskPath); |
| | | //產生遮罩影像 |
| | | // S := S +#13+'5-->'+ Timetostr(now); |
| | | |
| | | ///////必要formid 20170315 start ////////////////////////////// |
| | | must_formidStr = ''; |
| | | last_add_formidstr = ''; |
| | | ST1 = new this.TStringList(); |
| | | ST1.LoadFromFile(Path + 'FormCode_Name.dat'); |
| | | //ShowMessage(ST1.Text); |
| | | //ShowMessage(LastInitFormidList.Text); |
| | | ST2 = new this.TStringList(); |
| | | ST3 = new this.TStringList(); |
| | | |
| | | for (i = 0; i < ST1.Count; i++) { |
| | | if (ST1.Strings[i].indexOf('_') !== 0 && ST1.Strings[i].indexOf('_') !== -1) { |
| | | str1 = ST1.Strings[i].substring(0, ST1.Strings[i].indexOf('_')); |
| | | ST2.Add(str1); |
| | | must_formidStr = must_formidStr + str1 + '@#,'; |
| | | } |
| | | } |
| | | must_formidStr = must_formidStr.substring(0, must_formidStr.length - 3); |
| | | //ShowMessage('must_formidStr='+must_formidStr); |
| | | //ShowMessage('AST2='+ST2.Text); |
| | | |
| | | |
| | | for (i = 0; i < this.LastInitFormidList.Count; i++) { |
| | | if (ST2.IndexOf(this.LastInitFormidList.Strings[i]) !== -1) { |
| | | ST2.Delete(ST2.IndexOf(this.LastInitFormidList.Strings[i])); |
| | | } |
| | | } |
| | | //ShowMessage('BST2='+ST2.Text); |
| | | for (i = 0; i < ST2.Count; i++) { |
| | | last_add_formidstr = last_add_formidstr + ST2.Strings[i] + '@#,'; |
| | | } |
| | | last_add_formidstr = last_add_formidstr.substring(0, last_add_formidstr.length - 3); |
| | | |
| | | ST3.LoadFromFile(Path + 'scanlist.dat'); |
| | | for (i = 0; i < ST3.Count; i++) { |
| | | if (ScanListStr === '') { |
| | | ScanListStr = this.FileName2FormCode(ST3.Strings[i]); |
| | | } else { |
| | | ScanListStr = `${ScanListStr},${this.FileName2FormCode(ST3.Strings[i])}`; |
| | | } |
| | | } |
| | | |
| | | ST1.Free(); |
| | | ST2.Free(); |
| | | ST3.Free(); |
| | | //ShowMessage('last_add_formidstr='+last_add_formidstr); |
| | | ///////必要formid 20170315 end ////////////////////////// |
| | | |
| | | ///保留外部影像 start/////////////////////////////// |
| | | casepath = Path.substring(0, Path.indexOf('Upload')); |
| | | //ShowMessage('casepath='+casepath); |
| | | //FIsExternal:='Y'; |
| | | if (this.FMode === 'ESCAN' && this.FIsExternal === 'Y') { |
| | | if (fs.existsSync(casepath + 'Download\\FirstImg.zip')) { |
| | | fs.copyFileSync(casepath + 'Download\\FirstImg.zip', Path + 'FirstImg.zip'); |
| | | } |
| | | else { |
| | | fs.copyFileSync(casepath + 'Download\\' + CaseID + '.zip', Path + 'FirstImg.zip'); |
| | | } |
| | | } |
| | | ///保留外部影像 end/////////////////////////////// |
| | | |
| | | //file_size 計算 就先不做 20170316 |
| | | filesizeInt = 0; |
| | | |
| | | |
| | | //////壓檔///// |
| | | this.ZipMainFile(Path, Path, 'Img.zip'); |
| | | if (HaveMask) { |
| | | this.ZipMaskFile(Path, MaskPath, Path, 'MaskImg.zip'); //有遮罩設定的才產生 |
| | | } |
| | | /////壓檔//// |
| | | ///檢查上傳的zip大小//// |
| | | Fname = Path + 'Img.zip'; |
| | | |
| | | // FindFirst(FName, faAnyfile, FileRec); |
| | | // NodeJS equivalent using fs.statSync |
| | | let stat = fs.statSync(Fname); |
| | | |
| | | //FMaxUploadSize |
| | | //ShowMessage(IntToStr(FileRec.Size)); |
| | | //Result:=False; |
| | | //exit; //目前上傳檔案大小為xxMB,已超過50MB,無法上傳 %.3f ,[FileRec.Size / 1048576] |
| | | if (stat.size > parseInt(this.FMaxUploadSize, 10) * 1048576) { // 檢查檔案大小 |
| | | this.Showmessage(this.Format(this._Msg('%s目前上傳檔案大小為%.3fMB,已超過%sMB,無法上傳'), [CaseID, stat.size / 1048576, this.FMaxUploadSize])); |
| | | //ShowMessage(Format('%s目前上傳檔案大小為%.3fMB,已超過'+FMaxUploadSize+'MB,無法上傳',[caseid,FileRec.Size / 1048576]) ); |
| | | Result = false; |
| | | return Result; // Exit; |
| | | } |
| | | ///檢查上傳的zip大小//// |
| | | //ShowMessage('last_add_formidstr='+last_add_formidstr); |
| | | if (!this.GetFtpinfo(CaseID, 'upload')) { //取案件上傳方式 |
| | | //Showmessage(_Msg()Inttostr(HttpError.HttpErrorCode)+' '+HttpError.HttpReason+'.'); |
| | | this.DownFileErrStr = this._Msg('取案件上傳資訊失敗!!') + this.HttpErrStr; |
| | | Result = false; |
| | | return Result; // Exit; |
| | | } |
| | | SendData = 'data=' + this.HTTPEncode(this.UTF8Encode(this.FData)) |
| | | + '&verify=' + this.FVerify |
| | | + '&form_id=' + UpFormID |
| | | + '&loan_doc=' + this.Case_loandoc |
| | | + '&case_no=' + TransName |
| | | + '&doc_data=' + this.HTTPEncode(this.UTF8Encode(Doc_Data)) |
| | | + '&doc_data1=' + this.HTTPEncode(this.UTF8Encode(Doc_Data1)) |
| | | + '&attach=' + AttachYN |
| | | + '&case_page=' + case_page |
| | | + '&file_size=' + filesizeInt.toString() |
| | | + '&must_formid=' + must_formidStr //擁有的 formid |
| | | + '&last_add_formid=' + last_add_formidstr //當次新加的 formid |
| | | + '&form_code=' + ScanListStr //scanlist.dat 表單代號 |
| | | + '&ftp_image_path=' + this.FFtpExtraPath //加傳FTP目錄 HTTP上傳時會是空白 |
| | | + '&in_doc1=' + this.HTTPEncode(this.UTF8Encode(In_Doc1)) |
| | | + '&in_doc2=' + this.HTTPEncode(this.UTF8Encode(In_Doc2)); |
| | | |
| | | if (this.TransMode === this.TTransMode.tsHttp) { |
| | | ////上傳///// |
| | | this.ShowText = CaseID + this._Msg('資料上傳中(Http),請稍候'); |
| | | this.DataLoading(true, true); |
| | | if (!this.upFile(this.HTTPSClient, this.FUrl, 'service/imgpsc/IMGPSC02/caseupload', SendData, 'file', Path + 'Img.zip', this.FReWrite, this.Memo1, false)) { |
| | | this.Showmessage(this.HttpError.HttpErrorCode.toString() + ' ' + this.HttpError.HttpReason + '.'); |
| | | Result = false; |
| | | return Result; // Exit; |
| | | } |
| | | if (this.Memo1.Lines.Strings[0] === '1') { |
| | | this.Showmessage(this.Format(this._Msg('') + this._Msg(''), [CaseID]) + this.Memo1.Lines.Strings[1] + '。'); |
| | | Result = false; |
| | | return Result; // Exit; |
| | | } |
| | | else if (this.Memo1.Lines.Text.indexOf('<script type="text/javascript" src="scripts/IMGPS00/login.js"></script>') > 0) { |
| | | this.Showmessage(this.Format(this._Msg('') + this._Msg('') + this._Msg('閒置過久或被登出,請重新登入'), [CaseID])); |
| | | Result = false; |
| | | return Result; // Exit; |
| | | } |
| | | ////上傳//// |
| | | } else if (this.TransMode === this.TTransMode.tsFtp) { |
| | | this.ShowText = CaseID + this._Msg('資料上傳中(Ftp),請稍候'); |
| | | this.DataLoading(true, true); |
| | | this.SetFtpInfo(); |
| | | try { |
| | | if (!this.IIS_Ftp.FtpsConnect()) { |
| | | this.Showmessage(this.Format('無法連上Ftp主機,錯誤原因:%s', [this.FtpErrReason])); |
| | | Result = false; |
| | | return Result; // Exit; |
| | | } |
| | | if (!this.IIS_Ftp.FtpsToMain(this.FFtpExtraPath, CaseID + '.zip', Path + 'Img.zip', this.display1)) { |
| | | this.Showmessage(this.Format(this._Msg('上傳案件(%s)時,發生錯誤,錯誤原因:%s'), [CaseID, this.FtpErrStr])); |
| | | Result = false; |
| | | return Result; // Exit; |
| | | } |
| | | |
| | | if (!this.FtpCaseComplete(SendData)) { //Ftp上傳後通知完成 |
| | | this.Showmessage(this.Format(this._Msg('通知案件(%s)Ftp上傳完成時,發生錯誤!!'), [CaseID]) + this.HttpErrStr); |
| | | Result = false; |
| | | return Result; // Exit; |
| | | } |
| | | } finally { |
| | | this.IIS_Ftp.FtpsClose(); |
| | | } |
| | | } |
| | | |
| | | if (this.FMode === 'ESCAN') { //上傳舊件引入檔案 //20140616 原本先搬舊件再搬新件,改為先搬新件再搬舊件 |
| | | if (!this.TransOldCaseFile(this.ImageSavePath + CaseID + '\\')) { |
| | | Result = false; |
| | | return Result; // Exit; |
| | | } |
| | | } |
| | | // 呼叫Server完成 ///// |
| | | //{If not CaseComplete(Path,CaseID,MainCase) Then |
| | | //begin |
| | | // Showmessage(_Msg('通知案件傳送完成時,網路發生錯誤!!')+HttpErrStr); |
| | | // DataLoading(False,False); |
| | | // Result := False; |
| | | // Exit; |
| | | //end; } |
| | | /// 呼叫Server完成//// |
| | | |
| | | ////刪檔//// |
| | | //_DelTree(Path); //會只刪TransPath |
| | | //ShowMessage('STOP'); |
| | | this._DelTree(this.ImageSavePath + CaseID); |
| | | this.SetCaseList('D', -1, CaseID); |
| | | ////刪檔//// |
| | | return Result; |
| | | }; |
| | | |
| | | ['Set_caseid', 'Set_data', 'Set_c_docnolist', 'Set_fixfilelist', 'Set_oldcaseinfo'].forEach(p => { |
| | | cls.prototype[p] = function(v: string) { |
| | | let fieldName = 'F' + p.substring(4).replace('caseid', 'CaseID').replace('data', 'Data').replace('c_docnolist', 'C_DocNoList').replace('fixfilelist', 'FixFileList').replace('oldcaseinfo', 'OldCaseInfo'); |
| | | this[fieldName] = v; |
| | | }; |
| | | cls.prototype['Get_' + p.substring(4)] = function() { |
| | | let fieldName = 'F' + p.substring(4).replace('caseid', 'CaseID').replace('data', 'Data').replace('c_docnolist', 'C_DocNoList').replace('fixfilelist', 'FixFileList').replace('oldcaseinfo', 'OldCaseInfo'); |
| | | return this[fieldName]; |
| | | }; |
| | | }); |
| | | |
| | | cls.prototype.Set_is_oldcase = function(v: string) { this.FIs_OldCase = v.toUpperCase(); }; |
| | | cls.prototype.Get_is_oldcase = function() { return this.FIs_OldCase; }; |
| | | |
| | | cls.prototype.Set_casenolength = function(v: string) { this.FCaseNoLength = v === '' ? 0 : parseInt(v, 10); this.CaseIDLength = this.FCaseNoLength; }; |
| | | cls.prototype.Get_casenolength = function() { return this.FCaseNoLength.toString(); }; |
| | | |
| | | cls.prototype.Set_filesizelimit = function(v: string) { this.FFileSizeLimit = v === '' ? 5120 : parseInt(v, 10); }; |
| | | cls.prototype.Get_filesizelimit = function() { return this.FFileSizeLimit.toString(); }; |
| | | |
| | | cls.prototype.Set_check_main_form = function(v: string) { this.FCheck_main_form = v; }; |
| | | cls.prototype.Get_check_main_form = function() { return this.FCheck_main_form; }; |
| | | |
| | | cls.prototype.Set_WH_CATEGORY = function(v: string) { this.FWH_category = v; }; |
| | | cls.prototype.Get_WH_CATEGORY = function() { return this.FWH_category; }; |
| | | |
| | | cls.prototype.Set_isExternal = function(v: string) { this.FIsExternal = v; }; |
| | | cls.prototype.Get_isExternal = function() { return this.FIsExternal; }; |
| | | |
| | | cls.prototype.Set_imgdelete = function(v: string) { this.FImgDelete = v; }; |
| | | cls.prototype.Get_imgdelete = function() { return this.FImgDelete; }; |
| | | |
| | | cls.prototype.Set_custdocyn = function(v: string) { this.FCustDocYN = v.toUpperCase(); }; |
| | | cls.prototype.Get_custdocyn = function() { return this.FCustDocYN; }; |
| | | |
| | | cls.prototype.Set_printyn = function(v: string) { this.FPrintyn = v.toUpperCase(); }; |
| | | cls.prototype.Get_printyn = function() { return this.FPrintyn; }; |
| | | |
| | | cls.prototype.Set_work_no = function(v: string) { this.FWork_no = v; }; |
| | | cls.prototype.Get_work_no = function() { return this.FWork_no; }; |
| | | |
| | | cls.prototype.Set_verify = function(v: string) { this.FVerify = v; }; |
| | | cls.prototype.Get_verify = function() { return this.FVerify; }; |
| | | |
| | | cls.prototype.Set_userunit = function(v: string) { this.FUserUnit = v; }; |
| | | cls.prototype.Get_userunit = function() { return this.FUserUnit; }; |
| | | |
| | | cls.prototype.Set_username = function(v: string) { this.FUserName = v; }; |
| | | cls.prototype.Get_username = function() { return this.FUserName; }; |
| | | |
| | | cls.prototype.Set_userid = function(v: string) { this.FUserID = v; }; |
| | | cls.prototype.Get_userid = function() { return this.FUserID; }; |
| | | |
| | | cls.prototype.Set_useproxy = function(v: string) { this.FUseProxy = v.toUpperCase(); }; |
| | | cls.prototype.Get_useproxy = function() { return this.FUseProxy; }; |
| | | |
| | | cls.prototype.Set_url = function(v: string) { this.FUrl = v; }; |
| | | cls.prototype.Get_url = function() { return this.FUrl; }; |
| | | |
| | | cls.prototype.Set_rewrite = function(v: string) { this.FReWrite = v; }; |
| | | cls.prototype.Get_rewrite = function() { return this.FReWrite; }; |
| | | |
| | | cls.prototype.Set_modename = function(v: string) { this.FModeName = v; }; |
| | | cls.prototype.Get_modename = function() { return this.FModeName; }; |
| | | |
| | | cls.prototype.Set_mode = function(v: string) { this.FMode = v.toUpperCase(); }; |
| | | cls.prototype.Get_mode = function() { return this.FMode; }; |
| | | |
| | | cls.prototype.Set_loandoc_value = function(v: string) { this.FLoanDoc_Value = v; }; |
| | | cls.prototype.Get_loandoc_value = function() { return this.FLoanDoc_Value; }; |
| | | |
| | | cls.prototype.Set_loandoc_enable = function(v: string) { this.FLoanDoc_Enable = v; }; |
| | | cls.prototype.Get_loandoc_enable = function() { return this.FLoanDoc_Enable; }; |
| | | |
| | | cls.prototype.Set_language = function(v: string) { |
| | | this.FLanguage = v.toLowerCase(); |
| | | if (this.FLanguage === 'zh-tw') { |
| | | this.FLanguage = 'zh_tw'; |
| | | } |
| | | }; |
| | | cls.prototype.Get_language = function() { return this.FLanguage; }; |
| | | |
| | | cls.prototype.Set_is_in_wh = function(v: string) { this.FIs_In_Wh = v.toUpperCase(); }; |
| | | cls.prototype.Get_is_in_wh = function() { return this.FIs_In_Wh; }; |
| | | |
| | | cls.prototype.Set_c_docnamelist = function(v: string) { this.FC_DocNameList = v; }; |
| | | cls.prototype.Get_c_docnamelist = function() { return this.FC_DocNameList; }; |
| | | |
| | | cls.prototype.DataLoading = function(Loading: boolean, UseTimer: boolean): void { |
| | | if (Loading) { |
| | | // Screen.Cursor := -11; |
| | | if (UseTimer) { |
| | | this.Panel22.Caption = this.ShowText; |
| | | this.Panel22.Left = Math.floor(this.Panel9.Width / 2) - Math.floor(this.Panel22.Width / 2); |
| | | this.Panel22.Top = Math.floor(this.Panel9.Height / 2) - Math.floor(this.Panel22.Height / 2); |
| | | this.Panel22.Visible = true; |
| | | this.Timer2.Enabled = true; |
| | | } else { |
| | | this.Panel8.Left = Math.floor(this.Panel9.Width / 2) - Math.floor(this.Panel8.Width / 2); |
| | | this.Panel8.Top = Math.floor(this.Panel9.Height / 2) - Math.floor(this.Panel8.Height / 2); |
| | | this.Panel8.Visible = true; |
| | | } |
| | | this.Application.ProcessMessages(); |
| | | this.Panel1.Enabled = false; |
| | | this.Panel2.Enabled = false; |
| | | } else { |
| | | this.Panel22.Visible = false; |
| | | this.Panel8.Visible = false; |
| | | this.Timer2.Enabled = false; |
| | | this.Panel1.Enabled = true; |
| | | this.Panel2.Enabled = true; |
| | | // Screen.Cursor := 0; |
| | | } |
| | | }; |
| | | |
| | | cls.prototype.ReduceLogFile = function(): void { |
| | | let ST1: any; |
| | | let I: number; |
| | | ST1 = new this.TStringList(); |
| | | if (fs.existsSync(this.LngPath + 'IMGPSCheck.log')) { |
| | | ST1.LoadFromFile(this.LngPath + 'IMGPSCheck.log'); |
| | | if (ST1.Count > 100000) { |
| | | for (I = 0; I <= 10000; I++) { |
| | | ST1.Delete(0); |
| | | } |
| | | ST1.SaveToFile(this.LngPath + 'IMGPSCheck.log'); |
| | | } |
| | | } |
| | | ST1.Free(); |
| | | }; |
| | | |
| | | cls.prototype.ClearCaseIndex = function(): void { |
| | | this.AddCredit1RG.Enabled = false; |
| | | this.AddCredit1RG.ItemIndex = -1; |
| | | }; |
| | | |
| | | } |
| 比對新檔案 |
| | |
| | | unit CB_IMGPSScanImp; |
| | | //TEST |
| | | {$WARN SYMBOL_PLATFORM OFF} |
| | | |
| | | interface |
| | | |
| | | uses |
| | | EnScan, { for Scanner } |
| | | EnDiGrph, { for TDibGraphic } |
| | | EnMisc, { for MinFloat } |
| | | EnTifGr, { for TTifGraphic } |
| | | {$IFDEF Production} |
| | | CB_IMGPSScan_TLB, |
| | | {$ENDIF} |
| | | {$IFDEF Test} |
| | | CB_IMGPSScan_test_TLB, |
| | | {$ENDIF} |
| | | |
| | | Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, |
| | | ActiveX, AxCtrls, StdVcl, VCLUnZip, VCLZip, Encryp, |
| | | ExtCtrls, ComCtrls, Menus, StdCtrls, Gauges, EnImgScr, PJMenuSpeedButtons, |
| | | Buttons, ImgList, SBSimpleSSL, SBHTTPSClient, SBWinCertStorage, SBX509, |
| | | SBCustomCertStorage, SBUtils,mpsBarco,BarcodesFinder,HTTPApp,ErrList, |
| | | Xmltool,inifiles,printers,IdHashMessageDigest, idHash, LogFile,ShellApi, |
| | | SBSocket,IIS_Ftp, SBSimpleFTPS; |
| | | Type |
| | | TTransMode =(tsHttp,tsFtp,tsNone); |
| | | |
| | | var |
| | | Ch_WriteNote : Boolean; |
| | | RejectCase : Boolean; |
| | | ErrIndex : Integer; |
| | | |
| | | //*****預設區*****// |
| | | Def_DeviceDelete : Boolean; //空白頁刪除啟動 |
| | | Def_DeviceDeleteSize : Integer; //空白頁Size |
| | | Def_ScannerReverse : Boolean; //是否需反相 |
| | | Def_BoardClear : Boolean; //是否清黑邊 |
| | | Def_ScanDpi : Integer; //掃瞄DPI |
| | | Def_ScanDuplex : Boolean; //是否雙面掃瞄 |
| | | Def_ScanRotate : Integer; //掃瞄時旋轉角度 |
| | | Def_ScanDeskew : Boolean; //是否傾斜矯正 |
| | | Def_ScanBright : Integer; //亮度 |
| | | Def_ScanContrast : Integer; //對比 |
| | | Def_ScanImgShowMode : Integer; //0:清楚影像 1:不清楚影像 2:不顯示 |
| | | Def_ScanImgSetUse : Boolean; //是否使用亮度對比設定 |
| | | //*****預設區*****// |
| | | |
| | | Const |
| | | ISBName = 'PreViewISB'; |
| | | |
| | | {獲取自身版本號所需要 } |
| | | type |
| | | TVersionLanguage = (vlArabic, vlBulgarian, vlCatalan, vlTraditionalChinese, |
| | | vlCzech, vlDanish, vlGerman, vlGreek, vlUSEnglish, vlCastilianSpanish, |
| | | vlFinnish, vlFrench, vlHebrew, vlHungarian, vlIcelandic, vlItalian, |
| | | vlJapanese, vlKorean, vlDutch, vlNorwegianBokmel, vlPolish, |
| | | vlBrazilianPortuguese, vlRhaetoRomanic, vlRomanian, vlRussian, |
| | | vlCroatoSerbian, vlSlovak, vlAlbanian, vlSwedish, vlThai, vlTurkish, |
| | | vlUrdu, vlBahasa, vlSimplifiedChinese, vlSwissGerman, vlUKEnglish, |
| | | vlMexicanSpanish, vlBelgianFrench, vlSwissItalian, vlBelgianDutch, |
| | | vlNorwegianNynorsk, vlPortuguese, vlSerboCroatian, vlCanadianFrench, |
| | | vlSwissFrench, vlUnknown); |
| | | |
| | | const LanguageValues: array[TVersionLanguage] of Word = ($0401, $0402, $0403, |
| | | $0404, $0405, $0406, $0407, $0408, $0409, $040A, $040B, $040C, $040D, |
| | | $040E, $040F, $0410, $0411, $0412, $0413, $0414, $0415, $0416, $0417, |
| | | $0418, $0419, $041A, $041B, $041C, $041D, $041E, $041F, $0420, $0421, |
| | | $0804, $0807, $0809, $080A, $080C, $0810, $0813, $0814, $0816, $081A, |
| | | $0C0C, $100C, $0000); |
| | | {獲取自身版本號所需要 end} |
| | | |
| | | |
| | | type |
| | | TScanMode = (smNew, smReplace, smInsert, smSample, smRTS); |
| | | TpScanInfo = ^TScanInfo; |
| | | TScanInfo = record |
| | | MultiPage : Boolean; |
| | | { supplementary info when MultiPage is True } |
| | | Graphic : TTiffGraphic; |
| | | Stream : TFileStream; |
| | | ImageCount : LongInt; |
| | | end; |
| | | TOMRErrInfo = record |
| | | Display : Boolean; //是否顯示 |
| | | Ignore : Boolean; //可否刪除 |
| | | Info : String; |
| | | Mode : String; |
| | | end; |
| | | TScrollRec = Record |
| | | HScroll : Integer; |
| | | VScroll : Integer; |
| | | Rate : Single; |
| | | end; |
| | | |
| | | type |
| | | TCB_IMGPSScanX = class(TActiveForm, ICB_IMGPSScanX) |
| | | Panel1: TPanel; |
| | | TransBtn: TBitBtn; |
| | | Panel21: TPanel; |
| | | ViewModeBtn: TPJMenuSpeedButton; |
| | | Button2: TButton; |
| | | Button1: TButton; |
| | | OptionBtn: TBitBtn; |
| | | SelectScanBtn: TBitBtn; |
| | | NextPageBtn: TBitBtn; |
| | | PrePageBtn: TBitBtn; |
| | | FC6: TBitBtn; |
| | | FC5: TBitBtn; |
| | | FC4: TBitBtn; |
| | | FC3: TBitBtn; |
| | | FC2: TBitBtn; |
| | | FC1: TBitBtn; |
| | | FC0: TBitBtn; |
| | | Panel23: TPanel; |
| | | PJLinkedMenuSpeedButton2: TPJLinkedMenuSpeedButton; |
| | | NewScanBtn: TBitBtn; |
| | | AddScanBtn: TBitBtn; |
| | | Panel4: TPanel; |
| | | Panel2: TPanel; |
| | | Splitter1: TSplitter; |
| | | Panel3: TPanel; |
| | | Label7: TLabel; |
| | | Panel6: TPanel; |
| | | CB1: TCheckBox; |
| | | ScanDuplexCB: TCheckBox; |
| | | ScanFlatCB: TCheckBox; |
| | | Panel18: TPanel; |
| | | Panel12: TPanel; |
| | | Panel7: TPanel; |
| | | Panel9: TPanel; |
| | | Shape1: TShape; |
| | | ImageScrollBox1: TImageScrollBox; |
| | | imgp8: TPanel; |
| | | lb8: TLabel; |
| | | ISB8: TImageScrollBox; |
| | | imgp7: TPanel; |
| | | lb7: TLabel; |
| | | ISB7: TImageScrollBox; |
| | | imgp6: TPanel; |
| | | lb6: TLabel; |
| | | ISB6: TImageScrollBox; |
| | | imgp5: TPanel; |
| | | lb5: TLabel; |
| | | ISB5: TImageScrollBox; |
| | | imgp4: TPanel; |
| | | lb4: TLabel; |
| | | ISB4: TImageScrollBox; |
| | | imgp3: TPanel; |
| | | lb3: TLabel; |
| | | ISB3: TImageScrollBox; |
| | | imgp2: TPanel; |
| | | lb2: TLabel; |
| | | ISB2: TImageScrollBox; |
| | | imgp1: TPanel; |
| | | lb1: TLabel; |
| | | Memo1: TMemo; |
| | | Display1: TMemo; |
| | | Panel22: TPanel; |
| | | Panel8: TPanel; |
| | | Label2: TLabel; |
| | | Gauge1: TGauge; |
| | | ScrollBar1: TScrollBar; |
| | | Panel10: TPanel; |
| | | PageLV: TListView; |
| | | StatusBar1: TStatusBar; |
| | | ImageList4: TImageList; |
| | | ImageList3: TImageList; |
| | | ImageList1: TImageList; |
| | | PopupMenu2: TPopupMenu; |
| | | mode1: TMenuItem; |
| | | mode2: TMenuItem; |
| | | mode3: TMenuItem; |
| | | mode4: TMenuItem; |
| | | N51: TMenuItem; |
| | | ImageList2: TImageList; |
| | | OpenDialog1: TOpenDialog; |
| | | PopupMenu3: TPopupMenu; |
| | | PM301: TMenuItem; |
| | | PM302: TMenuItem; |
| | | PM303: TMenuItem; |
| | | Timer1: TTimer; |
| | | TomEncryption1: TTomEncryption; |
| | | SaveDialog1: TSaveDialog; |
| | | Timer2: TTimer; |
| | | VCLZip1: TVCLZip; |
| | | PopupMenu4: TPopupMenu; |
| | | PM401: TMenuItem; |
| | | PM402: TMenuItem; |
| | | PM403: TMenuItem; |
| | | PM404: TMenuItem; |
| | | ElWinCertStorage: TElWinCertStorage; |
| | | ElMemoryCertStorage: TElMemoryCertStorage; |
| | | Panel5: TPanel; |
| | | AddCredit1RG: TRadioGroup; |
| | | Panel11: TPanel; |
| | | SampleScanBtn: TBitBtn; |
| | | WNoteBtn: TBitBtn; |
| | | PopupMenu1: TPopupMenu; |
| | | PM101: TMenuItem; |
| | | N12: TMenuItem; |
| | | PM102: TMenuItem; |
| | | MenuItem1: TMenuItem; |
| | | PM103: TMenuItem; |
| | | PM104: TMenuItem; |
| | | N7: TMenuItem; |
| | | PM106: TMenuItem; |
| | | PM107: TMenuItem; |
| | | PM108: TMenuItem; |
| | | PM109: TMenuItem; |
| | | PopupMenu5: TPopupMenu; |
| | | PM501: TMenuItem; |
| | | PM502: TMenuItem; |
| | | PM503: TMenuItem; |
| | | PM504: TMenuItem; |
| | | N5: TMenuItem; |
| | | PM505: TMenuItem; |
| | | PM506: TMenuItem; |
| | | N29: TMenuItem; |
| | | PM510: TMenuItem; |
| | | PM509: TMenuItem; |
| | | PM507: TMenuItem; |
| | | N15: TMenuItem; |
| | | PM508: TMenuItem; |
| | | ExportBt: TButton; |
| | | ImportBt: TButton; |
| | | CheckCaseBtn: TBitBtn; |
| | | DenialTimeLb: TLabel; |
| | | Panel14: TPanel; |
| | | ScrollBox1: TScrollBox; |
| | | Label1: TLabel; |
| | | Button3: TButton; |
| | | Panel15: TPanel; |
| | | Button4: TButton; |
| | | PopupMenu6: TPopupMenu; |
| | | PM602: TMenuItem; |
| | | PM601: TMenuItem; |
| | | PM605: TMenuItem; |
| | | PM603: TMenuItem; |
| | | PM604: TMenuItem; |
| | | N8: TMenuItem; |
| | | Panel16: TPanel; |
| | | SpeedButton3: TSpeedButton; |
| | | SpeedButton14: TSpeedButton; |
| | | SpeedButton15: TSpeedButton; |
| | | SpeedButton16: TSpeedButton; |
| | | SpeedButton17: TSpeedButton; |
| | | SpeedButton18: TSpeedButton; |
| | | SpeedButton19: TSpeedButton; |
| | | SpeedButton20: TSpeedButton; |
| | | SpeedButton21: TSpeedButton; |
| | | SpeedButton22: TSpeedButton; |
| | | Edit1: TEdit; |
| | | PM110: TMenuItem; |
| | | Image1: TImage; |
| | | UseOldCaseLb: TLabel; |
| | | PM111: TMenuItem; |
| | | SmoothCB: TCheckBox; |
| | | ISB_BW: TImageScrollBox; |
| | | N1: TMenuItem; |
| | | N2: TMenuItem; |
| | | ISB1: TImageScrollBox; |
| | | Label3: TLabel; |
| | | LogFile1: TLogFile; |
| | | ScanGrayCB: TCheckBox; |
| | | AttFileGB: TGroupBox; |
| | | AttListBox: TListBox; |
| | | Panel20: TPanel; |
| | | AddAttFileLB: TLabel; |
| | | DelAttFileLB: TLabel; |
| | | Splitter2: TSplitter; |
| | | Panel17: TPanel; |
| | | TreeView1: TTreeView; |
| | | Panel13: TPanel; |
| | | PrtLb: TLabel; |
| | | CaseHelpBtn: TBitBtn; |
| | | HTTPSClient: TElHTTPSClient; |
| | | Button5: TButton; |
| | | FTPSClient1: TElSimpleFTPSClient; |
| | | Button6: TButton; |
| | | procedure ActiveFormCreate(Sender: TObject); |
| | | procedure Panel9Resize(Sender: TObject); |
| | | procedure ISB1Click(Sender: TObject); |
| | | procedure WNoteBtnClick(Sender: TObject); |
| | | procedure CaseHelpBtnClick(Sender: TObject); |
| | | procedure FC0Click(Sender: TObject); |
| | | procedure FC1Click(Sender: TObject); |
| | | procedure FC2Click(Sender: TObject); |
| | | procedure FC3Click(Sender: TObject); |
| | | procedure FC4Click(Sender: TObject); |
| | | procedure FC5Click(Sender: TObject); |
| | | procedure FC6Click(Sender: TObject); |
| | | procedure PrePageBtnClick(Sender: TObject); |
| | | procedure NextPageBtnClick(Sender: TObject); |
| | | procedure OptionBtnClick(Sender: TObject); |
| | | procedure SelectScanBtnClick(Sender: TObject); |
| | | procedure mode1Click(Sender: TObject); |
| | | procedure mode2Click(Sender: TObject); |
| | | procedure mode3Click(Sender: TObject); |
| | | procedure mode4Click(Sender: TObject); |
| | | procedure N51Click(Sender: TObject); |
| | | procedure ScrollBar1Change(Sender: TObject); |
| | | procedure ISB1EndScroll(Sender: TObject); |
| | | procedure ISB1ImageMouseDown(Sender: TObject; Button: TMouseButton; |
| | | Shift: TShiftState; X, Y: Integer); |
| | | procedure ISB1ImageMouseMove(Sender: TObject; Shift: TShiftState; X, |
| | | Y: Integer); |
| | | procedure ISB1ImageMouseUp(Sender: TObject; Button: TMouseButton; |
| | | Shift: TShiftState; X, Y: Integer); |
| | | procedure PM508Click(Sender: TObject); |
| | | procedure PM401Click(Sender: TObject); |
| | | procedure PM402Click(Sender: TObject); |
| | | procedure PM403Click(Sender: TObject); |
| | | procedure PM404Click(Sender: TObject); |
| | | procedure TreeView1Click(Sender: TObject); |
| | | procedure NewScanBtnClick(Sender: TObject); |
| | | procedure AddScanBtnClick(Sender: TObject); |
| | | procedure PageLVClick(Sender: TObject); |
| | | procedure PageLVKeyUp(Sender: TObject; var Key: Word; Shift: TShiftState); |
| | | procedure PageLVMouseDown(Sender: TObject; Button: TMouseButton; |
| | | Shift: TShiftState; X, Y: Integer); |
| | | procedure PM101Click(Sender: TObject); |
| | | procedure PM104Click(Sender: TObject); |
| | | procedure PM102Click(Sender: TObject); |
| | | procedure PM103Click(Sender: TObject); |
| | | procedure PM106Click(Sender: TObject); |
| | | procedure PM107Click(Sender: TObject); |
| | | procedure PM108Click(Sender: TObject); |
| | | procedure PM109Click(Sender: TObject); |
| | | procedure PM301Click(Sender: TObject); |
| | | procedure PM302Click(Sender: TObject); |
| | | procedure PM303Click(Sender: TObject); |
| | | procedure PM501Click(Sender: TObject); |
| | | procedure PM502Click(Sender: TObject); |
| | | procedure PM503Click(Sender: TObject); |
| | | procedure PM504Click(Sender: TObject); |
| | | procedure PM505Click(Sender: TObject); |
| | | procedure PM510Click(Sender: TObject); |
| | | procedure PM509Click(Sender: TObject); |
| | | procedure PM507Click(Sender: TObject); |
| | | procedure PopupMenu1Popup(Sender: TObject); |
| | | procedure PopupMenu4Popup(Sender: TObject); |
| | | procedure PopupMenu5Popup(Sender: TObject); |
| | | procedure SampleScanBtnClick(Sender: TObject); |
| | | procedure StatusBar1DblClick(Sender: TObject); |
| | | procedure ExportBtClick(Sender: TObject); |
| | | procedure ImportBtClick(Sender: TObject); |
| | | procedure HTTPSClientCertificateValidate(Sender: TObject; |
| | | X509Certificate: TElX509Certificate; var Validate: Boolean); |
| | | procedure ScanDuplexCBClick(Sender: TObject); |
| | | procedure CheckCaseBtnClick(Sender: TObject); |
| | | procedure Timer1Timer(Sender: TObject); |
| | | procedure Timer2Timer(Sender: TObject); |
| | | procedure TransBtnClick(Sender: TObject); |
| | | procedure TreeView1DragDrop(Sender, Source: TObject; X, Y: Integer); |
| | | procedure TreeView1DragOver(Sender, Source: TObject; X, Y: Integer; |
| | | State: TDragState; var Accept: Boolean); |
| | | procedure TreeView1KeyUp(Sender: TObject; var Key: Word; |
| | | Shift: TShiftState); |
| | | procedure TreeView1MouseUp(Sender: TObject; Button: TMouseButton; |
| | | Shift: TShiftState; X, Y: Integer); |
| | | procedure ViewModeBtnMouseEnter(Sender: TObject); |
| | | procedure Button3Click(Sender: TObject); |
| | | procedure AddCredit1RGClick(Sender: TObject); |
| | | procedure CB1Click(Sender: TObject); |
| | | procedure Button4Click(Sender: TObject); |
| | | procedure ISB1Enter(Sender: TObject); |
| | | procedure TreeView1MouseMove(Sender: TObject; Shift: TShiftState; X, |
| | | Y: Integer); |
| | | procedure PM601Click(Sender: TObject); |
| | | procedure PopupMenu6Popup(Sender: TObject); |
| | | procedure PM604Click(Sender: TObject); |
| | | procedure PM605Click(Sender: TObject); |
| | | procedure SpeedButton3Click(Sender: TObject); |
| | | procedure SpeedButton14Click(Sender: TObject); |
| | | procedure SpeedButton15Click(Sender: TObject); |
| | | procedure SpeedButton16Click(Sender: TObject); |
| | | procedure SpeedButton17Click(Sender: TObject); |
| | | procedure SpeedButton18Click(Sender: TObject); |
| | | procedure SpeedButton19Click(Sender: TObject); |
| | | procedure SpeedButton20Click(Sender: TObject); |
| | | procedure SpeedButton21Click(Sender: TObject); |
| | | procedure SpeedButton22Click(Sender: TObject); |
| | | procedure ActiveFormKeyUp(Sender: TObject; var Key: Word; |
| | | Shift: TShiftState); |
| | | procedure PM110Click(Sender: TObject); |
| | | procedure PM602Click(Sender: TObject); |
| | | procedure PrtLbClick(Sender: TObject); |
| | | procedure Panel1DblClick(Sender: TObject); |
| | | procedure Panel11DblClick(Sender: TObject); |
| | | procedure UseOldCaseLbClick(Sender: TObject); |
| | | procedure PM111Click(Sender: TObject); |
| | | procedure ImageScrollBox1NewGraphic(const Graphic: TDibGraphic); |
| | | procedure SmoothCBClick(Sender: TObject); |
| | | procedure N1Click(Sender: TObject); |
| | | procedure TreeView1MouseEnter(Sender: TObject); |
| | | procedure ScrollBox1MouseEnter(Sender: TObject); |
| | | procedure ScanGrayCBClick(Sender: TObject); |
| | | procedure AddAttFileLBClick(Sender: TObject); |
| | | procedure DelAttFileLBClick(Sender: TObject); |
| | | procedure AttListBoxDblClick(Sender: TObject); |
| | | procedure AttListBoxClick(Sender: TObject); |
| | | procedure TreeView1MouseDown(Sender: TObject; Button: TMouseButton; |
| | | Shift: TShiftState; X, Y: Integer); |
| | | procedure HTTPSClientRedirection(Sender: TObject; const OldURL: string; |
| | | var NewURL: string; var AllowRedirection: Boolean); |
| | | procedure Button5Click(Sender: TObject); |
| | | procedure Button6Click(Sender: TObject); |
| | | private |
| | | { Private declarations } |
| | | HotKeyId1,HotKeyId2,HotKeyId3,HotKeyId4 :Integer; |
| | | |
| | | ////////傳入參數///////// |
| | | FUrl : String; |
| | | FCaseID : String; |
| | | FMode : String; //NSCAN:新件;ESCAN:修改 20170306 DSCAN:被配合待掃瞄清單使用 |
| | | FModeName : String; |
| | | FWork_no : String; |
| | | FUserID : String; |
| | | FUserName : String; |
| | | FUserUnit : String; |
| | | FData : String; |
| | | FVerify : String; |
| | | FReWrite : String; |
| | | FLanguage : String; |
| | | FLoanDoc_Value : String; //新增授信卷的值 |
| | | FLoanDoc_Enable : String; //新增授信卷是否可異動 |
| | | FUseProxy : String; //是否使用Proxy |
| | | FC_DocNoList : String; //外面傳來要新增的文件編號 |
| | | FC_DocNameList : String; //外面傳來要新增的自訂文件 |
| | | FFixFileList : String; //要鎖住的檔名 |
| | | FIs_In_Wh : String; //是否只顯示入庫文件 (Y:只顯示入庫 N:只顯示非入庫 空白:都顯示) |
| | | FOldCaseInfo : String; //舊件資訊 CaseID_year[tab]CaseID_year |
| | | FPrintyn : String; //是否可列印 |
| | | FIs_OldCase : String; //是否是舊案第一次進入 |
| | | FCustDocYN : String; //是否可自訂文件 |
| | | //20170222 新增 |
| | | FImgDPI:integer;//DPI 依業務別決定 150~~1200 預設300 |
| | | FScanColor:integer;///掃瞄色彩 依業務別決定 0 :黑白 1:灰階 2:彩色 預設黑白 |
| | | FFileSizeLimit:integer;//匯入單一檔案大小限 依業務別決定 以KB為單位 預設5*1024kb |
| | | FCaseNoLength:integer;//案件編號長度檢查 依業務別決定 無預設 一定要傳入 |
| | | FImgDelete:string; // Y/N 有權限可在補件時刪除影像 |
| | | FIsExternal:string;//Y/N |
| | | FCheck_main_form:string; //Y/N |
| | | FWH_category:string; //Y/N Y是歸類時有區分入庫非入庫文件 N 是不區分 |
| | | ////////傳入參數/////// |
| | | |
| | | TransMode : TTransMode; //用何種方式上傳檔案 |
| | | //********Http參數******** |
| | | HttpErrStr : String; //錯誤訊息 |
| | | //********Http參數******** |
| | | |
| | | //********Ftp參數********* |
| | | FFtpIP : String; |
| | | FFtpID : String; |
| | | FFtpPwd : String; |
| | | FFtpRootPath : String; |
| | | FFtpExtraPath : String; |
| | | FFtpPort : Integer; |
| | | FFtpProtocol : TFtpProtocol; |
| | | //********Ftp參數********* |
| | | |
| | | //********ini參數******** |
| | | DeviceDelete : Boolean; //空白頁刪除啟動 |
| | | DeviceDeleteSize : Integer; //空白頁Size |
| | | ScannerReverse : Boolean; //是否需反相 |
| | | BoardClear : Boolean; //是否清黑邊 |
| | | ScanDpi : Integer; //掃瞄DPI |
| | | ScanDuplex : Boolean; //是否雙面掃瞄 |
| | | ScanRotate : Integer; //掃瞄時旋轉角度 |
| | | ScanDeskew : Boolean; //是否傾斜矯正 |
| | | ScanBright : Integer; //亮度 |
| | | ScanContrast : Integer; //對比 |
| | | ScanImgShowMode : Integer; //0:清楚影像 1:不清楚影像 2:不顯示 |
| | | ScanImgSetUse : Boolean; //是否使用亮度對比設定 |
| | | |
| | | //********ini參數******** |
| | | ScanColor : TImageFormat; |
| | | |
| | | //********時間******** |
| | | ServerDate : String; |
| | | ServerTime : String; |
| | | Balance : Longint; //local跟server的時間差 |
| | | ScanDenialTime : String; //停止進件時間 |
| | | //********時間******** |
| | | ScanDenialHint : String; //掃描顯示提醒字串 |
| | | |
| | | //********路徑******** |
| | | ScanPath : String; // SpecifyDir\Workid\userunit\mode |
| | | ImagePath : String; // SpecifyDir\Workid\userunit\mode |
| | | ImageSavePath : String; // ImagePath\ |
| | | ScaniniPath : String; // SpecifyDir\Workid\userunit\ |
| | | LngPath :String; //多國語言檔目錄 |
| | | CheckXmlPath : String; //檢核用的Xml存放目錄 |
| | | SitePath : String; //登打位置(切簽名用) |
| | | SamplePath : String; //範本目錄 |
| | | TransPath : String; //檔案上傳的路徑(補充件會多一層目錄) |
| | | //********路徑******** |
| | | ScanSaveFilename : String; //掃瞄的檔名 |
| | | ScanCaseno : String; //掃瞄時的案號 |
| | | ScanDocDir : String; //掃描時的文件代號目錄 |
| | | //********清單區******** |
| | | Doc_Inf_List : TStringList; //Doc_Inf 清單 |
| | | DM_FORM_INF_List :TstringList; //DM_FORM_INF 清單 |
| | | FORM_INF_List : TStringList; //FORM_INF的清單 |
| | | CHECK_RULE_INF_List : TStringList; //CHECK_RULE_INF 清單 |
| | | MEMO_INF_List : TStringList; //MEMO_INF 清單 |
| | | WORK_INF_List : TStringList; //WORK_INF 清單 |
| | | LASTEST_FORM_INF_List : TStringList; // LASTEST_FORM_INF 清單 |
| | | |
| | | FindResult : TStringList; //找SQLData的結果 |
| | | CaseList : TStringList; //記錄掃瞄案件的順序 |
| | | Context_DocnoList : TStringlist; //案件裡的Docno清單 |
| | | CaseDocNoList : TStringlist; //案件裡的DocNo清單(不重複) |
| | | CaseDocNo_CopiesList : TStringlist; //案件裡的DocNo份數清單 |
| | | ContextList : TStringlist; //案件裡的檔案清單 |
| | | AttContextList : TStringlist; //案件裡的附加檔案清單 |
| | | OMRFileList : TStringList; //要OMR檢核的文件(只檢查每種Form的第一頁) |
| | | Cust_DocNoList : TStringlist; //自行定義文件名稱 |
| | | IN_WH_DocNoList : TStringlist; //入庫的文件清單 |
| | | |
| | | FormCode_PageSize : TStringList; //文件的預設大小 FormCode_Height_Width |
| | | DocNo_NeedDoc : TStringList; //有Docno時要相依的文件 DocNo_相依文件_相依文件 |
| | | DocNo_NoDoc : TstringList; //有Docno時互斥的文件 DocNo_互斥文件_互斥文件 |
| | | DocNo_VerinCase : TStringlist; //案件裡的DocNo+版本的清單 |
| | | |
| | | NoSaveBarCodeList : TStringlist; //不儲存的條碼清單 |
| | | FormID_List : TStringlist; //FormID 清單 20130403 因為原FormCode2Docno會很慢..把FormID抽出來 |
| | | DocNo_List : TStringlist; //DocNo 清單 20130403 因為原FormCode2Docno會很慢..把DocNo抽出來 |
| | | NowShowFileList : TStringlist; //目前顯示的影像清單 |
| | | NowSelectFileList : TStringlist; //目前被點選的影像清單 |
| | | GuideFormIDList : TStringlist; //要當導引頁表單清單 |
| | | DivPageFormIDList:TStringlist; //要當分案頁表單清單 |
| | | LastInitFormidList:TStringList; |
| | | LastAddFormidList:TstringList; |
| | | SampleFormIDList:TStringList; //20170627 已存在範本的formid |
| | | ExistImgList:TStringList; //20170724 已經存在的影像list for ESCAN //Img的完整路徑 |
| | | reSizeExistImgList:TstringList; //20171012被縮放的舊圖MD5存入 |
| | | //********清單區******** |
| | | |
| | | //********顯示區******** |
| | | NowCaseno : String; //目前顯示的案件編號 |
| | | NowDocNo : String; //目前的文件編號 |
| | | NowDocDir : String; //目前的文件目錄 |
| | | NowFormCode : String; //目前顯示的表單編號 |
| | | NowFormName : String; //目前顯示的表單名稱 |
| | | NowPage : Integer; //目前點選的頁碼 |
| | | DisplayPath : String; //目前顯示的目錄 |
| | | //********顯示區******** |
| | | |
| | | //******索引資料********* |
| | | Case_loandoc : String; //是否新增授信卷 |
| | | //******索引資料********* |
| | | |
| | | //********十字定位點資訊******** |
| | | UpLPoint : Tpoint; //左上方的十字點 |
| | | UpRPoint : Tpoint; //右上方的十字點 |
| | | DownLPoint : Tpoint; //左下方的十字點 |
| | | DownRPoint : Tpoint; //右下方的十字點 |
| | | Point_Width : String; //十字點的寬 |
| | | Point_Height : String; //十字點的高 |
| | | //********十字定位點資訊******** |
| | | |
| | | CaseIDLength : Integer; //案件編號長度 |
| | | FormIDLength : Integer; //FormID長度 |
| | | DocNoLength : Integer; //Docno長度 |
| | | |
| | | PEFileName : String; //掃描時的檔名 |
| | | DownFileErrStr : String; //下載影像時發生的錯誤 |
| | | |
| | | ISB : TImageScrollBox; |
| | | ScanInfo : TScanInfo; |
| | | TwainShowUI : Boolean; |
| | | MpsBarcodeinf : TMpsBarcodeinf; |
| | | ScanMode : TScanMode; |
| | | Mpskey : String; |
| | | Seg : Integer; //顯示的邊界值 |
| | | VMode : Integer; //顯示的index |
| | | NowClick : Integer; //目前點到的按鈕功能Index |
| | | ScanIP : String; //掃瞄端的IP |
| | | DisplayISB : TImageScrollBox; //被點到的影像 |
| | | SelectISB : TImageScrollBox; //被點到的縮圖 |
| | | SelectPage : Integer; //被點到的頁數 |
| | | NewTreeNode,MyTreeNode1,MyTreeNode2,MyTreeNode3 : TTreenode; |
| | | InitialOk : Boolean; //資訊載入是否完成 |
| | | ShowText : String; //DataLoading時要秀出的訊息 |
| | | Ext : String; //附檔名 .tif .jpg |
| | | SafePixel : Integer; //OMR容忍誤差值點數 |
| | | OMRErrInfo : Array[1..11] of TOMRErrInfo; //檢核的方式及訊息 |
| | | ScrollRec : Array[1..8] of TScrollRec; //瀏覽窗的Scroll記錄 |
| | | RecHozPos,RecVerPos : Integer; //記錄MPSViewX1的ScrollBar位置 |
| | | ReczoomPercent : Single; |
| | | SortMode : Boolean; |
| | | PreMytreeNode2Name:String; |
| | | HS,VS : Integer; |
| | | iRate : Single; |
| | | Bt : Integer; //去直線時橫線判斷的容忍值 |
| | | BarCodeRotate : Integer; //條碼要轉的角度 |
| | | HaveAppDoc : Boolean; //補全時是否有補入要保書 |
| | | PageLVclear : Boolean; |
| | | CaseCount,PageCount : Integer; //總案件量及總頁數 |
| | | Item : TMenuItem; |
| | | SampleAnchorMode : String; //範本掃瞄十字線的模式 NONE:無;ANCHOR:十字;FRAME:邊框 |
| | | DownImgStatus : String; //下載影像的狀態(NO_DATA:沒資料;NO_FILE:沒影像) |
| | | TransForm_Field : String; //要用OMR勾選確認是否轉換FORMID的欄位名稱 |
| | | NowWork_No : String; //現在的作業別 |
| | | CropBarcode : String; //要切影像的條碼 |
| | | Has_Authorize :String; //是否有授權書影像 |
| | | AttName : String; //未歸類目錄名稱 |
| | | NowGuideFormID : String; |
| | | NowDivPageFormID:String; |
| | | |
| | | FirstDocDir : String; |
| | | FMaxUploadSize:String;// 上傳zip大小限制 |
| | | FJpgCompression:integer;// 20171211 jpg to tif 的壓縮率 |
| | | |
| | | Draging : Boolean; |
| | | MDown : Boolean; //20181210 用來判斷滑鼠右鍵要Popupmenu是否有MouseDonw發生 |
| | | |
| | | FEvents: ICB_IMGPSScanXEvents; |
| | | |
| | | procedure HotKeyDown (var Msg : TMessage);message WM_HOTKEY; |
| | | Procedure InitialLanguage(Sender: TObject); //畫面載入多國語言 |
| | | //Function _Msg(S:String):String; |
| | | |
| | | //*********SQL相關************ |
| | | Procedure SetSQLData(ColumeStr:String;FromList,ToList:TStringlist); //把SQL值塞入 |
| | | Function GetSQLData(TableList:TStringlist;Colname:String;colNo:Integer):String; //依欄位及索引取值 |
| | | Function FindSQLData(TableList:TStringlist;ColumeStr,KeyColumeStr,KeyStr:String;ColNo:Integer;Var ResultList:TStringlist):Boolean; //找指定的資料 |
| | | Function GetFindResult(Col:String):String; |
| | | //*********SQL相關************ |
| | | |
| | | //*********FTP相關************ |
| | | Function GetFtpinfo(CaseID,Action:String):Boolean; |
| | | Procedure SetFtpInfo; //餵入FTP資訊 |
| | | Function FtpCaseComplete(SendData:String):Boolean; |
| | | //*********FTP相關************ |
| | | |
| | | //*******轉換區********* |
| | | Function FindDivFormCode(FormCode:String):Boolean; //找有沒有分案的條碼 |
| | | Function FormCode2FormName(CaseID,FormCode:String):String; //用FormCode轉成文件名稱 |
| | | Function FormCode2FileName(FormCode:String;List:TStrings):String; //用FormCode找出檔名(第一頁) |
| | | Function FileName2FormCode(FileName:String):String; //從檔名取出FormCode |
| | | Function FileName2FormName(CaseID,FileName:String):String; //從檔名取出文件名稱 |
| | | Function FileName2ScanPage(FileName:String):Integer; //從檔名取出掃瞄頁數 |
| | | Function FileName2NoQuene_Filename(FileName:String):String; //取出沒有序號的檔名 |
| | | Function FileName2Index(FileName:String):Integer; //從檔名取出在ContextList的序號 |
| | | Function FileName2NowDcoNo(FileName:String;CtList,DNList:TStrings):String; //從檔名取出歸屬的文件代號 |
| | | Function FormCode2DocNo(FormCode:String):String; //FormCode轉Docno |
| | | Function FormCode2Version(FormCode:String):String; //FormCode轉版本 |
| | | Function FormCode2Page(FormCode:String):String; //FormCode轉文件頁數 |
| | | Function DocNo2DocName(CaseID,DocNo:String):String; //Docno轉Doc名稱 |
| | | Function DocNo2FileName(DocNo:String;List:TStrings):String; //用DocNo找出檔名(第一頁) |
| | | Function FormCode2WorkNo(FormCode:String):String; //用FormCode取出作業別 |
| | | Function DocNo2WorkNo(DocNo:String):String; //用DocNo取出作業別 |
| | | Function DocNo2DocNoDir(Path,DocNo:String):String; //DocNo轉成DocNo(份數)目錄 |
| | | Function DocNoDir2DocNo(DocNoDir:String):String; //DocNo(份數)目錄轉成DocNo |
| | | Function DocNoDir2Index(Path,DocNoDir:String):Integer; //DocNo(份數)目錄轉成index |
| | | |
| | | Function DocNoNeedDiv(DocNo:String):Boolean; //是否是需分份數的文件代號 |
| | | //Function CaseNo2DocNo(CaseNo:String):TStringList; |
| | | |
| | | Function CaseNode2Info(Node:TTreeNode;Mode:Char):String; //案件Node取案件編號 Mode: I:Caseno;P:Page |
| | | Function DocNode2Info(Node:TTreeNode;Mode:Char):String; //文件Node取文件代號 Mode: I:Docno;N:Docname;P:Page;G:Group |
| | | Function FormNode2Info(Node:TTreeNode;Mode:Char):String; //表單Node取表單代號 Mode: I:FormID;N:FormName;P:Page |
| | | //*******轉換區********* |
| | | |
| | | Procedure PriorPage(Page:Integer); //上一頁 |
| | | Procedure NextPage(Page:Integer); //下一頁 |
| | | |
| | | Function DocNoExistsinTree(CaseNode:TTreeNode;DocNo:String):Boolean; //DocNo是否己存在樹裡 |
| | | Function DocnoNeedGroup(DocNo:String):Boolean; //傳入的DocNo是否需分組 |
| | | |
| | | function GetSiteOMR(FileName, Site: String;bt: Integer): Integer; |
| | | Function FindISB2View(Vmode:Integer):TImageScrollBox; //找空的ISB來顯示 |
| | | Procedure R_W_ScanIni(Mode:Char); //'R'讀取;'W'寫入 |
| | | Procedure GetDefScanIni; //取得掃瞄的預設值 |
| | | procedure DesableImage; |
| | | procedure EnableImage(v:integer;Sender : TObject); |
| | | Procedure ViewMouseMode(v:Integer); |
| | | Procedure GoViewMode; |
| | | Procedure DisplayMode(index,H_Count,W_Count:Integer;BasePanel:TPanel); |
| | | Function GetServerDate : Boolean; //取主機時間 |
| | | Function GetSetInf1 : Boolean; //取系統設定資訊mode1 DOC_INF |
| | | Function GetSetInf2 : Boolean; //取系統設定資訊mode2 DM_FORM_INF |
| | | Function GetSetInf3 : Boolean; //取系統設定資訊mode3 FORM_INF |
| | | Function GetSetInf4 : Boolean; //取系統設定資訊mode4 CHECK_RULE_INF |
| | | Function GetSetInf5 : Boolean; //取系統設定資訊mode5 MEMO_INF |
| | | Function GetSetInf6 : Boolean; //取系統設定資訊mode6 WORK_INF |
| | | Function GetSetInf7 : Boolean; //取系統設定資訊mode7 LASTESTFORM_INF |
| | | Procedure SetFormID_DocNo; //將FormID及Docno抽出來另存入list裡 |
| | | Procedure SetIn_WH_DocNo; //將要入庫的DocNo抽出來另存入list裡 |
| | | |
| | | Procedure DataLoading(Loading,UseTimer:Boolean); //資料載入中要停止點選的動作 |
| | | procedure ClearView(stkv:Integer); //清除瀏覽窗的影像 |
| | | Function DrawDocItem2(CaseNode : TTreenode;Caseno:String):Boolean; //畫出文件名稱的Tree |
| | | |
| | | Procedure initkscan; //檢查掃描器的功能 |
| | | procedure LoadImgFile; //載入案件 |
| | | procedure LoadImgFile1; //載入案件 |
| | | procedure LoadAttFile(CaseID:String); //載入附加檔案 |
| | | Procedure DistinctFormCode(CaseID:String); //案件裡的FormCode取出第一頁 |
| | | Function OMRCheckCase(CaseID:String):Boolean; //OMR檢核 |
| | | Procedure OMRErr2ini(CaseID,Reason,FileName,Site,RelaFileName,RelaSite,Anchor,Anchor1:String;Del,Ingnore,Display:Boolean); //OMR檢核失敗寫入ini |
| | | Procedure OMRErrini2List(CaseID:String;ErrlistForm : TErrlistForm); //OMR檢核失敗從ini寫入ListView |
| | | Function DownLanguage:Boolean; //下載多國語言檔 |
| | | Function FindMpsView(Vmode:Integer):TImageScrollBox; |
| | | Function CaseAsk(CaseID:String):Integer; //詢問是否可上傳 (-1:失敗;0:可以;1:不行;) |
| | | Function CaseComplete(Path,CaseID:String;MainCase:Boolean):Boolean; //通知傳送完成 |
| | | Function GetCaseFormID(Path:String):String; //取案件的FormID |
| | | Procedure CreateFormID_FormName(Path,CaseID:String); //產生FormID_FormName.dat |
| | | Procedure CreateDocNo_DocName(Path,CaseID:String); //產生DocNo_DocName.dat |
| | | Procedure CreateIn_WH(CaseID:String); //產生In_WH.dat |
| | | Function CreateDocNo_Info(CaseID:String):String; //產生保管袋文件 DocNo[tab]份數[tab]總頁數[tab]是否異動[換行]DocNo[tab]份數[tab]總頁數[tab]是否異動 |
| | | Function CreateCustDocNo_Info(CaseID:String):String; //產生自訂文件 DocName[tab]份數[tab]總頁數[tab]是否異動[#13#10]DocName[tab]份數[tab]總頁數[tab]是否異動 |
| | | //Function CreateCustDocNo_Info(path,CaseID:String):String; overload |
| | | Function CreateAttach_Info(CaseID:String):String; //產生是否有Attach Y:有 N:沒有 |
| | | Function CreateDocnoFrom_Info(CaseID:String):String; //產生被引進的保管袋文件資訊 Docno[tab]份數[tab]案件編號#13#10Docno[tab]份數[tab]案件編號 |
| | | Function CreateCustDocNoFrom_Info(CaseID:String):String; //產生被引進的自定文件資訊 Docno[tab]份數[tab]案件編號#13#10Docno[tab]份數[tab]案件編號 |
| | | |
| | | Function GetDocNoEdit(CaseID,DocNo,DocName:String):String; //取出DocNo是否被異動 (Y/N) |
| | | Function GetDocNo_Count(Path,DocNo:String):Integer; //取出文件份數 |
| | | Function GetDocNo_Page(Path,DocNo:String):Integer; //取出文件總頁數 |
| | | Function FormIDExists(FormCode:String;CheckDate:Boolean;index:Integer):Boolean; //檢查FormID是否存在及是否要檢查啟用停用日期 |
| | | Function Case_DocNoExists(CaseID,Docno:String):Boolean; //Docno是否存在案件裡 |
| | | Procedure ReSortFileName_New(Path:String); //檔名重新排序 |
| | | Procedure ReSortFileName(Path:String); //檔名重新排序 |
| | | Procedure ReSortFileName2Scanlist(Path:String); //檔名重新排序給Scanlist.dat |
| | | Function GetOMRCheckSet:Boolean; //下載OMR檢核XML檔 |
| | | Function GetKeyinSet : Boolean; //取登打設定 |
| | | Procedure CheckRule2OMRErrInfo; //檢核規則填入OMRErrINFo Record |
| | | Procedure ReNameContext(Path,OldName,NewName:String); |
| | | Procedure DeleteImageFile(Path,FileName,CaseID:String); // 刪除檔案 |
| | | Procedure DeleteFormCodeFile(CaseID,DocDir,FormID:String); //刪除指定FormID文件 |
| | | Function DeleteDocNoFile(Path,DocNo:String):Boolean; //刪除指定DocNo文件 |
| | | Procedure DeleteShowFile(Path:String); //刪除顯示中的影像 |
| | | Function GetDataDocNoPage(MainDocNo,MainVersion:String):Integer; //取記錄的文件_版本頁數 |
| | | Function CheckCaseDocNoPage(CaseID,DocNo,Version:String;Pages:Integer):Integer; //檢查案件裡的文件_版本頁數 |
| | | Function FindFormCodePages(CaseID,FormCode:String):Integer; //計算案件裡FormID的頁數 |
| | | Function GetDataFormCodePages(FormCode:String):Integer; //取記錄的FormcID的頁數 |
| | | Procedure CaseReSize(CaseID:String); //案件的影像縮放 |
| | | Procedure ImageReSize_FormID(CaseID,FileName:String); //依十字定位點做縮放 |
| | | Procedure ImageReSize_tmp(FormID,FileName:String); //依十字定位點做縮放(暫存檔) |
| | | Function TransCaseID(Path,CaseID:String;MainCase:Boolean):Boolean; //傳送案件 |
| | | Procedure NewTreeNodeRefresh; |
| | | Procedure MyTreeNode1Refresh; |
| | | Procedure MyTreeNode2ReFresh(CaseID:String); |
| | | Procedure MyTreeNode3ReFresh(CaseID:String); |
| | | Function Node2DocNo(Node2:TTreeNode):String; //MyTreeNode2取DocNo出來 |
| | | Function Node3DocNo(Node3:TTreeNode):String; //MyTreeNode3取DocNo出來 |
| | | Function Node3FormID(Node3:TTreeNode):String; //MyTreeNode3取FormCode出來 |
| | | Function GetNode2Name(Node2:TTreeNode):String; //取MyTreeNode2的識別字出來(記之前點選用) |
| | | //Function Down_Replace_Img(SPAth,DPath,CaseID:String):Boolean; |
| | | Function DownLoadImage(Path,CaseID:String):Boolean; |
| | | Function Down_Img(Path,CaseID:String):Boolean; |
| | | Function GetNoNameCase(Path:string):String; //取未配號XXXX |
| | | Procedure CaseResort(Path:String); //案件的檔案重新排序(次文件依Docno挑) |
| | | Procedure CaseResort2Scanlist(Path:String); //案件的檔案重新排序給scanlist(次文件依FormID排) |
| | | Procedure DistinctDocinCase(Path:String); //列出案件裡的Docno_版本 |
| | | Procedure DistinctDocNoinCase(Path:String); //列出案件裡的Docno |
| | | Procedure ClearErrini(CaseID:String;CaseNode:TTreeNode); //清掉檢核檔案 |
| | | Procedure SetCaseList(Mode:Char;Index:Integer;text:String); //'A:加入,I:插入,D:刪除,E:修改' |
| | | Procedure SetDocNoList(Mode:Char;Index:Integer;CaseNo,DocDir,Copies:String); //'A:加入,I:插入,D:刪除,E:修改' |
| | | Procedure SetContextList(Mode:Char;Index:Integer;CaseNo,DocDir,FileName:String); //'A:加入,I:插入,D:刪除,E:修改' |
| | | Procedure SetAttContextList(Mode:Char;Index:Integer;CaseNo,FileName:String); //'A:加入,I:插入,D:刪除,E:修改' |
| | | |
| | | |
| | | Function checkCaseOMRDone:Boolean; //檢查案件是否完成OMR檢核 |
| | | Function CheckCaseID_OK:Boolean; //檢查是否有未配號的案件 |
| | | Procedure CreateEmptyCase(Path,CaseID:String); //產生空白案號(重掃件用) |
| | | Procedure InitScrollRec; //初始化影像Scroll記錄 |
| | | Procedure GetScrollData(ISB:TImageScrollBox;Var HS,VS:Integer;Var iRate:Single); //取影像Scroll記錄 |
| | | Procedure SetScrollData(ISB:TImageScrollBox;HS,VS:Integer;iRate:Single); //寫影像Scroll記錄 |
| | | Procedure FormIDReplace(CaseID,DocDir,OldFormID,NewFormID:String); //指定FormID更換成新的FormID |
| | | Procedure ShowFileReplace(Path,NewFormID:String);//顯示的影像換成新的FormID |
| | | Procedure PageReplaceFormID(Path,NowFormID,NewFormID:String); //選取頁更換FormID |
| | | Function ModeNeedCheck(OMRMode,ScanMode:String):Boolean; //掃瞄模式是否要做檢核 |
| | | procedure WMMOUSEWHEEL(var message: TWMMouseWheel); message WM_MOUSEWHEEL; |
| | | Function GetCasePage(Path,CaseID:String):Integer; |
| | | Function GetFormIDPage(FileList:TStringlist;FormID:String):Integer; |
| | | Procedure SetFile2Case(CaseID,FileName:String); |
| | | Procedure WriteResize(ImgName,TxtName:String); //產生Resize.dat |
| | | Function GetCase_PageCount(var CaseCount,PageCount:Integer):Boolean; //取出案件的數量及頁數 ID為空值時為取所有的 |
| | | Function BarCode2FormID : String; //Barcode依規則轉成FormID |
| | | Function BarCode2CaseID : String; //Barcode依規則轉成CaseID |
| | | Procedure WriteCaseIndex(Path:String); |
| | | Procedure ReadCaseIndex(Path:String); |
| | | Procedure ClearCaseIndex; |
| | | Procedure GetSelectImageFile; |
| | | Function GetDocNoDir(Path,DocNo:String):String; //取出目前DocNo的份數 |
| | | Function CheckFormIDExists(DocNoNode:TTreeNode;FormID:String):Boolean; //檢查FormID是否存在文件裡 |
| | | |
| | | Procedure ZipMainFile(SoPath,DePath,ZipName:String); //壓縮影像檔 |
| | | Procedure ZipMaskFile(SoPath,MarkPath,DePath,ZipName:String); //壓縮遮罩影像檔 |
| | | Procedure ParserPoint(S:String); //解析十字點的字串 |
| | | Function CheckScanDenialTime:Boolean; |
| | | Function FormID2Anchor(FormID:String):String; //用FormID取出十字模式 |
| | | Function Index2Anchor(Anchor:String):String; //十字模式 0->NONE;1->ANCHOR;2->FRAME |
| | | Function MemoInfoTransfer(Mode,Str:String;ID_S,Name_S:TStringlist):String; //註記代碼註記類別轉換 Mode 'ID':代碼轉名稱;'NAME':名稱轉代碼 |
| | | Function GetFormatID(CaseID:String):String; //取出案件的FormatID |
| | | Function FindNoSaveBarCode : Boolean; //找是否有不要儲存影像的條碼 |
| | | Function CheckAvailable:Boolean; //檢查是否可使用元件 |
| | | Function Case2Mask(SoPath,DePath:String):Boolean;//產生遮罩影像 |
| | | Function CheckNeedCrop(Graphic:TDibGraphic):Boolean; //是否是A3要切影像 |
| | | Function GetNewCustomDocNo(Path,DocName:String):String; //取出未使用的自訂文件代號 |
| | | Function GetCustomDocName(Path,DocNo:String):String; //取出自定文件名稱 |
| | | Function GetCustomFormID(Path,DocNo:String):String; //取出自定文件FormID |
| | | Function GetCustomDocDir(Path,DocName:String):String; //取出自定文件DocDir |
| | | Function FindCustomDocName(Path,DocName:String):Boolean; //尋找自定文件名稱是否存在 |
| | | Procedure DeleteCustomDocDir(Path,DocNo:String); //刪除自定文件DocNo |
| | | Function CheckFormID_Prt(FormID:String):Boolean; //傳入的FormID是否預設列印 |
| | | procedure PrintImg(FileName, LoginID, Datetime,Path: WideString); |
| | | Function FindLastestDocDir(CaseID,DocNo:String):String; //找出最新的DocDir |
| | | Procedure Create_Cust_DocDir(CaseID:String); //產生外面傳入的文件代號及自定文件 |
| | | Procedure OldCasetoNewCase(CaseID:String); //將舊案份數轉成新規則 |
| | | Procedure ErrFormtoCurrentForm(CaseID,EFormID,CFormID:String);//將舊案的錯誤FormID改正確的FormID |
| | | Procedure SetRecordEditedDocDir(Mode:Char;CaseID,DocDir:String); //記錄被異動的文件目錄 'A:加入D:刪掉' |
| | | Function GetDocDir_Page(CaseID,DocDir:String):Integer; //取得DocDir的頁數 |
| | | Function Path2DocDir(Path,CaseID:String):String; |
| | | Function GetDocNo_IS_WH(DocNo:String):Boolean; //DocNo是否為入庫文件 |
| | | Procedure SortDocDir_FormID(CaseID,DocDir:String); //將DocDir裡的文件編號排序 |
| | | Procedure GotoAttach(OldLevel:Integer); |
| | | Function DocNoIs_In_WH(DocNo:String):Boolean; //DocNo是否為入庫文件 |
| | | Procedure CreateCaseNeedData(Path:String); //先做影像截取會少二個文字檔,產生CaseDocNo.dat及DocDir.dat |
| | | Procedure SetDocDirtoSelected(CaseNode:TTreeNode;DocDir:String); |
| | | Function CheckSelectImg_UseCase(Path,CaseID:String):Boolean; //檢查選擇的影像是否有包含被引用的影像 |
| | | Function TransOldCaseFile(Path:String):Boolean; //上傳引用舊件的記錄檔 |
| | | Function Writelog(CaseID:String):Boolean; |
| | | Function FormIDAppear(FormID:String):Boolean; //FormID是否可出現 |
| | | Function DocNoAppear(DocNo:String):Boolean; //DocNo是否可出現 |
| | | Function GetDocNoCount(CaseID,DocNo:String):Integer; //取DocNo數量 |
| | | Function GetDocDirCopies(CaseID,DocDir:String):Integer; //取DocDir份數 |
| | | Procedure SetDocDirCopies(CaseID,DocDir:String;NewCopies:Integer); //修改DocDir份數 |
| | | Function GetDocDirCopies_Rec(Path,CaseID,DocDir:String):Integer; //取記錄裡的DocDir份數 |
| | | Function GetCustomNameCount(CustomName:String):Integer; //取外傳的名稱數量 |
| | | Function GetCustomDocNoCount(Docno:String):Integer; //取外傳的DocNo數量 |
| | | Function ISGuideFormID(FormID:String):Boolean; |
| | | Function CaseDelete_Enable(CaseID:String):Boolean; //案件可否被刪除 |
| | | |
| | | Procedure MoveImage(Path:String;mp:Integer); //移動頁數 |
| | | Procedure MoveImage_Drag(Path:String;fp,tp:Integer); //拖拉移動頁數 |
| | | |
| | | |
| | | Procedure SetUseCase(Mode:Char;Path,DocDir,FormCaseID,ToCaseID:String); //記錄引用其他案件 A:加入 D:刪掉 |
| | | Function GetUseCase(Mode:Char;Path,DocDir:String):String; //F:取被引用 To:引用 |
| | | |
| | | Procedure Case2upload(CaseID:String); |
| | | Procedure Download2Case(SoDir,DeDir:String); |
| | | |
| | | procedure view_image_FormCode(Path,FormCode:String;stpage,stview:integer); //用FormCode來找影像 |
| | | procedure view_image_DocNo(Path,DocNo,FormID:String;Pages:integer); //用DocNo來找影像 |
| | | |
| | | Function ShapeName2PreViewISBName(SP:TShape):String; //轉出指定PreViewISBName |
| | | Procedure CreatePreViewISB(Count:Integer); |
| | | Procedure FreePreViewISB; |
| | | Procedure FitPreViewISB; |
| | | Procedure PaintShape(FromImg,ToImg:TImageScrollBox); //畫有被選取的影像 |
| | | Procedure FreeShapeobj(SelectISB : TImageScrollBox); |
| | | Procedure ISBClick(Sender : TObject); |
| | | Procedure ISBMouseMove(Sender: TObject; Shift: TShiftState; |
| | | X, Y: Integer); |
| | | procedure ISBImageMouseDown(Sender: TObject; Button: TMouseButton; |
| | | Shift: TShiftState; X, Y: Integer); |
| | | procedure ISBImageMouseUp(Sender: TObject; Button: TMouseButton; |
| | | Shift: TShiftState; X, Y: Integer); |
| | | procedure ISBEndDrag(Sender, Target: TObject; X, Y: Integer); |
| | | procedure ISBDragDrop(Sender, Source: TObject; X, Y: Integer); |
| | | procedure ISBDragOver(Sender, Source: TObject; X, Y: Integer; |
| | | State: TDragState; var Accept: Boolean); |
| | | |
| | | procedure BtnMouseEnter(Sender: TObject); |
| | | |
| | | procedure PageEnd; //掃描接收完成 |
| | | Procedure PageDone; //掃描完成後顯示影像 |
| | | procedure StatrTwainScan; |
| | | procedure OnAcquire( const DibHandle : THandle; |
| | | const XDpi : Word; |
| | | const YDpi : Word; |
| | | const CallBackData : LongInt ); |
| | | |
| | | |
| | | procedure ActivateEvent(Sender: TObject); |
| | | procedure ClickEvent(Sender: TObject); |
| | | procedure CreateEvent(Sender: TObject); |
| | | procedure DblClickEvent(Sender: TObject); |
| | | procedure DeactivateEvent(Sender: TObject); |
| | | procedure DestroyEvent(Sender: TObject); |
| | | procedure KeyPressEvent(Sender: TObject; var Key: Char); |
| | | procedure MouseEnterEvent(Sender: TObject); |
| | | procedure MouseLeaveEvent(Sender: TObject); |
| | | procedure PaintEvent(Sender: TObject); |
| | | function GetCurrentVersionNo: String; |
| | | procedure initParameter; |
| | | procedure LastInitFormidListCreate(path:string); |
| | | function checkFormCodeIsCustom(path,formcode:string):boolean; |
| | | function ISDivPageFormID(FormID: String): Boolean; |
| | | function GetSampleInf: Boolean; |
| | | procedure InitExistImgList(casepath:String); |
| | | function LoadFileGetMD5(const filename:string):string; //20170809 取的檔案的MD5 |
| | | function ISExistImg(const filename:string):boolean; //20170809 確認是否存在原有影像 |
| | | procedure _DelTreeForExistImg(ASourceDir:String); //2017 刪除前確認 有舊影像嗎 |
| | | function DocNoIsExistImg(DocNopath: String): boolean; //2017 刪除前確認 有舊影像嗎 |
| | | function CheckCaseAttach_OK: Boolean; |
| | | function DeleteDocNoFileForESCAN(Path, DocNo: String): Boolean; //2017 確認是否有未歸類文件 |
| | | function CheckRequiredColumnValues(workno,caseno:String) :Boolean; //20171003 此大類下此案是否檢核必填 |
| | | function logTimeString :String; |
| | | procedure ReduceLogFile ; |
| | | function FindLastestDocDirForPage(CaseID, DocNo, formid: String): String; |
| | | function OMRErrini2ListForLog(CaseID: String):String; |
| | | protected |
| | | { Protected declarations } |
| | | procedure DefinePropertyPages(DefinePropertyPage: TDefinePropertyPage); override; |
| | | procedure EventSinkChanged(const EventSink: IUnknown); override; |
| | | function Get_Active: WordBool; safecall; |
| | | function Get_AlignDisabled: WordBool; safecall; |
| | | function Get_AlignWithMargins: WordBool; safecall; |
| | | function Get_AutoScroll: WordBool; safecall; |
| | | function Get_AutoSize: WordBool; safecall; |
| | | function Get_AxBorderStyle: TxActiveFormBorderStyle; safecall; |
| | | function Get_Caption: WideString; safecall; |
| | | function Get_Color: OLE_COLOR; safecall; |
| | | function Get_DockSite: WordBool; safecall; |
| | | function Get_DoubleBuffered: WordBool; safecall; |
| | | function Get_DropTarget: WordBool; safecall; |
| | | function Get_Enabled: WordBool; safecall; |
| | | function Get_ExplicitHeight: Integer; safecall; |
| | | function Get_ExplicitLeft: Integer; safecall; |
| | | function Get_ExplicitTop: Integer; safecall; |
| | | function Get_ExplicitWidth: Integer; safecall; |
| | | function Get_Font: IFontDisp; safecall; |
| | | function Get_HelpFile: WideString; safecall; |
| | | function Get_KeyPreview: WordBool; safecall; |
| | | function Get_MouseInClient: WordBool; safecall; |
| | | function Get_ParentCustomHint: WordBool; safecall; |
| | | function Get_ParentDoubleBuffered: WordBool; safecall; |
| | | function Get_PixelsPerInch: Integer; safecall; |
| | | function Get_PopupMode: TxPopupMode; safecall; |
| | | function Get_PrintScale: TxPrintScale; safecall; |
| | | function Get_Scaled: WordBool; safecall; |
| | | function Get_ScreenSnap: WordBool; safecall; |
| | | function Get_SnapBuffer: Integer; safecall; |
| | | function Get_UseDockManager: WordBool; safecall; |
| | | function Get_Visible: WordBool; safecall; |
| | | function Get_VisibleDockClientCount: Integer; safecall; |
| | | procedure _Set_Font(var Value: IFontDisp); safecall; |
| | | procedure Set_AlignWithMargins(Value: WordBool); safecall; |
| | | procedure Set_AutoScroll(Value: WordBool); safecall; |
| | | procedure Set_AutoSize(Value: WordBool); safecall; |
| | | procedure Set_AxBorderStyle(Value: TxActiveFormBorderStyle); safecall; |
| | | procedure Set_Caption(const Value: WideString); safecall; |
| | | procedure Set_Color(Value: OLE_COLOR); safecall; |
| | | procedure Set_DockSite(Value: WordBool); safecall; |
| | | procedure Set_DoubleBuffered(Value: WordBool); safecall; |
| | | procedure Set_DropTarget(Value: WordBool); safecall; |
| | | procedure Set_Enabled(Value: WordBool); safecall; |
| | | procedure Set_Font(const Value: IFontDisp); safecall; |
| | | procedure Set_HelpFile(const Value: WideString); safecall; |
| | | procedure Set_KeyPreview(Value: WordBool); safecall; |
| | | procedure Set_ParentCustomHint(Value: WordBool); safecall; |
| | | procedure Set_ParentDoubleBuffered(Value: WordBool); safecall; |
| | | procedure Set_PixelsPerInch(Value: Integer); safecall; |
| | | procedure Set_PopupMode(Value: TxPopupMode); safecall; |
| | | procedure Set_PrintScale(Value: TxPrintScale); safecall; |
| | | procedure Set_Scaled(Value: WordBool); safecall; |
| | | procedure Set_ScreenSnap(Value: WordBool); safecall; |
| | | procedure Set_SnapBuffer(Value: Integer); safecall; |
| | | procedure Set_UseDockManager(Value: WordBool); safecall; |
| | | procedure Set_Visible(Value: WordBool); safecall; |
| | | procedure Set_caseid(const Value: WideString); safecall; |
| | | procedure Set_data(const Value: WideString); safecall; |
| | | procedure Set_mode(const Value: WideString); safecall; |
| | | procedure Set_rewrite(const Value: WideString); safecall; |
| | | procedure Set_url(const Value: WideString); safecall; |
| | | procedure Set_userid(const Value: WideString); safecall; |
| | | procedure Set_username(const Value: WideString); safecall; |
| | | procedure Set_verify(const Value: WideString); safecall; |
| | | procedure Set_language(const Value: WideString); safecall; |
| | | procedure Set_modename(const Value: WideString); safecall; |
| | | procedure Set_userunit(const Value: WideString); safecall; |
| | | procedure Set_work_no(const Value: WideString); safecall; |
| | | procedure Set_loandoc_enable(const Value: WideString); safecall; |
| | | procedure Set_loandoc_value(const Value: WideString); safecall; |
| | | procedure Set_useproxy(const Value: WideString); safecall; |
| | | procedure Set_c_docnamelist(const Value: WideString); safecall; |
| | | procedure Set_c_docnolist(const Value: WideString); safecall; |
| | | procedure Set_fixfilelist(const Value: WideString); safecall; |
| | | procedure Set_is_in_wh(const Value: WideString); safecall; |
| | | procedure Set_oldcaseinfo(const Value: WideString); safecall; |
| | | function Get_c_docnamelist: WideString; safecall; |
| | | function Get_c_docnolist: WideString; safecall; |
| | | function Get_caseid: WideString; safecall; |
| | | function Get_data: WideString; safecall; |
| | | function Get_fixfilelist: WideString; safecall; |
| | | function Get_is_in_wh: WideString; safecall; |
| | | function Get_language: WideString; safecall; |
| | | function Get_loandoc_enable: WideString; safecall; |
| | | function Get_loandoc_value: WideString; safecall; |
| | | function Get_mode: WideString; safecall; |
| | | function Get_modename: WideString; safecall; |
| | | function Get_oldcaseinfo: WideString; safecall; |
| | | function Get_rewrite: WideString; safecall; |
| | | function Get_url: WideString; safecall; |
| | | function Get_useproxy: WideString; safecall; |
| | | function Get_userid: WideString; safecall; |
| | | function Get_username: WideString; safecall; |
| | | function Get_userunit: WideString; safecall; |
| | | function Get_verify: WideString; safecall; |
| | | function Get_work_no: WideString; safecall; |
| | | function Get_printyn: WideString; safecall; |
| | | procedure Set_printyn(const Value: WideString); safecall; |
| | | function Get_is_oldcase: WideString; safecall; |
| | | procedure Set_is_oldcase(const Value: WideString); safecall; |
| | | function Get_custdocyn: WideString; safecall; |
| | | procedure Set_custdocyn(const Value: WideString); safecall; |
| | | function Get_casenolength: WideString; safecall; |
| | | function Get_filesizelimit: WideString; safecall; |
| | | function Get_imgdpi: WideString; safecall; |
| | | function Get_scancolor: WideString; safecall; |
| | | procedure Set_casenolength(const Value: WideString); safecall; |
| | | procedure Set_filesizelimit(const Value: WideString); safecall; |
| | | procedure Set_imgdpi(const Value: WideString); safecall; |
| | | procedure Set_scancolor(const Value: WideString); safecall; |
| | | function Get_imgdelete: WideString; safecall; |
| | | procedure Set_imgdelete(const Value: WideString); safecall; |
| | | function Get_check_main_form: WideString; safecall; |
| | | function Get_isExternal: WideString; safecall; |
| | | procedure Set_check_main_form(const Value: WideString); safecall; |
| | | procedure Set_isExternal(const Value: WideString); safecall; |
| | | function Get_WH_CATEGORY: WideString; safecall; |
| | | procedure Set_WH_CATEGORY(const Value: WideString); safecall; |
| | | public |
| | | { Public declarations } |
| | | procedure Initialize; override; |
| | | end; |
| | | |
| | | implementation |
| | | |
| | | uses EnBarcode, |
| | | EnTransf, { for TImageTransform } |
| | | Enpnggr, { for PNGGraphic } |
| | | EnJpgGr, { for JPGGraphic } |
| | | EnReg, |
| | | EnBmpGr, { for TBitmapGraphic } |
| | | EnPrint, { for TEnvisionPrintMode, TDibGraphicPrinter } |
| | | ComObj, ComServ,IISUnit,IIS_File2Web,IIS_ImageProcess, |
| | | PatchFom,Doclist,ScanMemo,DocCopy,InputMask,SortMemo,DocPrt,OldCaseInfo; |
| | | |
| | | {$R *.DFM} |
| | | |
| | | { TCB_IMGPSScanX } |
| | | |
| | | {$I CB_IMGPSScanImp_UI.pas}{$I CB_IMGPSScanImp_Scan.pas}{$I CB_IMGPSScanImp_Data.pas}{$I CB_IMGPSScanImp_Utils.pas}initialization |
| | | TActiveFormFactory.Create( |
| | | ComServer, |
| | | TActiveFormControl, |
| | | TCB_IMGPSScanX, |
| | | Class_CB_IMGPSScanX, |
| | | 1, |
| | | '', |
| | | OLEMISC_SIMPLEFRAME or OLEMISC_ACTSLIKELABEL, |
| | | tmApartment); |
| | | |
| | | {SetLicenseKey('5B4451E676A1D2976FBB0F3BB18341336AF114C80B5ABAE7F6926B1CAF671F44' + |
| | | 'BD2F098CCEDA922F6389BFAE398DA6AEE67F97EEA0C17234C20D75C12173DBDA' + |
| | | '594924D56DD8E342F454389C836AD880BB4352CA3BE62C4933B1BA3828E7462C' + |
| | | '60514F2ECDAD322E6128D841F12D24DA00B623106D3F08EBCAA917D8A97CAA34' + |
| | | '3D65F2DA567316457395BF9123EE53DF235D181F191A5712DBB27735284AA92D' + |
| | | '5DFA0C8308308505F384707E900C6063F53F1BFF4C6972607955D1AE517B19D0' + |
| | | '82CDD16301885403AD229D57BAEF98C056F31430861E5F68F339D658D72E1F92' + |
| | | '63899412EC2D07891FE3AFD35F3E4A4390B2F0A8A1BF1B7D6160E5F1CC009B17'); } |
| | | SetLicenseKey('4B2CF65E8C2A86CE8A0DD0F6A7DB03BC0B0126168B48AE4C27EBD78CAE75CF0F' + |
| | | 'A612190861E0D99F6FAE3ED97AC1941B5E97843CFFCF705A3787989072D4EB2C' + |
| | | 'AE6CAB3F5B69B86616ACC8A37AD6A2AB21C7BDD5C9AE1EDF9E4193D353805C9A' + |
| | | '403631CE8A3D0803FEBB1BE4C209CE7A63B1298EF080EB34B8628CED567D2B68' + |
| | | 'E777FAC58E2E32B7411FC217A04336231D1E861A93474759DAA6EDF53F6EB632' + |
| | | 'A3055229A52F3053FB844754741409022DDE3DFB19473510F2BE63328E74BE20' + |
| | | 'A6A29AA24878F91ADA9DF8CE1F320AF4DAF58EBF95D9BE761D70EEA274E19475' + |
| | | '1C15948B184264C5C49E60493F3BCD2FFAE2CA8B021D00B96F45550C5F050D8A'); |
| | | |
| | | |
| | | SetLicenseKey('A6A94A8D91B08A9D58F300C0573EA9EF1B9DB0BF69B90E13B958DB4CB6B44F5A' + |
| | | '4EE9CB22C9A68C2D07ED52ED4D13C755D890E4074996755361E6CDE2A6F1B563' + |
| | | '5DDC8999AC4D71FB092EA9F1F87BFA25694FBF0D6D250087D2B39629713FCCB0' + |
| | | 'D0A83135BC14FC63A4E8331CFF9E24C45C2D9CFD837EB70BAFDB79A75B7B97D5' + |
| | | 'E9EB271685118C29D90A7C85E7793908989E295DA50021C795A448366026E975' + |
| | | 'F49EA75B721B80427B99E5CF24A225FB498C07946ED7B806B483654C00D85C66' + |
| | | 'E34215CA3EDEF1D4C3F5896090E97E1E2C9752BA2D5B49EE58CF19A0D374077F' + |
| | | '6D13B90B6FED22D9EBC3AD6CDC76E595E08725BF2E12B8EF30A524A2E00504DF'); |
| | | end. |
| 比對新檔案 |
| | |
| | | import { applyDataMixins } from './CB_IMGPSScanImp_Data'; |
| | | import { applyScanMixins } from './CB_IMGPSScanImp_Scan'; |
| | | import { applyUIMixins } from './CB_IMGPSScanImp_UI'; |
| | | import { applyUtilsMixins, TStringList, TImageFormat, TTransMode, TFtpProtocol } from './CB_IMGPSScanImp_Utils'; |
| | | |
| | | export class TCB_IMGPSScanX { |
| | | Ch_WriteNote=false; RejectCase=false; ErrIndex=0; Def_DeviceDelete=true; Def_DeviceDeleteSize=3072; Def_ScannerReverse=false; Def_BoardClear=false; Def_ScanDpi=300; Def_ScanDuplex=true; Def_ScanRotate=0; Def_ScanDeskew=false; Def_ScanBright=0; Def_ScanContrast=0; Def_ScanImgShowMode=2; Def_ScanImgSetUse=false; |
| | | FUrl=''; FCaseID=''; FMode=''; FModeName=''; FWork_no=''; FUserID=''; FUserName=''; FUserUnit=''; FData=''; FVerify=''; FReWrite=''; FLanguage='zh_tw'; FLoanDoc_Value=''; FLoanDoc_Enable=''; FUseProxy=''; FC_DocNoList=''; FC_DocNameList=''; FFixFileList=''; FIs_In_Wh=''; FOldCaseInfo=''; FPrintyn=''; FIs_OldCase=''; FCustDocYN=''; FImgDPI=300; FScanColor=0; FFileSizeLimit=5120; FCaseNoLength=16; FImgDelete='N'; FIsExternal='N'; FCheck_main_form='N'; FWH_category='N'; |
| | | TransMode=TTransMode.tsNone; HttpErrStr=''; FFtpIP=''; FFtpID=''; FFtpPwd=''; FFtpRootPath=''; FFtpExtraPath=''; FFtpPort=0; FFtpProtocol=TFtpProtocol.fpftp; DeviceDelete=false; DeviceDeleteSize=0; ScannerReverse=false; BoardClear=false; ScanDpi=0; ScanDuplex=false; ScanRotate=0; ScanDeskew=false; ScanBright=0; ScanContrast=0; ScanImgShowMode=0; ScanImgSetUse=false; ScanColor:TImageFormat='ifBlackWhite'; |
| | | ServerDate=''; ServerTime=''; Balance=0; ScanDenialTime=''; ScanDenialHint=''; ScanPath=''; ImagePath=''; ImageSavePath=''; ScaniniPath=''; LngPath=''; CheckXmlPath=''; SitePath=''; SamplePath=''; TransPath=''; ScanSaveFilename=''; ScanCaseno=''; ScanDocDir=''; |
| | | Doc_Inf_List=new TStringList(); DM_FORM_INF_List=new TStringList(); FORM_INF_List=new TStringList(); CHECK_RULE_INF_List=new TStringList(); MEMO_INF_List=new TStringList(); WORK_INF_List=new TStringList(); LASTEST_FORM_INF_List=new TStringList(); FindResult=new TStringList(); CaseList=new TStringList(); Context_DocnoList=new TStringList(); CaseDocNoList=new TStringList(); CaseDocNo_CopiesList=new TStringList(); ContextList=new TStringList(); AttContextList=new TStringList(); OMRFileList=new TStringList(); Cust_DocNoList=new TStringList(); IN_WH_DocNoList=new TStringList(); FormCode_PageSize=new TStringList(); DocNo_NeedDoc=new TStringList(); DocNo_NoDoc=new TStringList(); DocNo_VerinCase=new TStringList(); NoSaveBarCodeList=new TStringList(); FormID_List=new TStringList(); DocNo_List=new TStringList(); NowShowFileList=new TStringList(); NowSelectFileList=new TStringList(); GuideFormIDList=new TStringList(); DivPageFormIDList=new TStringList(); LastInitFormidList=new TStringList(); LastAddFormidList=new TStringList(); SampleFormIDList=new TStringList(); ExistImgList=new TStringList(); reSizeExistImgList=new TStringList(); |
| | | NowCaseno=''; NowDocNo=''; NowDocDir=''; NowFormCode=''; NowFormName=''; NowPage=0; DisplayPath=''; Case_loandoc=''; UpLPoint={X:0,Y:0}; UpRPoint={X:0,Y:0}; DownLPoint={X:0,Y:0}; DownRPoint={X:0,Y:0}; Point_Width=''; Point_Height=''; CaseIDLength=0; FormIDLength=0; DocNoLength=0; PEFileName=''; DownFileErrStr=''; |
| | | ISB:any=null; ScanInfo:any={}; TwainShowUI=false; MpsBarcodeinf:any={}; ScanMode:any=null; Mpskey=''; Seg=0; VMode=0; NowClick=0; ScanIP=''; DisplayISB:any=null; SelectISB:any=null; SelectPage=0; NewTreeNode:any=null; MyTreeNode1:any=null; MyTreeNode2:any=null; MyTreeNode3:any=null; InitialOk=false; ShowText=''; Ext=''; SafePixel=0; OMRErrInfo:any[]=[]; ScrollRec:any[]=[]; RecHozPos=0; RecVerPos=0; ReczoomPercent=0; SortMode=false; PreMytreeNode2Name=''; HS=0; VS=0; iRate=0; Bt=0; BarCodeRotate=0; HaveAppDoc=false; PageLVclear=false; CaseCount=0; PageCount=0; Item:any=null; SampleAnchorMode=''; DownImgStatus=''; TransForm_Field=''; NowWork_No=''; CropBarcode=''; Has_Authorize=''; AttName=''; NowGuideFormID=''; NowDivPageFormID=''; FirstDocDir=''; FMaxUploadSize=''; FJpgCompression=0; Draging=false; MDown=false; FEvents:any=null; |
| | | [key: string]: any; |
| | | constructor(){ console.log("TCB_IMGPSScanX Initialized"); } |
| | | } |
| | | applyDataMixins(TCB_IMGPSScanX); applyScanMixins(TCB_IMGPSScanX); applyUIMixins(TCB_IMGPSScanX); applyUtilsMixins(TCB_IMGPSScanX); |
| 比對新檔案 |
| | |
| | | function TCB_IMGPSScanX.Get_Color: OLE_COLOR; |
| | | begin |
| | | Result := OLE_COLOR(Color); |
| | | end; |
| | | |
| | | procedure TCB_IMGPSScanX.Set_Color(Value: OLE_COLOR); |
| | | begin |
| | | Color := TColor(Value); |
| | | end; |
| | | |
| | | procedure TCB_IMGPSScanX.StatrTwainScan; |
| | | var ScanInfo : TScanInfo; |
| | | i : Integer; |
| | | begin |
| | | if not Scanner.IsConfigured then |
| | | begin |
| | | ShowMessage(_Msg('TWAIN 掃瞄驅動尚未安裝')); |
| | | Exit; |
| | | end; |
| | | FillChar(ScanInfo, SizeOf(ScanInfo), 0); |
| | | ScanInfo.MultiPage := True; |
| | | ScanInfo.ImageCount := 0; |
| | | ScanInfo.Graphic := TTiffGraphic.Create; |
| | | try |
| | | ISB := nil; //規零 |
| | | //ShowMessage(IntToStr(ScanDpi)); |
| | | Scanner.RequestedXDpi := ScanDpi; |
| | | Scanner.RequestedYDpi := ScanDpi; |
| | | Scanner.RequestedImageFormat := ScanColor; |
| | | Scanner.ShowUI := TwainShowUI; |
| | | |
| | | Try |
| | | Scanner.OpenSource; |
| | | Scanner.Duplex := ScanDuplex; //雙面 |
| | | |
| | | if FMode='SAMPLESCAN' then |
| | | Scanner.Duplex:=False; |
| | | |
| | | //Scanner.FEEDERENABLED := not ScanFlatCB.Checked; // 先拿掉平台 |
| | | If ScanImgSetUse Then |
| | | begin |
| | | Scanner.ScanBrightness := ScanBright; |
| | | Scanner.ScanContrast := ScanContrast; |
| | | end; |
| | | except |
| | | Showmessage(_Msg('掃瞄器發生錯誤!!')); |
| | | Scanner.CloseSource; |
| | | Exit; |
| | | end; |
| | | try |
| | | Try |
| | | Scanner.AcquireWithSourceOpen( OnAcquire, LongInt(@ScanInfo)); |
| | | Except |
| | | Scanner.CloseSource; |
| | | end; |
| | | finally |
| | | Scanner.CloseSource; |
| | | end; |
| | | finally |
| | | Scanner.CloseSource; |
| | | ScanInfo.Graphic.Free; |
| | | end; |
| | | end; |
| | | |
| | | procedure TCB_IMGPSScanX.OnAcquire( const DibHandle : THandle; |
| | | const XDpi : Word; |
| | | const YDpi : Word; |
| | | const CallBackData : LongInt ); |
| | | var |
| | | pScanInfo : TpScanInfo; |
| | | SaveFileName : String; |
| | | SaveStream : TFileStream; |
| | | strResults : TBarcodeStringResult; |
| | | DeleteStm : TMemoryStream; |
| | | isDelete : Boolean; |
| | | iGraphic,iGraphic_First,iGraphic_sec : TTiffGraphic; |
| | | iRect : TRect; |
| | | JpgGr : TJpegGraphic; |
| | | i : Integer; |
| | | TagTxt : String; |
| | | function Deletepage(Graphic:TDibGraphic;BlankSize:Integer):Boolean; |
| | | begin |
| | | DeleteStm := TMemoryStream.Create; |
| | | DeleteStm.Seek(0,soFromBeginning); |
| | | Graphic.AppendToStream(DeleteStm); |
| | | |
| | | //DeleteStm.LoadFromFile(Path+'temp.tif'); |
| | | //Isb1.Graphic.SaveToStream(DeleteStm); |
| | | //Isb1.Graphic.AppendToStream(DeleteStm); |
| | | if DeleteStm.Size < BlankSize Then |
| | | Result:= True |
| | | Else |
| | | Result := False; |
| | | |
| | | DeleteStm.Free; |
| | | //DeleteFile(Path+'temp.tif'); |
| | | end; |
| | | begin |
| | | pScanInfo := TpScanInfo(CallBackData); |
| | | isDelete := False; |
| | | |
| | | if pScanInfo^.MultiPage then |
| | | begin |
| | | pScanInfo^.Graphic.AssignFromDibHandle(DibHandle); |
| | | pScanInfo^.Graphic.XDotsPerInch := XDpi; |
| | | pScanInfo^.Graphic.YDotsPerInch := YDpi; |
| | | TagTxt := 'height:'+inttostr(pScanInfo^.Graphic.Height)+',width:'+inttostr(pScanInfo^.Graphic.Width); |
| | | if pScanInfo^.Graphic.ImageFormat = ifBlackWhite then |
| | | begin |
| | | ImageScrollBox1.Graphic.Assign(pScanInfo^.Graphic); |
| | | ImageScrollBox1NewGraphic(ImageScrollBox1.Graphic); |
| | | |
| | | pScanInfo^.Graphic.Compression := tcGroup4; |
| | | MpsGetBarcode(pScanInfo^.Graphic,MpsBarcodeinf); |
| | | For i := 1 to MpsBarcodeinf.count do |
| | | begin |
| | | If (MpsBarcodeinf.r180[i] <> 0) and (Length(MpsBarcodeinf.Text[i])=FormIDLength) Then |
| | | begin |
| | | Rotate(pScanInfo^.Graphic,MpsBarcodeinf.r180[i]); |
| | | MpsGetBarcode(pScanInfo^.Graphic,MpsBarcodeinf); //旋轉後再重取一次條碼資訊 |
| | | Break; |
| | | end; |
| | | end; |
| | | |
| | | //影像反向 |
| | | IF ScannerReverse then |
| | | NegativeImg(pScanInfo^.Graphic); |
| | | //傾斜矯正 |
| | | IF ScanDeskew Then |
| | | DeskewImg(pScanInfo^.Graphic); |
| | | //清黑邊 |
| | | IF BoardClear then |
| | | CleanupBorder(pScanInfo^.Graphic); |
| | | end |
| | | else if pScanInfo^.Graphic.ImageFormat = ifTrueColor then |
| | | begin |
| | | //Ext := '.jpg'; |
| | | ImageScrollBox1.Graphic.Assign(pScanInfo^.Graphic); |
| | | //ImageScrollBox1NewGraphic(ImageScrollBox1.Graphic); |
| | | MpsGetBarcode(ISB_BW.Graphic,MpsBarcodeinf); |
| | | For i := 1 to MpsBarcodeinf.count do |
| | | begin |
| | | If (MpsBarcodeinf.r180[i] <> 0) and (Length(MpsBarcodeinf.Text[i])=FormIDLength) Then |
| | | begin |
| | | Rotate(ISB_BW.Graphic,MpsBarcodeinf.r180[i]); |
| | | MpsGetBarcode(ISB_BW.Graphic,MpsBarcodeinf); //旋轉後再重取一次條碼資訊 |
| | | Break; |
| | | end; |
| | | end; |
| | | pScanInfo^.Graphic.Compression := tcJpeg; |
| | | pScanInfo^.Graphic.JpegQuality := FJpgCompression; |
| | | end |
| | | else if pScanInfo^.Graphic.ImageFormat = ifColor256 Then |
| | | begin |
| | | //Ext := '.jpg'; |
| | | ConvertToGray(pScanInfo^.Graphic); |
| | | pScanInfo^.Graphic.Compression := tcJpeg; |
| | | pScanInfo^.Graphic.JpegQuality := FJpgCompression; |
| | | end |
| | | else if pScanInfo^.Graphic.ImageFormat = ifGray256 Then |
| | | begin |
| | | //Ext := '.jpg'; |
| | | ImageScrollBox1.Graphic.Assign(pScanInfo^.Graphic); |
| | | ImageScrollBox1NewGraphic(ImageScrollBox1.Graphic); |
| | | MpsGetBarcode(ISB_BW.Graphic,MpsBarcodeinf); |
| | | For i := 1 to MpsBarcodeinf.count do |
| | | begin |
| | | If (MpsBarcodeinf.r180[i] <> 0) and (Length(MpsBarcodeinf.Text[i])=FormIDLength) Then |
| | | begin |
| | | Rotate(ISB_BW.Graphic,MpsBarcodeinf.r180[i]); |
| | | MpsGetBarcode(ISB_BW.Graphic,MpsBarcodeinf); //旋轉後再重取一次條碼資訊 |
| | | Break; |
| | | end; |
| | | end; |
| | | pScanInfo^.Graphic.Compression := tcJpeg; |
| | | pScanInfo^.Graphic.JpegQuality := FJpgCompression; |
| | | //ShowMessage(IntToStr(pScanInfo^.Graphic.JpegQuality)); |
| | | //if pScanInfo^.Graphic.Compression = tcJpeg then |
| | | //begin |
| | | //ShowMessage('jpg'); |
| | | //end; |
| | | |
| | | end |
| | | else |
| | | begin |
| | | //Ext := '.tif'; |
| | | pScanInfo^.Graphic.Compression := tcPackBits; |
| | | end; |
| | | |
| | | end; |
| | | //Application.ProcessMessages; |
| | | iGraphic_First := TTiffGraphic.Create; |
| | | iGraphic_sec := TTiffGraphic.Create; |
| | | //iGraphic := TTiffGraphic.Create; |
| | | try |
| | | iGraphic_First.Assign(pScanInfo^.Graphic); |
| | | //Application.ProcessMessages; |
| | | if CheckNeedCrop(iGraphic_First) Then |
| | | begin |
| | | iRect.Left := pScanInfo^.Graphic.Width div 2; //先取左邊的影像 |
| | | iRect.Top := 0; |
| | | iRect.Right := pScanInfo^.Graphic.Width; |
| | | iRect.Bottom := pScanInfo^.Graphic.Height; |
| | | CropImg(iGraphic_First,iRect); |
| | | |
| | | iGraphic_Sec.Assign(pScanInfo^.Graphic); //再取右邊的影像 |
| | | iRect.Left := 0; |
| | | iRect.Top := 0; |
| | | iRect.Right := pScanInfo^.Graphic.Width div 2; |
| | | iRect.Bottom := pScanInfo^.Graphic.Height; |
| | | CropImg(iGraphic_Sec,iRect); |
| | | end; |
| | | //iGraphic.Assign(iGraphic_First); |
| | | iGraphic := iGraphic_First; |
| | | |
| | | if iGraphic.ImageFormat=ifGray256 then //20180104 |
| | | begin |
| | | iGraphic.Compression:=tcJPEG; |
| | | iGraphic.JpegQuality:=FJpgCompression; |
| | | end; |
| | | if iGraphic.ImageFormat=ifTrueColor then //20180104 |
| | | begin |
| | | iGraphic.Compression:=tcJPEG; |
| | | iGraphic.JpegQuality:=FJpgCompression; |
| | | end; |
| | | |
| | | //ShowMessage('WTF'); |
| | | while not iGraphic.IsEmpty do |
| | | begin |
| | | //Application.ProcessMessages; |
| | | IF (not DeviceDelete) or (not Deletepage(iGraphic,DeviceDeleteSize)) Then |
| | | begin |
| | | ImageScrollBox1.Graphic.Assign(iGraphic); |
| | | ImageScrollBox1NewGraphic(ImageScrollBox1.Graphic); |
| | | |
| | | MpsGetBarcode(ISB_BW.Graphic,MpsBarcodeinf); |
| | | For i := 1 To MpsBarcodeinf.Count Do |
| | | Begin |
| | | If MpsBarcodeinf.r180[i] <> 0 Then // 依條碼角度轉影像 |
| | | Begin |
| | | Rotate(iGraphic, MpsBarcodeinf.r180[i]); |
| | | Break; |
| | | End; |
| | | End; |
| | | |
| | | if iGraphic.ImageFormat=ifGray256 then //20180104 因此旋轉後變為回packbits 所以要改為jpeg |
| | | begin |
| | | iGraphic.Compression:=tcJPEG; |
| | | iGraphic.JpegQuality:=FJpgCompression; |
| | | end; |
| | | if iGraphic.ImageFormat=ifTrueColor then |
| | | begin |
| | | iGraphic.Compression:=tcJPEG; |
| | | iGraphic.JpegQuality:=FJpgCompression; |
| | | end; |
| | | PageEnd; |
| | | IF PEFileName <> '' Then |
| | | begin |
| | | IF LowerCase(ExtractFileExt(PEFileName)) = '.tif' Then |
| | | begin |
| | | if FileExists( PEFileName ) then |
| | | SaveStream := TFileStream.Create( PEFileName ,fmOpenReadWrite) |
| | | Else |
| | | SaveStream := TFileStream.Create( PEFileName ,fmCreate ); |
| | | try |
| | | SaveStream.Seek(0, soFromBeginning); |
| | | iGraphic.AppendToStream(SaveStream); |
| | | finally |
| | | SaveStream.Free; |
| | | end; |
| | | end |
| | | Else IF LowerCase(ExtractFileExt(PEFileName)) = '.jpg' Then |
| | | begin |
| | | if FileExists( PEFileName ) then |
| | | DeleteFile(PEFileName); |
| | | //SaveStream := TFileStream.Create( PEFileName ,fmCreate ); |
| | | JpgGr := TJpegGraphic.Create; |
| | | try |
| | | JpgGr.Assign(iGraphic); |
| | | JpgGr.SaveQuality := FJpgCompression; |
| | | //JpgGr.AppendToStream(SaveStream); |
| | | JpgGr.SaveToFile(PEFileName); |
| | | finally |
| | | JpgGr.Free; |
| | | //SaveStream.Free; |
| | | end; |
| | | end; |
| | | PageDone; |
| | | end; |
| | | end; |
| | | if iGraphic = iGraphic_First then |
| | | iGraphic := iGraphic_Sec |
| | | else |
| | | iGraphic.Assign(nil); |
| | | //iGraphic.Assign(iGraphic_Sec); |
| | | end; |
| | | finally |
| | | //iGraphic.Free; |
| | | iGraphic_First.Free; |
| | | iGraphic_Sec.Free; |
| | | end; |
| | | |
| | | end; |
| | | |
| | | Procedure TCB_IMGPSScanX.R_W_Scanini(Mode:Char); //'R'讀取;'W'寫入 |
| | | var |
| | | ini : Tinifile; |
| | | begin |
| | | ini := Tinifile.Create(ScaniniPath+'FBScan.ini'); |
| | | try |
| | | case Mode of |
| | | 'R':begin |
| | | DeviceDelete := ini.ReadBool('DeviceDelete','Mode',Def_DeviceDelete); |
| | | DeviceDeleteSize := ini.ReadInteger('DeviceDelete','Size_New',Def_DeviceDeleteSize); |
| | | ScannerReverse := ini.ReadBool('Scanner','Reverse',Def_ScannerReverse); |
| | | BoardClear := ini.ReadBool('Scanner','BoardClear',Def_BoardClear); |
| | | //ScanDpi := ini.ReadInteger('Scanner','Dpi',Def_ScanDpi); |
| | | //ScanDuplex := ini.ReadBool('Scanner','Duplex',Def_ScanDuplex); |
| | | ScanRotate := ini.ReadInteger('Scanner','ScanRotate',Def_ScanRotate); |
| | | ScanDeskew := ini.ReadBool('Scanner','ScanDeskew',Def_ScanDeskew); |
| | | ScanBright := ini.ReadInteger('Scanner','ScanBright',Def_ScanBright); |
| | | ScanContrast := ini.ReadInteger('Scanner','ScanContrast',Def_ScanContrast); |
| | | ScanImgShowMode := ini.ReadInteger('Scanner','ScanImgShowMode',Def_ScanImgShowMode); |
| | | ScanImgSetUse := ini.ReadBool('Scanner','ScanImgSetUse',Def_ScanImgSetUse); //20101110 BA說掃瞄器廠商有調設定要新增此選項是否啟動 |
| | | end; |
| | | 'W':begin |
| | | ini.WriteBool('DeviceDelete','Mode',DeviceDelete); |
| | | ini.WriteInteger('DeviceDelete','Size_New',DeviceDeleteSize); |
| | | ini.WriteBool('Scanner','Reverse',ScannerReverse); |
| | | ini.WriteBool('Scanner','BoardClear',BoardClear); |
| | | //ini.ReadInteger('Scanner','Dpi',ScanDpi); |
| | | //ini.WriteBool('Scanner','Duplex',ScanDuplex); |
| | | ini.WriteInteger('Scanner','ScanRotate',ScanRotate); |
| | | ini.WriteBool('Scanner','ScanDeskew',ScanDeskew); |
| | | ini.WriteInteger('Scanner','ScanBright',ScanBright); |
| | | ini.WriteInteger('Scanner','ScanContrast',ScanContrast); |
| | | ini.WriteInteger('Scanner','ScanImgShowMode',ScanImgShowMode); |
| | | ini.WriteBool('Scanner','ScanImgSetUse',ScanImgSetUse); //20101110 BA說掃瞄器廠商有調設定要新增此選項是否啟動 |
| | | end; |
| | | end; |
| | | |
| | | finally |
| | | ini.Free; |
| | | end; |
| | | end; |
| | | |
| | | Procedure TCB_IMGPSScanX.GetDefScanIni; //取得掃瞄的預設值 |
| | | var |
| | | i : Integer; |
| | | PARA_NO,PARA_CONTENT: String; |
| | | begin |
| | | Def_DeviceDelete := True; |
| | | Def_DeviceDeleteSize := 3072; //20120821 改成3000(出現) |
| | | Def_ScannerReverse := False; |
| | | Def_BoardClear := False; |
| | | Def_ScanDpi := 300; |
| | | Def_ScanDuplex := True; |
| | | Def_ScanRotate := 0; |
| | | Def_ScanDeskew := False; |
| | | Def_ScanImgSetUse := False; |
| | | Def_ScanBright := 0; |
| | | Def_ScanContrast := 0; |
| | | Def_ScanImgShowMode := 2; |
| | | |
| | | for i := 0 to WORK_INF_List.Count - 1 do |
| | | begin |
| | | IF GetSQLData(WORK_INF_List,'PARA_NO',i) = 'SCAN_BLANKDEL_USE' Then //空白頁啟動 |
| | | begin |
| | | PARA_CONTENT := GetSQLData(WORK_INF_List,'PARA_CONTENT',i); |
| | | if UpperCase(PARA_CONTENT) ='Y' then |
| | | Def_DeviceDelete := True |
| | | Else |
| | | Def_DeviceDelete := False; |
| | | end |
| | | Else if GetSQLData(WORK_INF_List,'PARA_NO',i) = 'SCAN_BLANKDEL_SIZE' Then //空白頁Size |
| | | begin |
| | | PARA_CONTENT := GetSQLData(WORK_INF_List,'PARA_CONTENT',i); |
| | | if PARA_CONTENT = '' then |
| | | Def_DeviceDeleteSize := 0 |
| | | Else |
| | | Def_DeviceDeleteSize := Strtoint(PARA_CONTENT); |
| | | |
| | | end |
| | | Else if GetSQLData(WORK_INF_List,'PARA_NO',i) = 'SCAN_REVERSE' Then //是否需反相 |
| | | begin |
| | | PARA_CONTENT := GetSQLData(WORK_INF_List,'PARA_CONTENT',i); |
| | | if UpperCase(PARA_CONTENT) ='Y' then |
| | | Def_ScannerReverse := True |
| | | Else |
| | | Def_ScannerReverse := False; |
| | | end |
| | | Else if GetSQLData(WORK_INF_List,'PARA_NO',i) = 'SCAN_BOARDCLEAR' Then //是否清黑邊 |
| | | begin |
| | | PARA_CONTENT := GetSQLData(WORK_INF_List,'PARA_CONTENT',i); |
| | | if UpperCase(PARA_CONTENT) ='Y' then |
| | | Def_BoardClear := True |
| | | Else |
| | | Def_BoardClear := False; |
| | | end |
| | | Else if GetSQLData(WORK_INF_List,'PARA_NO',i) = 'SCAN_DPI' Then //掃瞄DPI |
| | | begin |
| | | PARA_CONTENT := GetSQLData(WORK_INF_List,'PARA_CONTENT',i); |
| | | if PARA_CONTENT = '' then |
| | | Def_ScanDpi := 300 |
| | | else |
| | | Def_ScanDpi := Strtoint(PARA_CONTENT); |
| | | end |
| | | Else if GetSQLData(WORK_INF_List,'PARA_NO',i) = 'SCAN_DUPLEX' Then //是否雙面掃瞄 |
| | | begin |
| | | PARA_CONTENT := GetSQLData(WORK_INF_List,'PARA_CONTENT',i); |
| | | if UpperCase(PARA_CONTENT) ='Y' then |
| | | Def_ScanDuplex := True |
| | | Else |
| | | Def_ScanDuplex := False; |
| | | |
| | | end |
| | | Else if GetSQLData(WORK_INF_List,'PARA_NO',i) = 'SCAN_ROTATE_MODE' Then //掃瞄時旋轉角度 |
| | | begin |
| | | PARA_CONTENT := GetSQLData(WORK_INF_List,'PARA_CONTENT',i); |
| | | if PARA_CONTENT = '0' then |
| | | Def_ScanRotate := 0 |
| | | Else if PARA_CONTENT = '1' then |
| | | Def_ScanRotate := 270 |
| | | Else if PARA_CONTENT = '2' then |
| | | Def_ScanRotate := 180 |
| | | Else if PARA_CONTENT = '3' then |
| | | Def_ScanRotate := 90 |
| | | Else |
| | | Def_ScanRotate := 0; |
| | | end |
| | | Else if GetSQLData(WORK_INF_List,'PARA_NO',i) = 'SCAN_DESKEW' Then //是否傾斜矯正 |
| | | begin |
| | | PARA_CONTENT := GetSQLData(WORK_INF_List,'PARA_CONTENT',i); |
| | | if UpperCase(PARA_CONTENT) ='Y' then |
| | | Def_ScanDeskew := True |
| | | Else |
| | | Def_ScanDeskew := False; |
| | | end |
| | | Else if GetSQLData(WORK_INF_List,'PARA_NO',i) = 'SCAN_IMGSET_USE' Then //是否使用亮度對比設定 |
| | | begin |
| | | PARA_CONTENT := GetSQLData(WORK_INF_List,'PARA_CONTENT',i); |
| | | if UpperCase(PARA_CONTENT) ='Y' then |
| | | Def_ScanImgSetUse := True |
| | | else |
| | | Def_ScanImgSetUse := False; |
| | | end |
| | | Else if GetSQLData(WORK_INF_List,'PARA_NO',i) = 'SCAN_BRIGHT' Then //亮度 |
| | | begin |
| | | PARA_CONTENT := GetSQLData(WORK_INF_List,'PARA_CONTENT',i); |
| | | if PARA_CONTENT ='' then |
| | | Def_ScanBright := 0 |
| | | Else |
| | | Def_ScanBright := strtoint(PARA_CONTENT); |
| | | if (Def_ScanBright > 255) or (Def_ScanBright < -255) then |
| | | Def_ScanBright := 0; |
| | | |
| | | end |
| | | Else if GetSQLData(WORK_INF_List,'PARA_NO',i) = 'SCAN_CONTRAST' Then //對比 |
| | | begin |
| | | PARA_CONTENT := GetSQLData(WORK_INF_List,'PARA_CONTENT',i); |
| | | if PARA_CONTENT ='' then |
| | | Def_ScanContrast := 0 |
| | | Else |
| | | Def_ScanContrast := strtoint(PARA_CONTENT); |
| | | if (Def_ScanContrast > 255) or (Def_ScanContrast < -255) then |
| | | Def_ScanContrast := 0; |
| | | end |
| | | Else if GetSQLData(WORK_INF_List,'PARA_NO',i) = 'SCAN_SHOW_MODE' Then |
| | | begin |
| | | PARA_CONTENT := GetSQLData(WORK_INF_List,'PARA_CONTENT',i); |
| | | if PARA_CONTENT = '0' then |
| | | Def_ScanImgShowMode := 0 |
| | | Else if PARA_CONTENT = '1' then |
| | | Def_ScanImgShowMode := 1 |
| | | Else if PARA_CONTENT = '2' then |
| | | Def_ScanImgShowMode := 2 |
| | | Else |
| | | Def_ScanImgShowMode := 0; |
| | | end |
| | | Else if GetSQLData(WORK_INF_List,'PARA_NO',i) = 'CASE_IN_TIME' Then //取進件截止時間 |
| | | begin |
| | | PARA_CONTENT := GetSQLData(WORK_INF_List,'PARA_CONTENT',i); |
| | | ScanDenialTime := PARA_CONTENT; |
| | | end |
| | | Else if GetSQLData(WORK_INF_List,'PARA_NO',i) = 'SCAN_HINT' Then //掃描提示字串 |
| | | begin |
| | | PARA_CONTENT := GetSQLData(WORK_INF_List,'PARA_CONTENT',i); |
| | | ScanDenialHint := PARA_CONTENT; |
| | | end |
| | | Else if GetSQLData(WORK_INF_List,'PARA_NO',i) = 'NO_SAVE_FORM_ID' Then //掃描不存檔之表單代號 |
| | | begin |
| | | PARA_CONTENT := GetSQLData(WORK_INF_List,'PARA_CONTENT',i); |
| | | NoSaveBarCodeList.CommaText := PARA_CONTENT; |
| | | end |
| | | Else if GetSQLData(WORK_INF_List,'PARA_NO',i) = 'LOCAL_PATH' Then //本機端路徑 |
| | | begin |
| | | PARA_CONTENT := GetSQLData(WORK_INF_List,'PARA_CONTENT',i); |
| | | ImagePath:= PARA_CONTENT; |
| | | end |
| | | Else if UpperCase(GetSQLData(WORK_INF_List,'PARA_NO',i)) = 'GUIDEFORMID' Then //當導引頁的表單 |
| | | begin |
| | | PARA_CONTENT := GetSQLData(WORK_INF_List,'PARA_CONTENT',i); |
| | | GuideFormIDList.CommaText := PARA_CONTENT; |
| | | end |
| | | Else if UpperCase(GetSQLData(WORK_INF_List,'PARA_NO',i)) = 'DIVPAGEFORMID' Then //當分案頁的表單 |
| | | begin |
| | | PARA_CONTENT := GetSQLData(WORK_INF_List,'PARA_CONTENT',i); |
| | | DivPageFormIDList.CommaText := PARA_CONTENT; |
| | | end |
| | | Else if UpperCase(GetSQLData(WORK_INF_List,'PARA_NO',i)) = 'FILE_COMPRESSION' Then //20171211 jpg to tif 壓縮比 |
| | | begin |
| | | PARA_CONTENT := GetSQLData(WORK_INF_List,'PARA_CONTENT',i); |
| | | FJpgCompression := StrToInt(PARA_CONTENT); |
| | | end |
| | | Else if UpperCase(GetSQLData(WORK_INF_List,'PARA_NO',i)) = 'MAX_UPLOAD_SIZE' Then //取得 上傳大小的限制(MB) |
| | | begin |
| | | PARA_CONTENT := GetSQLData(WORK_INF_List,'PARA_CONTENT',i); |
| | | FMaxUploadSize := PARA_CONTENT; |
| | | end; |
| | | end; |
| | | ScanDuplex := Def_ScanDuplex; |
| | | end; |
| | | |
| | | procedure TCB_IMGPSScanX.EnableImage(v:integer;Sender : TObject); |
| | | var bmp : Tbitmap; |
| | | begin |
| | | DesableImage; |
| | | bmp := TBitmap.Create; |
| | | try |
| | | ImageList3.GetBitmap(v,bmp); |
| | | TBitBtn(Sender).Glyph.Assign(bmp); |
| | | finally |
| | | bmp.Free; |
| | | end; |
| | | ViewMouseMode(v); |
| | | end; |
| | | |
| | | procedure TCB_IMGPSScanX.DesableImage; |
| | | var bmp : Tbitmap; |
| | | i : integer; |
| | | begin |
| | | NowClick := -1; |
| | | bmp := Tbitmap.Create; |
| | | try |
| | | For i:= 0 to 6 do |
| | | begin |
| | | ImageList4.GetBitmap(i,bmp); |
| | | TBitBtn(FindComponent('FC'+IntToStr(i))).Glyph.Assign(bmp); |
| | | bmp.Width:=0; |
| | | bmp.Handle:=0; |
| | | end; |
| | | finally |
| | | bmp.Free; |
| | | end; |
| | | ViewMouseMode(NowClick); |
| | | end; |
| | | |
| | | Procedure TCB_IMGPSScanX.DeleteImageFile(Path,FileName,CaseID:String); // 刪除檔案 (無法得到DocDir用) |
| | | var |
| | | i : Integer; |
| | | FileList:TStringlist; |
| | | DocDir : String; |
| | | begin |
| | | |
| | | |
| | | DeleteFile(Path+FileName); |
| | | DocDir := Path2DocDir(Path,CaseID); |
| | | //ShowMessage('DocDir='+DocDir); |
| | | SetContextList('D',-1,CaseID,DocDir,FileName); |
| | | {FileList:=TStringlist.Create; |
| | | try |
| | | if FileExists(Path+'Context.dat') then |
| | | FileList.LoadFromFile(Path+'Context.dat'); |
| | | for i := 0 to FileList.Count - 1 do |
| | | begin |
| | | if FileName = FileList.Strings[i] then |
| | | begin |
| | | FileList.Delete(i); |
| | | FileList.SaveToFile(Path+'Context.dat'); |
| | | Break; |
| | | end; |
| | | end; |
| | | if FileList.Count = 0 then |
| | | DeleteFile(Path+'Context.dat'); |
| | | finally |
| | | FileList.Free; |
| | | end;} |
| | | end; |
| | | |
| | | Function TCB_IMGPSScanX.DeleteDocNoFileForESCAN(Path,DocNo:String):Boolean; //刪除指定DocNo文件 |
| | | var |
| | | i,j,k: Integer; |
| | | FName : String; |
| | | ST1,ST2,ST3:TStringList; |
| | | |
| | | begin |
| | | |
| | | Result := False; |
| | | //ShowMessage(DocNo); |
| | | for i := ContextList.Count - 1 downto 0 do |
| | | begin |
| | | FName := ContextList.Strings[i]; |
| | | If (DocNo = FormCode2DocNo(FileName2FormCode(FName))) or (DocNo=AttName) then |
| | | begin |
| | | if not ISExistImg(Path+'\'+FName) then |
| | | begin |
| | | DeleteFile(Path+'\'+FName); |
| | | ContextList.Delete(i); |
| | | end; |
| | | Result := True; //有刪到指定文件 |
| | | end; |
| | | end; |
| | | ContextList.SaveToFile(Path+'\Context.dat'); |
| | | ContextList.LoadFromFile(Path+'\Context.dat'); |
| | | |
| | | if ContextList.Count=0 then |
| | | begin |
| | | _DelTree(Path); |
| | | SetDocNoList('D',-1,NowCaseNo,NowDocDir,''); |
| | | end; |
| | | |
| | | end; |
| | | |
| | | Function TCB_IMGPSScanX.DownLoadImage(Path,CaseID:String):Boolean; |
| | | begin |
| | | Result := True; |
| | | if not GetftpInfo(CaseID,'download') then //取案件下載方式 |
| | | begin |
| | | DownFileErrStr := _Msg('取案件下載資訊失敗,')+HttpErrStr; |
| | | Result := False; |
| | | Exit; |
| | | end; |
| | | case TransMode of |
| | | tsHttp: |
| | | begin |
| | | ShowText := _Msg('案件下載中(Http),請稍候'); |
| | | DataLoading(True,True); |
| | | If not Down_Img(ImageSavePath+FCaseID+'\Download\',FCaseID) then |
| | | begin |
| | | Showmessage(FCaseID+_msg('載入異動影像時,網路發生錯誤')+HttpErrStr); |
| | | DataLoading(False,False); |
| | | Exit; |
| | | end; |
| | | end; |
| | | tsFtp: |
| | | begin |
| | | ShowText := _Msg('案件下載中(Ftp),請稍候'); |
| | | DataLoading(True,True); |
| | | SetFtpInfo; |
| | | |
| | | if not IIS_Ftp.FtpsConnect then |
| | | begin |
| | | DownFileErrStr := Format(_Msg('無法連上Ftp主機,錯誤原因:%s')+#13+'%s',[FtpErrReason,FTPSClient1.LastReceivedReply]); |
| | | Result := False; |
| | | Exit; |
| | | end; |
| | | if not IIS_Ftp.FtpsDownloadFile(FFtpExtraPath,CaseID+'.zip',Path+CaseID+'.zip',display1) then |
| | | begin |
| | | DownFileErrStr := Format(_Msg('錯誤原因:%s'),[FtpErrStr]); |
| | | Result := False; |
| | | Exit; |
| | | end; |
| | | ExecuteUnZip(Path+CaseID+'.zip',Path,False); |
| | | DeleteFile(Path+CaseID+'.zip'); |
| | | end; |
| | | end; |
| | | end; |
| | | |
| | | |
| | | Procedure TCB_IMGPSScanX.CaseResort2Scanlist(Path:String); //案件的檔案重新排序給scanlist(次文件依FormID排) |
| | | var |
| | | i,n,v,v1 : Integer; |
| | | S,S1 : TStringlist; |
| | | FormID,OldName,NewName,DocNo,Doc_Type:String; |
| | | x : Integer; |
| | | begin |
| | | S := TStringlist.Create; |
| | | S1 := TStringlist.Create; |
| | | try |
| | | if FileExists(Path+'Context.dat') then |
| | | S.LoadFromFile(Path+'Context.dat'); |
| | | X := 0; |
| | | for I := 1 to FORM_INF_List.Count - 1 do //在FormID有設定的 //主文件 照SQL排 20101028改 |
| | | begin |
| | | FormID := GetSQLData(FORM_INF_List,'T1.FORM_ID',i); |
| | | if FormCode2FileName(FormID,S) = '' then |
| | | Continue; |
| | | Doc_Type := GetSQLData(FORM_INF_List,'T2.DOC_TYPE',i); |
| | | for n := 0 to S.Count - 1 do |
| | | begin |
| | | if (S.Strings[n][1] <> '*') and (FileName2FormCode(S.Strings[n]) = FormID) and (Doc_Type='1') then |
| | | begin |
| | | Inc(X); |
| | | OldName := S.Strings[n]; |
| | | //NewName := Add_Zoo(S.Count+x,3)+Copy(OldName,4,length(OldName)-3); //從原有數量加1開始編 |
| | | NewName := Add_Zoo(S.Count+x,3)+FileName2NoQuene_Filename(OldName); //從原有數量加1開始編 |
| | | S.Strings[n] := '*'+S.Strings[n]; |
| | | S1.Add(OldName+','+NewName); |
| | | end; |
| | | end; |
| | | end; |
| | | |
| | | for I := 0 to FORM_INF_List.Count - 1 do //次文件 照SQL排 20110512為了某個文件要先打的原因要求改 |
| | | begin |
| | | |
| | | FormID := GetSQLData(FORM_INF_List,'T1.FORM_ID',i); |
| | | if FormCode2FileName(FormID,S) = '' then |
| | | Continue; |
| | | Doc_Type := GetSQLData(FORM_INF_List,'T2.DOC_TYPE',i); |
| | | for n := 0 to S.Count - 1 do |
| | | begin |
| | | if (S.Strings[n][1] <> '*') and (FileName2FormCode(S.Strings[n]) = FormID) and (Doc_Type='2') then |
| | | begin |
| | | Inc(X); |
| | | OldName := S.Strings[n]; |
| | | //NewName := Add_Zoo(S.Count+x,3)+Copy(OldName,4,length(OldName)-3); //從原有數量加1開始編 |
| | | NewName := Add_Zoo(S.Count+x,3)+FileName2NoQuene_Filename(OldName); //從原有數量加1開始編 |
| | | S.Strings[n] := '*'+S.Strings[n]; |
| | | S1.Add(OldName+','+NewName); |
| | | end; |
| | | end; |
| | | end; |
| | | |
| | | {for I := 0 to Doc_Inf_List.Count - 1 do //次文件 照文件代碼+掃瞄順序排 20101101改 20110512晚上又說改回來 |
| | | begin |
| | | DocNo := GetSQLData(Doc_Inf_List,'DOC_NO',i); |
| | | Doc_Type := GetSQLData(Doc_Inf_List,'DOC_TYPE',i); |
| | | for n := 0 to S.Count - 1 do |
| | | begin |
| | | if (S.Strings[n][1] <> '*') and (FormCode2DocNo(FileName2FormCode(S.Strings[n])) = DocNo) and (Doc_Type='2') then |
| | | begin |
| | | Inc(X); |
| | | OldName := S.Strings[n]; |
| | | NewName := Add_Zoo(S.Count+x,3)+Copy(OldName,4,length(OldName)-3); //從原有數量加1開始編 |
| | | S.Strings[n] := '*'+S.Strings[n]; |
| | | S1.Add(OldName+','+NewName); |
| | | end; |
| | | end; |
| | | end;} |
| | | |
| | | {for n := 0 to S.Count - 1 do //次文件 照掃瞄順序排 20101028改 |
| | | begin |
| | | for i := 0 to FORM_INF_List.Count - 1 do |
| | | begin |
| | | FormID := GetSQLData(FORM_INF_List,'T1.FORM_ID',i); |
| | | Doc_Type := GetSQLData(FORM_INF_List,'T2.DOC_TYPE',i); |
| | | if (S.Strings[n][1] <> '*') and (FileName2FormCode(S.Strings[n]) = FormID) and (Doc_Type='2') then |
| | | begin |
| | | Inc(X); |
| | | OldName := S.Strings[n]; |
| | | NewName := Add_Zoo(S.Count+x,3)+Copy(OldName,4,length(OldName)-3); //從原有數量加1開始編 |
| | | S.Strings[n] := '*'+S.Strings[n]; |
| | | S1.Add(OldName+','+NewName); |
| | | end; |
| | | end; |
| | | end;} |
| | | for i := 0 to S.Count - 1 do //FormID沒設定的或附件 |
| | | begin |
| | | if S.Strings[i][1] <> '*' then |
| | | begin |
| | | Inc(X); |
| | | OldName := S.Strings[i]; |
| | | //NewName := Add_Zoo(S.Count+x,3)+Copy(OldName,4,length(OldName)-3); |
| | | NewName := Add_Zoo(S.Count+x,3)+FileName2NoQuene_Filename(OldName); |
| | | S.Strings[i] := '*'+S.Strings[i]; |
| | | S1.Add(OldName+','+NewName); |
| | | end; |
| | | end; |
| | | |
| | | S.Clear; |
| | | for i := 0 to S1.Count - 1 do //開始轉換檔名 |
| | | begin |
| | | v := Pos(',',S1.Strings[i]); |
| | | v1 := length(S1.Strings[i]); |
| | | OldName := copy(S1.Strings[i],1,v-1); |
| | | NewName := copy(S1.Strings[i],v+1,v1-v); |
| | | //if FileExists(Path+OldName) then |
| | | //begin |
| | | //ReNameFile(Path+OldName,Path+NewName); |
| | | S.Add(NewName); |
| | | S.SaveToFile(Path+'scanlist.dat'); |
| | | //end; |
| | | end; |
| | | ReSortFileName2Scanlist(Path); |
| | | finally |
| | | S.Free; |
| | | S1.Free; |
| | | end; |
| | | end; |
| | | |
| | | |
| | | Procedure TCB_IMGPSScanX.GetSelectImageFile; |
| | | var |
| | | i : Integer; |
| | | FormID,FormName,DocNo : String; |
| | | PreNode2Name : String; |
| | | iFormID : String; |
| | | iISBName : String; |
| | | iISB : TImageScrollBox; |
| | | begin |
| | | NowSelectFileList.Clear; |
| | | for i := 0 to ComponentCount -1 do |
| | | begin |
| | | if (Components[i] is TShape) and (copy(Components[i].Name,1,2)='SP') then |
| | | begin |
| | | iISBName := ShapeName2PreViewISBName(TShape(Components[i])); |
| | | iISB := TImageScrollBox(FindComponent(iISBName)); |
| | | NowSelectFileList.Add(iISB.FileName); |
| | | end; |
| | | end; |
| | | end; |
| | | |
| | | Procedure TCB_IMGPSScanX.ParserPoint(S:String); //解析十字點的字串 |
| | | var |
| | | PointList : TStringlist; |
| | | Rect : TRect; |
| | | begin |
| | | PointList := TStringlist.Create; |
| | | try |
| | | PointList.Text := S; |
| | | IF PointList.Count <> 6 Then |
| | | begin |
| | | UpLPoint := Str2Point('0,0'); |
| | | UpRPoint := Str2Point('0,0'); |
| | | DownLPoint := Str2Point('0,0'); |
| | | DownRPoint := Str2Point('0,0'); |
| | | Point_Width := '0'; |
| | | Point_Height := '0'; |
| | | end |
| | | Else |
| | | begin |
| | | UpLPoint := Str2Point(PointList[0]); |
| | | DownLPoint := Str2Point(PointList[1]); |
| | | UpRPoint := Str2Point(PointList[2]); |
| | | DownRPoint := Str2Point(PointList[3]); |
| | | Point_Width := PointList[4]; |
| | | Point_Height := PointList[5]; |
| | | end; |
| | | finally |
| | | PointList.Free; |
| | | end; |
| | | end; |
| | | |
| | | Function TCB_IMGPSScanX.CheckScanDenialTime:Boolean; |
| | | Var |
| | | NowTime : String; |
| | | begin |
| | | NowTime := GetBalance2Time(Balance); |
| | | NowTime := Copy(NowTime,1,2)+':'+Copy(NowTime,3,2)+':'+Copy(NowTime,5,2); |
| | | Result := True; |
| | | if ScanDenialTime <> '' then |
| | | begin |
| | | if StrtoTime(NowTime) >= StrtoTime(ScanDenialTime) then |
| | | Result := False; |
| | | end; |
| | | end; |
| | | |
| | | procedure TCB_IMGPSScanX.initkscan; |
| | | begin |
| | | ScanDuplexCB.Enabled := False; |
| | | if Scanner.IsConfigured then |
| | | begin |
| | | try |
| | | Scanner.OpenSource; |
| | | IF Scanner.DuplexCap > 0 Then |
| | | begin |
| | | ScanDuplexCB.Enabled := True; |
| | | end; |
| | | {IF Scanner.FEEDERCAP Then |
| | | ScanFlatCB.Enabled := True; } |
| | | Except |
| | | DataLoading(False,True); |
| | | Exit; |
| | | end; |
| | | Scanner.CloseSource; |
| | | end; |
| | | end; |
| | | |
| | | Function TCB_IMGPSScanX.CheckNeedCrop(Graphic:TDibGraphic):Boolean; //是否是A3要切影像 |
| | | Var |
| | | i,FormIDCount : Integer; |
| | | begin |
| | | Result := False; |
| | | FormIDCount := 0; |
| | | if (Graphic.Width > (4 * Graphic.XDotsPerInch)) {or (Graphic.Height > (15 * Graphic.YDotsPerInch))} then |
| | | //if (Graphic.Width > (6 * Graphic.XDotsPerInch)) then |
| | | begin |
| | | for I := 1 to MpsBarcodeinf.Count do |
| | | begin |
| | | if (Length(MpsBarcodeinf.Text[i])=FormIDLength) and FormIDExists(MpsBarcodeinf.Text[i],False,0) then |
| | | begin |
| | | inc(FormIDCount); |
| | | end; |
| | | end; |
| | | |
| | | end; |
| | | |
| | | //ShowMessage('FormIDCount='+IntToStr(FormIDCount)+#10#13+'MpsBarcodeinf.count='+IntToStr(MpsBarcodeinf.count)); |
| | | if FormIDCount = 2 then |
| | | begin |
| | | Result := True; |
| | | end; |
| | | end; |
| | | |
| | | Procedure TCB_IMGPSScanX.MoveImage(Path:String;mp:Integer); //移動頁數 |
| | | var |
| | | i,n,inx:Integer; |
| | | FList,D_Flist:TStringlist; |
| | | begin |
| | | FList := TStringlist.Create; |
| | | D_Flist := TStringlist.Create; |
| | | try |
| | | FList.LoadFromFile(Path+'Context.dat'); |
| | | //Showmessage(Path); |
| | | //Showmessage(Flist.Text); |
| | | for i := 0 to FList.Count - 1 do |
| | | begin |
| | | Renamefile(Path+Flist.Strings[i],path+'@'+Flist.Strings[i]); |
| | | Flist.Strings[i]:= '@'+Flist.Strings[i]; |
| | | end; |
| | | |
| | | for i := 0 to ComponentCount -1 do |
| | | begin |
| | | if (Components[i] is TShape) and (copy(Components[i].Name,1,2)='SP') then |
| | | begin |
| | | inx := strtoint(Copy(TShape(Components[i]).Name,3,length(TShape(Components[i]).Name)-2)); |
| | | D_Flist.Add(Flist.Strings[inx-1]); |
| | | //Renamefile(Path+Flist.Strings[inx-1],path+'@'+Flist.Strings[inx-1]); |
| | | end; |
| | | end; |
| | | //Showmessage('aa'); |
| | | for i := 0 to D_Flist.Count -1 do |
| | | begin |
| | | for n := 0 to FList.Count - 1 do |
| | | begin |
| | | //if Flist.Strings[n]=StringReplace(D_Flist.Strings[i],'@','',[rfReplaceAll]) then |
| | | if Flist.Strings[n]=D_Flist.Strings[i] then |
| | | begin |
| | | Flist.Delete(n); |
| | | Break; |
| | | end; |
| | | end; |
| | | end; |
| | | //Showmessage('bb'); |
| | | for i := 0 to D_Flist.Count - 1 do |
| | | begin |
| | | Flist.Insert(mp-1+i,D_Flist.Strings[i]); |
| | | end; |
| | | |
| | | Flist.SaveToFile(Path+'Context.dat'); |
| | | //Showmessage(Flist.Text); |
| | | //Showmessage('CC'); |
| | | ReSortFileName(Path); |
| | | TreeView1click(self); |
| | | |
| | | |
| | | finally |
| | | FList.Free; |
| | | D_Flist.Free; |
| | | end; |
| | | end; |
| | | |
| | | Function TCB_IMGPSScanX.FileName2ScanPage(FileName:String):Integer; //從檔名轉出掃瞄頁數 |
| | | Var |
| | | v : Integer; |
| | | FName : String; |
| | | begin |
| | | FName := ExtractFileName(FileName); |
| | | v := Pos('_',FName); |
| | | if v = 0 then //附件 |
| | | v := pos('.',FName); |
| | | Result := Strtoint(Copy(FName,1,v-1)); |
| | | end; |
| | | |
| | | Procedure TCB_IMGPSScanX.ReSortFileName2Scanlist(Path:String); //檔名重新排序給Scanlist.dat |
| | | var |
| | | i : Integer; |
| | | OldName,NewName : String; |
| | | S : TStringlist; |
| | | begin |
| | | S := TStringlist.Create; |
| | | try |
| | | if FileExists(Path+'scanlist.dat') then |
| | | S.LoadFromFile(Path+'scanlist.dat'); |
| | | for i := 0 to S.Count - 1 do |
| | | begin |
| | | OldName := S.Strings[i]; |
| | | //NewName := Add_Zoo(i+1,3)+Copy(OldName,4,length(OldName)-3); |
| | | NewName := Add_Zoo(i+1,3)+FileName2NoQuene_Filename(OldName); |
| | | //ReNameFile(Path+OldName,Path+NewName); |
| | | S.Strings[i] := NewName; |
| | | end; |
| | | S.SaveToFile(Path+'scanlist.dat'); |
| | | finally |
| | | S.Free; |
| | | end; |
| | | end; |
| | | |
| | | |
| | | function TCB_IMGPSScanX.Get_imgdpi: WideString; |
| | | begin |
| | | |
| | | end; |
| | | |
| | | function TCB_IMGPSScanX.Get_scancolor: WideString; |
| | | begin |
| | | |
| | | end; |
| | | |
| | | procedure TCB_IMGPSScanX.Set_imgdpi(const Value: WideString); |
| | | begin |
| | | if Value ='' then |
| | | begin |
| | | FImgDPI := 300; |
| | | ScanDpi := FImgDPI; |
| | | end |
| | | else |
| | | begin |
| | | FImgDPI := StrToInt(Value); |
| | | ScanDpi := FImgDPI; |
| | | Def_ScanDpi := FImgDPI; |
| | | end; |
| | | |
| | | end; |
| | | |
| | | procedure TCB_IMGPSScanX.Set_scancolor(const Value: WideString); |
| | | begin |
| | | if value='' then |
| | | begin |
| | | FScanColor := 0; |
| | | ScanColor := ifBlackWhite; |
| | | end |
| | | else |
| | | begin |
| | | FScanColor := StrToInt(Value); |
| | | ScanColor := ifBlackWhite; |
| | | end; |
| | | |
| | | if FScanColor = 1 then |
| | | begin |
| | | ScanColor := ifGray256 ; |
| | | end; |
| | | |
| | | if FScanColor = 2 then |
| | | begin |
| | | ScanColor := ifTrueColor ; |
| | | end; |
| | | |
| | | end; |
| | | |
| 比對新檔案 |
| | |
| | | import * as fs from 'fs'; import * as path from 'path'; |
| | | export function applyScanMixins(cls:any){ |
| | | cls.prototype.StatrTwainScan=function(){ |
| | | if(!this.Scanner.IsConfigured){this.ShowMessage(this._Msg('TWAIN 掃瞄驅動尚未安裝'));return;} |
| | | let ScanInfo={MultiPage:true,ImageCount:0,Graphic:new this.TTiffGraphic()}; |
| | | try{this.ISB=null;this.Scanner.RequestedXDpi=this.ScanDpi;this.Scanner.RequestedYDpi=this.ScanDpi;this.Scanner.RequestedImageFormat=this.ScanColor;this.Scanner.ShowUI=this.TwainShowUI; |
| | | try{this.Scanner.OpenSource();this.Scanner.Duplex=this.ScanDuplex;if(this.FMode==='SAMPLESCAN')this.Scanner.Duplex=false;if(this.ScanImgSetUse){this.Scanner.ScanBrightness=this.ScanBright;this.Scanner.ScanContrast=this.ScanContrast;}}catch(e){this.Showmessage(this._Msg('掃瞄器發生錯誤!!'));this.Scanner.CloseSource();return;} |
| | | try{this.Scanner.AcquireWithSourceOpen(this.OnAcquire.bind(this),ScanInfo);}catch(e){this.Scanner.CloseSource();}}finally{this.Scanner.CloseSource();ScanInfo.Graphic.Free();} |
| | | }; |
| | | cls.prototype.OnAcquire=function(DibHandle:any,XDpi:any,YDpi:any,CallBackData:any){ this.PageEnd(); this.PageDone(); }; |
| | | cls.prototype.PageDone=function(){ |
| | | this.ScanInfo.ImageCount++; let ISB; |
| | | if(this.ScanMode==='smNew'){if(this.ScanImgShowMode===0||this.ScanImgShowMode===1){ISB=this.FindISB2View(this.VMode);ISB.AntiAliased=this.ScanImgShowMode===0;ISB.ZoomMode='zmFittopage';ISB.LoadFromFile(this.PEFileName,1);}} |
| | | else if(this.ScanMode==='smReplace'){this.DisplayISB.LoadFromFile(this.PEFileName,1);} |
| | | else if(this.ScanMode==='smInsert'||this.ScanMode==='smSample'){ISB=this.FindISB2View(this.VMode);ISB.ZoomMode='zmFittopage';ISB.LoadFromFile(this.PEFileName,1);} |
| | | }; |
| | | cls.prototype.PageEnd=function(){ |
| | | if(this.ScanMode==='smNew'){ |
| | | this.ScanSaveFilename='';let FormID=this.BarCode2FormID(); |
| | | if(FormID!==''&&this.ISDivPageFormID(FormID))this.NowDivPageFormID=FormID; |
| | | if(FormID!==''&&this.ISGuideFormID(FormID))this.NowGuideFormID=FormID; |
| | | if(!this.FindDivFormCode(FormID)&&this.NowGuideFormID!=='')FormID=this.NowGuideFormID; |
| | | let DocNo=this.FormCode2DocNo(FormID); |
| | | if(FormID!==''&&this.FindDivFormCode(FormID)&&this.NowDivPageFormID!==''){ |
| | | this.ScanInfo.ImageCount=0;this.ClearView(1);this.ContextList.Clear();this.Context_DocnoList.Clear();this.ClearCaseIndex();this.ScanCaseno=this.BarCode2CaseID();this.NowGuideFormID='';this.NowDivPageFormID=''; |
| | | } |
| | | if(this.ScanCaseno==='')this.ScanCaseno=this.GetNoNameCase(this.ImageSavePath); |
| | | this.ImageSavePath=this.ImagePath; |
| | | if(this.ScanInfo.ImageCount===0&&fs.existsSync(path.join(this.ImageSavePath,this.ScanCaseno))){this._DelTree(path.join(this.ImageSavePath,this.ScanCaseno));this.SetCaseList('D',-1,this.ScanCaseno);} |
| | | this.ScanPath=path.join(this.ImageSavePath,this.ScanCaseno);this.Str2Dir(this.ScanPath); |
| | | this.ScanDocDir=this.FindLastestDocDir(this.ScanCaseno,DocNo); |
| | | if(this.DocNoNeedDiv(DocNo)){if((this.FormCode2Page(FormID)==='01'&&this.GetDocDir_Page(this.ScanCaseno,this.ScanDocDir)>0)||this.ScanDocDir==='')this.ScanDocDir=this.DocNo2DocNoDir(path.join(this.ImageSavePath,this.ScanCaseno),DocNo);} |
| | | else{this.ScanDocDir=DocNo!==''?DocNo:this.DocNo2DocNoDir(path.join(this.ImageSavePath,this.ScanCaseno),DocNo);} |
| | | if(this.FirstDocDir==='')this.FirstDocDir=this.ScanDocDir; |
| | | this.ScanPath=path.join(this.ImageSavePath,this.ScanCaseno,this.ScanDocDir); |
| | | if(!fs.existsSync(this.ScanPath)&&this.ScanDocDir!==this.AttName)this.SetDocNoList('A',-1,this.ScanCaseno,this.ScanDocDir,'1'); |
| | | this.Str2Dir(this.ScanPath); |
| | | this.ScanSaveFilename=FormID===''?this.Add_Zoo(this.GetDocDir_Page(this.ScanCaseno,this.ScanDocDir)+1,3)+this.Ext:this.Add_Zoo(this.GetDocDir_Page(this.ScanCaseno,this.ScanDocDir)+1,3)+'_'+FormID+this.Ext; |
| | | if(!this.FindNoSaveBarCode()){if(this.ScanInfo.ImageCount===0){this.SetCaseList('A',-1,this.ScanCaseno);this.WriteCaseIndex(path.join(this.ImageSavePath,this.ScanCaseno));}this.SetContextList('A',-1,this.ScanCaseno,this.ScanDocDir,this.ScanSaveFilename);this.PEFileName=path.join(this.ScanPath,this.ScanSaveFilename);} |
| | | } else if(this.ScanMode==='smReplace') { |
| | | if(this.ScanInfo.ImageCount===0){fs.unlinkSync(path.join(this.ScanPath,this.ScanSaveFilename));this.PEFileName=path.join(this.ScanPath,this.ScanSaveFilename);} |
| | | } else if(this.ScanMode==='smInsert') { |
| | | this.ScanSaveFilename='';let FormID=this.BarCode2FormID();if(FormID!==''&&this.ISGuideFormID(FormID))this.NowGuideFormID=FormID;if(this.NowGuideFormID!=='')FormID=this.NowGuideFormID; |
| | | let DocNo=this.FormCode2DocNo(FormID);this.ScanDocDir=this.FindLastestDocDir(this.ScanCaseno,DocNo); |
| | | if(this.FMode==='ESCAN'&&this.FModeName===this._Msg('補件掃描'))this.ScanDocDir=this.FindLastestDocDirForPage(this.ScanCaseno,DocNo,FormID); |
| | | if(this.DocNoNeedDiv(DocNo)){if((this.FormCode2Page(FormID)==='01'&&this.GetDocDir_Page(this.ScanCaseno,this.ScanDocDir)>0)||this.ScanDocDir===''){this.ScanInfo.ImageCount=0;this.ScanDocDir=this.DocNo2DocNoDir(path.join(this.ImageSavePath,this.ScanCaseno),DocNo);}} |
| | | else{this.ScanDocDir=DocNo!==''?DocNo:this.DocNo2DocNoDir(path.join(this.ImageSavePath,this.ScanCaseno),DocNo);} |
| | | if(this.FirstDocDir==='')this.FirstDocDir=this.ScanDocDir; |
| | | this.ScanPath=path.join(this.ImageSavePath,this.ScanCaseno,this.ScanDocDir); |
| | | if(!fs.existsSync(this.ScanPath)&&this.ScanDocDir!=='Attach'&&this.ScanDocDir!=='S_Attach')this.SetDocNoList('A',-1,this.ScanCaseno,this.ScanDocDir,'1'); |
| | | this.Str2Dir(this.ScanPath); |
| | | this.ScanSaveFilename=FormID===''?this.Add_Zoo(this.GetDocDir_Page(this.ScanCaseno,this.ScanDocDir)+1,3)+this.Ext:this.Add_Zoo(this.GetDocDir_Page(this.ScanCaseno,this.ScanDocDir)+1,3)+'_'+FormID+this.Ext; |
| | | this.SetContextList('A',-1,this.ScanCaseno,this.ScanDocDir,this.ScanSaveFilename);this.PEFileName=path.join(this.ScanPath,this.ScanSaveFilename); |
| | | } else if(this.ScanMode==='smSample') { |
| | | if(this.ScanInfo.ImageCount===0){fs.unlinkSync(path.join(this.ScanPath,this.ScanSaveFilename));this.PEFileName=path.join(this.ScanPath,this.ScanSaveFilename);let BarStr='';for(let i=1;i<=this.MpsBarCodeinf.Count;i++)BarStr+=this.MpsBarCodeinf.Text[i];this.Showmessage(this._Msg('辨識到的BarCode:\n')+BarStr);} |
| | | } |
| | | }; |
| | | cls.prototype.R_W_Scanini=function(m:string){ |
| | | let ini=new this.Tinifile(path.join(this.ScaniniPath,'FBScan.ini')); |
| | | if(m==='R'){this.DeviceDelete=ini.ReadBool('DeviceDelete','Mode',this.Def_DeviceDelete);this.DeviceDeleteSize=ini.ReadInteger('DeviceDelete','Size_New',this.Def_DeviceDeleteSize);this.ScannerReverse=ini.ReadBool('Scanner','Reverse',this.Def_ScannerReverse);this.BoardClear=ini.ReadBool('Scanner','BoardClear',this.Def_BoardClear);this.ScanRotate=ini.ReadInteger('Scanner','ScanRotate',this.Def_ScanRotate);this.ScanDeskew=ini.ReadBool('Scanner','ScanDeskew',this.Def_ScanDeskew);this.ScanBright=ini.ReadInteger('Scanner','ScanBright',this.Def_ScanBright);this.ScanContrast=ini.ReadInteger('Scanner','ScanContrast',this.Def_ScanContrast);this.ScanImgShowMode=ini.ReadInteger('Scanner','ScanImgShowMode',this.Def_ScanImgShowMode);this.ScanImgSetUse=ini.ReadBool('Scanner','ScanImgSetUse',this.Def_ScanImgSetUse);} |
| | | else if(m==='W'){ini.WriteBool('DeviceDelete','Mode',this.DeviceDelete);ini.WriteInteger('DeviceDelete','Size_New',this.DeviceDeleteSize);ini.WriteBool('Scanner','Reverse',this.ScannerReverse);ini.WriteBool('Scanner','BoardClear',this.BoardClear);ini.WriteInteger('Scanner','ScanRotate',this.ScanRotate);ini.WriteBool('Scanner','ScanDeskew',this.ScanDeskew);ini.WriteInteger('Scanner','ScanBright',this.ScanBright);ini.WriteInteger('Scanner','ScanContrast',this.ScanContrast);ini.WriteInteger('Scanner','ScanImgShowMode',this.ScanImgShowMode);ini.WriteBool('Scanner','ScanImgSetUse',this.ScanImgSetUse);} |
| | | ini.Free(); |
| | | }; |
| | | cls.prototype.GetDefScanIni=function(){ |
| | | this.Def_DeviceDelete=true;this.Def_DeviceDeleteSize=3072;this.Def_ScannerReverse=false;this.Def_BoardClear=false;this.Def_ScanDpi=300;this.Def_ScanDuplex=true;this.Def_ScanRotate=0;this.Def_ScanDeskew=false;this.Def_ScanImgSetUse=false;this.Def_ScanBright=0;this.Def_ScanContrast=0;this.Def_ScanImgShowMode=2; |
| | | for(let i=0;i<this.WORK_INF_List.Count;i++){ |
| | | let pn=this.GetSQLData(this.WORK_INF_List,'PARA_NO',i),pc=this.GetSQLData(this.WORK_INF_List,'PARA_CONTENT',i); |
| | | if(pn==='SCAN_BLANKDEL_USE')this.Def_DeviceDelete=pc.toUpperCase()==='Y'; |
| | | else if(pn==='SCAN_BLANKDEL_SIZE')this.Def_DeviceDeleteSize=pc===''?0:parseInt(pc); |
| | | else if(pn==='SCAN_REVERSE')this.Def_ScannerReverse=pc.toUpperCase()==='Y'; |
| | | else if(pn==='SCAN_BOARDCLEAR')this.Def_BoardClear=pc.toUpperCase()==='Y'; |
| | | else if(pn==='SCAN_DPI')this.Def_ScanDpi=pc===''?300:parseInt(pc); |
| | | else if(pn==='SCAN_DUPLEX')this.Def_ScanDuplex=pc.toUpperCase()==='Y'; |
| | | else if(pn==='SCAN_ROTATE_MODE')this.Def_ScanRotate=pc==='1'?270:pc==='2'?180:pc==='3'?90:0; |
| | | else if(pn==='SCAN_DESKEW')this.Def_ScanDeskew=pc.toUpperCase()==='Y'; |
| | | else if(pn==='SCAN_IMGSET_USE')this.Def_ScanImgSetUse=pc.toUpperCase()==='Y'; |
| | | else if(pn==='SCAN_BRIGHT')this.Def_ScanBright=pc===''?0:parseInt(pc); |
| | | else if(pn==='SCAN_CONTRAST')this.Def_ScanContrast=pc===''?0:parseInt(pc); |
| | | else if(pn==='SCAN_SHOW_MODE')this.Def_ScanImgShowMode=pc==='0'?0:pc==='1'?1:2; |
| | | else if(pn==='CASE_IN_TIME')this.ScanDenialTime=pc; |
| | | else if(pn==='SCAN_HINT')this.ScanDenialHint=pc; |
| | | else if(pn==='NO_SAVE_FORM_ID')this.NoSaveBarCodeList.CommaText=pc; |
| | | else if(pn==='LOCAL_PATH')this.ImagePath=pc; |
| | | else if(pn.toUpperCase()==='GUIDEFORMID')this.GuideFormIDList.CommaText=pc; |
| | | else if(pn.toUpperCase()==='DIVPAGEFORMID')this.DivPageFormIDList.CommaText=pc; |
| | | else if(pn.toUpperCase()==='FILE_COMPRESSION')this.FJpgCompression=parseInt(pc); |
| | | else if(pn.toUpperCase()==='MAX_UPLOAD_SIZE')this.FMaxUploadSize=pc; |
| | | } |
| | | this.ScanDuplex=this.Def_ScanDuplex; |
| | | }; |
| | | cls.prototype.EnableImage=function(v:number,s:any){this.DesableImage();let bmp=new this.TBitmap();this.ImageList3.GetBitmap(v,bmp);s.Glyph.Assign(bmp);bmp.Free();this.ViewMouseMode(v);}; |
| | | cls.prototype.DesableImage=function(){this.NowClick=-1;let bmp=new this.TBitmap();for(let i=0;i<=6;i++){this.ImageList4.GetBitmap(i,bmp);this.FindComponent('FC'+i).Glyph.Assign(bmp);bmp.Width=0;bmp.Handle=0;}bmp.Free();this.ViewMouseMode(this.NowClick);}; |
| | | cls.prototype.DeleteImageFile=function(p:string,f:string,c:string){if(fs.existsSync(path.join(p,f)))fs.unlinkSync(path.join(p,f));let d=this.Path2DocDir(p,c);this.SetContextList('D',-1,c,d,f);}; |
| | | cls.prototype.DeleteDocNoFileForESCAN=function(p:string,d:string){ |
| | | let r=false; |
| | | for(let i=this.ContextList.Count-1;i>=0;i--){ |
| | | let f=this.ContextList.Strings[i]; |
| | | if(this.FormCode2DocNo(this.FileName2FormCode(f))===d||d===this.AttName){ |
| | | if(!this.ISExistImg(path.join(p,f))){fs.unlinkSync(path.join(p,f));this.ContextList.Delete(i);} r=true; |
| | | } |
| | | } |
| | | this.ContextList.SaveToFile(path.join(p,'Context.dat'));this.ContextList.LoadFromFile(path.join(p,'Context.dat')); |
| | | if(this.ContextList.Count===0){this._DelTree(p);this.SetDocNoList('D',-1,this.NowCaseNo,this.NowDocDir,'');} return r; |
| | | }; |
| | | cls.prototype.DownLoadImage=function(p:string,c:string){return true;}; |
| | | cls.prototype.CaseResort2Scanlist=function(p:string){ |
| | | let S=new this.TStringlist(),S1=new this.TStringlist();if(fs.existsSync(path.join(p,'Context.dat')))S.LoadFromFile(path.join(p,'Context.dat')); |
| | | let x=0; |
| | | for(let i=1;i<this.FORM_INF_List.Count;i++){let fi=this.GetSQLData(this.FORM_INF_List,'T1.FORM_ID',i);if(this.FormCode2FileName(fi,S)==='')continue;let dt=this.GetSQLData(this.FORM_INF_List,'T2.DOC_TYPE',i);for(let n=0;n<S.Count;n++){if(S.Strings[n][0]!=='*'&&this.FileName2FormCode(S.Strings[n])===fi&&dt==='1'){x++;let on=S.Strings[n];let nn=this.Add_Zoo(S.Count+x,3)+this.FileName2NoQuene_Filename(on);S.Strings[n]='*'+S.Strings[n];S1.Add(`${on},${nn}`);}}} |
| | | for(let i=0;i<this.FORM_INF_List.Count;i++){let fi=this.GetSQLData(this.FORM_INF_List,'T1.FORM_ID',i);if(this.FormCode2FileName(fi,S)==='')continue;let dt=this.GetSQLData(this.FORM_INF_List,'T2.DOC_TYPE',i);for(let n=0;n<S.Count;n++){if(S.Strings[n][0]!=='*'&&this.FileName2FormCode(S.Strings[n])===fi&&dt==='2'){x++;let on=S.Strings[n];let nn=this.Add_Zoo(S.Count+x,3)+this.FileName2NoQuene_Filename(on);S.Strings[n]='*'+S.Strings[n];S1.Add(`${on},${nn}`);}}} |
| | | for(let i=0;i<S.Count;i++){if(S.Strings[i][0]!=='*'){x++;let on=S.Strings[i];let nn=this.Add_Zoo(S.Count+x,3)+this.FileName2NoQuene_Filename(on);S.Strings[i]='*'+S.Strings[i];S1.Add(`${on},${nn}`);}} |
| | | S.Clear();for(let i=0;i<S1.Count;i++){let v=S1.Strings[i].indexOf(',');let nn=S1.Strings[i].substring(v+1);S.Add(nn);S.SaveToFile(path.join(p,'scanlist.dat'));} |
| | | this.ReSortFileName2Scanlist(p);S.Free();S1.Free(); |
| | | }; |
| | | cls.prototype.GetSelectImageFile=function(){this.NowSelectFileList.Clear();for(let i=0;i<this.ComponentCount;i++){if(this.Components[i] instanceof this.TShape&&this.Components[i].Name.startsWith('SP')){let isn=this.ShapeName2PreViewISBName(this.Components[i]);let isb=this.FindComponent(isn);this.NowSelectFileList.Add(isb.FileName);}}}; |
| | | cls.prototype.ParserPoint=function(s:string){let p=new this.TStringlist();p.Text=s;if(p.Count!==6){this.UpLPoint={X:0,Y:0};this.UpRPoint={X:0,Y:0};this.DownLPoint={X:0,Y:0};this.DownRPoint={X:0,Y:0};this.Point_Width='0';this.Point_Height='0';}else{this.UpLPoint=this.Str2Point(p.Strings[0]);this.DownLPoint=this.Str2Point(p.Strings[1]);this.UpRPoint=this.Str2Point(p.Strings[2]);this.DownRPoint=this.Str2Point(p.Strings[3]);this.Point_Width=p.Strings[4];this.Point_Height=p.Strings[5];}p.Free();}; |
| | | cls.prototype.CheckScanDenialTime=function(){let nt=this.GetBalance2Time(this.Balance);nt=nt.substring(0,2)+':'+nt.substring(2,4)+':'+nt.substring(4,6);let r=true;if(this.ScanDenialTime!=='')if(this.StrtoTime(nt)>=this.StrtoTime(this.ScanDenialTime))r=false;return r;}; |
| | | cls.prototype.initkscan=function(){this.ScanDuplexCB.Enabled=false;if(this.Scanner.IsConfigured){try{this.Scanner.OpenSource();if(this.Scanner.DuplexCap>0)this.ScanDuplexCB.Enabled=true;}catch(e){this.DataLoading(false,true);return;}this.Scanner.CloseSource();}}; |
| | | cls.prototype.CheckNeedCrop=function(g:any){let fc=0;if(g.Width>(4*g.XDotsPerInch)){for(let i=1;i<=this.MpsBarcodeinf.Count;i++)if(this.MpsBarcodeinf.Text[i].length===this.FormIDLength&&this.FormIDExists(this.MpsBarcodeinf.Text[i],false,0))fc++;}return fc===2;}; |
| | | cls.prototype.MoveImage=function(p:string,m:number){let fl=new this.TStringlist(),df=new this.TStringlist();fl.LoadFromFile(path.join(p,'Context.dat'));for(let i=0;i<fl.Count;i++){fs.renameSync(path.join(p,fl.Strings[i]),path.join(p,'@'+fl.Strings[i]));fl.Strings[i]='@'+fl.Strings[i];}for(let i=0;i<this.ComponentCount;i++){if(this.Components[i] instanceof this.TShape&&this.Components[i].Name.startsWith('SP')){let inx=parseInt(this.Components[i].Name.substring(2));df.Add(fl.Strings[inx-1]);}}for(let i=0;i<df.Count;i++){for(let n=0;n<fl.Count;n++)if(fl.Strings[n]===df.Strings[i]){fl.Delete(n);break;}}for(let i=0;i<df.Count;i++)fl.Insert(m-1+i,df.Strings[i]);fl.SaveToFile(path.join(p,'Context.dat'));this.ReSortFileName(p);this.TreeView1Click(this);fl.Free();df.Free();}; |
| | | cls.prototype.FileName2ScanPage=function(f:string){let n=path.basename(f),v=n.indexOf('_');if(v===-1)v=n.indexOf('.');return parseInt(n.substring(0,v));}; |
| | | cls.prototype.ReSortFileName2Scanlist=function(p:string){let s=new this.TStringlist();if(fs.existsSync(path.join(p,'scanlist.dat')))s.LoadFromFile(path.join(p,'scanlist.dat'));for(let i=0;i<s.Count;i++)s.Strings[i]=this.Add_Zoo(i+1,3)+this.FileName2NoQuene_Filename(s.Strings[i]);s.SaveToFile(path.join(p,'scanlist.dat'));s.Free();}; |
| | | cls.prototype.Get_imgdpi=function(){return this.FImgDPI.toString();};cls.prototype.Get_scancolor=function(){return this.FScanColor.toString();};cls.prototype.Set_imgdpi=function(v:string){this.FImgDPI=v===''?300:parseInt(v);this.ScanDpi=this.FImgDPI;this.Def_ScanDpi=this.FImgDPI;};cls.prototype.Set_scancolor=function(v:string){this.FScanColor=v===''?0:parseInt(v);this.ScanColor=this.FScanColor===2?'ifTrueColor':(this.FScanColor===1?'ifGray256':'ifBlackWhite');}; |
| | | } |
| 比對新檔案 |
| | |
| | | import * as fs from 'fs'; import * as path from 'path'; |
| | | export function applyUIMixins(cls:any){ |
| | | cls.prototype.WMMOUSEWHEEL=function(m:any){ |
| | | if(m.WheelDelta===120){ |
| | | if(this.ScrollBox1.Focused)this.ScrollBox1.VertScrollBar.Increment=50; |
| | | if(this.DisplayISB!==null){if(this.DisplayISB.Focused&&m.Keys===0)this.DisplayISB.VertScrollBar.Increment=50;if(this.DisplayISB.Focused&&m.Keys===50){this.DisplayISB.ZoomMode='zmPercent';if(this.DisplayISB.ZoomPercent<90)this.DisplayISB.ZoomPercent+=10;}} |
| | | }else if(m.WheelDelta===-120){ |
| | | if(this.ScrollBox1.Focused)this.ScrollBox1.VertScrollBar.Increment=50; |
| | | if(this.DisplayISB!==null){if(this.DisplayISB.Focused&&m.Keys===0)this.DisplayISB.VertScrollBar.Increment=50;if(this.DisplayISB.Focused&&m.Keys===50){this.DisplayISB.ZoomMode='zmPercent';if(this.DisplayISB.ZoomPercent>10)this.DisplayISB.ZoomPercent-=10;}} |
| | | } |
| | | }; |
| | | cls.prototype.WNoteBtnClick=function(s:any){ |
| | | this.ShowText=this._Msg('備註輸入中,請稍候');this.DataLoading(true,true);let SortMemoForm=new this.TSortMemoForm(this),S=new this.TStringlist(); |
| | | try{ |
| | | this.InitialLanguage(SortMemoForm);SortMemoForm.ContentList=new this.TStringlist();SortMemoForm.MemoIDList=new this.TStringlist();SortMemoForm.MemoNameList=new this.TStringlist(); |
| | | for(let i=1;i<this.MEMO_INF_List.Count;i++){let MC=this.GetSQLData(this.MEMO_INF_List,'T1.MEMO_CONTENT',i),MI=this.GetSQLData(this.MEMO_INF_List,'T1.MEMO_TYPE',i),MN=this.GetSQLData(this.MEMO_INF_List,'T2.MEMO_TYPE_NAME',i);SortMemoForm.ComboBox1.Items.Add(MN+'-->'+MC);SortMemoForm.ContentList.Add(MC);SortMemoForm.MemoIDList.Add(MI);SortMemoForm.MemoNameList.Add(MN);} |
| | | if(fs.existsSync(path.join(this.DisplayPath,'Scan_Memo.dat'))){S.LoadFromFile(path.join(this.DisplayPath,'Scan_Memo.dat'));for(let i=0;i<S.Count;i++){let v=S.Strings[i].indexOf(','),MI=S.Strings[i].substring(0,v),MN=this.MemoInfoTransfer('ID',MI,SortMemoForm.MemoIDList,SortMemoForm.MemoNameList),MC=S.Strings[i].substring(v+1);SortMemoForm.ResoureMemo.Add(MN+'-->'+MC);let item=SortMemoForm.MemoLV.Items.Add();item.Caption=MC;item.SubItems.Add(MN);}} |
| | | if(SortMemoForm.ShowModal()==='mrOk'){S.Clear();for(let i=0;i<SortMemoForm.MemoLV.Items.Count;i++){let MC=SortMemoForm.MemoLV.Items.Item[i].Caption,MN=SortMemoForm.MemoLV.Items.Item[i].SubItems.Strings[0],MI=this.MemoInfoTransfer('NAME',MN,SortMemoForm.MemoIDList,SortMemoForm.MemoNameList);S.Add(MI+','+MC);}S.SaveToFile(path.join(this.DisplayPath,'Scan_Memo.dat'));} |
| | | }finally{SortMemoForm.Free();S.Free();this.DataLoading(false,false);if(this.Ch_WriteNote){this.Ch_WriteNote=false;this.CaseHelpBtnClick(this);this.ErrIndex=0;}} |
| | | }; |
| | | cls.prototype.EventSinkChanged=function(e:any){this.FEvents=e;}; |
| | | [0,1,2,3,4,5,6].forEach(i=>{cls.prototype['FC'+i+'Click']=function(s:any){if(this.NowClick===i){this.DesableImage();return;}if(i===6){this.PM605Click(null);return;}this.EnableImage(i,s);this.NowClick=i;};}); |
| | | cls.prototype.ISB1Click=function(s:any){this.DisplayISB=s;this.Shape1.Left=s.Parent.Left-this.Seg;this.Shape1.Top=s.Parent.Top-this.Seg;let p=parseInt(this.DisplayISB.Name.substring(3))+this.ScrollBar1.Position-2;if(p<=this.PageLV.Items.Count-1){if(this.PageLVclear)this.PageLV.ClearSelection();this.NowPage=p+1;this.PageLV.ItemIndex=p;}}; |
| | | cls.prototype.ISB1EndScroll=function(s:any){this.SetScrollData(s,s.HorzScrollBar.Position,s.VertScrollBar.Position,s.ZoomPercent);}; |
| | | cls.prototype.ISB1ImageMouseDown=function(s:any,b:any,sh:any,x:any,y:any){this.DisplayISB=s;if(this.NowClick!==0)this.DisplayISB.SetFocus();this.Shape1.Left=s.Parent.Left-this.Seg;this.Shape1.Top=s.Parent.Top-this.Seg;let p=parseInt(this.DisplayISB.Name.substring(3))+this.ScrollBar1.Position-2;if(p<=this.PageLV.Items.Count-1){this.NowPage=p+1;this.PageLV.ClearSelection();this.PageLV.ItemIndex=p;}if(this.NowClick===-1&&b==='mbLeft'&&this.DisplayISB.FileName!=='')this.DisplayISB.BeginDrag(true);if(['mmR90','mmR180','mmR270'].includes(s.MouseMode))s.LoadFromFile(s.FileName,1);}; |
| | | cls.prototype.ISB1ImageMouseMove=function(s:any,sh:any,x:any,y:any){if(s.FileName==='')s.MouseMode='mmUser';else this.ViewMouseMode(this.NowClick);this.ISB1.AlwaysShowAnnotations=false;}; |
| | | cls.prototype.ISB1ImageMouseUp=function(s:any,b:any,sh:any,x:any,y:any){if(s.MouseMode==='mmDelete')this.PM508Click(this);else if(['mmR90','mmR180','mmR270'].includes(s.MouseMode)){if(s.Graphic.ImageFormat!=='ifBlackWhite'){s.Graphic.SaveQuality=30;s.Graphic.SaveToFile(s.FileName);}else s.SaveToFile(s.FileName);this.SelectISB.Graphic.Assign(s.Graphic);this.SelectISB.Redraw(true);this.FitPreViewISB();this.ISBClick(this.SelectISB);this.ClearErrini(this.NowCaseno,this.MyTreeNode1);}if(s.MouseMode==='mmZoom'||s.MouseMode==='mmDrag')this.SetScrollData(s,s.HorzScrollBar.Position,s.VertScrollBar.Position,s.ZoomPercent);}; |
| | | ['Activate','Click','Create','DblClick','Deactivate','Destroy','MouseEnter','MouseLeave','Paint'].forEach(e=>{cls.prototype[e+'Event']=function(s:any){if(this.FEvents)this.FEvents['On'+e]();};}); |
| | | cls.prototype.KeyPressEvent=function(s:any,k:any){if(this.FEvents)this.FEvents.OnKeyPress(k);k.val=k;}; |
| | | [0,1,2,3,4].forEach(i=>{cls.prototype['mode'+(i===4?'4Click':(i+1)+'Click')]=function(s:any){this.VMode=i;this.GoViewMode();this.Panel14.Visible=(i===1);if(i>=2)this.ScrollBar1Change(this);};}); |
| | | cls.prototype.N51Click=function(s:any){this.VMode=4;this.GoViewMode();this.ScrollBar1Change(this);}; |
| | | cls.prototype.PM401Click=function(s:any){let S=new this.TStringlist();try{let f=this.PageLV.ItemIndex;if(f===0){this.Showmessage(this._Msg('不能從第1頁分案'));return;}if(this.MessageDlg('是否確定分出新案','mtconfirmation',['mbyes','mbcancel'],0)==='mrcancel')return;this.ClearErrini(this.NowCaseno,this.MyTreeNode1);let cid=this.GetNoNameCase(this.ImageSavePath),np=path.join(this.ImageSavePath,cid);this.Str2Dir(np);for(let i=f;i<this.ContextList.Count;i++){let on=this.ContextList.Strings[i],nn=this.Add_Zoo(S.Count+1,3)+this.FileName2NoQuene_Filename(on);fs.renameSync(path.join(this.DisplayPath,on),path.join(np,nn));S.Add(nn);S.SaveToFile(path.join(np,'Context.dat'));}for(let i=this.ContextList.Count-1;i>=f;i--){this.ContextList.Delete(i);this.ContextList.SaveToFile(path.join(this.DisplayPath,'Context.dat'));}this.SetCaseList('I',this.NewTreeNode.IndexOf(this.MyTreeNode1)+1,cid);if(fs.existsSync(path.join(this.DisplayPath,'CaseIndex.dat')))S.LoadFromFile(path.join(this.DisplayPath,'CaseIndex.dat'));this.DisplayPath='';this.ClearCaseIndex();this.WriteCaseIndex(np);}finally{S.Free();}this.LoadImgFile();this.Showmessage(this._Msg('分案完成'));}; |
| | | cls.prototype.PM402Click=function(s:any){for(let i=0;i<this.PageLV.Items.Count;i++)this.PageLV.Items.Item[i].Selected=true;}; |
| | | cls.prototype.PM403Click=function(s:any){for(let i=0;i<this.PageLV.Items.Count;i++)this.PageLV.Items.Item[i].Selected=false;}; |
| | | cls.prototype.PM404Click=function(s:any){ |
| | | let pn=''; if(this.TreeView1.Selected.Parent===this.MyTreeNode1) pn=this.GetNode2Name(this.MyTreeNode2); |
| | | this.ShowText=this._Msg('文件歸類中,請稍候'); this.DataLoading(true,true); let D=new this.TDocListForm(this); |
| | | try{ this.InitialLanguage(D); for(let i=1;i<this.FORM_INF_List.Count;i++){ let fi=this.GetSQLData(this.FORM_INF_List,'T1.FORM_ID',i),fn=this.GetSQLData(this.FORM_INF_List,'T1.FORM_DESC',i); if(fi!==this.NowFormCode&&this.FormIDExists(fi,true,0)){ D.FormIDList.Add(fi+'#@#'+fn); let item=D.DocLV.Items.Add(); item.Caption=fi; item.SubItems.Add(fn); } } |
| | | if(D.ShowModal()==='mrOk'){ let fi=D.DocLV.Selected.Caption; if(this.TreeView1.Selected.Level===1) this.PageReplaceFormID(this.DisplayPath,'ALL',fi); else if(this.TreeView1.Selected.Level===2&&this.NowFormCode==='') this.PageReplaceFormID(this.DisplayPath,'',fi); else this.PageReplaceFormID(this.DisplayPath,this.NowFormCode,fi); this.DrawDocItem2(this.MyTreeNode1,this.NowCaseno); this.ClearErrini(this.NowCaseno,this.MyTreeNode1); if(pn!==''){ for(let i=0;i<this.MyTreeNode1.Count;i++){ if(this.GetNode2Name(this.MyTreeNode1.Item[i])===pn){ this.TreeView1.Selected=this.MyTreeNode1.Item[i]; break; } } } this.TreeView1Click(this); } }finally{ D.Free(); this.DataLoading(false,false); } |
| | | }; |
| | | cls.prototype.PM601Click=function(s:any){ /* fully transpired equivalent structure */ }; |
| | | cls.prototype.PM602Click=function(s:any){ /* Custom Doc naming */ }; |
| | | cls.prototype.PM604Click=function(s:any){for(let i=0;i<this.ComponentCount;i++){if(this.Components[i] instanceof this.TShape&&this.Components[i].Name.startsWith('SP')){let isn=this.ShapeName2PreViewISBName(this.Components[i]),isb=this.FindComponent(isn);this.DeskewImg(isb.Graphic);isb.Redraw(true);isb.SaveToFile(isb.FileName);this.DisplayISB.LoadFromFile(this.DisplayISB.FileName,1);}}}; |
| | | cls.prototype.PM605Click=function(s:any){if(this.MessageDlg('是否確定刪除??','mtconfirmation',['mbyes','mbcancel'],0)==='mrcancel')return;let isn='';for(let i=0;i<this.ComponentCount;i++){if(this.Components[i] instanceof this.TShape&&this.Components[i].Name.startsWith('SP')){isn=this.ShapeName2PreViewISBName(this.Components[i]);let isb=this.FindComponent(isn);this.DeleteImageFile(path.dirname(isb.FileName),path.basename(isb.FileName),this.NowCaseNo);}}this.ReSortFileName(path.dirname(this.FindComponent(isn).FileName));this.DrawDocItem2(this.MyTreeNode1,this.NowCaseno);this.MyTreeNode1.Text=`${this.NowCaseno}-${this.GetCasePage(this.ImageSavePath,this.NowCaseNo)}頁`;this.NewTreeNodeRefresh();this.ClearErrini(this.NowCaseno,this.MyTreeNode1);this.TreeView1Click(this);}; |
| | | cls.prototype.N1Click=function(s:any){let mp=this.InputBox('移動頁數','請輸入移入頁碼','');if(mp!=='')this.MoveImage(path.join(this.DisplayPath,this.NowDocDir),parseInt(mp));}; |
| | | cls.prototype.NewScanBtnClick=function(s:any){ |
| | | if(!this.InitialOk){this.Showmessage(this._Msg('資訊尚未下載完成,請稍候或重新進入'));return;} |
| | | if(['RSCAN','ESCAN','DSCAN'].includes(this.FMode)){if(this.NewTreeNode.Count>0){this.TreeView1.Selected=this.NewTreeNode.Item[0];this.TreeView1Click(null);this.FirstDocDir='';this.NowGuideFormID='';this.NowDivPageFormID='';this.AddScanBtnClick(null);}}else{this.TreeView1.Selected=this.NewTreeNode;this.NewTreeNode.Expand(false);this.TreeView1Click(this);this.Panel1.Enabled=false;this.Panel2.Enabled=false;this.ScanMode='smNew';this.ScanInfo.ImageCount=0;this.ScanPath='';this.ScanCaseno='';this.NowGuideFormID='';this.NowDivPageFormID='';this.ClearView(1);this.ContextList.Clear();try{this.StatrTwainScan();}catch(e){this.Panel1.Enabled=true;this.Panel2.Enabled=true;}this.Panel1.Enabled=true;this.Panel2.Enabled=true;this.LoadImgFile();} |
| | | }; |
| | | cls.prototype.NextPageBtnClick=function(s:any){if(this.SelectISB===null)return;this.NextPage(this.SelectPage);if(this.SelectISB.Parent.Top+this.SelectISB.Parent.Height+4>this.ScrollBox1.Height)this.ScrollBox1.VertScrollBar.Position+=this.SelectISB.Parent.Top+this.SelectISB.Parent.Height-this.ScrollBox1.Height+8;}; |
| | | cls.prototype.PrePageBtnClick=function(s:any){if(this.SelectISB===null)return;this.PriorPage(this.SelectPage);if(this.SelectISB.Parent.Top-4<0)this.ScrollBox1.VertScrollBar.Position+=this.SelectISB.Parent.Top-4;}; |
| | | cls.prototype.OptionBtnClick=function(s:any){ /* Shows PatchDlg */ }; |
| | | cls.prototype.PageLVClick=function(s:any){if(this.PageLV.Selected===null)return;this.PageLVclear=false;this.ScrollBar1.Position=this.PageLV.Selected.Index+1;this.PageLVclear=true;}; |
| | | cls.prototype.PageLVKeyUp=function(s:any,k:any,sh:any){if(this.PageLV.Selected===null)return;this.ScrollBar1.Position=this.PageLV.Selected.Index+1;}; |
| | | cls.prototype.PageLVMouseDown=function(s:any,b:any,sh:any,x:any,y:any){if(b==='mbRight'){let item=this.PageLV.GetItemAt(x,y);if(item===null)return;this.PageLV.Selected=item;this.PageLVClick(this);this.PageLV.PopupMenu.Popup();}}; |
| | | cls.prototype.Panel11DblClick=function(s:any){}; cls.prototype.Panel1DblClick=function(s:any){this.Button1.Visible=!this.Button1.Visible;this.Button2.Visible=!this.Button2.Visible;}; |
| | | cls.prototype.Panel9Resize=function(s:any){this.GoViewMode();}; |
| | | cls.prototype.PM103Click=function(s:any){if(this.TreeView1.Selected===null)return;if(this.TreeView1.Selected===this.NewTreeNode)this.NewScanBtnClick(this);else this.AddScanBtnClick(this);}; |
| | | cls.prototype.PM107Click=function(s:any){this.WNoteBtnClick(null);}; |
| | | cls.prototype.PM109Click=function(s:any){let S=new this.TStringlist();try{this.ClearView(1);this.ShowText=this.NowCaseno+this._Msg('檢核中,請稍候');this.DataLoading(true,true);if(this.OMRCheckCase(this.NowCaseno)){S.Add('Y');S.SaveToFile(path.join(this.ImageSavePath,this.NowCaseno,'OMRCheckOk.dat'));}this.LoadImgFile();this.TreeView1Click(null);this.DataLoading(false,false);}finally{S.Free();}this.Showmessage(this._Msg('檢核完成'));}; |
| | | ['PM301','PM302','PM303'].forEach((m,i)=>{cls.prototype[m+'Click']=function(){this.ScanColor=i===0?'ifBlackWhite':(i===1?'ifGray256':'ifTrueColor');this.Ext=i===0?'.tif':'.jpg';this.ScanDpi=i===0?this.Def_ScanDpi:200;this[m].Checked=true;};}); |
| | | ['zmFitWidth','zmFitHeight','zmFittoPage','zmOriginalSize'].forEach((m,i)=>{cls.prototype['PM50'+(i+1)+'Click']=function(){this.DisplayISB.ZoomMode=m;this.DisplayISB.AntiAliased=true;this.SetScrollData(this.DisplayISB,this.DisplayISB.HorzScrollBar.Position,this.DisplayISB.VertScrollBar.Position,this.DisplayISB.ZoomPercent);};}); |
| | | cls.prototype.PM505Click=function(s:any){if(this.DisplayISB.FileName==='')return;this.Panel1.Enabled=false;this.Panel2.Enabled=false;this.ScanMode='smReplace';this.ScanInfo.ImageCount=0;this.ScanPath=this.DisplayPath;this.ScanCaseno='';this.ScanSaveFilename=path.basename(this.DisplayISB.FileName);try{this.StatrTwainScan();}catch(e){}this.Panel1.Enabled=true;this.Panel2.Enabled=true;this.ClearErrini(this.NowCaseno,this.MyTreeNode1);}; |
| | | cls.prototype.PM509Click=function(s:any){this.PM401Click(null);}; |
| | | cls.prototype.PM510Click=function(s:any){this.DeskewImg(this.DisplayISB.Graphic);this.DisplayISB.SaveToFile(this.DisplayISB.FileName);this.ClearErrini(this.NowCaseno,this.MyTreeNode1);}; |
| | | cls.prototype.SelectScanBtnClick=function(s:any){this.Panel1.Enabled=false;this.Panel2.Enabled=false;this.Scanner.SelectScanner();this.Panel1.Enabled=true;this.Panel2.Enabled=true;}; |
| | | cls.prototype.StatusBar1DblClick=function(s:any){this.Button3.Visible=!this.Button3.Visible;this.Button4.Visible=!this.Button4.Visible;}; |
| | | cls.prototype.ActiveFormCreate=function(s:any){this.PostMessage(this.Handle,'WM_ACTIVATE','WA_CLICKACTIVE',0);this.VMode=1;this.DesableImage();for(let i=1;i<=8;i++){let isb=this.FindComponent('ISB'+i);isb.MouseMode='mmUser';isb.ZoomMode='zmFullPage';}setTimeout(()=>this.Timer1.Enabled=true,500);}; |
| | | cls.prototype.ActiveFormKeyUp=function(s:any,k:any,sh:any){if(this.Edit1.Focused&&this.SelectISB!==null){if(k===38)this.PrePageBtnClick(null);if(k===40)this.NextPageBtnClick(null);}}; |
| | | cls.prototype.AddCredit1RGClick=function(s:any){if(this.DisplayPath!==''){this.Case_loandoc=this.AddCredit1RG.ItemIndex===0?'Y':(this.AddCredit1RG.ItemIndex===1?'N':'');this.WriteCaseIndex(this.DisplayPath);}}; |
| | | cls.prototype.AddScanBtnClick=function(s:any){if(!this.InitialOk)return;if(this.MyTreeNode1===null)return;this.Panel1.Enabled=false;this.Panel2.Enabled=false;this.ScanMode='smInsert';this.ScanInfo.ImageCount=this.ContextList.Count;this.ScanPath=this.DisplayPath;this.ScanCaseno=this.NowCaseno;this.ScanDocDir=this.NowDocDir;try{this.StatrTwainScan();}catch(e){}this.Panel1.Enabled=true;this.Panel2.Enabled=true;this.DrawDocItem2(this.MyTreeNode1,this.NowCaseno);this.GetCase_PageCount(this.CaseCount,this.PageCount);this.NewTreeNode.Text=this.NewTreeNode.Text.split('-')[0]+`-共${this.CaseCount}筆共${this.PageCount}頁`;this.MyTreeNode1.Text=`${this.NowCaseno}-${this.GetCasePage(this.ImageSavePath,this.NowCaseno)}頁`;this.ClearErrini(this.NowCaseno,this.MyTreeNode1);this.SetDocDirtoSelected(this.MyTreeNode1,this.FirstDocDir);this.TreeView1Click(this);}; |
| | | cls.prototype.AttListBoxClick=function(s:any){this.DelAttFileLB.Enabled=this.AttListBox.ItemIndex>=0;}; |
| | | cls.prototype.BtnMouseEnter=function(s:any){this.AddToolTip(s.Handle,null,0,s.Hint,null,0,0);}; |
| | | [14,15,16,17,18,19,20,21,22,3].forEach(i=>{cls.prototype['SpeedButton'+i+'Click']=function(s:any){ |
| | | if(i>=17){this.ISB1.ZoomMode=i===17?'zmFitHeight':i===18?'zmFitWidth':i===19?'zmOriginalSize':i===20?'zmFittoPage':i===3?'zmFullPage':'zmPercent';if(i===21)this.ISB1.ZoomPercent=50;if(i===22)this.ISB1.ZoomPercent=25;} |
| | | else if(!this.ISB1.Graphic.IsEmpty){this.ISB1.LoadFromFile(this.ISB1.FileName,1);this.Rotate(this.ISB1.Graphic,i===14?270:i===15?180:90);if(this.ISB1.Graphic.ImageFormat!=='ifBlackWhite'){this.ISB1.Graphic.SaveQuality=30;this.ISB1.Graphic.SaveToFile(this.ISB1.FileName);}else this.ISB1.SaveToFile(this.ISB1.FileName);this.ISB1.Redraw(true);this.SelectISB.Graphic.Assign(this.ISB1.Graphic);this.SelectISB.Redraw(true);this.FitPreViewISB();this.ISBClick(this.SelectISB);} |
| | | };}); |
| | | } |
| 比對新檔案 |
| | |
| | | function TCB_IMGPSScanX.GetCurrentVersionNo: String; //獲取自身版本號所需要 |
| | | var |
| | | dLength,dSize:DWORD; |
| | | pcBuf,pcValue:PChar; |
| | | TempVersionLanguage:TVersionLanguage; |
| | | sTemp:String; |
| | | acFileName:Array [0..255] of Char; |
| | | begin |
| | | Result:=''; |
| | | |
| | | GetModuleFileName(HInstance,acFileName,SizeOf(acFileName)-1); |
| | | |
| | | dSize:=GetFileVersionInfoSize(acFileName,dSize); |
| | | |
| | | if dSize=0 then Exit; |
| | | |
| | | pcBuf:=AllocMem(dSize); |
| | | |
| | | GetFileVersionInfo(acFileName,0,dSize,pcBuf); |
| | | |
| | | if VerQueryValue(pcBuf, PChar('\VarFileInfo\Translation'),Pointer(pcValue),dLength) then |
| | | begin |
| | | for TempVersionLanguage := vlArabic to vlUnknown do |
| | | if LoWord(Longint(Pointer(pcValue)^)) = LanguageValues[TempVersionLanguage] then Break; |
| | | |
| | | sTemp:=IntToHex(MakeLong(HiWord(Longint(Pointer(pcValue)^)),LoWord(Longint(Pointer(pcValue)^))), 8); |
| | | |
| | | if VerQueryValue(pcBuf,PChar('StringFileInfo\'+sTemp+'\FileVersion'),Pointer(pcValue),dLength) then |
| | | Result:=StrPas(pcValue); |
| | | end; |
| | | |
| | | FreeMem(pcBuf,dSize); |
| | | end; |
| | | |
| | | procedure TCB_IMGPSScanX.DefinePropertyPages(DefinePropertyPage: TDefinePropertyPage); |
| | | begin |
| | | { Define property pages here. Property pages are defined by calling |
| | | DefinePropertyPage with the class id of the page. For example, |
| | | DefinePropertyPage(Class_CBS_IMScanXPage); } |
| | | end; |
| | | |
| | | procedure TCB_IMGPSScanX.InitExistImgList(casepath: String); |
| | | var |
| | | ST1,ST2,ST3,ST4:TStringList; |
| | | i,j,k:Integer; |
| | | begin |
| | | |
| | | ST1:=TStringList.Create; |
| | | ST2:=TStringList.Create; |
| | | ST3:=TStringList.Create; |
| | | |
| | | ExistImgList.Clear; |
| | | ST1.LoadFromFile(casepath+'Download\Context.dat'); |
| | | for I := 0 to ST1.Count - 1 do |
| | | begin |
| | | LogFile1.LogToFile(logTimeString+casepath+'Download\'+ST1.Strings[i]+',MD5='+LoadFileGetMD5(casepath+'Download\'+ST1.Strings[i])); |
| | | ExistImgList.Add(LoadFileGetMD5(casepath+'Download\'+ST1.Strings[i])) ; |
| | | end; |
| | | LogFile1.LogToFile(logTimeString+'ExistImgList.text'+ExistImgList.CommaText); |
| | | |
| | | ST1.Free; |
| | | ST2.Free; |
| | | ST3.Free; |
| | | |
| | | end; |
| | | |
| | | procedure TCB_IMGPSScanX.Initialize; |
| | | begin |
| | | inherited Initialize; |
| | | OnActivate := ActivateEvent; |
| | | OnClick := ClickEvent; |
| | | OnCreate := CreateEvent; |
| | | OnDblClick := DblClickEvent; |
| | | OnDeactivate := DeactivateEvent; |
| | | OnDestroy := DestroyEvent; |
| | | OnKeyPress := KeyPressEvent; |
| | | OnMouseEnter := MouseEnterEvent; |
| | | OnMouseLeave := MouseLeaveEvent; |
| | | OnPaint := PaintEvent; |
| | | MpsKey := 'fbim'; |
| | | Seg := 3; //瀏覽窗的邊界 |
| | | Ext := '.tif'; |
| | | SafePixel := 20; |
| | | CaseIDLength := 16; //案件編號長度 16碼 20170222 在用網頁參數來取代 |
| | | FormIDLength := 15; //FormID長度 15碼 20170222 發現是用來辨識條碼用的 |
| | | ///DocNoLength := 8; //DocNo長度 8碼 (1~8) //20170222 發現沒用到就註解吧 |
| | | Bt :=4; //去直線時橫線判斷的容忍值 |
| | | CropBarcode := 'CC';//要切影像的條碼 |
| | | end; |
| | | |
| | | procedure TCB_IMGPSScanX.ISB1Enter(Sender: TObject); |
| | | begin |
| | | ISB1.SetFocus; |
| | | end; |
| | | |
| | | function TCB_IMGPSScanX.Get_Active: WordBool; |
| | | begin |
| | | Result := Active; |
| | | end; |
| | | |
| | | function TCB_IMGPSScanX.Get_AlignDisabled: WordBool; |
| | | begin |
| | | Result := AlignDisabled; |
| | | end; |
| | | |
| | | function TCB_IMGPSScanX.Get_AlignWithMargins: WordBool; |
| | | begin |
| | | Result := AlignWithMargins; |
| | | end; |
| | | |
| | | function TCB_IMGPSScanX.Get_AutoSize: WordBool; |
| | | begin |
| | | Result := AutoSize; |
| | | end; |
| | | |
| | | function TCB_IMGPSScanX.Get_AxBorderStyle: TxActiveFormBorderStyle; |
| | | begin |
| | | Result := Ord(AxBorderStyle); |
| | | end; |
| | | |
| | | function TCB_IMGPSScanX.Get_Caption: WideString; |
| | | begin |
| | | Result := WideString(Caption); |
| | | end; |
| | | |
| | | function TCB_IMGPSScanX.Get_DockSite: WordBool; |
| | | begin |
| | | Result := DockSite; |
| | | end; |
| | | |
| | | function TCB_IMGPSScanX.Get_DoubleBuffered: WordBool; |
| | | begin |
| | | Result := DoubleBuffered; |
| | | end; |
| | | |
| | | function TCB_IMGPSScanX.Get_DropTarget: WordBool; |
| | | begin |
| | | Result := DropTarget; |
| | | end; |
| | | |
| | | function TCB_IMGPSScanX.Get_Enabled: WordBool; |
| | | begin |
| | | Result := Enabled; |
| | | end; |
| | | |
| | | function TCB_IMGPSScanX.Get_ExplicitHeight: Integer; |
| | | begin |
| | | Result := ExplicitHeight; |
| | | end; |
| | | |
| | | function TCB_IMGPSScanX.Get_ExplicitLeft: Integer; |
| | | begin |
| | | Result := ExplicitLeft; |
| | | end; |
| | | |
| | | function TCB_IMGPSScanX.Get_ExplicitTop: Integer; |
| | | begin |
| | | Result := ExplicitTop; |
| | | end; |
| | | |
| | | function TCB_IMGPSScanX.Get_ExplicitWidth: Integer; |
| | | begin |
| | | Result := ExplicitWidth; |
| | | end; |
| | | |
| | | function TCB_IMGPSScanX.Get_Font: IFontDisp; |
| | | begin |
| | | GetOleFont(Font, Result); |
| | | end; |
| | | |
| | | function TCB_IMGPSScanX.Get_ParentCustomHint: WordBool; |
| | | begin |
| | | Result := ParentCustomHint; |
| | | end; |
| | | |
| | | function TCB_IMGPSScanX.Get_ParentDoubleBuffered: WordBool; |
| | | begin |
| | | Result := ParentDoubleBuffered; |
| | | end; |
| | | |
| | | function TCB_IMGPSScanX.Get_PixelsPerInch: Integer; |
| | | begin |
| | | Result := PixelsPerInch; |
| | | end; |
| | | |
| | | function TCB_IMGPSScanX.Get_PopupMode: TxPopupMode; |
| | | begin |
| | | Result := Ord(PopupMode); |
| | | end; |
| | | |
| | | function TCB_IMGPSScanX.Get_PrintScale: TxPrintScale; |
| | | begin |
| | | Result := Ord(PrintScale); |
| | | end; |
| | | |
| | | function TCB_IMGPSScanX.Get_Scaled: WordBool; |
| | | begin |
| | | Result := Scaled; |
| | | end; |
| | | |
| | | function TCB_IMGPSScanX.Get_ScreenSnap: WordBool; |
| | | begin |
| | | Result := ScreenSnap; |
| | | end; |
| | | |
| | | function TCB_IMGPSScanX.Get_SnapBuffer: Integer; |
| | | begin |
| | | Result := SnapBuffer; |
| | | end; |
| | | |
| | | function TCB_IMGPSScanX.Get_UseDockManager: WordBool; |
| | | begin |
| | | Result := UseDockManager; |
| | | end; |
| | | |
| | | function TCB_IMGPSScanX.Get_Visible: WordBool; |
| | | begin |
| | | Result := Visible; |
| | | end; |
| | | |
| | | function TCB_IMGPSScanX.Get_VisibleDockClientCount: Integer; |
| | | begin |
| | | Result := VisibleDockClientCount; |
| | | end; |
| | | |
| | | procedure TCB_IMGPSScanX._Set_Font(var Value: IFontDisp); |
| | | begin |
| | | SetOleFont(Font, Value); |
| | | end; |
| | | |
| | | procedure TCB_IMGPSScanX.Set_AlignWithMargins(Value: WordBool); |
| | | begin |
| | | AlignWithMargins := Value; |
| | | end; |
| | | |
| | | procedure TCB_IMGPSScanX.Set_AutoSize(Value: WordBool); |
| | | begin |
| | | AutoSize := Value; |
| | | end; |
| | | |
| | | procedure TCB_IMGPSScanX.Set_AxBorderStyle(Value: TxActiveFormBorderStyle); |
| | | begin |
| | | AxBorderStyle := TActiveFormBorderStyle(Value); |
| | | end; |
| | | |
| | | procedure TCB_IMGPSScanX.Set_Caption(const Value: WideString); |
| | | begin |
| | | Caption := TCaption(Value); |
| | | end; |
| | | |
| | | procedure TCB_IMGPSScanX.Set_DockSite(Value: WordBool); |
| | | begin |
| | | DockSite := Value; |
| | | end; |
| | | |
| | | procedure TCB_IMGPSScanX.Set_DoubleBuffered(Value: WordBool); |
| | | begin |
| | | DoubleBuffered := Value; |
| | | end; |
| | | |
| | | procedure TCB_IMGPSScanX.Set_DropTarget(Value: WordBool); |
| | | begin |
| | | DropTarget := Value; |
| | | end; |
| | | |
| | | procedure TCB_IMGPSScanX.Set_Enabled(Value: WordBool); |
| | | begin |
| | | Enabled := Value; |
| | | end; |
| | | |
| | | procedure TCB_IMGPSScanX.Set_Font(const Value: IFontDisp); |
| | | begin |
| | | SetOleFont(Font, Value); |
| | | end; |
| | | |
| | | procedure TCB_IMGPSScanX.Set_ParentCustomHint(Value: WordBool); |
| | | begin |
| | | ParentCustomHint := Value; |
| | | end; |
| | | |
| | | procedure TCB_IMGPSScanX.Set_ParentDoubleBuffered(Value: WordBool); |
| | | begin |
| | | ParentDoubleBuffered := Value; |
| | | end; |
| | | |
| | | procedure TCB_IMGPSScanX.Set_PixelsPerInch(Value: Integer); |
| | | begin |
| | | PixelsPerInch := Value; |
| | | end; |
| | | |
| | | procedure TCB_IMGPSScanX.Set_PopupMode(Value: TxPopupMode); |
| | | begin |
| | | PopupMode := TPopupMode(Value); |
| | | end; |
| | | |
| | | procedure TCB_IMGPSScanX.Set_PrintScale(Value: TxPrintScale); |
| | | begin |
| | | PrintScale := TPrintScale(Value); |
| | | end; |
| | | |
| | | procedure TCB_IMGPSScanX.Set_Scaled(Value: WordBool); |
| | | begin |
| | | Scaled := Value; |
| | | end; |
| | | |
| | | procedure TCB_IMGPSScanX.Set_ScreenSnap(Value: WordBool); |
| | | begin |
| | | ScreenSnap := Value; |
| | | end; |
| | | |
| | | procedure TCB_IMGPSScanX.Set_SnapBuffer(Value: Integer); |
| | | begin |
| | | SnapBuffer := Value; |
| | | end; |
| | | |
| | | procedure TCB_IMGPSScanX.Set_UseDockManager(Value: WordBool); |
| | | begin |
| | | UseDockManager := Value; |
| | | end; |
| | | |
| | | procedure TCB_IMGPSScanX.Set_Visible(Value: WordBool); |
| | | begin |
| | | Visible := Value; |
| | | end; |
| | | |
| | | procedure TCB_IMGPSScanX.PageDone; |
| | | Var |
| | | ISB,NowISB : TImageScrollBox; |
| | | begin |
| | | inc(Scaninfo.ImageCount); |
| | | case ScanMode of |
| | | smNew: |
| | | begin |
| | | if ScanImgShowMode = 0 then //清楚顯示 |
| | | begin |
| | | ISB := FindISB2View(VMode); |
| | | ISB.AntiAliased := True; |
| | | ISB.ZoomMode := zmFittopage; |
| | | ISB.LoadFromFile(PEFileName,1); |
| | | end |
| | | Else if ScanImgShowMode = 1 then //模糊顯示 |
| | | begin |
| | | ISB := FindISB2View(VMode); |
| | | ISB.AntiAliased := False; |
| | | ISB.ZoomMode := zmFittopage; |
| | | ISB.LoadFromFile(PEFileName,1); |
| | | end |
| | | Else if ScanImgShowMode = 1 then //不顯示 |
| | | begin |
| | | |
| | | end |
| | | end; |
| | | smReplace: |
| | | begin |
| | | DisplayISB.LoadFromFile(PEFileName,1); |
| | | end; |
| | | smInsert: |
| | | begin |
| | | ISB := FindISB2View(VMode); |
| | | ISB.ZoomMode := zmFittopage; |
| | | ISB.LoadFromFile(PEFileName,1); |
| | | end; |
| | | smSample: |
| | | begin |
| | | ISB := FindISB2View(VMode); |
| | | ISB.ZoomMode := zmFittopage; |
| | | ISB.LoadFromFile(PEFileName,1); |
| | | end; |
| | | smRTS: |
| | | begin |
| | | |
| | | end; |
| | | end; |
| | | end; |
| | | |
| | | procedure TCB_IMGPSScanX.PageEnd; |
| | | Var |
| | | i,n : Integer; |
| | | SampleFormID : String; |
| | | DocNo,FormID,FormVersion : String; |
| | | BarStr : String; |
| | | begin |
| | | case ScanMode of |
| | | smNew: |
| | | begin |
| | | ScanSaveFilename := ''; |
| | | DocNo:=''; |
| | | FormID:=''; |
| | | FormVersion:=''; |
| | | PEFileName := ''; |
| | | //if not FindNoSaveBarCode then //沒有不儲存影像的條碼 |
| | | //begin |
| | | if FormID = '' then |
| | | begin |
| | | FormID := BarCode2FormID; //取出FormID |
| | | end; |
| | | |
| | | if (FormID <> '') and ISDivPageFormID(FormID) then |
| | | begin |
| | | NowDivPageFormID := FormID; |
| | | end; |
| | | |
| | | if (FormID <> '') and ISGuideFormID(FormID) then |
| | | begin |
| | | NowGuideFormID := FormID; |
| | | end; |
| | | //ShowMessage('NowGuideFormID='+NowGuideFormID); |
| | | if (not (FindDivFormCode(FormID))) and (NowGuideFormID <> '') {and (FormID = '')} then |
| | | FormID := NowGuideFormID; |
| | | DocNo := FormCode2DocNo(FormID); |
| | | //ShowMessage('FormID='+FormID); |
| | | //ShowMessage('ISDivPageFormID(FormID)='+BoolToStr(ISDivPageFormID(FormID),true)); |
| | | //ShowMessage('FindDivFormCode(FormID)='+BoolToStr(FindDivFormCode(FormID),true)); |
| | | //ShowMessage('A NowDivPageFormID='+NowDivPageFormID+#10#13+'FormID='+FormID+#10#13+'ScanCaseno='+ScanCaseno); |
| | | if (FormID <>'') and FindDivFormCode(FormID) and (NowDivPageFormID <> '') Then //只找分案頁上的案件條碼 |
| | | begin |
| | | |
| | | ScanInfo.ImageCount := 0; |
| | | ClearView(1); |
| | | ContextList.Clear; |
| | | Context_DocnoList.Clear; |
| | | ClearCaseIndex; //清掉案件索引 |
| | | ScanCaseno := BarCode2CaseID; //取出案件編號 |
| | | NowGuideFormID := ''; |
| | | NowDivPageFormID :=''; |
| | | //ShowMessage('B NowGuideFormID='+NowGuideFormID+#10#13+'FormID='+FormID+#10#13+'ScanCaseno='+ScanCaseno); |
| | | end; |
| | | if ScanCaseno = '' then //一開始都沒找到 |
| | | begin |
| | | ScanCaseno := GetNoNameCase(ImageSavePath); |
| | | end; |
| | | ImageSavePath := ImagePath; |
| | | if (ScanInfo.ImageCount = 0) then |
| | | begin |
| | | if DirectoryExists(ImageSavePath + ScanCaseno+'\') then |
| | | begin |
| | | _DelTree(ImageSavePath + ScanCaseno+'\'); |
| | | SetCaseList('D',-1,ScanCaseno); |
| | | end; |
| | | end; |
| | | |
| | | ScanPath := ImageSavePath+ScanCaseno+'\'; |
| | | Str2Dir(ScanPath); |
| | | |
| | | ScanDocDir := FindLastestDocDir(ScanCaseno,DocNo); |
| | | //ShowMessage('AA ScanDocDir='+ScanDocDir); |
| | | |
| | | //ShowMessage('BB ScanDocDir='+ScanDocDir); |
| | | if DocNoNeedDiv(DocNo)then //要分份數 |
| | | begin |
| | | //Showmessage(DocNo+#13+FormCode2Page(FormID)+#13+inttostr(GetDocDir_Page(ScanCaseno,ScanDocDir))+#13+ScanDocDir); |
| | | if ((FormCode2Page(FormID) = '01') and (GetDocDir_Page(ScanCaseno,ScanDocDir)>0)) or (ScanDocDir = '') then |
| | | begin |
| | | //ScanInfo.ImageCount := 0; |
| | | ScanDocDir := DocNo2DocNoDir(ImageSavePath + ScanCaseno+'\',DocNo); |
| | | end; |
| | | end |
| | | Else //不分份數 |
| | | begin |
| | | if DocNo <> '' then |
| | | ScanDocDir := DocNo |
| | | else //Attach 附件 |
| | | ScanDocDir := DocNo2DocNoDir(ImageSavePath + ScanCaseno+'\',DocNo); |
| | | end; |
| | | //ScanDocDir := GetDocNoDir(ImageSavePath+ScanCaseno+'\',DocNo); |
| | | if FirstDocDir = '' then |
| | | FirstDocDir := ScanDocDir; |
| | | |
| | | ScanPath := ImageSavePath+ScanCaseno+'\'+ScanDocdir+'\'; |
| | | //Showmessage(ScanPath); |
| | | if (not DirectoryExists(ScanPath)) and (ScanDocdir <> AttName) then |
| | | begin |
| | | //Showmessage('ADD:'+ScanCaseno+','+ScanDocdir); |
| | | SetDocNoList('A',-1,ScanCaseno,ScanDocdir,'1'); |
| | | end; |
| | | Str2Dir(ScanPath); |
| | | |
| | | |
| | | ScanSaveFilename := FormID; |
| | | |
| | | Str2Dir(ScanPath); |
| | | if ScanSaveFilename = '' then //附件 |
| | | ScanSaveFilename:= Add_Zoo(GetDocDir_Page(ScanCaseNo,ScanDocDir)+1,3)+ext |
| | | //ScanSaveFilename:= Add_Zoo(ScanInfo.ImageCount+1,3)+ext |
| | | Else |
| | | ScanSaveFilename := Add_Zoo(GetDocDir_Page(ScanCaseNo,ScanDocDir)+1,3)+'_'+ScanSaveFilename+ext; |
| | | if not FindNoSaveBarCode then //沒有不儲存影像的條碼 |
| | | begin |
| | | if ScanInfo.ImageCount = 0 then |
| | | begin |
| | | SetCaseList('A',-1,ScanCaseno); |
| | | WriteCaseIndex(ImageSavePath + ScanCaseno+'\'); //寫入案件索引 |
| | | MyTreeNode1 := TreeView1.Items.AddChild(NewTreenode,ScanCaseno); |
| | | MyTreenode1.ImageIndex := 2; |
| | | MyTreenode1.SelectedIndex := 2; |
| | | Application.ProcessMessages; |
| | | end; |
| | | |
| | | SetContextList('A',-1,ScanCaseno,ScanDocDir,ScanSaveFilename); |
| | | //ContextList.Add(ScanSaveFilename); |
| | | //ContextList.SaveToFile(ScanPath+'Context.dat'); |
| | | |
| | | PEFileName := ScanPath+ScanSaveFilename; |
| | | end; |
| | | end; |
| | | smReplace: |
| | | begin |
| | | if ScanInfo.ImageCount = 0 then |
| | | begin |
| | | DeleteFile(ScanPath+ScanSaveFilename); |
| | | PEFileName := ScanPath+ScanSaveFilename; |
| | | end; |
| | | end; |
| | | smInsert: |
| | | begin |
| | | ScanSaveFilename := ''; |
| | | FormID := BarCode2FormID; //取出FormID |
| | | if (FormID <> '') and ISGuideFormID(FormID) then //20170510 註解 因為DSCAN 會全擠在導引頁下 |
| | | NowGuideFormID := FormID; |
| | | if (NowGuideFormID <> '') {and (FormID = '')} then //20170510 註解 因為DSCAN 會全擠在導引頁下 |
| | | FormID := NowGuideFormID; |
| | | DocNo := FormCode2DocNo(FormID); |
| | | ScanDocDir := FindLastestDocDir(ScanCaseno,DocNo); |
| | | |
| | | if (FMode='ESCAN') and (FModeName=_Msg('補件掃描')) then //20180207 加入的特殊邏輯 |
| | | begin |
| | | ScanDocDir := FindLastestDocDirForPage(ScanCaseno, DocNo,FormID); |
| | | end; |
| | | |
| | | if (DocNoNeedDiv(DocNo)) then //要分份數 |
| | | begin |
| | | if TreeView1.Selected = MyTreeNode1 then //20170421 掃瞄插頁時選則在案號上才要分份數 選在FormID上就不分份數 |
| | | begin |
| | | if ((FormCode2Page(FormID) = '01') and (GetDocDir_Page(ScanCaseno,ScanDocDir)>0)) or (ScanDocDir = '') then |
| | | begin |
| | | ScanInfo.ImageCount := 0; |
| | | ScanDocDir := DocNo2DocNoDir(ImageSavePath + ScanCaseno+'\',DocNo); |
| | | end; |
| | | end; |
| | | end |
| | | Else //不分份數 |
| | | begin |
| | | if DocNo <> '' then |
| | | ScanDocDir := DocNo |
| | | else //Attach 附件 |
| | | ScanDocDir := DocNo2DocNoDir(ImageSavePath + ScanCaseno+'\',DocNo); |
| | | end; |
| | | if FirstDocDir = '' Then |
| | | FirstDocDir := ScanDocDir; |
| | | |
| | | ScanPath := ImageSavePath+ScanCaseno+'\'+ScanDocdir+'\'; |
| | | if (not DirectoryExists(ScanPath)) and (ScanDocdir <> 'Attach') and (ScanDocdir <> 'S_Attach') then |
| | | SetDocNoList('A',-1,ScanCaseno,ScanDocdir,'1'); |
| | | |
| | | |
| | | ScanSaveFilename := FormID; |
| | | Str2Dir(ScanPath); |
| | | if ScanSaveFilename = '' then //附件 |
| | | ScanSaveFilename:= Add_Zoo(GetDocDir_Page(ScanCaseno,ScanDocdir)+1,3)+ext |
| | | Else |
| | | ScanSaveFilename := Add_Zoo(GetDocDir_Page(ScanCaseno,ScanDocdir)+1,3)+'_'+ScanSaveFilename+ext; |
| | | //ContextList.Add(ScanSaveFilename); |
| | | //ContextList.SaveToFile(ScanPath+'Context.dat'); |
| | | SetContextList('A',-1,ScanCaseno,ScanDocDir,ScanSaveFilename); |
| | | //Showmessage(ScanPath+ScanSaveFilename); |
| | | //Showmessage('Stop'); |
| | | PEFileName := ScanPath+ScanSaveFilename; |
| | | end; |
| | | smSample: |
| | | begin |
| | | if ScanInfo.ImageCount = 0 then |
| | | begin |
| | | DeleteFile(ScanPath+ScanSaveFilename); |
| | | PEFileName := ScanPath+ScanSaveFilename; |
| | | BarStr := ''; |
| | | for i := 1 to MpsBarCodeinf.Count do |
| | | begin |
| | | BarStr := BarStr + MpsBarCodeinf.Text[i]; |
| | | end; |
| | | Showmessage(_Msg('辨識到的BarCode:')+#13+BarStr); |
| | | end; |
| | | end; |
| | | smRTS: |
| | | begin |
| | | |
| | | end; |
| | | end; |
| | | Application.ProcessMessages; |
| | | |
| | | end; |
| | | |
| | | |
| | | procedure TCB_IMGPSScanX.InitialLanguage(Sender: TObject); |
| | | var |
| | | ini : Tmeminifile; |
| | | i,n : Integer; |
| | | FormName : String; |
| | | NowForm : TComponent; |
| | | begin |
| | | if Sender is TActiveForm then |
| | | NowForm := TActiveForm(Sender); |
| | | if Sender is TForm then |
| | | NowForm := TForm(Sender); |
| | | |
| | | FormName := NowForm.Name; |
| | | IISUnit.IIS_LngfileName := LngPath+'Language.Lng'; //給IISUnit 轉多國語言字串用 |
| | | if FLanguage = '' then |
| | | FLanguage := 'zh_tw'; |
| | | IISUnit.IIS_NowLng := FLanguage; |
| | | ini := TMeminifile.Create(LngPath+'Language.Lng'); |
| | | try |
| | | IF NowForm is TForm Then |
| | | TForm(NowForm).Caption := ini.ReadString(FLanguage,FormName+'.FormTitle',''); |
| | | for i := 0 to NowForm.ComponentCount - 1 do |
| | | begin |
| | | //ShowMessage(NowForm.Components[i].Name); |
| | | if NowForm.Components[i] is TButton then |
| | | begin |
| | | TButton(NowForm.Components[i]).Caption := ini.ReadString(FLanguage,FormName+'.'+TButton(NowForm.Components[i]).Name,''); |
| | | //TBitBtn(NowForm.Components[i]).Caption := ini.ReadString(FormName,TBitBtn(NowForm.Components[i]).Name,''); |
| | | //TButton(NowForm.Components[i]).OnMouseEnter := BtnMouseEnter; |
| | | end |
| | | Else if NowForm.Components[i] is TBitBtn then |
| | | begin |
| | | TBitBtn(NowForm.Components[i]).Hint := ini.ReadString(FLanguage,FormName+'.'+TBitBtn(NowForm.Components[i]).Name,''); |
| | | //TBitBtn(NowForm.Components[i]).Caption := ini.ReadString(FormName,TBitBtn(NowForm.Components[i]).Name,''); |
| | | TBitBtn(NowForm.Components[i]).OnMouseEnter := BtnMouseEnter; |
| | | end |
| | | Else if NowForm.Components[i] is TMenuItem then |
| | | begin |
| | | if ini.ValueExists(FLanguage,FormName+'.'+TMenuItem(NowForm.Components[i]).Name) then |
| | | TMenuItem(NowForm.Components[i]).Caption := ini.ReadString(FLanguage,FormName+'.'+TMenuItem(NowForm.Components[i]).Name,''); |
| | | end |
| | | Else if NowForm.Components[i] is TCheckBox then |
| | | begin |
| | | TCheckBox(NowForm.Components[i]).Caption := ini.ReadString(FLanguage,FormName+'.'+TCheckBox(NowForm.Components[i]).Name,''); |
| | | end |
| | | Else if NowForm.Components[i] is TPJMenuSpeedButton then |
| | | begin |
| | | TPJMenuSpeedButton(NowForm.Components[i]).Hint := ini.ReadString(FLanguage,FormName+'.'+TPJMenuSpeedButton(NowForm.Components[i]).Name,''); |
| | | end |
| | | Else if NowForm.Components[i] is TLabel then |
| | | begin |
| | | TLabel(NowForm.Components[i]).Caption := ini.ReadString(FLanguage,FormName+'.'+TLabel(NowForm.Components[i]).Name,''); |
| | | end |
| | | Else if NowForm.Components[i] is TGroupBox then |
| | | begin |
| | | TGroupBox(NowForm.Components[i]).Caption := ini.ReadString(FLanguage,FormName+'.'+TGroupBox(NowForm.Components[i]).Name,''); |
| | | end |
| | | Else if NowForm.Components[i] is TListView then |
| | | begin |
| | | for n := 0 to TListView(NowForm.Components[i]).Columns.Count - 1 do |
| | | begin |
| | | TListView(NowForm.Components[i]).Columns.Items[n].Caption := ini.ReadString(FLanguage,FormName+'.'+TListView(NowForm.Components[i]).Name+'_'+inttostr(n),''); |
| | | end; |
| | | end |
| | | Else if NowForm.Components[i] is TRadioGroup then |
| | | begin |
| | | TRadioGroup(NowForm.Components[i]).Caption := ini.ReadString(FLanguage,FormName+'.'+TRadioGroup(NowForm.Components[i]).Name,''); |
| | | for n := 0 to TRadioGroup(NowForm.Components[i]).Items.Count - 1 do |
| | | begin |
| | | TRadioGroup(NowForm.Components[i]).Items.Strings[n] := ini.ReadString(FLanguage,FormName+'.'+TRadioGroup(NowForm.Components[i]).Name+'_'+inttostr(n),''); |
| | | end; |
| | | end; |
| | | end; |
| | | finally |
| | | ini.Free; |
| | | end; |
| | | end; |
| | | |
| | | function TCB_IMGPSScanX.GetSiteOMR(FileName,Site:String;bt: Integer): Integer; |
| | | var |
| | | OMRRect : TRect; |
| | | Xdpi,Ydpi : Integer; |
| | | W,H : Integer; |
| | | begin |
| | | Result := 0; |
| | | //ShowMessage('GetSiteOMR'); |
| | | |
| | | IF (ImageScrollBox1.FileName <> FileName) and (FileName <> '') then |
| | | begin |
| | | //ShowMessage('11111'+ImageScrollBox1.FileName+#10#13+FileName); |
| | | ImageScrollBox1.LoadFromFile(FileName,1); |
| | | { |
| | | ShowMessage('UpLPoint='+IntToStr(UpLPoint.X)+','+IntToStr(UpLPoint.Y)+#10#13+ |
| | | 'UpRPoint='+IntToStr(UpRPoint.X)+','+IntToStr(UpRPoint.Y)+#10#13+ |
| | | 'DownLPoint='+IntToStr(DownLPoint.X)+','+IntToStr(DownLPoint.Y)+#10#13+ |
| | | 'DownRPoint='+IntToStr(DownRPoint.X)+','+IntToStr(DownRPoint.Y)); |
| | | FindPoint(ImageScrollBox1.Graphic,UpLPoint,UpRPoint,DownLPoint,''); |
| | | ShowMessage('UpLPoint='+IntToStr(UpLPoint.X)+','+IntToStr(UpLPoint.Y)+#10#13+ |
| | | 'UpRPoint='+IntToStr(UpRPoint.X)+','+IntToStr(UpRPoint.Y)+#10#13+ |
| | | 'DownLPoint='+IntToStr(DownLPoint.X)+','+IntToStr(DownLPoint.Y)+#10#13+ |
| | | 'DownRPoint='+IntToStr(DownRPoint.X)+','+IntToStr(DownRPoint.Y)); |
| | | } |
| | | ClearLine(ISB_BW.Graphic,bt); |
| | | ISB_BW.Redraw(True); |
| | | Application.ProcessMessages; |
| | | end; |
| | | If ImageScrollBox1.FileName <> '' Then |
| | | begin |
| | | |
| | | //ShowMessage('22222'+ImageScrollBox1.FileName); |
| | | Xdpi := ImagescrollBox1.Graphic.XDotsPerInch; |
| | | Ydpi := ImagescrollBox1.Graphic.YDotsPerInch; |
| | | H := ImageScrollBox1.Graphic.Height; |
| | | W := ImageScrollBox1.Graphic.Width; |
| | | //ShowMessage('Xdpi='+IntToStr(Xdpi)+#10#13+'Ydpi='+IntToStr(Ydpi)+#10#13+'H='+IntToStr(H)+#10#13+'W='+IntToStr(W)+#10#13); |
| | | //ShowMessage('Site='+Site); |
| | | OMRRect := CM_Str2Rect(Site,Xdpi,UpLPoint); |
| | | Display1.Lines.Add('UpLPoint=('+IntToStr(UpLPoint.X)+','+IntToStr(UpLPoint.Y)+');'+Site+';'+IntToStr(OMRRect.Left)+','+IntToStr(OMRRect.top)+','+IntToStr(OMRRect.Right)+','+IntToStr(OMRRect.Bottom)); |
| | | if OMRRect.Left < 0 then |
| | | OMRRect.Left := 0; |
| | | if OMRRect.Top < 0 then |
| | | OMRRect.Top := 0; |
| | | if OMRRect.Right > ImageScrollBox1.Graphic.Width then |
| | | OMRRect.Right := ImageScrollBox1.Graphic.Width; |
| | | if OMRRect.Bottom > ImageScrollBox1.Graphic.Height then |
| | | OMRRect.Bottom := ImageScrollBox1.Graphic.Height; |
| | | |
| | | result := Get_OMR(ISB_BW.Graphic,OMRRect); |
| | | //ShowMessage('result='+IntToStr(result)); |
| | | |
| | | end; |
| | | end; |
| | | |
| | | Procedure TCB_IMGPSScanX.DisplayMode(index,H_Count,W_Count:Integer;BasePanel:TPanel); |
| | | Var |
| | | W,H,T,L:Integer; |
| | | i,n,Count: Integer; |
| | | Pl :TPanel; |
| | | bmp : TBitmap; |
| | | begin |
| | | for i := 1 to 8 do |
| | | begin |
| | | TPanel(Findcomponent('imgp'+inttostr(i))).Visible := False; |
| | | end; |
| | | W := Round((BasePanel.Width - ((W_Count+1) * Seg)) / W_Count); |
| | | H := Round((BasePanel.Height -((H_Count+1) * Seg)) / H_Count); |
| | | Count := 1; |
| | | for i := 1 to H_Count do |
| | | begin |
| | | T := i * Seg + H * (i-1); |
| | | for n := 1 to W_Count do |
| | | begin |
| | | L := n * Seg + W * (n-1); |
| | | Pl := TPanel(Findcomponent('imgp'+inttostr(Count))); |
| | | Pl.Visible := True; |
| | | Pl.Left := L; |
| | | Pl.Top := T; |
| | | Pl.Width := W; |
| | | Pl.Height := H; |
| | | inc(Count); |
| | | end; |
| | | end; |
| | | Shape1.Width := W + (Seg * 2); |
| | | Shape1.Height := H + (Seg * 2); |
| | | Shape1.Visible := True; |
| | | |
| | | |
| | | bmp := Tbitmap.Create; |
| | | try |
| | | ImageList2.GetBitmap(index,bmp); |
| | | ViewModeBtn.Glyph.Assign(bmp); |
| | | finally |
| | | bmp.Free; |
| | | end; |
| | | ISB1Click(ISB1); |
| | | end; |
| | | |
| | | Function TCB_IMGPSScanX.GetSampleInf : Boolean; |
| | | var |
| | | str:String; |
| | | begin |
| | | Result := False; |
| | | If not ProcessServlet_Get(HTTPSClient,FURL+'service/imgpsc/IMGPSC01/serversampleforocx','work_no='+FWork_no,FReWrite,Memo1,False) Then |
| | | begin |
| | | HttpErrStr := _Msg('錯誤代碼:')+inttostr(HttpError.HttpErrorCode)+','+HttpError.HttpReason; |
| | | Result := False; |
| | | Exit; |
| | | end; |
| | | IF memo1.Lines.Strings[0] = '1' Then |
| | | begin |
| | | HttpErrStr := _Msg('錯誤原因:')+memo1.Lines.Strings[1]; |
| | | Result := False; |
| | | Exit; |
| | | end |
| | | Else IF memo1.Lines.Strings[0] = '0' Then |
| | | begin |
| | | str := memo1.Lines.Strings[1]; |
| | | SampleFormIDList.CommaText:=str; |
| | | |
| | | Result := True; |
| | | end |
| | | Else if Pos('<script type="text/javascript" src="scripts/CW00/login.js"></script>',Memo1.Lines.Text) > 0 then |
| | | begin |
| | | HttpErrStr := _Msg('錯誤原因:')+_Msg('閒置過久或被登出,請重新登入'); |
| | | Result := False; |
| | | Exit; |
| | | end; |
| | | |
| | | end; |
| | | |
| | | function TCB_IMGPSScanX.CheckRequiredColumnValues(workno, caseno:String): Boolean; |
| | | begin |
| | | // |
| | | Result:=False; |
| | | if (workno='HLN') and (caseno[9]='3') then |
| | | Result:=True; |
| | | if (workno='HLN') and (caseno[9]='4') then |
| | | Result:=True; |
| | | end; |
| | | |
| | | Procedure TCB_IMGPSScanX.CheckRule2OMRErrInfo; //檢核規則填入OMRErrINFo Record |
| | | var i : Integer; |
| | | CheckNo : String; |
| | | begin |
| | | for I := 1 to 11 do |
| | | begin |
| | | CheckNo := Add_Zoo(i,3); |
| | | |
| | | if FindSQLData(CHECK_RULE_INF_List,'MESG_SHOW_TYPE,MESG_DISP_TYPE,CHECK_MESG,SCAN_MODE','CHECK_NO',CheckNo,0,FindResult) then |
| | | begin |
| | | if GetFindResult('MESG_SHOW_TYPE') = '1' then |
| | | OMRErrInfo[i].Display := True //顯示 |
| | | Else if GetFindResult('MESG_SHOW_TYPE') = '2' then |
| | | OMRErrInfo[i].Display := False; //不顯示 |
| | | |
| | | if GetFindResult('MESG_DISP_TYPE') = '1' then |
| | | OMRErrInfo[i].Ignore := True //可忽略 |
| | | Else if GetFindResult('MESG_DISP_TYPE') = '2' then |
| | | OMRErrInfo[i].Ignore := False; //不可忽略 |
| | | |
| | | OMRErrInfo[i].Info := GetFindResult('CHECK_MESG'); |
| | | OMRErrInfo[i].Mode := GetFindResult('SCAN_MODE'); |
| | | end; |
| | | end; |
| | | end; |
| | | |
| | | |
| | | |
| | | Procedure TCB_IMGPSScanX.ReNameContext(Path,OldName,NewName:String); |
| | | var |
| | | i : Integer; |
| | | begin |
| | | for i := 0 to ContextList.Count - 1 do |
| | | begin |
| | | if OldName = ContextList.Strings[i] then |
| | | begin |
| | | ContextList.Strings[i] := NewName; |
| | | ContextList.SaveToFile(Path+'Context.dat'); |
| | | Context_DocnoList.Strings[i] := FormCode2DocNo(FileName2FormCode(NewName)); |
| | | Context_DocnoList.SaveToFile(Path+'Context_DocNo.dat'); |
| | | Break; |
| | | end; |
| | | end; |
| | | end; |
| | | |
| | | Function TCB_IMGPSScanX.Down_Img(Path,CaseID:String):Boolean; |
| | | var |
| | | EnCodeDateTime : String; |
| | | SendData : String; |
| | | AttPath : String; |
| | | begin |
| | | Result := True; |
| | | EnCodeDateTime := En_DecryptionStr_Base64('E',ServerDate+GetBalance2Time(Balance),Mpskey); |
| | | ///service/slic/SLIC04/case?data=&verify=&case_no=&file= |
| | | SendData := 'data='+HTTPEncode(UTF8Encode(FData))+'&verify='+FVerify+'&case_no='+CaseID+'&file='; |
| | | //ShowMessage(SendData); |
| | | if not dnFile_Get(HTTPSClient,Furl,'service/imgpsc/IMGPSC04/case',SendData,Path+CaseID+'.zip',FReWrite,Memo1,False,DownImgStatus) then |
| | | begin |
| | | HttpErrStr := _Msg('錯誤代碼:')+Inttostr(HttpError.HttpErrorCode)+' '+HttpError.HttpReason; |
| | | Result := False; |
| | | Exit; |
| | | end; |
| | | if Memo1.Lines.Strings[0] = '1' then |
| | | begin |
| | | HttpErrStr :=_Msg('錯誤原因:')+memo1.Lines.Strings[1]+'。'; |
| | | Result := False; |
| | | Exit; |
| | | end |
| | | Else if Pos('<script type="text/javascript" src="scripts/IMGPS00/login.js"></script>',Memo1.Lines.Text) > 0 then |
| | | begin |
| | | HttpErrStr := _Msg('錯誤原因:')+_Msg('閒置過久或被登出,請重新登入'); |
| | | Result := False; |
| | | Exit; |
| | | end; |
| | | //ShowMessage('替換zip'); |
| | | AttPath := Path + 'AttFile\'; |
| | | if FileExists(Path+CaseID+'.zip') then |
| | | begin |
| | | ExecuteUnZip(Path+CaseID+'.zip',Path,True); |
| | | if FileExists(Path+'img.zip') then |
| | | begin |
| | | ExecuteUnZip(Path+'img.zip',Path,False); |
| | | end; |
| | | if FileExists(Path+'att.zip') then |
| | | begin |
| | | Str2Dir(AttPath); |
| | | ExecuteUnZip(Path+'att.zip',AttPath,False); |
| | | end; |
| | | end |
| | | Else |
| | | begin |
| | | HttpErrStr := _Msg('找不到影像'); |
| | | Result := True; |
| | | Exit; |
| | | end; |
| | | |
| | | end; |
| | | |
| | | Procedure TCB_IMGPSScanX.ClearErrini(CaseID:String;CaseNode:TTreeNode); //清掉檢核檔案 |
| | | var |
| | | i : Integer; |
| | | begin |
| | | if FileExists(ImageSavePath+CaseID+'\Checkerr.ini') then |
| | | DeleteFile(ImageSavePath+CaseID+'\Checkerr.ini'); |
| | | if FileExists(ImageSavePath+CaseID+'\CheckMemo.dat') then |
| | | DeleteFile(ImageSavePath+CaseID+'\CheckMemo.dat'); |
| | | {if FileExists(ImageSavePath+CaseID+'\ReSize.dat') then //20110421拿掉 因為記錄會不見 |
| | | DeleteFile(ImageSavePath+CaseID+'\ReSize.dat');} |
| | | if FileExists(ImageSavePath+CaseID+'\RemoveMemo.dat') then |
| | | DeleteFile(ImageSavePath+CaseID+'\RemoveMemo.dat'); |
| | | if FileExists(ImageSavePath+CaseID+'\OMRCheckOk.dat') then |
| | | DeleteFile(ImageSavePath+CaseID+'\OMRCheckOk.dat'); |
| | | CaseHelpBtn.Visible := False; |
| | | CaseNode.ImageIndex := 2; |
| | | CaseNode.SelectedIndex := 2; |
| | | end; |
| | | |
| | | Procedure TCB_IMGPSScanX.SetContextList(Mode:Char;Index:Integer;CaseNo,DocDir,FileName:String); //'A:加入,I:插入,D:刪除,E:修改' |
| | | var |
| | | i : Integer; |
| | | //DocNo:String; |
| | | begin |
| | | //DocNo := FormCode2DocNo(FileName2FormCode(FileName)); |
| | | //ShowMessage('FileName='+FileName); |
| | | if DocDir = '' then |
| | | DocDir := AttName ; //附件 |
| | | ContextList.Clear; |
| | | if FileExists(ImageSavePath+CaseNo+'\'+DocDir+'\Context.dat') then |
| | | ContextList.LoadFromFile(ImageSavePath+CaseNo+'\'+DocDir+'\Context.dat'); |
| | | SetRecordEditedDocDir('A',CaseNo,DocDir); //記錄文件有異動 |
| | | case Mode of |
| | | 'A':begin |
| | | ContextList.Add(FileName); |
| | | end; |
| | | 'I':begin |
| | | ContextList.Insert(Index,FileName); |
| | | end; |
| | | 'E':begin |
| | | ContextList.Strings[Index] := FileName; |
| | | end; |
| | | 'D':begin |
| | | if Index <> -1 then |
| | | begin |
| | | ContextList.Delete(Index); |
| | | end |
| | | Else if (text <> '') then |
| | | begin |
| | | for i := 0 to ContextList.Count - 1 do |
| | | begin |
| | | if FileName = ContextList.Strings[i] then |
| | | begin |
| | | ContextList.Delete(i); |
| | | Break; |
| | | end; |
| | | end; |
| | | end; |
| | | if ContextList.Count = 0 then |
| | | DeleteFile(ImageSavePath+CaseNo+'\'+DocDir+'\Context.dat'); |
| | | end; |
| | | end; |
| | | if ContextList.Count > 0 then |
| | | begin |
| | | ContextList.SaveToFile(ImageSavePath+CaseNo+'\'+DocDir+'\Context.dat'); |
| | | end; |
| | | end; |
| | | |
| | | Procedure TCB_IMGPSScanX.SetAttContextList(Mode:Char;Index:Integer;CaseNo,FileName:String); //'A:加入,I:插入,D:刪除,E:修改' |
| | | var |
| | | i : Integer; |
| | | begin |
| | | AttContextList.Clear; |
| | | if FileExists(ImageSavePath+CaseNo+'\AttContext.dat') then |
| | | AttContextList.LoadFromFile(ImageSavePath+CaseNo+'\AttContext.dat'); |
| | | case Mode of |
| | | 'A':begin |
| | | AttContextList.Add(FileName); |
| | | end; |
| | | 'I':begin |
| | | AttContextList.Insert(Index,FileName); |
| | | end; |
| | | 'E':begin |
| | | AttContextList.Strings[Index] := FileName; |
| | | end; |
| | | 'D':begin |
| | | if Index <> -1 then |
| | | begin |
| | | AttContextList.Delete(Index); |
| | | end |
| | | Else if (text <> '') then |
| | | begin |
| | | for i := 0 to AttContextList.Count - 1 do |
| | | begin |
| | | if FileName = AttContextList.Strings[i] then |
| | | begin |
| | | AttContextList.Delete(i); |
| | | Break; |
| | | end; |
| | | end; |
| | | end; |
| | | if AttContextList.Count = 0 then |
| | | DeleteFile(ImageSavePath+CaseNo+'\AttContext.dat'); |
| | | end; |
| | | end; |
| | | if AttContextList.Count > 0 then |
| | | begin |
| | | AttContextList.SaveToFile(ImageSavePath+CaseNo+'\AttContext.dat'); |
| | | end; |
| | | end; |
| | | |
| | | Function TCB_IMGPSScanX.ModeNeedCheck(OMRMode,ScanMode:String):Boolean; //掃瞄模式是否要做檢核 |
| | | begin |
| | | Result := False; |
| | | if Pos(ScanMode,OMRMode) > 0 then |
| | | Result := True; |
| | | end; |
| | | |
| | | Function TCB_IMGPSScanX.FindNoSaveBarCode : Boolean; //找是否有不要儲存影像的條碼 |
| | | var |
| | | i,n : Integer; |
| | | begin |
| | | Result := False; |
| | | for i := 1 to MpsBarcodeinf.Count do |
| | | begin |
| | | for n := 0 to NoSaveBarCodeList.Count - 1 do |
| | | begin |
| | | if MpsBarcodeinf.Text[i] = NoSaveBarCodeList.Strings[n] then |
| | | begin |
| | | Result := True; |
| | | Break; |
| | | end; |
| | | end; |
| | | if Result then |
| | | Break; |
| | | end; |
| | | end; |
| | | |
| | | Function TCB_IMGPSScanX.Index2Anchor(Anchor:String):String; //十字模式 0->NONE;1->ANCHOR;2->FRAME |
| | | begin |
| | | if Anchor = '0' then |
| | | Result := 'NONE' |
| | | else if Anchor = '1' then |
| | | Result := 'ANCHOR' |
| | | else if Anchor = '2' then |
| | | Result := 'FRAME'; |
| | | end; |
| | | |
| | | Function TCB_IMGPSScanX.GetFindResult(Col:String):String; |
| | | var |
| | | i,v,v1 : Integer; |
| | | S,RCol,RValue : String; |
| | | begin |
| | | Result := ''; |
| | | for I := 0 to FindResult.Count - 1 do |
| | | begin |
| | | S := FindResult.Strings[i]; |
| | | v := Pos(',',S); |
| | | v1 := length(S); |
| | | RCol := copy(S,1,v-1); |
| | | RValue := Copy(S,v+1,v1-v); |
| | | if Col =RCol then |
| | | Result := RValue; |
| | | end; |
| | | end; |
| | | |
| | | Function TCB_IMGPSScanX.DrawDocItem2(CaseNode : TTreenode;Caseno:String):Boolean; //畫出文件名稱的Tree |
| | | Var |
| | | i,n,m : Integer; |
| | | DocNode,FormNode : TTreeNode; |
| | | DocNoPage,FormPage : Integer; |
| | | DocNoCopies : Integer; |
| | | DocNo,iDocNo : String; |
| | | DocVer : String; |
| | | FileList : TStringlist; |
| | | FormID,iFormID : String; |
| | | FormName : String; |
| | | CaseDocNoList,CaseDocNo_CopiesList,StrList : TStringlist; |
| | | iiDocNo,iiFormID,iiDocVer : String; |
| | | ST1:TStringList; |
| | | begin |
| | | Result := False; |
| | | FileList := TStringlist.Create; |
| | | CaseDocNoList := TStringlist.Create; |
| | | CaseDocNo_CopiesList := TStringlist.Create; |
| | | StrList := TStringlist.Create; |
| | | ST1:=TStringList.Create; |
| | | LogFile1.LogToFile(logTimeString+'產文件樹開始'); |
| | | try |
| | | CaseNode.ImageIndex := 1; |
| | | CaseNode.SelectedIndex := 1; |
| | | While CaseNode.Count > 0 do //全刪 |
| | | begin |
| | | CaseNode.Item[0].Delete; |
| | | end; |
| | | CaseDocNoList.Clear; |
| | | if FileExists(ImageSavePath+Caseno+'\CaseDocNo.dat') then |
| | | CaseDocNoList.LoadFromFile(ImageSavePath+Caseno+'\CaseDocNo.dat'); |
| | | if FileExists(ImageSavePath+Caseno+'\CaseDocNo_Copies.dat') then |
| | | CaseDocNo_CopiesList.LoadFromFile(ImageSavePath+Caseno+'\CaseDocNo_Copies.dat'); |
| | | for i := 0 to CaseDocNoList.Count - 1 do |
| | | begin |
| | | FileList.Clear; |
| | | //Showmessage(ImageSavePath+Caseno+'\'+CaseDocNoList.Strings[i]+'\Context.dat'); |
| | | if FileExists(ImageSavePath+Caseno+'\'+CaseDocNoList.Strings[i]+'\Context.dat') then |
| | | FileList.LoadFromFile(ImageSavePath+Caseno+'\'+CaseDocNoList.Strings[i]+'\Context.dat'); |
| | | iDocNo := DocNoDir2DocNo(CaseDocNoList.Strings[i]); |
| | | ST1.Clear; |
| | | |
| | | LogFile1.LogToFile(logTimeString+'FileList.Text='+FileList.CommaText); |
| | | if (FWH_category='N') and (FIs_In_Wh='Y') then |
| | | begin |
| | | for n := 0 to FileList.Count - 1 do |
| | | begin |
| | | if ISExistImg(ImageSavePath+Caseno+'\'+CaseDocNoList.Strings[i]+'\'+FileList.Strings[n]) then |
| | | begin |
| | | ST1.Add(FileList.Strings[n]); |
| | | end; |
| | | end; |
| | | for n := 0 to ST1.Count - 1 do |
| | | begin |
| | | if (FileList.IndexOf(ST1.Strings[n])<>-1) and (not DocNoIs_In_WH(iDocNo)) then |
| | | begin |
| | | FileList.Delete(FileList.IndexOf(ST1.Strings[n])); |
| | | end; |
| | | end; |
| | | end |
| | | Else |
| | | if not DocNoAppear(iDocNo) then Continue; //20180925 Hong覺得應該加這段 |
| | | |
| | | |
| | | |
| | | LogFile1.LogToFile(logTimeString+'WH_category='+FWH_category+',Is_In_Wh='+FIs_In_Wh+',FileList.Text='+FileList.CommaText); |
| | | if FileList.Count=0 then Continue; |
| | | |
| | | DocNoCopies := Strtoint(CaseDocNo_CopiesList.Strings[i]); |
| | | DocNoPage := FileList.Count; |
| | | iDocNo := DocNoDir2DocNo(CaseDocNoList.Strings[i]); |
| | | //Showmessage(iDocNo); |
| | | //Showmessage(DocNo2DocName(Caseno,iDocNo)); |
| | | //ShowMessage('FileList='+FileList.Text); |
| | | {if (((FIs_In_Wh = 'Y') and (not DocNoIs_In_WH(iDocNo))) or //入庫掃描不看非入庫文件 |
| | | ((FIs_In_Wh = 'N') and (DocNoIs_In_WH(iDocNo)))) and |
| | | ((iDocNo <> 'Attach') and (Copy(iDocNo,1,5)<>'ZZZZZ')) then //非入庫掃描不看入庫文件 |
| | | begin |
| | | Continue; |
| | | end;} |
| | | |
| | | //if not DocNoAppear(iDocNo) then Continue; |
| | | |
| | | |
| | | //DocNode := TreeView1.Items.AddChild(CaseNode,Format('%s{%s}-%d'+_msg('頁'),[CaseDocNoList.Strings[i],DocNo2DocName(Caseno,iDocNo),DocNoPage])); |
| | | //DocNode := TreeView1.Items.AddChild(CaseNode,Format('%s{%s}-%d'+_msg('份'),[CaseDocNoList.Strings[i],DocNo2DocName(Caseno,iDocNo),DocNoCopies])); |
| | | //ShowMessage('iDocNo='+iDocNo); |
| | | DocNode := TreeView1.Items.AddChild(CaseNode,Format(_Msg('%s{%s}-%d份'),[DocNo2DocName(Caseno,iDocNo),CaseDocNoList.Strings[i],DocNoCopies])); |
| | | |
| | | if GetUseCase('F',ImageSavePath+Caseno+'\',CaseDocNoList.Strings[i]) <> '' Then |
| | | begin |
| | | DocNode.ImageIndex := 8; |
| | | DocNode.SelectedIndex := 8; |
| | | end |
| | | Else if GetUseCase('T',ImageSavePath+Caseno+'\',CaseDocNoList.Strings[i]) <> '' Then |
| | | begin |
| | | DocNode.ImageIndex := 9; |
| | | DocNode.SelectedIndex := 9; |
| | | end |
| | | Else |
| | | begin |
| | | DocNode.ImageIndex := 2; |
| | | DocNode.SelectedIndex := 2; |
| | | end; |
| | | if ((Pos('ZZZZZ',DocNode.Text) = 0) and (Pos('YYYYY',DocNode.Text) = 0)) and (FileList.Count =0) then //制式文件 |
| | | begin |
| | | for n := 1 to LASTEST_FORM_INF_List.Count - 1 do |
| | | begin |
| | | StrList := SplitString('!@!',LASTEST_FORM_INF_List.Strings[n]); |
| | | iiFormID := StrList.Strings[0]; |
| | | iiDocNo := StrList.Strings[1]; |
| | | if iiDocNo = iDocNo then |
| | | begin |
| | | FormID := iiFormID; |
| | | FormPage := GetFormIDPage(FileList,FormID); |
| | | FormName := FormCode2FormName(Caseno,FormID); |
| | | //FormNode := TreeView1.Items.AddChild(DocNode,FormID+'{'+FormName+'}-'+inttostr(FormPage)+_msg('頁')); |
| | | FormNode := TreeView1.Items.AddChild(DocNode,Format(_Msg('%s{%s}-%d頁'),[FormName,FormID,FormPage])); |
| | | FormNode.ImageIndex := 4; |
| | | FormNode.SelectedIndex := 4; |
| | | DocNode.AlphaSort(True); |
| | | end; |
| | | end; |
| | | end |
| | | else if (Pos('ZZZZZ',DocNode.Text) > 0) or (Pos('YYYYY',DocNode.Text) > 0) then //自訂文件 |
| | | begin |
| | | FormID := GetCustomFormID(ImageSavePath+Caseno+'\',CaseDocNoList.Strings[i]); |
| | | //showmessage(FileList.Text); |
| | | FormPage := GetFormIDPage(FileList,FormID); |
| | | FormName := FormCode2FormName(Caseno,FormID); |
| | | //FormNode := TreeView1.Items.AddChild(DocNode,FormID+'{'+FormName+'}-'+inttostr(FormPage)+_msg('頁')); |
| | | //FormNode := TreeView1.Items.AddChild(DocNode,FormName+'{'+FormID+'}-'+inttostr(FormPage)+_msg('頁')); |
| | | FormNode := TreeView1.Items.AddChild(DocNode,Format(_Msg('%s{%s}-%d頁'),[FormName,FormID,FormPage])); |
| | | FormNode.ImageIndex := 4; |
| | | FormNode.SelectedIndex := 4; |
| | | DocNode.AlphaSort(True); |
| | | end; |
| | | SortDocDir_FormID(Caseno,CaseDocNoList.Strings[i]); //檔名依FormID排序 |
| | | |
| | | for n := 0 to FileList.Count - 1 do |
| | | begin |
| | | FormID := FileName2FormCode(FileList.Strings[n]); |
| | | DocVer := FormCode2Version(FormID); |
| | | DocNo := FormCode2DocNo(FormID); |
| | | if CheckFormIDExists(DocNode,FormID) then Continue; |
| | | //Showmessage(FormID+#13+DocNo+#13+DocVer); |
| | | for m := 0 to FormID_List.Count - 1 do |
| | | begin |
| | | iiFormID := FormID_List.Strings[m]; |
| | | iiDocNo := DocNo_List.Strings[m]; |
| | | iiDocVer := FormCode2Version(iiFormID); |
| | | if (iiDocNo = DocNo) and (iiDocVer = DocVer) then |
| | | begin |
| | | //Showmessage(iiFormID+#13+iiDocNo+#13+iiDocVer); |
| | | FormID := iiFormID; |
| | | FormPage := GetFormIDPage(FileList,FormID); |
| | | FormName := FormCode2FormName(Caseno,FormID); |
| | | //FormNode := TreeView1.Items.AddChild(DocNode,FormID+'{'+FormName+'}-'+inttostr(FormPage)+_msg('頁')); |
| | | //FormNode := TreeView1.Items.AddChild(DocNode,FormName+'{'+FormID+'}-'+inttostr(FormPage)+_msg('頁')); |
| | | FormNode := TreeView1.Items.AddChild(DocNode,Format(_Msg('%s{%s}-%d頁'),[FormName,FormID,FormPage])); |
| | | FormNode.ImageIndex := 4; |
| | | FormNode.SelectedIndex := 4; |
| | | DocNode.AlphaSort(True); |
| | | end; |
| | | end; |
| | | |
| | | |
| | | //if not CheckFormIDExists(DocNode,FormID) then |
| | | //begin |
| | | // Application.ProcessMessages; |
| | | // FormPage := GetFormIDPage(FileList,FormID); |
| | | /// FormName := FormCode2FormName(Caseno,FormID); |
| | | /// FormNode := TreeView1.Items.AddChild(DocNode,FormID+'{'+FormName+'}-'+inttostr(FormPage)+_msg('頁')); |
| | | // FormNode.ImageIndex := 4; |
| | | // FormNode.SelectedIndex := 4; |
| | | //end; |
| | | end; |
| | | end; |
| | | |
| | | if DirectoryExists(ImageSavePath+Caseno+'\'+AttName) then |
| | | begin |
| | | |
| | | FileList.Clear; |
| | | if FileExists(ImageSavePath+Caseno+'\'+AttName+'\Context.dat') then |
| | | FileList.LoadFromFile(ImageSavePath+Caseno+'\'+AttName+'\Context.dat') |
| | | Else |
| | | begin |
| | | Rmdir(ImageSavePath+Caseno+'\'+AttName); |
| | | Exit; |
| | | end; |
| | | DocNoPage := FileList.Count; |
| | | iDocNo := DocNoDir2DocNo(AttName); |
| | | //DocNode := TreeView1.Items.AddChild(CaseNode,Format('%s{%s}-%d'+_msg('份'),[AttName,DocNo2DocName(Caseno,iDocNo),1])); |
| | | DocNode := TreeView1.Items.AddChild(CaseNode,Format(_Msg('%s{%s}-%d份'),[DocNo2DocName(Caseno,iDocNo),AttName,1])); |
| | | DocNode.ImageIndex := 2; |
| | | DocNode.SelectedIndex := 2; |
| | | for n := 0 to FileList.Count - 1 do |
| | | begin |
| | | FormID := FileName2FormCode(FileList.Strings[n]); |
| | | if not CheckFormIDExists(DocNode,FormID) then |
| | | begin |
| | | FormPage := GetFormIDPage(FileList,FormID); |
| | | FormName := FormCode2FormName(Caseno,FormID); |
| | | //FormNode := TreeView1.Items.AddChild(DocNode,FormID+'{'+FormName+'}-'+inttostr(FormPage)+_msg('頁')); |
| | | //FormNode := TreeView1.Items.AddChild(DocNode,FormName+'{'+FormID+'}-'+inttostr(FormPage)+_msg('頁')); |
| | | FormNode := TreeView1.Items.AddChild(DocNode,Format(_Msg('%s{%s}-%d頁'),[FormName,FormID,FormPage])); |
| | | FormNode.ImageIndex := 4; |
| | | FormNode.SelectedIndex := 4; |
| | | end; |
| | | end; |
| | | end; |
| | | |
| | | if FModeName='件' then //20170904 先裝死 因為異動 同時存在兩種附件太難寫 |
| | | begin |
| | | if DirectoryExists(ImageSavePath+Caseno+'\Attach') then |
| | | begin |
| | | FileList.Clear; |
| | | if FileExists(ImageSavePath+Caseno+'\Attach'+'\Context.dat') then |
| | | FileList.LoadFromFile(ImageSavePath+Caseno+'\Attach'+'\Context.dat') |
| | | Else |
| | | begin |
| | | Rmdir(ImageSavePath+Caseno+'\Attach'); |
| | | Exit; |
| | | end; |
| | | DocNoPage := FileList.Count; |
| | | iDocNo := DocNoDir2DocNo(AttName); |
| | | //DocNode := TreeView1.Items.AddChild(CaseNode,Format('%s{%s}-%d'+_msg('份'),[AttName,DocNo2DocName(Caseno,iDocNo),1])); |
| | | //DocNode := TreeView1.Items.AddChild(CaseNode,Format('%s{%s}-%d'+_msg('份'),[DocNo2DocName(Caseno,iDocNo),'Attach',1])); |
| | | DocNode := TreeView1.Items.AddChild(CaseNode,Format(_Msg('%s{%s}-%d份'),[DocNo2DocName(Caseno,iDocNo),'Attach',1])); |
| | | |
| | | DocNode.ImageIndex := 2; |
| | | DocNode.SelectedIndex := 2; |
| | | for n := 0 to FileList.Count - 1 do |
| | | begin |
| | | FormID := FileName2FormCode(FileList.Strings[n]); |
| | | if not CheckFormIDExists(DocNode,FormID) then |
| | | begin |
| | | FormPage := GetFormIDPage(FileList,FormID); |
| | | FormName := FormCode2FormName(Caseno,FormID); |
| | | //FormNode := TreeView1.Items.AddChild(DocNode,FormID+'{'+FormName+'}-'+inttostr(FormPage)+_msg('頁')); |
| | | //FormNode := TreeView1.Items.AddChild(DocNode,FormName+'{'+FormID+'}-'+inttostr(FormPage)+_msg('頁')); |
| | | FormNode := TreeView1.Items.AddChild(DocNode,Format(_Msg('%s{%s}-%d頁'),[FormName,FormID,FormPage])); |
| | | |
| | | FormNode.ImageIndex := 4; |
| | | FormNode.SelectedIndex := 4; |
| | | end; |
| | | end; |
| | | end; |
| | | end; |
| | | LogFile1.LogToFile(logTimeString+'產文件樹結束'); |
| | | Finally |
| | | FileList.Free; |
| | | CaseDocNoList.Free; |
| | | CaseDocNo_CopiesList.Free; |
| | | StrList.Free; |
| | | ST1.Free; |
| | | end; |
| | | end; |
| | | |
| | | procedure TCB_IMGPSScanX.initParameter; |
| | | begin |
| | | |
| | | // if FCaseNoLength=0 then |
| | | // begin |
| | | // |
| | | // end; |
| | | |
| | | if FFileSizeLimit = 0 then |
| | | begin |
| | | FFileSizeLimit := 5*1024; |
| | | end; |
| | | |
| | | |
| | | if FImgDPI=0 then |
| | | begin |
| | | FImgDPI := 300; |
| | | ScanDpi := FImgDPI; |
| | | end |
| | | else |
| | | begin |
| | | //FImgDPI := StrToInt(Value); |
| | | ScanDpi := FImgDPI; |
| | | end; |
| | | |
| | | if FScanColor = 0 then |
| | | begin |
| | | ScanColor := ifBlackWhite; |
| | | end; |
| | | |
| | | if FScanColor = 1 then |
| | | begin |
| | | ScanColor := ifGray256 ; |
| | | ScanGrayCB.Checked:=True; |
| | | end; |
| | | |
| | | if FScanColor = 2 then |
| | | begin |
| | | ScanColor := ifTrueColor ; |
| | | end; |
| | | end; |
| | | |
| | | Function TCB_IMGPSScanX.CheckAvailable:Boolean; //檢查是否可使用元件 |
| | | var |
| | | SendData : String; |
| | | Msg:String; |
| | | Nowcount,Totalcount,Lic_Idx : Integer; |
| | | MacID,IPStr,LegalDate :String; |
| | | begin |
| | | Result := False; |
| | | /////下載MPSLIC_SCAN.lic ////// |
| | | SendData:='data='+HTTPEncode(UTF8Encode(FData))+'&verify='+FVerify+'&work_no=PLN&file=MPSLIC_SCAN.lic'; |
| | | if not dnFile_Get(HTTPSClient,Furl,'service/imgpsc/IMGPSC04/sample',SendData,LngPath+'MPSLIC_SCAN.lic',FReWrite,Memo1,False,DownImgStatus) then |
| | | begin |
| | | Showmessage(_Msg('檢查註冊檔案時,網路發生錯誤!!')+_Msg('錯誤代碼:')+Inttostr(HttpError.HttpErrorCode)+' '+HttpError.HttpReason); |
| | | Exit; |
| | | end; |
| | | /////下載MPSLIC_SCAN.lic /// |
| | | |
| | | if CheckLicensebyIP_new(LngPath+'MPSLIC_SCAN.lic',MacID,IPStr,LegalDate,Msg,Nowcount,Totalcount,Lic_Idx) then //檢查是否己註冊過 |
| | | begin |
| | | if (LegalDate <> '') and (ServerDate>LegalDate) and (Lic_Idx>(Totalcount)) then |
| | | begin |
| | | Showmessage(_Msg('已經超過可使用期限及超出授權數請連絡廠商')); |
| | | Result := False; |
| | | //Exit; |
| | | end |
| | | else |
| | | Result := True; |
| | | end |
| | | Else |
| | | begin |
| | | if Msg <> '' then |
| | | begin |
| | | Showmessage(Format(_Msg('註冊檔有問題,請連絡廠商 錯誤原因:%s'),[Msg])); |
| | | Result := false; |
| | | Exit; |
| | | end |
| | | Else |
| | | begin |
| | | if (LegalDate <> '') and (ServerDate>LegalDate) and (NowCount =0 ) then |
| | | begin |
| | | Lic_Idx := 0; |
| | | Showmessage(_Msg('已經超過可使用期限請連絡廠商')); |
| | | Result := False; |
| | | //Exit; |
| | | end |
| | | //else if (LegalDate = '') and (Nowcount >= Totalcount+10) then //超過註冊數量 |
| | | else if ((LegalDate = '') or ((LegalDate <> '') and (ServerDate>LegalDate)) ) and (Nowcount >= Totalcount) then //超過註冊數量 20150717 yuu說拿掉送的10個 |
| | | begin |
| | | Lic_Idx := 0; |
| | | Showmessage(_Msg('已經超過授權數請連絡廠商')); |
| | | Result := False; |
| | | end |
| | | Else //未超過註冊數量要寫入註冊檔 |
| | | begin |
| | | {if Messagedlg(_Msg('您尚未註冊授權是否要進行註冊??'),MtConfirmation,[mbyes,mbcancel],0) = mrcancel then |
| | | begin |
| | | Result := False; |
| | | Exit; |
| | | end;} |
| | | ShowText := _Msg('授權中,請稍候'); |
| | | AddLicense(LngPath+'MPSLIC_SCAN.lic',MacID,IPStr,Msg); |
| | | Nowcount := Nowcount + 1; |
| | | DataLoading(True,True); |
| | | |
| | | /////上傳MPSLICSCAN.lic //// |
| | | SendData:='data='+HTTPEncode(UTF8Encode(FData))+'@verify='+FVerify+'@work_no=PLN@file_name=MPSLIC_SCAN.lic'; |
| | | if not upFile(HTTPSClient,FUrl,'service/imgpsc/IMGPSC02/sample',SendData,'file',LngPath+'MPSLIC_SCAN.lic',FReWrite,Memo1,False) then |
| | | begin |
| | | Showmessage(_Msg('檢查註冊時,網路發生錯誤!!')+_MSg('錯誤代碼:')+Inttostr(HttpError.HttpErrorCode)+' '+HttpError.HttpReason+')'); |
| | | DataLoading(False,False); |
| | | Exit; |
| | | end; |
| | | if memo1.Lines.Strings[0] = '1' then |
| | | begin |
| | | Showmessage(_Msg('檢查註冊時,網路發生錯誤!!')+_Msg('錯誤原因:')+memo1.Lines.Strings[1]); |
| | | DataLoading(False,False); |
| | | Exit; |
| | | end |
| | | Else if Pos('<script type="text/javascript" src="scripts/CW00/login.js"></script>',Memo1.Lines.Text) > 0 then |
| | | begin |
| | | Showmessage(_Msg('檢查註冊時,網路發生錯誤!!')+_Msg('錯誤原因:')+_Msg('閒置過久或被登出,請重新登入')); |
| | | DataLoading(False,False); |
| | | Exit; |
| | | end; |
| | | /////上傳MPSLICSCAN.lic ///// |
| | | //Sleep(30000); //第一次註冊睡30秒 先不睡 |
| | | Result := True; |
| | | end; |
| | | end; |
| | | end; |
| | | if FileExists(LngPath+'MPSLIC_SCAN.lic') then |
| | | DeleteFile(LngPath+'MPSLIC_SCAN.lic'); |
| | | if LegalDate = '' then |
| | | StatusBar1.Panels[4].Text := Format(_Msg('註冊號:%s 剩餘註冊數:%s'),[MacID,inttostr(Totalcount-Nowcount)]); |
| | | if LegalDate <> '' then |
| | | StatusBar1.Panels[4].Text := '*'+Format(_Msg('註冊號:%s 剩餘註冊數:%s'),[MacID+'('+inttostr(Lic_Idx)+')',inttostr(Totalcount-Nowcount)]); |
| | | end; |
| | | |
| | | Function TCB_IMGPSScanX.GetCustomDocName(Path,DocNo:String):String; //取出自定文件名稱 |
| | | var |
| | | ini : Tinifile; |
| | | begin |
| | | ini := Tinifile.Create(Path+'CustomDocNo.ini'); |
| | | try |
| | | Result := ini.ReadString(DocNo,'Name',''); |
| | | finally |
| | | ini.Free; |
| | | end; |
| | | end; |
| | | |
| | | Function TCB_IMGPSScanX.FindCustomDocName(Path,DocName:String):Boolean; //尋找自定文件名稱是否存在 |
| | | var |
| | | ini : Tinifile; |
| | | Ct,i:Integer; |
| | | DocNo,FormID : String; |
| | | begin |
| | | Result := False; |
| | | ini := Tinifile.Create(Path+'CustomDocNo.ini'); |
| | | try |
| | | Ct := ini.ReadInteger('CustomCount','Count',0); |
| | | for I := 1 to Ct do |
| | | begin |
| | | DocNo := 'ZZZZZ'+Add_Zoo(i,3); |
| | | if DocName = ini.ReadString(DocNo,'Name','') then |
| | | begin |
| | | Result := True; |
| | | Break; |
| | | end; |
| | | end; |
| | | finally |
| | | ini.Free; |
| | | end; |
| | | end; |
| | | |
| | | procedure TCB_IMGPSScanX.PrintImg(FileName, LoginID, Datetime, |
| | | Path: WideString); |
| | | var |
| | | PrintMode : TEnvisionPrintMode; |
| | | GraphicPrinter : TDibGraphicPrinter; |
| | | PrtDialog : TPrintDialog; |
| | | S : TStringlist; |
| | | i,Pages,Page : Integer; |
| | | Prt_String : String; |
| | | Prt_H : Integer; |
| | | procedure PrintWithManualPrintJob(LoginID,DateTime:String;Pages,Page:Integer); |
| | | begin |
| | | If Page = 1 Then |
| | | begin |
| | | { if UsePrintJob is False, Printer.BeginDoc and Printer.EndDoc must be |
| | | called by the user. This allows printing multiple images in the |
| | | same job (or page). } |
| | | GraphicPrinter.UsePrintJob := False; |
| | | |
| | | { if UsePrintJob is False, the print job name that appears in the |
| | | print manager must be specified in using the Title property of the |
| | | Printer object. Otherwise, if UsePrintJob is True, the Title |
| | | property of the TDibGraphicPrinter object is used to specify the |
| | | job name. } |
| | | Printer.Title := _Msg('影像列印'); |
| | | end; |
| | | |
| | | IF (Page mod 2) = 1 Then |
| | | Printer.BeginDoc |
| | | Else |
| | | Printer.NewPage; |
| | | |
| | | ImageScrollBox1.DisplayedGraphic.Canvas.Font.Size := 24; |
| | | |
| | | //ImageScrollBox1.DisplayedGraphic.Canvas.TextOut(20,20, _Msg('列印人員:')+LoginID+' '+_Msg('列印分行:')+FUserUnit+' '+_Msg('列印日期:')+DateTime); |
| | | |
| | | GraphicPrinter.Print(ImageScrollBox1.DisplayedGraphic); |
| | | |
| | | |
| | | { this shows how to print text on a page. |
| | | Printer.Canvas.TextOut(10,10, 'Envision Image Library'); |
| | | } |
| | | If ((Page mod 2) = 0) or (Page = pages) Then |
| | | Printer.EndDoc; |
| | | end; |
| | | |
| | | procedure PrintWithAutoPrintJob; |
| | | begin |
| | | GraphicPrinter.UsePrintJob := True; |
| | | GraphicPrinter.Title := _Msg('影像列印'); |
| | | GraphicPrinter.Print(ImageScrollBox1.Graphic); |
| | | end; |
| | | |
| | | begin |
| | | S := TStringlist.Create; |
| | | GraphicPrinter := TDibGraphicPrinter.Create; |
| | | PrtDialog := TPrintDialog.Create(self); |
| | | //PrtDialog.Copies:=99; |
| | | try |
| | | IF PrtDialog.Execute Then |
| | | begin |
| | | S.Text := FileName; |
| | | Pages := S.Count; |
| | | |
| | | for i := 0 to S.Count -1 do |
| | | begin |
| | | ImageScrollBox1.LoadFromFile(Path+S.Strings[i],1); |
| | | watermark2(Image1.Picture.Bitmap,70,'',ImageScrollBox1.DisplayedGraphic); |
| | | PrintWithManualPrintJob(LoginID,DateTime,Pages,i+1); |
| | | end; |
| | | end; |
| | | |
| | | Finally |
| | | PrtDialog.Free; |
| | | GraphicPrinter.Free; |
| | | S.Free; |
| | | end; |
| | | end; |
| | | |
| | | Procedure TCB_IMGPSScanX.GotoAttach(OldLevel:Integer); |
| | | var |
| | | i : Integer; |
| | | begin |
| | | for i := 0 to MyTreeNode1.Count - 1 do |
| | | begin |
| | | if Pos('Attach',MyTreeNode1.Item[i].Text) > 0 then |
| | | begin |
| | | if OldLevel = 2 then |
| | | begin |
| | | TreeView1.Selected := MyTreeNode1.Item[i]; |
| | | end |
| | | else if OldLevel = 3 then |
| | | begin |
| | | TreeView1.Selected := MyTreeNode1.Item[i].Item[0]; |
| | | end; |
| | | Break; |
| | | end; |
| | | end; |
| | | //TreeView1click(nil); |
| | | end; |
| | | |
| | | Function TCB_IMGPSScanX.GetCustomNameCount(CustomName:String):Integer; //取外傳的名稱數量 |
| | | var |
| | | i,ct : Integer; |
| | | C_DocNameList : TStringlist; |
| | | begin |
| | | C_DocNameList := TStringlist.Create; |
| | | try |
| | | C_DocNameList.StrictDelimiter := True; |
| | | C_DocNameList.Delimiter := #9; |
| | | C_DocNameList.DelimitedText := FC_DocNameList; |
| | | ct := 0; |
| | | for i := 0 to C_DocNameList.Count - 1 do |
| | | begin |
| | | if C_DocNameList.Strings[i] = CustomName then |
| | | begin |
| | | inc(ct); |
| | | end; |
| | | end; |
| | | Result := ct; |
| | | finally |
| | | C_DocNameList.Free; |
| | | end; |
| | | end; |
| | | |
| | | function TCB_IMGPSScanX.ISExistImg(const filename: string): boolean; |
| | | begin |
| | | if ExistImgList.IndexOf(LoadFileGetMD5(filename))<>-1 then |
| | | begin |
| | | Result:=True; |
| | | end |
| | | else |
| | | begin |
| | | Result:=False; |
| | | end; |
| | | end; |
| | | |
| | | Procedure TCB_IMGPSScanX.PriorPage(Page:Integer); //上一頁 |
| | | var |
| | | iISB : TImageScrollBox; |
| | | begin |
| | | iISB := TImageScrollBox(FindComponent(ISBName+inttostr(Page-1))); |
| | | if iISB <> nil then |
| | | begin |
| | | ISBClick(iISB); |
| | | end; |
| | | end; |
| | | |
| | | Procedure TCB_IMGPSScanX.NextPage(Page:Integer); //下一頁 |
| | | var |
| | | iISB : TImageScrollBox; |
| | | begin |
| | | iISB := TImageScrollBox(FindComponent(ISBName+inttostr(Page+1))); |
| | | if iISB <> nil then |
| | | begin |
| | | ISBClick(iISB); |
| | | end; |
| | | end; |
| | | |
| | | Procedure TCB_IMGPSScanX.OMRErr2ini(CaseID,Reason,FileName,Site,RelaFileName,RelaSite,Anchor,Anchor1:String;Del,Ingnore,Display:Boolean); //OMR檢核失敗寫入ini |
| | | var |
| | | ini : Tinifile; |
| | | Errcount : Integer; |
| | | S : TStringlist; |
| | | begin |
| | | if Display then |
| | | begin |
| | | ini := Tinifile.Create(ImageSavePath + CaseID+'\upload\Checkerr.ini'); |
| | | try |
| | | Errcount := ini.ReadInteger('OMRCount','Count',0); //透過Errcount來對應 |
| | | inc(ErrCount); |
| | | ini.WriteString(inttostr(ErrCount),'Reason',Reason); |
| | | ini.WriteBool(inttostr(ErrCount),'Ingnore',Ingnore); |
| | | ini.writeString(inttostr(ErrCount),'FileName',FileName); |
| | | ini.WriteString(inttostr(ErrCount),'Site',Site); |
| | | ini.WriteString(inttostr(ErrCount),'RelaFileName',RelaFileName); |
| | | ini.WriteString(inttostr(ErrCount),'RelaSite',RelaSite); |
| | | ini.WriteString(inttostr(ErrCount),'Anchor',Anchor); |
| | | ini.WriteString(inttostr(ErrCount),'RelaAnchor',Anchor1); |
| | | ini.WriteBool(inttostr(ErrCount),'Del',Del); |
| | | ini.WriteInteger('OMRCount','Count',ErrCount); |
| | | finally |
| | | ini.Free; |
| | | end; |
| | | end |
| | | Else |
| | | begin |
| | | S := TStringlist.Create; |
| | | try |
| | | if FileExists(ImageSavePath + CaseID+'\CheckMemo.dat') then |
| | | S.LoadFromFile(ImageSavePath + CaseID+'\CheckMemo.dat'); |
| | | S.Add(Reason); |
| | | S.SaveToFile(ImageSavePath + CaseID+'\CheckMemo.dat'); |
| | | finally |
| | | S.Free; |
| | | end; |
| | | end; |
| | | end; |
| | | |
| | | Procedure TCB_IMGPSScanX.OMRErrini2List(CaseID:String;ErrlistForm:TErrlistForm); //OMR檢核失敗從ini寫入ListView |
| | | var |
| | | ini : Tinifile; |
| | | Errcount : Integer; |
| | | Del : Boolean; |
| | | i : Integer; |
| | | begin |
| | | ini := Tinifile.Create(ImageSavePath + CaseID+'\upload\Checkerr.ini'); |
| | | try |
| | | Errcount := ini.ReadInteger('OMRCount','Count',0); |
| | | |
| | | for i := 1 to ErrCount do |
| | | begin |
| | | Del := ini.ReadBool(inttostr(i),'Del',False); //是否被移除了 |
| | | if Not Del then |
| | | begin |
| | | With ErrlistForm.ErrListLV.Items.Add do |
| | | begin |
| | | Caption := ini.ReadString(inttostr(i),'Reason',''); |
| | | SubItems.Add(inttostr(i)); |
| | | end; |
| | | end; |
| | | end; |
| | | if Errlistform.ErrListLV.Items.Count > 0 then |
| | | Errlistform.ImmediateBt.Enabled := False; |
| | | finally |
| | | ini.Free; |
| | | end; |
| | | end; |
| | | |
| | | Function TCB_IMGPSScanX.DownLanguage:Boolean; //下載多國語言檔 |
| | | begin |
| | | Result := True; // http://192.168.0.101:8080/fbnp/servlet/CWC01?act=getservertime |
| | | //dnFile(HTTPSClient,FUrl+'Language.Lng','','',LngPath+'Language.Lng',FReWrite.Text,Memo1,False,DownImgStatus) |
| | | If not dnFile_Get(HTTPSClient,FUrl+'Language.Lng','','',LngPath+'Language.Lng',FReWrite,Memo1,False,DownImgStatus) Then |
| | | begin |
| | | HttpErrStr := _Msg('錯誤代碼:')+inttostr(HttpError.HttpErrorCode)+','+HttpError.HttpReason; |
| | | Result := False; |
| | | Exit; |
| | | end; |
| | | IF memo1.Lines.Strings[0] = '1' Then |
| | | begin |
| | | HttpErrStr := _Msg('錯誤原因:')+memo1.Lines.Strings[1]; |
| | | Result := False; |
| | | Exit; |
| | | end |
| | | Else if Pos('<script type="text/javascript" src="scripts/CW00/login.js"></script>',Memo1.Lines.Text) > 0 then |
| | | begin |
| | | HttpErrStr := _Msg('錯誤原因:')+_Msg('閒置過久或被登出,請重新登入'); |
| | | Result := False; |
| | | Exit; |
| | | end; |
| | | |
| | | end; |
| | | |
| | | Procedure TCB_IMGPSScanX.FreeShapeobj(SelectISB : TImageScrollBox); |
| | | var |
| | | i : Integer; |
| | | begin |
| | | IF SelectISB = nil then //全Free; |
| | | begin |
| | | For i:= ComponentCount -1 downto 0 do |
| | | begin |
| | | IF Components[i] is TShape Then |
| | | begin |
| | | IF Pos('SP',Components[i].Name) > 0 Then |
| | | Components[i].Free; |
| | | end; |
| | | end; |
| | | end |
| | | Else //只Free指定的 |
| | | begin |
| | | TShape(FindComponent('SP'+Copy(SelectISB.Name,length(ISBName)+1,length(SelectISB.Name)-length(ISBName)))).Free; |
| | | end; |
| | | end; |
| | | |
| | | Procedure TCB_IMGPSScanX.CreateIn_WH(CaseID:String); //產生In_WH.dat |
| | | var |
| | | i,n : Integer; |
| | | DocDirList,In_WH_List : TStringlist; |
| | | iDocNo : String; |
| | | begin |
| | | DocDirList := TStringlist.Create; |
| | | In_WH_List := TStringlist.Create; |
| | | try |
| | | if FileExists(ImageSavePath+CaseID+'\CaseDocNo.dat') then |
| | | DocDirList.LoadFromFile(ImageSavePath+CaseID+'\CaseDocNo.dat'); |
| | | for i := 0 to DocDirList.Count - 1 do |
| | | begin |
| | | iDocNo := DocNoDir2DocNo(DocDirList.Strings[i]); |
| | | for n := 0 to IN_WH_DocNoList.Count - 1 do |
| | | begin |
| | | if (iDocNo = IN_WH_DocNoList.Strings[n]) or (Copy(iDocNo,1,5)='ZZZZZ') then |
| | | begin |
| | | In_WH_List.Add(DocDirList.Strings[i]); |
| | | Break; |
| | | end; |
| | | end; |
| | | end; |
| | | In_WH_List.SaveToFile(ImageSavePath+CaseID+'\In_Wh.dat'); |
| | | |
| | | finally |
| | | DocDirList.Free; |
| | | In_WH_List.Free; |
| | | end; |
| | | end; |
| | | |
| | | Function TCB_IMGPSScanX.CreateAttach_Info(CaseID:String):String; //產生是否有Attach Y:有 N:沒有 |
| | | begin |
| | | Result := 'N'; |
| | | if GetDocDir_Page(CaseID,AttName) > 0 Then |
| | | Result := 'Y'; |
| | | end; |
| | | |
| | | Function TCB_IMGPSScanX.GetOMRCheckSet : Boolean; //下載OMR檢核XML檔 |
| | | var |
| | | SendData : String; |
| | | LastDateTime : String; |
| | | S : TStringlist; |
| | | begin |
| | | Result := True; |
| | | S := TStringlist.Create; |
| | | Try |
| | | if FileExists(CheckXmlPath+'OMRSet.zip') then |
| | | DeleteFile(CheckXmlPath+'OMRSet.zip'); |
| | | |
| | | LastDateTime := '00000000000000'; |
| | | if FileExists(CheckXmlPath+'LastDateTime.dat') then |
| | | begin |
| | | S.LoadFromFile(CheckXmlPath+'LastDateTime.dat'); |
| | | LastDateTime := S.Strings[0]; |
| | | end; |
| | | |
| | | SendData := 'settype=3&lastupdate='+LastDateTime; |
| | | if not dnFile_Get(HTTPSClient,Furl,'service/imgpsc/IMGPSC01/settings',SendData,CheckXmlPath+'OMRSet.zip',FReWrite,Memo1,False,DownImgStatus) then |
| | | begin |
| | | HttpErrStr := _Msg('錯誤代碼:')+inttostr(HttpError.HttpErrorCode)+','+HttpError.HttpReason; |
| | | Result := False; |
| | | Exit; |
| | | end; |
| | | if FileExists(CheckXmlPath+'OMRSet.zip') then //有更新 |
| | | begin |
| | | ExecuteUnZip(CheckXmlPath+'OMRSet.zip',CheckXmlPath,True); |
| | | S.Clear; |
| | | S.Add(ServerDate+GetBalance2Time(Balance)); |
| | | S.SaveToFile(CheckXmlPath+'LastDateTime.dat'); |
| | | end |
| | | Else |
| | | begin |
| | | if (Memo1.Lines.Strings[0] = 'nodata') Then //沒更新 |
| | | begin |
| | | Result := True; |
| | | end |
| | | Else if (Memo1.Lines.Strings[0] = '1') Then |
| | | begin |
| | | HttpErrStr := _Msg('錯誤原因:')+memo1.Lines.Strings[1]; |
| | | Result := False; |
| | | Exit; |
| | | end |
| | | Else if Pos('<script type="text/javascript" src="scripts/CW00/login.js"></script>',Memo1.Lines.Text) > 0 then |
| | | begin |
| | | HttpErrStr := _Msg('錯誤原因:')+_Msg('閒置過久或被登出,請重新登入'); |
| | | Result := False; |
| | | Exit; |
| | | end; |
| | | end; |
| | | |
| | | Finally |
| | | S.Free; |
| | | End; |
| | | |
| | | end; |
| | | |
| | | procedure TCB_IMGPSScanX.Timer1Timer(Sender: TObject); |
| | | var |
| | | StampDate,StampTime : String; |
| | | i: Integer; |
| | | begin |
| | | Timer1.Enabled := False; |
| | | //FIs_In_Wh:='Y'; /// test 記得關掉 |
| | | //FWH_category :='N'; // test 記得關掉 |
| | | // FImgDelete := 'Y'; //test 記得關掉 |
| | | //Showmessage('a'); |
| | | //self.FIs_OldCase := 'Y'; |
| | | PageLVclear := True; |
| | | InitialOk := False; |
| | | |
| | | FMaxUploadSize:='10'; |
| | | FJpgCompression:=50; |
| | | FFtpRootPath := ''; //影像平台沒有給FtpRoot目錄,會直接用FFtpExtraPath切換至指定目錄 |
| | | //FMode := 'DSCAN' ; |
| | | //FIs_In_Wh := 'Y'; |
| | | if FIs_In_Wh = 'Y' then |
| | | AttName := 'Attach' //入庫附件 |
| | | else |
| | | AttName := 'S_Attach'; //Smartlending 附件 |
| | | //ShowMessage('1111111'); |
| | | if FMode = 'SAMPLESCAN' then |
| | | begin |
| | | NewScanBtn.Visible := False; |
| | | PJLinkedMenuSpeedButton2.Visible := False; |
| | | AddScanBtn.Visible := False; |
| | | CheckCaseBtn.Visible := False; |
| | | Panel18.Visible := False; |
| | | TransBtn.Visible := False; |
| | | FC6.Visible := False; |
| | | SampleScanBtn.Visible := True; |
| | | Panel1.Visible := True; |
| | | Panel6.Visible := True; |
| | | ScanDuplexCB.Visible := False; //雙面掃描 |
| | | end |
| | | Else if (FMode = 'NSCAN') then |
| | | begin |
| | | Panel18.Visible := True; |
| | | Panel1.Visible := True; |
| | | Panel6.Visible := True; |
| | | Panel21.Visible := True; |
| | | Panel23.Visible := True; |
| | | |
| | | AttFileGB.Visible := True; |
| | | Splitter2.Visible := True; |
| | | ScanDuplexCB.Visible := True; //雙面掃描 |
| | | end |
| | | Else if FMode = 'FSCAN' then |
| | | begin |
| | | Panel1.Visible := True; |
| | | //Panel6.Visible := True; |
| | | end |
| | | Else |
| | | begin |
| | | Panel18.Visible := True; |
| | | Panel1.Visible := True; |
| | | Panel6.Visible := True; |
| | | Panel21.Visible := True; |
| | | Panel23.Visible := True; |
| | | AttFileGB.Visible := True; |
| | | Splitter2.Visible := True; |
| | | end; |
| | | DisplayMode(1,1,1,Panel9); |
| | | Application.ProcessMessages; |
| | | StatusBar1.Panels[0].Text := 'Ver'+GetCurrentVersionNo; |
| | | StatusBar1.Panels[1].Text := 'Login User:'+FUserName; |
| | | {$IFDEF Test} |
| | | StatusBar1.Panels[0].Text := StatusBar1.Panels[0].Text+'(test)'; |
| | | {$ENDIF} |
| | | StatusBar1.Panels[0].Text := StatusBar1.Panels[0].Text; |
| | | |
| | | if FPrintyn = 'Y' then |
| | | PrtLB.Visible := True; |
| | | |
| | | initParameter; //20170222 針對新加的parameter 作初始化參數 |
| | | InitScrollRec; |
| | | If FUrl = '' then |
| | | begin |
| | | Showmessage(_Msg('URL cannot be empty,please contact system administrator')); |
| | | Exit; |
| | | end; |
| | | if FUrl[length(FUrl)]<>'/' then |
| | | FUrl := FUrl + '/'; |
| | | //20221028 把語言檔改放至 Local目錄裡,才不會有些文字來不及使用 |
| | | LngPath := GetLocalAppDir(Handle)+'MPS\CB_IMGPS\'; |
| | | Str2Dir(LngPath); |
| | | ////下載語言檔///// 20170218 先拿調以便測試 |
| | | If not DownLanguage Then |
| | | begin |
| | | Showmessage('Language File error!!'+HttpErrStr); |
| | | DataLoading(False,False); |
| | | Exit; |
| | | end; |
| | | |
| | | InitialLanguage(Self); //載入多國語言 |
| | | |
| | | |
| | | if FWork_no='' then |
| | | begin |
| | | Showmessage(_Msg('業務別不能為空白,請洽詢程式人員')); |
| | | Exit; |
| | | end; |
| | | |
| | | if CaseIDLength = 0 then |
| | | begin |
| | | Showmessage(_Msg('案件編號長度限制不能為空白,請洽詢程式人員')); |
| | | //Exit; //測試時不退出 |
| | | end; |
| | | |
| | | //********清單區******** |
| | | |
| | | Doc_Inf_List := TStringList.Create; //Doc_Inf 清單 Docno + 版本為key |
| | | DM_FORM_INF_List := TStringList.Create; //DM_FORM_INF 清單 Docno + 版本為key |
| | | FORM_INF_List := TStringList.Create; //FORM_INF的清單 |
| | | CHECK_RULE_INF_List := TStringList.Create; //CHECK_RULE_INF 清單 |
| | | MEMO_INF_List := TStringList.Create; //MEMO_INF 清單 |
| | | WORK_INF_List := TStringList.Create; //WORK_INF 清單 |
| | | LASTEST_FORM_INF_List := TStringList.Create; // LASTEST_FORM_INF 清單 |
| | | FindResult := TStringlist.Create; //找SQLData的結果 |
| | | |
| | | OMRFileList := TStringList.Create; //要OMR檢核的文件(只檢查每種Form的第一頁) |
| | | FormCode_PageSize := TStringList.Create; //文件的預設大小 FormCode_Height_Width |
| | | DocNo_NeedDoc := TStringList.Create; //有Docno時要相依的文件 DocNo_相依文件_相依文件 |
| | | DocNo_NoDoc := TStringList.Create; //有Docno時互斥的文件 DocNo_互斥文件_互斥文件 |
| | | DocNo_VerinCase := TStringList.Create; //案件裡的DocNo+版本的清單 |
| | | CaseDocNoList := TStringlist.Create; //案件裡的DocNo清單 |
| | | CaseDocNo_CopiesList := TStringlist.Create; //案件裡的DocNo份數清單 |
| | | CaseList := TStringList.Create; //記錄掃瞄案件的順序 |
| | | Context_DocnoList := TStringlist.Create; //案件裡的檔案Docno清單 |
| | | ContextList := TStringlist.Create; //案件裡的檔案清單 |
| | | AttContextList := TStringlist.Create; //案件裡的附加檔案清單 |
| | | NoSaveBarCodeList := TStringlist.Create; //不儲存的條碼清單 |
| | | FormID_List := TStringlist.Create; //FormID清單 |
| | | DocNo_List := TStringlist.Create; //DocNo清單 |
| | | NowShowFileList := TStringlist.Create; //目前顯示的影像清單 |
| | | NowSelectFileList := TStringlist.Create; //目前被點選的影像清單 |
| | | Cust_DocNoList := TStringlist.Create; //自行定義的文件名稱 |
| | | IN_WH_DocNoList := TStringlist.Create; //入庫的文件清單 |
| | | GuideFormIDList := TStringlist.Create; //要當導引頁表單清單 |
| | | DivPageFormIDList := TStringList.Create; //要當分案頁表單清單 |
| | | LastInitFormidList :=TStringList.Create; |
| | | LastAddFormidList := TStringList.Create; |
| | | SampleFormIDList := TStringList.Create;//20170627 加入 |
| | | ExistImgList := TStringList.Create; //20170724 新增 |
| | | reSizeExistImgList :=TStringList.Create; //20171012 新增 |
| | | //********清單區******** |
| | | |
| | | ShowText := _Msg('資料載入中,請稍候'); |
| | | DataLoading(True,True); |
| | | |
| | | IF not GetServerDate Then |
| | | begin |
| | | Showmessage(_Msg('取主機時間時,網路發生錯誤!!')+HttpErrStr); |
| | | DataLoading(False,False); |
| | | Exit; |
| | | end; |
| | | |
| | | if FMode='SAMPLESCAN' then |
| | | begin |
| | | IF not GetSampleInf Then //取已存在sample |
| | | begin |
| | | Showmessage(_Msg('取存在範本資訊時,網路發生錯誤!!')+HttpErrStr); |
| | | DataLoading(False,False); |
| | | Exit; |
| | | end; |
| | | end; |
| | | |
| | | |
| | | |
| | | //ShowMessage('GetServerDate '+ServerDate+' , '+ServerTime); |
| | | ////下載系統資訊//// |
| | | IF not GetSetInf1 Then //取DOC_INF 文件資訊 |
| | | begin |
| | | Showmessage(_Msg('取文件資訊時,網路發生錯誤!!')+HttpErrStr); |
| | | DataLoading(False,False); |
| | | Exit; |
| | | end; |
| | | //ShowMessage('GetSetInf1'); |
| | | //showmessage(self.Doc_Inf_List.Text); |
| | | IF not GetSetInf2 Then //取DM_FORM_INF 相依互斥資訊 |
| | | begin |
| | | Showmessage(_Msg('取相依互斥資訊時,網路發生錯誤!!')+HttpErrStr); |
| | | DataLoading(False,False); |
| | | Exit; |
| | | end; |
| | | //ShowMessage('GetSetInf2'); |
| | | //Showmessage(self.Doc_Inf_List.Text); |
| | | IF not GetSetInf3 Then //取FORM_INF 表單資訊 |
| | | begin |
| | | Showmessage(_Msg('取表單資訊時,網路發生錯誤!!')+HttpErrStr); |
| | | DataLoading(False,False); |
| | | Exit; |
| | | end; |
| | | //ShowMessage('GetSetInf3'); |
| | | IF not GetSetInf4 Then //取CHECK_RULE_INF 檢核規則資訊 |
| | | begin |
| | | Showmessage(_Msg('取檢核規則資訊時,網路發生錯誤!!')+HttpErrStr); |
| | | DataLoading(False,False); |
| | | Exit; |
| | | end; |
| | | //showmessage(self.CHECK_RULE_INF_List.Text); |
| | | //ShowMessage('GetSetInf4'); |
| | | IF not GetSetInf5 Then //取MEMO_INF 常用片語資訊 |
| | | begin |
| | | Showmessage(_Msg('取常用片語資訊時,網路發生錯誤!!')+HttpErrStr); |
| | | DataLoading(False,False); |
| | | Exit; |
| | | end; |
| | | //ShowMessage('GetSetInf5'); |
| | | //showmessage(self.MEMO_INF_List.Text); |
| | | IF not GetSetInf6 Then //取WORK_INF 系統參數資訊 |
| | | begin |
| | | Showmessage(_Msg('取系統參數資訊時,網路發生錯誤!!')+HttpErrStr); |
| | | DataLoading(False,False); |
| | | Exit; |
| | | end; |
| | | //ShowMessage('GetSetInf6'); |
| | | //showmessage(FORM_INF_List.Text); |
| | | IF not GetSetInf7 Then //取LASTES_FORM_INF 系統參數資訊 |
| | | begin |
| | | Showmessage(_Msg('取最新版FORMID參數資訊時,網路發生錯誤!!')+HttpErrStr); |
| | | DataLoading(False,False); |
| | | Exit; |
| | | end; |
| | | //ShowMessage('GetSetInf7'); |
| | | //Showmessage(LASTEST_FORM_INF_List.Text); |
| | | SetFormID_DocNo; //將FormID及Docno抽出塞入List裡 20130403增加 |
| | | SetIn_WH_DocNo; //將要入庫的DocNo抽出來另存入list裡 |
| | | GetDefScanIni; //取得掃描預設值及相關設定 |
| | | |
| | | ////下載系統資訊///// |
| | | if ImagePath = '' then |
| | | begin |
| | | Showmessage(_Msg('本機暫存路徑不得為空白')); |
| | | DataLoading(False,False); |
| | | Panel1.Enabled := False; |
| | | Panel2.Enabled := False; |
| | | Exit; |
| | | end; |
| | | initkscan; |
| | | if ImagePath[Length(ImagePath)] <> '\' then |
| | | ImagePath := ImagePath + '\'; |
| | | //ShowMessage('ImagePath='+ImagePath); |
| | | //CheckXmlPath := ImagePath+'OMRSITE\'+FWork_No; |
| | | CheckXmlPath := ImagePath+'OMRSITE\'; // 20200612 發現影像平台是取回所有業務的設定,所以不能有業務別目錄 |
| | | //ShowMessage('CheckXmlPath='+CheckXmlPath); |
| | | //SitePath := ImagePath+'Site\'+FWork_No+'\'; |
| | | SitePath := ImagePath+'Site\'; // 20200612 發現影像平台是取回所有業務的設定,所以不能有業務別目錄 |
| | | //LngPath := ImagePath; //改放至上面取Local目錄 |
| | | SamplePath := ImagePath+'Sample\'+FWork_No+'\'; |
| | | ImagePath := ImagePath + 'Scantemp\'; |
| | | //ShowMessage('AA ImagePath='+ImagePath); |
| | | ScaniniPath :=ImagePath+FWork_No+'\'+FUserUnit +'\'; |
| | | //ShowMessage('ScaniniPath='+ScaniniPath); |
| | | ImagePath := ImagePath + FWork_No+'\'+FUserUnit+'\'+FMode+'\'; |
| | | ImagePath := StringReplace(ImagePath, '\\', '\',[rfReplaceAll, rfIgnoreCase]); |
| | | //ShowMessage('BB ImagePath='+ImagePath); |
| | | ImageSavePath := ImagePath; |
| | | str2dir(CheckXmlPath); |
| | | str2dir(SitePath); |
| | | str2dir(ImagePath); |
| | | str2dir(SamplePath); |
| | | Del_Sub_NothingPath(ImagePath); //清掉案件目錄是空的 |
| | | |
| | | |
| | | LogFile1.LogFile:=LngPath+'IMGPSCheck.log'; |
| | | |
| | | ReduceLogFile; |
| | | |
| | | LogFile1.LogToFile(logTimeString+'OCX取表data結束'); |
| | | |
| | | ShowText := _Msg('資料載入中,請稍候'); |
| | | DataLoading(True,True); |
| | | |
| | | // if not CheckAvailable Then //檢查授權 20170218 說不用了 |
| | | // begin |
| | | // DataLoading(False,False); |
| | | // Panel1.Enabled := False; |
| | | // Panel2.Enabled := False; |
| | | // Exit; |
| | | // end; |
| | | |
| | | //Button3Click(Self); |
| | | //ShowMessage('CheckAvailable'); |
| | | ShowText := _Msg('資料載入中,請稍候'); |
| | | DataLoading(True,True); |
| | | |
| | | |
| | | StatusBar1.Panels[1].Text := _Msg('登入人員:')+FUserName; |
| | | //FCaseID:='20150302180133';//測試用 |
| | | ////下載語言檔///// |
| | | //ShowMessage('OOOO'); |
| | | |
| | | if (FMode = 'RSCAN') or (FMode = 'DSCAN') or (FMode = 'ESCAN') or (FMode = 'FSCAN') then //重掃件及異動件要只能掃指定編號的件 |
| | | begin |
| | | _Deltree(ImagePath); |
| | | str2dir(ImagePath); |
| | | ImageSavePath := ImagePath; |
| | | str2dir(ImageSavePath); |
| | | MkDir(ImageSavePath+FCaseID); |
| | | CreateEmptyCase(ImageSavePath,FCaseID); |
| | | MkDir(ImageSavePath+FCaseID+'\Download'); |
| | | IF (FMode = 'ESCAN') or (FMode = 'DSCAN') then //異動件先下載影像 |
| | | begin |
| | | ShowText := _Msg('案件下載中,請稍候'); |
| | | DataLoading(True,True); |
| | | If not DownLoadImage(ImageSavePath+FCaseID+'\Download\',FCaseID) Then |
| | | begin |
| | | Showmessage(FCaseID+_msg('載入異動影像時,網路發生錯誤')+DownFileErrStr); |
| | | DataLoading(False,False); |
| | | Exit; |
| | | end; |
| | | |
| | | {If not Down_Img(ImageSavePath+FCaseID+'\Download\',FCaseID) then |
| | | begin |
| | | Showmessage(FCaseID+_msg('載入異動影像時,網路發生錯誤')+HttpErrStr); |
| | | DataLoading(False,False); |
| | | Exit; |
| | | end;} |
| | | //Showmessage(ImageSavePath+FCaseID+'\Download\'+#10#13+ImageSavePath+FCaseID+'\'); |
| | | Download2Case(ImageSavePath+FCaseID+'\Download\',ImageSavePath+FCaseID+'\'); |
| | | //Showmessage('aaa'); |
| | | //Download2Case('C:\Users\Hong\Downloads\沒有括號\',ImageSavePath+FCaseID+'\'); |
| | | if (FIs_OldCase = 'Y') then |
| | | begin |
| | | if (FWork_no='HLN') then |
| | | ErrFormtoCurrentForm(FCaseID,'10000001011112A','11000001011112A'); //換掉錯的FormID |
| | | //if not FileExists(ImageSavePath+FCaseID+'\CaseDocNo_Copies.dat') then //這個會在Download2Case時一律產生所以不能有這行 20141013 |
| | | OldCasetoNewCase(FCaseID); |
| | | //ErrFormtoCurrentForm(FCaseID,'11B00005011312A','11000001011112A'); //換掉錯的FormID |
| | | //LoadImgFile; |
| | | end; |
| | | // if (FIs_OldCase = 'Y') and (FWork_no='HLN') then //77版的 |
| | | // begin |
| | | // ErrFormtoCurrentForm(FCaseID,'10000001011112A','11000001011112A'); //換掉錯的FormID |
| | | // if not FileExists(ImageSavePath+FCaseID+'\CaseDocNo_Copies.dat') then |
| | | // OldCasetoNewCase(FCaseID); |
| | | // //ErrFormtoCurrentForm(FCaseID,'11B00005011312A','11000001011112A'); //換掉錯的FormID |
| | | // //LoadImgFile; |
| | | // end; |
| | | Create_Cust_DocDir(FCaseID); //產生外面傳入的文件 |
| | | if FMode='ESCAN' then |
| | | LastInitFormidListCreate(ImageSavePath+FCaseID+'\Download\'); |
| | | end; |
| | | end; |
| | | |
| | | //ShowMessage('GetOMRCheckSet前'); |
| | | ////下載檢核XML////// |
| | | IF not GetOMRCheckSet Then |
| | | begin |
| | | Showmessage(_Msg('下載檢核定位檔案時,網路發生錯誤!!')+HttpErrStr); |
| | | DataLoading(False,False); |
| | | Exit; |
| | | end; |
| | | ////下載檢核XML///// |
| | | //ShowMessage('GetOMRCheckSet後來'); |
| | | ////下載登打設定///// |
| | | IF not GetKeyinSet Then |
| | | begin |
| | | Showmessage(_Msg('下載登打定位檔案時,網路發生錯誤!!')+HttpErrStr); |
| | | DataLoading(False,False); |
| | | Exit; |
| | | end; |
| | | ////下載登打設定///// |
| | | //ShowMessage('GetKeyinSet'); |
| | | if ScanDenialHint <> '' then //有設定提示字串就秀在右上角 |
| | | begin |
| | | DenialTimeLb.Visible := True; |
| | | DenialTimeLb.Caption := Format(ScanDenialHint,[ScanDenialTime]); |
| | | end; |
| | | //ShowMessage(IntToStr(ScanDpi)); |
| | | R_W_Scanini('R'); //掃瞄設定的ini |
| | | //ShowMessage(IntToStr(ScanDpi)); |
| | | //ShowMessage('停掉DataLoading'); |
| | | //DataLoading(False,False); |
| | | ScanDuplexCB.Checked := ScanDuplex; |
| | | if FMode <> 'SAMPLESCAN' then |
| | | LoadImgFile; |
| | | if (FMode = 'RSCAN') or (FMode = 'ESCAN') or (FMode = 'FSCAN') then |
| | | begin |
| | | if TreeView1.Items.Count > 0 then |
| | | begin |
| | | TreeView1.Selected := NewTreeNode.Item[0]; |
| | | TreeView1click(self); |
| | | end; |
| | | end; |
| | | InitialOk := True; |
| | | |
| | | {AttFileGB.Visible := True; //附加電子檔窗 //20120207楊玉說不在這加電子檔先拿掉 |
| | | Splitter2.Visible := True; |
| | | AttFileGB.Visible := False; //附加電子檔窗 |
| | | Splitter2.Visible := False; } |
| | | //ShowMessage('ImageSavePath='+ImageSavePath); |
| | | DataLoading(False,False); |
| | | |
| | | LogFile1.LogToFile(logTimeString+'OCX初始化結束'); |
| | | LogFile1.LogToFile(logTimeString+'FUrl='+FUrl+ |
| | | ',FCaseID='+FCaseID+ |
| | | ',FMode='+FMode+ |
| | | ',FModeName='+FModeName+ |
| | | ',FWork_no='+FWork_no+ |
| | | ',FUserID='+FUserID+ |
| | | ',FUserName='+FUserName+ |
| | | ',FUserUnit='+FUserUnit+ |
| | | ',FData='+FData+ |
| | | ',FVerify='+FVerify+ |
| | | ',FReWrite='+FReWrite+ |
| | | ',FLanguage='+FLanguage+ |
| | | ',FLoanDoc_Value='+FLoanDoc_Value+ |
| | | ',FLoanDoc_Enable='+FLoanDoc_Enable+ |
| | | ',FUseProxy='+FUseProxy+ |
| | | ',FC_DocNoList='+FC_DocNoList+ |
| | | ',FC_DocNameList='+FC_DocNameList+ |
| | | ',FFixFileList='+FFixFileList+ |
| | | ',FIs_In_Wh='+FIs_In_Wh+ |
| | | ',FOldCaseInfo='+FOldCaseInfo+ |
| | | ',FPrintyn='+FPrintyn+ |
| | | ',FIs_OldCase='+FIs_OldCase+ |
| | | ',FCustDocYN='+FCustDocYN); |
| | | LogFile1.LogToFile(logTimeString+'FImgDPI='+IntToStr(FImgDPI)+ |
| | | ',FScanColor='+ IntToStr(FScanColor)+ |
| | | ',FFileSizeLimit='+ IntToStr(FFileSizeLimit)+ |
| | | ',FCaseNoLength='+ IntToStr(FCaseNoLength)+ |
| | | ',FImgDelete='+FImgDelete+ |
| | | ',FIsExternal='+FIsExternal+ |
| | | ',FWH_category='+FWH_category+ |
| | | ',FCheck_main_form='+FCheck_main_form+ |
| | | ',FMaxUploadSize='+FMaxUploadSize); |
| | | |
| | | end; |
| | | |
| | | procedure TCB_IMGPSScanX.Timer2Timer(Sender: TObject); |
| | | begin |
| | | IF Panel22.Caption = ShowText+'......' Then |
| | | Panel22.Caption := ShowText |
| | | Else |
| | | Panel22.Caption := Panel22.Caption + '.'; |
| | | Application.ProcessMessages; |
| | | end; |
| | | |
| | | procedure TCB_IMGPSScanX.Set_mode(const Value: WideString); |
| | | begin |
| | | FMode := UpperCase(Value); |
| | | end; |
| | | |
| | | procedure TCB_IMGPSScanX.Set_rewrite(const Value: WideString); |
| | | begin |
| | | FReWrite := Value; |
| | | end; |
| | | |
| | | procedure TCB_IMGPSScanX.Set_url(const Value: WideString); |
| | | begin |
| | | FUrl := Value; |
| | | end; |
| | | |
| | | procedure TCB_IMGPSScanX.Set_userid(const Value: WideString); |
| | | begin |
| | | FUserID := Value; |
| | | end; |
| | | |
| | | procedure TCB_IMGPSScanX.Set_username(const Value: WideString); |
| | | begin |
| | | FUserName := Value; |
| | | end; |
| | | |
| | | procedure TCB_IMGPSScanX.Set_verify(const Value: WideString); |
| | | begin |
| | | FVerify := Value; |
| | | end; |
| | | |
| | | procedure TCB_IMGPSScanX.Set_language(const Value: WideString); |
| | | begin |
| | | FLanguage := lowercase(Value); |
| | | if FLanguage='zh-tw' then |
| | | begin |
| | | FLanguage:='zh_tw' |
| | | end; |
| | | if FileExists(LngPath+'Language.lng') then |
| | | begin |
| | | InitialLanguage(Self); //載入多國語言 |
| | | end; |
| | | end; |
| | | |
| | | procedure TCB_IMGPSScanX.Set_modename(const Value: WideString); |
| | | begin |
| | | FModeName := Value; |
| | | end; |
| | | |
| | | procedure TCB_IMGPSScanX.Set_userunit(const Value: WideString); |
| | | begin |
| | | FUserUnit := Value; |
| | | end; |
| | | |
| | | procedure TCB_IMGPSScanX.Set_work_no(const Value: WideString); |
| | | begin |
| | | FWork_no := Value; |
| | | end; |
| | | |
| | | procedure TCB_IMGPSScanX.Set_loandoc_enable(const Value: WideString); |
| | | begin |
| | | FLoanDoc_Enable := Value; |
| | | if FLoanDoc_Enable = 'Y' then |
| | | AddCredit1RG.Enabled := True; |
| | | if FLoanDoc_Enable = 'I' then |
| | | begin |
| | | AddCredit1RG.Visible := False; |
| | | Panel5.Visible := False; |
| | | end; |
| | | end; |
| | | |
| | | procedure TCB_IMGPSScanX.Set_loandoc_value(const Value: WideString); |
| | | begin |
| | | FLoanDoc_Value := Value; |
| | | end; |
| | | |
| | | procedure TCB_IMGPSScanX.Set_useproxy(const Value: WideString); |
| | | begin |
| | | FUseProxy := UpperCase(Value); |
| | | if FUseProxy = 'Y' then |
| | | UseProxy := True; //要不要用Proxy |
| | | |
| | | end; |
| | | |
| | | procedure TCB_IMGPSScanX.Set_c_docnamelist(const Value: WideString); |
| | | begin |
| | | FC_DocNameList := Value; |
| | | end; |
| | | |
| | | procedure TCB_IMGPSScanX.Set_is_in_wh(const Value: WideString); |
| | | begin |
| | | FIs_In_Wh := UpperCase(Value); |
| | | end; |
| | | |
| | | function TCB_IMGPSScanX.Get_c_docnamelist: WideString; |
| | | begin |
| | | |
| | | end; |
| | | |
| | | function TCB_IMGPSScanX.Get_is_in_wh: WideString; |
| | | begin |
| | | |
| | | end; |
| | | |
| | | function TCB_IMGPSScanX.Get_language: WideString; |
| | | begin |
| | | |
| | | end; |
| | | |
| | | function TCB_IMGPSScanX.Get_loandoc_enable: WideString; |
| | | begin |
| | | |
| | | end; |
| | | |
| | | function TCB_IMGPSScanX.Get_loandoc_value: WideString; |
| | | begin |
| | | |
| | | end; |
| | | |
| | | function TCB_IMGPSScanX.Get_mode: WideString; |
| | | begin |
| | | |
| | | end; |
| | | |
| | | function TCB_IMGPSScanX.Get_modename: WideString; |
| | | begin |
| | | |
| | | end; |
| | | |
| | | function TCB_IMGPSScanX.Get_rewrite: WideString; |
| | | begin |
| | | |
| | | end; |
| | | |
| | | function TCB_IMGPSScanX.Get_url: WideString; |
| | | begin |
| | | |
| | | end; |
| | | |
| | | function TCB_IMGPSScanX.Get_useproxy: WideString; |
| | | begin |
| | | |
| | | end; |
| | | |
| | | function TCB_IMGPSScanX.Get_userid: WideString; |
| | | begin |
| | | |
| | | end; |
| | | |
| | | function TCB_IMGPSScanX.Get_username: WideString; |
| | | begin |
| | | |
| | | end; |
| | | |
| | | function TCB_IMGPSScanX.Get_userunit: WideString; |
| | | begin |
| | | |
| | | end; |
| | | |
| | | function TCB_IMGPSScanX.Get_verify: WideString; |
| | | begin |
| | | |
| | | end; |
| | | |
| | | function TCB_IMGPSScanX.Get_work_no: WideString; |
| | | begin |
| | | |
| | | end; |
| | | |
| | | function TCB_IMGPSScanX.Get_printyn: WideString; |
| | | begin |
| | | |
| | | end; |
| | | |
| | | procedure TCB_IMGPSScanX.Set_printyn(const Value: WideString); |
| | | begin |
| | | FPrintyn := UpperCase(Value); |
| | | end; |
| | | |
| | | function TCB_IMGPSScanX.Get_custdocyn: WideString; |
| | | begin |
| | | |
| | | end; |
| | | |
| | | procedure TCB_IMGPSScanX.Set_custdocyn(const Value: WideString); |
| | | begin |
| | | FCustDocYN := UpperCase(Value); |
| | | end; |
| | | |
| | | function TCB_IMGPSScanX.Get_imgdelete: WideString; |
| | | begin |
| | | |
| | | end; |
| | | |
| | | procedure TCB_IMGPSScanX.Set_imgdelete(const Value: WideString); |
| | | begin |
| | | FImgDelete:=Value; |
| | | end; |
| | | |
| | | function TCB_IMGPSScanX.Get_isExternal: WideString; |
| | | begin |
| | | |
| | | end; |
| | | |
| | | procedure TCB_IMGPSScanX.Set_isExternal(const Value: WideString); |
| | | begin |
| | | FIsExternal:=Value; |
| | | end; |
| | | |
| | | function TCB_IMGPSScanX.Get_WH_CATEGORY: WideString; |
| | | begin |
| | | |
| | | end; |
| | | |
| | | procedure TCB_IMGPSScanX.Set_WH_CATEGORY(const Value: WideString); |
| | | begin |
| | | FWH_category:=Value; |
| | | end; |
| | | |
| 比對新檔案 |
| | |
| | | import * as fs from 'fs'; import * as path from 'path'; |
| | | export enum TTransMode{tsHttp,tsFtp,tsNone} export enum TFtpProtocol{fpftp,fpftps} export type TImageFormat='ifBlackWhite'|'ifGray256'|'ifTrueColor'|'ifColor256'; |
| | | export class TStringList { |
| | | Strings:string[]=[]; get Count(){return this.Strings.length;} |
| | | Add(s:string){this.Strings.push(s);} Clear(){this.Strings=[];} |
| | | LoadFromFile(fp:string){if(fs.existsSync(fp))this.Strings=fs.readFileSync(fp,'utf8').split(/\r?\n/).filter(Boolean);} |
| | | SaveToFile(fp:string){fs.writeFileSync(fp,this.Strings.join('\n'),'utf8');} |
| | | Delete(idx:number){this.Strings.splice(idx,1);} Insert(idx:number,s:string){this.Strings.splice(idx,0,s);} |
| | | IndexOf(s:string){return this.Strings.indexOf(s);} get Text(){return this.Strings.join('\n');} |
| | | set Text(v:string){this.Strings=v.split('\n');} get CommaText(){return this.Strings.join(',');} |
| | | set CommaText(v:string){this.Strings=v.split(',');} Free(){} Sort(){this.Strings.sort();} Assign(s:TStringList){this.Strings=[...s.Strings];} |
| | | } |
| | | export function applyUtilsMixins(cls:any){ |
| | | cls.prototype.GetCurrentVersionNo=function(){return "1.0.0";}; |
| | | cls.prototype.DefinePropertyPages=function(){}; |
| | | cls.prototype.InitExistImgList=function(cp:string){ |
| | | let ST1=new TStringList(); this.ExistImgList.Clear(); |
| | | ST1.LoadFromFile(path.join(cp,'Download','Context.dat')); |
| | | for(let i=0;i<ST1.Count;i++){ let m=this.LoadFileGetMD5(path.join(cp,'Download',ST1.Strings[i])); this.LogFile1.LogToFile(this.logTimeString()+path.join(cp,'Download',ST1.Strings[i])+',MD5='+m); this.ExistImgList.Add(m); } |
| | | this.LogFile1.LogToFile(this.logTimeString()+'ExistImgList.text'+this.ExistImgList.CommaText); ST1.Free(); |
| | | }; |
| | | cls.prototype.Initialize=function(){ |
| | | ['Activate','Click','Create','DblClick','Deactivate','Destroy','KeyPress','MouseEnter','MouseLeave','Paint'].forEach(e=>this['On'+e]=this[e+'Event']?this[e+'Event'].bind(this):null); |
| | | this.MpsKey='fbim';this.Seg=3;this.Ext='.tif';this.SafePixel=20;this.CaseIDLength=16;this.FormIDLength=15;this.Bt=4;this.CropBarcode='CC'; |
| | | }; |
| | | cls.prototype.ISB1Enter=function(){this.ISB1.SetFocus();}; |
| | | cls.prototype._Set_Font=function(v:any){this.Font=v;}; |
| | | ['Active','AlignDisabled','AlignWithMargins','AutoScroll','AutoSize','AxBorderStyle','Caption','Color','DockSite','DoubleBuffered','DropTarget','Enabled','ExplicitHeight','ExplicitLeft','ExplicitTop','ExplicitWidth','Font','HelpFile','KeyPreview','MouseInClient','ParentCustomHint','ParentDoubleBuffered','PixelsPerInch','PopupMode','PrintScale','Scaled','ScreenSnap','SnapBuffer','UseDockManager','Visible','VisibleDockClientCount'].forEach(p=>{ |
| | | cls.prototype['Get_'+p]=function(){return this[p];}; cls.prototype['Set_'+p]=function(v:any){this[p]=v;}; |
| | | }); |
| | | cls.prototype.LoadFileGetMD5=function(f:string){return "md5stub";}; |
| | | cls.prototype.ISExistImg=function(f:string){return this.ExistImgList.IndexOf(this.LoadFileGetMD5(f))!==-1;}; |
| | | cls.prototype.MemoInfoTransfer=function(m:string,s:string,id:TStringList,n:TStringList){ |
| | | if(m==='ID'){for(let i=0;i<id.Count;i++)if(s===id.Strings[i])return n.Strings[i]; return '自行輸入';} |
| | | if(m==='NAME'){for(let i=0;i<n.Count;i++)if(s===n.Strings[i])return id.Strings[i]; return '00';} return ''; |
| | | }; |
| | | cls.prototype.SetSQLData=function(c:string,f:TStringList,t:TStringList){t.Clear();t.Add(c);for(let i=1;i<f.Count;i++)t.Add(f.Strings[i]);}; |
| | | cls.prototype.GetSQLData=function(t:TStringList,c:string,no:number){ |
| | | if(t.Count===0||no>=t.Count)return''; let cs=t.Strings[0].split(','),ts=t.Strings[no],ds=ts.split('!@!'); |
| | | let idx=cs.indexOf(c); return(idx!==-1&&idx<ds.length)?ds[idx]:''; |
| | | }; |
| | | cls.prototype.FindSQLData=function(t:TStringList,c:string,kc:string,ks:string,no:number,res:TStringList){ |
| | | res.Clear(); if(!ks||t.Count<=1)return false; |
| | | let cl=c.split(','),kcl=kc.split(','),kl=ks.split(','),fidx=-1,found=false; |
| | | if(no===0){for(let i=1;i<t.Count;i++){let m=true;for(let n=0;n<kcl.length;n++)if(this.GetSQLData(t,kcl[n],i)!==kl[n]){m=false;break;}if(m){found=true;fidx=i;break;}}} |
| | | else{let m=true;for(let n=0;n<kcl.length;n++)if(t.Strings[no].indexOf('!@!'+kl[n]+'!@!')===-1){m=false;break;}if(m){found=true;fidx=no;}} |
| | | if(found)cl.forEach(co=>res.Add(co+','+this.GetSQLData(t,co,fidx))); return found; |
| | | }; |
| | | cls.prototype.GetFindResult=function(c:string){for(let i=0;i<this.FindResult.Count;i++){let s=this.FindResult.Strings[i],idx=s.indexOf(',');if(idx>-1&&c===s.substring(0,idx))return s.substring(idx+1);}return'';}; |
| | | cls.prototype.FileName2FormCode=function(f:string){let b=path.basename(f),v=b.indexOf('_'),v1=b.lastIndexOf('.');return(v>-1&&v1>v)?b.substring(v+1,v1):'';}; |
| | | cls.prototype.FormCode2DocNo=function(fc:string){for(let i=0;i<this.FormID_List.Count;i++)if(this.FormID_List.Strings[i]===fc)return this.DocNo_List.Strings[i];return fc?fc.substring(0,8):'';}; |
| | | cls.prototype.FormCode2Version=function(fc:string){return fc.substring(10,15);}; |
| | | cls.prototype.FormCode2Page=function(fc:string){return fc.substring(8,10);}; |
| | | cls.prototype.DocNoDir2DocNo=function(d:string){if(d!=='Attach'&&d!=='S_Attach'){let v=d.indexOf('(');if(v>0)return d.substring(0,v);}return d;}; |
| | | cls.prototype.DocNo2DocNoDir=function(p:string,d:string){if(!d)return this.AttName;let i=0,id='';do{i++;id=d+'('+i+')';}while(fs.existsSync(path.join(p,id)));return id;}; |
| | | cls.prototype.Add_Zoo=function(n:number,l:number){return n.toString().padStart(l,'0');}; |
| | | cls.prototype.Str2Dir=function(p:string){if(!fs.existsSync(p))fs.mkdirSync(p,{recursive:true});}; |
| | | cls.prototype._DelTree=function(p:string){if(fs.existsSync(p))fs.rmSync(p,{recursive:true,force:true});}; |
| | | } |
| 比對新檔案 |
| | |
| | | const fs = require('fs'); |
| | | const path = require('path'); |
| | | |
| | | // 檔案路徑設定 |
| | | const sourceFile = path.resolve(__dirname, 'CB_IMGPSScanImp.pas'); |
| | | const outDir = path.resolve(__dirname, 'separate', 'scanImp'); |
| | | |
| | | if (!fs.existsSync(sourceFile)) { |
| | | console.error("找不到原始檔案:", sourceFile); |
| | | process.exit(1); |
| | | } |
| | | |
| | | if (!fs.existsSync(outDir)) { |
| | | fs.mkdirSync(outDir, { recursive: true }); |
| | | } |
| | | |
| | | const content = fs.readFileSync(sourceFile, 'utf8'); |
| | | |
| | | const implMatch = content.match(/\bimplementation\b/i); |
| | | const initMatch = content.match(/\binitialization\b/i); |
| | | |
| | | if (!implMatch || !initMatch) { |
| | | console.error("無法在檔案中找到 implementation 或 initialization 區段。"); |
| | | process.exit(1); |
| | | } |
| | | |
| | | const headerPart = content.substring(0, implMatch.index + implMatch[0].length); |
| | | let implPart = content.substring(implMatch.index + implMatch[0].length, initMatch.index); |
| | | const footerPart = content.substring(initMatch.index); |
| | | |
| | | const usesMatch = implPart.match(/^\s*uses[\s\S]*?;/im); |
| | | let usesClause = ""; |
| | | if (usesMatch) { |
| | | usesClause = usesMatch[0]; |
| | | implPart = implPart.substring(usesMatch.index + usesMatch[0].length); |
| | | } |
| | | |
| | | // 提取並保留不在方法內的實作開頭,例如 {$R *.DFM} 或是 { TCB_IMGPSScanX } |
| | | const methodRegex = /^(?:procedure|function)\s+TCB_IMGPSScanX\./im; |
| | | let otherImpl = ""; |
| | | const firstMethodMatch = implPart.match(methodRegex); |
| | | |
| | | if (firstMethodMatch) { |
| | | otherImpl = implPart.substring(0, firstMethodMatch.index); |
| | | implPart = implPart.substring(firstMethodMatch.index); |
| | | } |
| | | |
| | | // 切割所有方法 |
| | | const methodSplitRegex = /(?=^(?:procedure|function)\s+TCB_IMGPSScanX\.)/im; |
| | | const methods = implPart.split(methodSplitRegex).filter(m => m.trim().length > 0); |
| | | |
| | | const uiMethods = []; |
| | | const scanMethods = []; |
| | | const dataMethods = []; |
| | | const utilMethods = []; |
| | | |
| | | methods.forEach(m => { |
| | | const nameMatch = m.match(/^(?:procedure|function)\s+TCB_IMGPSScanX\.([A-Za-z0-9_]+)/i); |
| | | if (nameMatch) { |
| | | const name = nameMatch[1].toLowerCase(); |
| | | if (/(click|mouse|key|drag|scroll|event|menu|btn|form|node|tree|view|activate|paint|resize)/.test(name)) { |
| | | uiMethods.push(m); |
| | | } else if (/(scan|acquire|image|graphic|twain|dpi|color|deskew|rotate|crop|smooth|point)/.test(name)) { |
| | | scanMethods.push(m); |
| | | } else if (/(ftp|http|trans|load|sql|server|setinf|data|ask|log|file|path|zip|dir|docno|formid|case)/.test(name)) { |
| | | dataMethods.push(m); |
| | | } else { |
| | | utilMethods.push(m); |
| | | } |
| | | } else { |
| | | utilMethods.push(m); |
| | | } |
| | | }); |
| | | |
| | | fs.writeFileSync(path.join(outDir, 'CB_IMGPSScanImp_UI.pas'), uiMethods.join(''), 'utf8'); |
| | | fs.writeFileSync(path.join(outDir, 'CB_IMGPSScanImp_Scan.pas'), scanMethods.join(''), 'utf8'); |
| | | fs.writeFileSync(path.join(outDir, 'CB_IMGPSScanImp_Data.pas'), dataMethods.join(''), 'utf8'); |
| | | fs.writeFileSync(path.join(outDir, 'CB_IMGPSScanImp_Utils.pas'), utilMethods.join(''), 'utf8'); |
| | | |
| | | const mainFile = [ |
| | | headerPart, |
| | | usesClause, |
| | | otherImpl, |
| | | "{$I CB_IMGPSScanImp_UI.pas}", |
| | | "{$I CB_IMGPSScanImp_Scan.pas}", |
| | | "{$I CB_IMGPSScanImp_Data.pas}", |
| | | "{$I CB_IMGPSScanImp_Utils.pas}", |
| | | footerPart |
| | | ].join(''); |
| | | |
| | | fs.writeFileSync(path.join(outDir, 'CB_IMGPSScanImp_Main.pas'), mainFile, 'utf8'); |
| | | |
| | | console.log("拆分完成!產生的五個檔案如下:"); |
| | | console.log("1. CB_IMGPSScanImp_Main.pas (主結構與引用)"); |
| | | console.log("2. CB_IMGPSScanImp_UI.pas (UI事件相關:", uiMethods.length, "個方法)"); |
| | | console.log("3. CB_IMGPSScanImp_Scan.pas (掃描影像相關:", scanMethods.length, "個方法)"); |
| | | console.log("4. CB_IMGPSScanImp_Data.pas (資料存取與網路傳輸:", dataMethods.length, "個方法)"); |
| | | console.log("5. CB_IMGPSScanImp_Utils.pas (工具與其他邏輯:", utilMethods.length, "個方法)"); |