Welcome to HACMS

HACMS网页版是基于PHP+(MySQL/SQLite)开发的开源程序,采用CodeIgniter框架、Adminlte后台模板,以会员核心系统为中心,以自身需求为出发点,采用模块分离的方式快速建立站群,具有速度快、效率高、功能强大、操作体验佳等特点,支持文章页面生成HTML静态页面、URL Rewrite、支持多数据库、多表操作与管理,包括网页版进销存、文章、MD文档等等。

HACMS桌面版是以C#、Python为开发语言,选择PostgreSQL、MySQL、SQLite为数据库,还是以自身需求为出发点,现已形成以网页源码采集器、进销存系统、键盘鼠标自动化工具、软件多开助手、单表自定义设置管理、EPUB电子书阅读器等小工具。

PostgreSQL表名

CREATE TABLE IF NOT EXISTS demo_test (
    tid SERIAL PRIMARY KEY,
    title VARCHAR(255) NOT NULL,
    notes TEXT NOT NULL,
    dateline BIGINT NOT NULL DEFAULT 0
);

C#操作PostgreSQL

using System;
using System.Data;
using System.Drawing;
using System.Windows.Forms;
using Npgsql;   // 需要安装 Npgsql

namespace PostgreSqlDemoManager
{
    public partial class Form1 : Form
    {
        // ======================= 配置区域 =======================
        private const string TABLE_NAME = "demo_test";
        private const int PAGE_SIZE = 10;
        // PostgreSQL 连接字符串(请修改为实际值)
        private const string CONN_STR = "Host=localhost;Port=5432;Username=postgres;Password=your_password;Database=your_database;Client Encoding=UTF8;";
        // =======================================================

        private int currentPage = 1;
        private int totalPages = 1;
        private int totalRecords = 0;

        // 控件声明
        private DataGridView dgvData;
        private Button btnFirst, btnPrev, btnNext, btnLast, btnAdd, btnEdit, btnDelete, btnRefresh;
        private Label lblPageInfo;
        private NumericUpDown nudJumpPage;
        private Button btnJump;
        private MenuStrip menuStrip;
        private ToolStripMenuItem menuAdd, menuEdit, menuDelete, menuRefresh, menuView;

        public Form1()
        {
            InitializeManualComponents();
            LoadData();
        }

        private void InitializeManualComponents()
        {
            this.Text = "Demo_Test 数据管理 (PostgreSQL)";
            this.Size = new Size(1000, 600);
            this.StartPosition = FormStartPosition.CenterScreen;

            // 菜单栏
            menuStrip = new MenuStrip();
            menuAdd = new ToolStripMenuItem("新增");
            menuEdit = new ToolStripMenuItem("编辑");
            menuDelete = new ToolStripMenuItem("删除");
            menuRefresh = new ToolStripMenuItem("刷新");
            menuView = new ToolStripMenuItem("显示");
            menuAdd.Click += (s, e) => OpenEditWindow(false, null);
            menuEdit.Click += (s, e) => EditRecord();
            menuDelete.Click += (s, e) => DeleteRecord();
            menuRefresh.Click += (s, e) => LoadData();
            menuView.Click += (s, e) => ViewRecord();
            menuStrip.Items.AddRange(new ToolStripItem[] { menuAdd, menuEdit, menuDelete, menuRefresh, menuView });
            this.MainMenuStrip = menuStrip;
            this.Controls.Add(menuStrip);

            // DataGridView
            dgvData = new DataGridView
            {
                Dock = DockStyle.Fill,
                AllowUserToAddRows = false,
                AllowUserToDeleteRows = false,
                ReadOnly = true,
                SelectionMode = DataGridViewSelectionMode.FullRowSelect,
                MultiSelect = false,
                AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill
            };
            dgvData.CellDoubleClick += (s, e) => ViewRecord();
            this.Controls.Add(dgvData);

            // 底部按钮面板
            Panel bottomPanel = new Panel { Dock = DockStyle.Bottom, Height = 45 };
            btnFirst = new Button { Text = "首页", Width = 60 };
            btnPrev = new Button { Text = "上一页", Width = 60 };
            lblPageInfo = new Label { Text = "第1页/共1页", Width = 120, TextAlign = ContentAlignment.MiddleCenter };
            btnNext = new Button { Text = "下一页", Width = 60 };
            btnLast = new Button { Text = "末页", Width = 60 };
            nudJumpPage = new NumericUpDown { Width = 60, Minimum = 1, DecimalPlaces = 0, ThousandsSeparator = false };
            btnJump = new Button { Text = "跳转", Width = 50 };
            btnAdd = new Button { Text = "新增", Width = 60 };
            btnEdit = new Button { Text = "编辑", Width = 60 };
            btnDelete = new Button { Text = "删除", Width = 60 };
            btnRefresh = new Button { Text = "刷新", Width = 60 };

            // 绑定事件
            btnFirst.Click += (s, e) => { currentPage = 1; LoadData(); };
            btnPrev.Click += (s, e) => { if (currentPage > 1) { currentPage--; LoadData(); } };
            btnNext.Click += (s, e) => { if (currentPage < totalPages) { currentPage++; LoadData(); } };
            btnLast.Click += (s, e) => { currentPage = totalPages; LoadData(); };
            btnJump.Click += (s, e) => { int p = (int)nudJumpPage.Value; if (p >= 1 && p <= totalPages) { currentPage = p; LoadData(); } };
            btnAdd.Click += (s, e) => OpenEditWindow(false, null);
            btnEdit.Click += (s, e) => EditRecord();
            btnDelete.Click += (s, e) => DeleteRecord();
            btnRefresh.Click += (s, e) => LoadData();

            // 布局
            bottomPanel.Controls.Add(btnFirst);
            bottomPanel.Controls.Add(btnPrev);
            bottomPanel.Controls.Add(lblPageInfo);
            bottomPanel.Controls.Add(btnNext);
            bottomPanel.Controls.Add(btnLast);
            bottomPanel.Controls.Add(nudJumpPage);
            bottomPanel.Controls.Add(btnJump);
            bottomPanel.Controls.Add(btnAdd);
            bottomPanel.Controls.Add(btnEdit);
            bottomPanel.Controls.Add(btnDelete);
            bottomPanel.Controls.Add(btnRefresh);
            int x = 10;
            foreach (Control c in bottomPanel.Controls)
            {
                c.Location = new Point(x, 10);
                x += c.Width + 5;
            }
            this.Controls.Add(bottomPanel);

            // 调整 DataGridView 位置,避免被菜单栏覆盖
            menuStrip.Dock = DockStyle.Top;
            dgvData.Top = menuStrip.Height;
            dgvData.Height = this.ClientSize.Height - menuStrip.Height - bottomPanel.Height;
        }

        /// <summary>
        /// 获取总记录数(使用独立连接)
        /// </summary>
        private int GetTotalCount()
        {
            using (var conn = new NpgsqlConnection(CONN_STR))
            {
                conn.Open();
                string sql = $"SELECT COUNT(*) FROM {TABLE_NAME}";
                using (var cmd = new NpgsqlCommand(sql, conn))
                {
                    return Convert.ToInt32(cmd.ExecuteScalar());
                }
            }
        }

        /// <summary>
        /// 加载当前页数据(每次使用独立连接)
        /// </summary>
        private void LoadData()
        {
            try
            {
                totalRecords = GetTotalCount();
                totalPages = (int)Math.Ceiling((double)totalRecords / PAGE_SIZE);
                if (totalPages == 0) totalPages = 1;
                if (currentPage > totalPages) currentPage = totalPages;
                if (currentPage < 1) currentPage = 1;

                int offset = (currentPage - 1) * PAGE_SIZE;
                DataTable dt = new DataTable();

                using (var conn = new NpgsqlConnection(CONN_STR))
                {
                    conn.Open();
                    // PostgreSQL 支持 LIMIT / OFFSET
                    string sql = $"SELECT tid, title, notes, dateline FROM {TABLE_NAME} ORDER BY tid DESC LIMIT @limit OFFSET @offset";
                    using (var cmd = new NpgsqlCommand(sql, conn))
                    {
                        cmd.Parameters.AddWithValue("@limit", PAGE_SIZE);
                        cmd.Parameters.AddWithValue("@offset", offset);
                        using (var adapter = new NpgsqlDataAdapter(cmd))
                        {
                            adapter.Fill(dt);
                        }
                    }
                }

                // 转换时间戳(dateline 为 BIGINT 存储 Unix 秒数)
                if (dt.Columns.Contains("dateline"))
                {
                    dt.Columns.Add("日期", typeof(string));
                    foreach (DataRow row in dt.Rows)
                    {
                        long unix = Convert.ToInt64(row["dateline"]);
                        DateTime dtTime = DateTimeOffset.FromUnixTimeSeconds(unix).LocalDateTime;
                        row["日期"] = dtTime.ToString("yyyy-MM-dd");
                    }
                    dt.Columns.Remove("dateline");
                }

                dgvData.DataSource = dt;
                if (dgvData.Columns.Count > 0)
                {
                    dgvData.Columns["tid"].HeaderText = "序号";
                    dgvData.Columns["title"].HeaderText = "标题";
                    dgvData.Columns["notes"].HeaderText = "内容";
                    dgvData.Columns["日期"].HeaderText = "日期";
                }

                lblPageInfo.Text = $"第{currentPage}页/共{totalPages}页";
                nudJumpPage.Maximum = totalPages;
                nudJumpPage.Value = currentPage;
            }
            catch (Exception ex)
            {
                MessageBox.Show($"加载数据失败:{ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

        /// <summary>
        /// 获取当前选中的 tid
        /// </summary>
        private int? GetSelectedTid()
        {
            if (dgvData.SelectedRows.Count == 0)
            {
                MessageBox.Show("请先选择一条记录", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning);
                return null;
            }
            object val = dgvData.SelectedRows[0].Cells["tid"].Value;
            if (val == null || val == DBNull.Value) return null;
            return Convert.ToInt32(val);
        }

        /// <summary>
        /// 编辑记录:获取数据后打开编辑窗口
        /// </summary>
        private void EditRecord()
        {
            int? tid = GetSelectedTid();
            if (tid == null) return;

            using (var conn = new NpgsqlConnection(CONN_STR))
            {
                conn.Open();
                string sql = $"SELECT tid, title, notes, dateline FROM {TABLE_NAME} WHERE tid=@tid";
                using (var cmd = new NpgsqlCommand(sql, conn))
                {
                    cmd.Parameters.AddWithValue("@tid", tid.Value);
                    using (var reader = cmd.ExecuteReader())
                    {
                        if (reader.Read())
                        {
                            var record = new
                            {
                                tid = reader["tid"],
                                title = reader["title"].ToString(),
                                notes = reader["notes"].ToString(),
                                dateline = Convert.ToInt64(reader["dateline"])
                            };
                            OpenEditWindow(true, record);
                        }
                        else
                        {
                            MessageBox.Show("记录不存在", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
                        }
                    }
                }
            }
        }

        /// <summary>
        /// 打开新增/编辑窗口
        /// </summary>
        private void OpenEditWindow(bool isEdit, dynamic record)
        {
            Form editForm = new Form();
            editForm.Text = isEdit ? "编辑记录" : "新增记录";
            editForm.Size = new Size(500, 400);
            editForm.StartPosition = FormStartPosition.CenterParent;
            editForm.FormBorderStyle = FormBorderStyle.FixedDialog;
            editForm.MaximizeBox = false;
            editForm.MinimizeBox = false;

            Label lblTitle = new Label { Text = "标题:", Location = new Point(20, 20), AutoSize = true };
            TextBox txtTitle = new TextBox { Location = new Point(100, 18), Width = 300 };
            Label lblNotes = new Label { Text = "内容:", Location = new Point(20, 60), AutoSize = true };
            TextBox txtNotes = new TextBox { Location = new Point(100, 58), Width = 300, Height = 150, Multiline = true, ScrollBars = ScrollBars.Vertical };
            Label lblDate = new Label { Text = "日期:", Location = new Point(20, 230), AutoSize = true };
            DateTimePicker dtpDate = new DateTimePicker { Location = new Point(100, 228), Width = 150, Format = DateTimePickerFormat.Custom, CustomFormat = "yyyy-MM-dd" };
            Button btnSave = new Button { Text = "保存", Location = new Point(100, 280), Width = 80 };
            Button btnCancel = new Button { Text = "取消", Location = new Point(200, 280), Width = 80 };

            if (isEdit && record != null)
            {
                txtTitle.Text = record.title;
                txtNotes.Text = record.notes;
                DateTime date = DateTimeOffset.FromUnixTimeSeconds(record.dateline).LocalDateTime;
                dtpDate.Value = date;
            }
            else
            {
                dtpDate.Value = DateTime.Now;
            }

            btnSave.Click += (s, e) =>
            {
                string title = txtTitle.Text.Trim();
                string notes = txtNotes.Text.Trim();
                if (string.IsNullOrEmpty(title) || string.IsNullOrEmpty(notes))
                {
                    MessageBox.Show("标题和内容不能为空", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning);
                    return;
                }
                long timestamp = ((DateTimeOffset)dtpDate.Value).ToUnixTimeSeconds();

                try
                {
                    using (var conn = new NpgsqlConnection(CONN_STR))
                    {
                        conn.Open();
                        if (isEdit)
                        {
                            string updateSql = $"UPDATE {TABLE_NAME} SET title=@title, notes=@notes, dateline=@dateline WHERE tid=@tid";
                            using (var cmd = new NpgsqlCommand(updateSql, conn))
                            {
                                cmd.Parameters.AddWithValue("@title", title);
                                cmd.Parameters.AddWithValue("@notes", notes);
                                cmd.Parameters.AddWithValue("@dateline", timestamp);
                                cmd.Parameters.AddWithValue("@tid", record.tid);
                                cmd.ExecuteNonQuery();
                            }
                            MessageBox.Show("更新成功", "成功", MessageBoxButtons.OK, MessageBoxIcon.Information);
                        }
                        else
                        {
                            string insertSql = $"INSERT INTO {TABLE_NAME} (title, notes, dateline) VALUES (@title, @notes, @dateline)";
                            using (var cmd = new NpgsqlCommand(insertSql, conn))
                            {
                                cmd.Parameters.AddWithValue("@title", title);
                                cmd.Parameters.AddWithValue("@notes", notes);
                                cmd.Parameters.AddWithValue("@dateline", timestamp);
                                cmd.ExecuteNonQuery();
                            }
                            MessageBox.Show("新增成功", "成功", MessageBoxButtons.OK, MessageBoxIcon.Information);
                        }
                    }
                    editForm.Close();
                    LoadData();
                }
                catch (Exception ex)
                {
                    MessageBox.Show($"保存失败:{ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
                }
            };

            btnCancel.Click += (s, e) => editForm.Close();

            editForm.Controls.AddRange(new Control[] { lblTitle, txtTitle, lblNotes, txtNotes, lblDate, dtpDate, btnSave, btnCancel });
            editForm.ShowDialog(this);
        }

        /// <summary>
        /// 删除记录
        /// </summary>
        private void DeleteRecord()
        {
            int? tid = GetSelectedTid();
            if (tid == null) return;
            if (MessageBox.Show($"确定要删除序号为 {tid} 的记录吗?", "确认删除", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes)
            {
                try
                {
                    using (var conn = new NpgsqlConnection(CONN_STR))
                    {
                        conn.Open();
                        string sql = $"DELETE FROM {TABLE_NAME} WHERE tid=@tid";
                        using (var cmd = new NpgsqlCommand(sql, conn))
                        {
                            cmd.Parameters.AddWithValue("@tid", tid.Value);
                            cmd.ExecuteNonQuery();
                        }
                    }
                    MessageBox.Show("删除成功", "成功", MessageBoxButtons.OK, MessageBoxIcon.Information);
                    LoadData();
                }
                catch (Exception ex)
                {
                    MessageBox.Show($"删除失败:{ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
                }
            }
        }

        /// <summary>
        /// 查看详情(双击或菜单“显示”)
        /// </summary>
        private void ViewRecord()
        {
            int? tid = GetSelectedTid();
            if (tid == null) return;

            using (var conn = new NpgsqlConnection(CONN_STR))
            {
                conn.Open();
                string sql = $"SELECT tid, title, notes, dateline FROM {TABLE_NAME} WHERE tid=@tid";
                using (var cmd = new NpgsqlCommand(sql, conn))
                {
                    cmd.Parameters.AddWithValue("@tid", tid.Value);
                    using (var reader = cmd.ExecuteReader())
                    {
                        if (reader.Read())
                        {
                            string title = reader["title"].ToString();
                            string notes = reader["notes"].ToString();
                            long unix = Convert.ToInt64(reader["dateline"]);
                            DateTime date = DateTimeOffset.FromUnixTimeSeconds(unix).LocalDateTime;
                            string dateStr = date.ToString("yyyy-MM-dd");

                            Form viewForm = new Form();
                            viewForm.Text = $"查看记录 - 序号 {tid}";
                            viewForm.Size = new Size(500, 450);
                            viewForm.StartPosition = FormStartPosition.CenterParent;
                            viewForm.FormBorderStyle = FormBorderStyle.FixedDialog;
                            viewForm.MaximizeBox = false;
                            viewForm.MinimizeBox = false;

                            Label lblTitle = new Label { Text = $"标题:{title}", Location = new Point(20, 20), AutoSize = true, Font = new Font("微软雅黑", 10, FontStyle.Bold) };
                            Label lblDate = new Label { Text = $"日期:{dateStr}", Location = new Point(20, 60), AutoSize = true };
                            Label lblNotesLabel = new Label { Text = "内容:", Location = new Point(20, 100), AutoSize = true, Font = new Font("微软雅黑", 10, FontStyle.Bold) };
                            TextBox txtNotes = new TextBox { Location = new Point(20, 130), Width = 440, Height = 220, Multiline = true, ReadOnly = true, ScrollBars = ScrollBars.Vertical, Text = notes };
                            Button btnClose = new Button { Text = "关闭", Location = new Point(200, 370), Width = 80 };
                            btnClose.Click += (s, e) => viewForm.Close();

                            viewForm.Controls.AddRange(new Control[] { lblTitle, lblDate, lblNotesLabel, txtNotes, btnClose });
                            viewForm.ShowDialog(this);
                        }
                        else
                        {
                            MessageBox.Show("记录不存在", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
                        }
                    }
                }
            }
        }
    }
}

帮助中心

HACMS进销存管理系统V1.0(桌面版)

HACMS进销存管理系统V1.0(桌面版)采用的是Python+MySQL开发,开发初衷仅为自己所用,主要包括采购管理、采购退货管理、销售管理、销售退货管理、库存预警、库存报表、报盈管理、报损管理、流水导出等功能 ...

使用手册

PHP

PHP is a popular general-purpose scripting language that is especially suited to web development. Fast, flexible and pragmatic, PHP powers everything from your blog to the most popular websites in the world.

PHP 手册

CodeIgniter

CodeIgniter is a powerful PHP framework with a very small footprint, built for developers who need a simple and elegant toolkit to create full-featured web applications.

CodeIgniter4用户指南