统计页面 修改店铺选择,增加店铺搜索Combobox

This commit is contained in:
chfei committed 2026-08-07 11:25:19 +08:00
1 parent b6b72e6bc3
commit f12a7f14ea
16 files changed
+1801 -41

No files matched your search

@@ -0,0 +1,15 @@
namespace TinyPinyin.Data
{
public static class PinyinCode1
{
public static byte[] PINYIN_CODE_PADDING = new byte[]
{
177,79,200,37,169,41,96,13,134,58,2,134,67,33,184,128,16,79,211,92,75,40,54,206,19,34,241,102,32,39,53,25,201,37,194,233,28,47,168,151,133,170,159,151,26,160,0,204,151,176,3,116,69,64,36,15,103,74,178,194,31,81,68,50,154,196,73,21,145,176,40,124,41,118,173,84,223,152,167,37,218,134,12,253,48,228,242,232,2,220,89,192,136,66,156,160,153,96,12,40,128,151,49,26,223,65,240,131,96,69,20,170,80,34,146,163,28,34,64,80,202,4,65,65,0,0,96,129,24,65,74,66,9,4,192,1,136,5,16,20,71,34,9,17,48,128,40,52,194,242,128,131,128,41,192,62,148,35,141,141,1,9,16,48,24,16,144,45,231,185,29,8,169,105,87,155,161,252,152,154,78,100,248,142,19,89,252,210,2,21,93,3,226,16,23,198,12,80,30,73,1,130,10,209,98,47,153,142,12,136,140,112,112,89,135,173,172,38,10,128,2,201,12,65,67,153,115,4,12,203,129,118,114,248,32,67,169,57,26,21,118,144,159,16,143,38,93,12,166,43,104,84,78,70,32,232,192,8,97,1,22,153,114,24,135,204,72,230,10,92,139,200,168,129,16,69,40,64,210,12,5,57,131,142,77,34,28,209,82,231,236,153,159,155,24,0,59,32,9,72,233,81,34,66,2,24,144,132,169,33,192,176,192,160,2,147,74,74,210,41,4,34,129,130,136,152,65,10,192,88,183,197,86,122,0,112,160,18,228,208,131,56,161,128,240,80,72,56,210,216,51,170,39,204,233,50,16,103,186,32,16,132,168,69,76,40,0,66,86,228,97,177,178,134,243,32,10,126,188,46,1,37,196,205,107,76,10,72,147,253,87,6,0,26,71,65,9,50,210,199,207,2,195,250,217,96,251,0,93,53,160,90,40,16,160,13,76,7,167,129,32,210,17,163,225,99,225,114,67,8,77,69,236,125,133,218,186,167,179,130,42,5,26,35,84,15,22,140,20,159,130,142,157,46,1,67,136,107,106,208,137,176,14,218,82,131,64,225,144,75,25,2,29,8,174,162,42,76,152,226,48,2,100,128,220,164,187,126,9,230,188,77,19,27,130,1,157,102,82,129,212,93,165,121,196,180,192,69,66,136,237,37,37,153,67,150,177,61,184,10,91,0,13,137,0,0,1,53,86,145,18,33,191,99,64,127,5,73,34,114,2,193,251,0,132,219,38,70,69,216,39,32,128,64,64,121,254,64,51,7,203,162,5,162,6,236,49,8,93,97,135,53,245,87,140,14,14,225,144,96,142,81,147,139,28,22,120,195,88,208,2,163,225,22,37,205,97,88,0,136,34,24,121,209,44,0,28,199,152,139,18,36,7,221,96,155,237,106,2,192,50,168,115,66,54,65,192,139,4,68,92,41,57,83,160,6,32,91,0,195,136,246,2,1,65,168,39,4,241,220,144,147,176,24,35,77,66,249,46,193,128,242,17,81,24,0,104,10,32,162,120,98,136,8,75,244,72,194,6,110,36,67,14,182,96,1,25,225,41,145,200,82,68,43,130,72,50,122,249,180,35,16,94,197,136,115,82,181,131,133,46,85,181,46,16,1,41,176,244,132,16,236,36,82,0,129,139,72,226,80,36,72,22,15,132,202,186,32,100,49,52,129,72,129,16,33,20,1,42,98,81,29,227,73,80,209,70,222,47,181,8,136,75,209,33,63,84,167,137,32,250,182,11,233,131,80,165,180,62,236,29,62,31,23,215,65,7,13,34,24,68,194,177,219,131,27,200,112,47,137,21,7,57,24,15,228,6,98,173,182,92,162,66,25,24,85,13,34,117,233,12,98,46,120,6,36,216,111,26,0,226,146,49,3,47,22,171,150,144,36,168,216,71,151,7,190,96,33,28,58,56,164,164,176,77,48,2,0,160,24,149,254,136,67,33,228,24,66,32,104,77,95,77,23,52
};
public static byte[] PINYIN_CODE = new byte[]
{
104,68,147,250,33,86,0,78,127,24,33,86,129,21,111,197,92,40,40,139,255,241,39,39,3,19,103,49,70,49,37,69,3,172,69,110,172,100,19,25,41,138,96,99,253,134,129,134,87,105,43,32,175,143,136,0,78,58,80,136,136,169,139,244,90,104,104,207,0,138,138,75,192,104,0,133,84,124,121,82,166,134,246,236,254,121,104,37,37,104,106,0,199,138,250,103,85,88,92,69,0,0,41,0,39,129,209,130,0,39,0,0,188,181,0,17,85,100,90,30,206,93,0,0,0,0,252,133,107,93,181,175,104,141,166,0,111,132,39,39,80,41,111,158,111,114,121,250,84,136,49,54,98,98,99,90,99,250,99,129,70,79,146,57,133,111,104,32,118,205,0,88,136,68,172,118,136,103,0,20,110,90,58,171,76,80,12,12,129,0,79,104,37,12,166,68,120,135,249,40,6,127,135,134,19,13,49,88,24,182,0,25,144,39,57,127,90,87,87,30,119,69,12,252,93,96,63,57,176,104,34,33,22,33,104,205,194,12,130,34,101,252,134,241,78,84,131,130,102,87,25,12,79,86,63,84,134,250,238,111,66,75,81,104,91,146,104,129,2,84,129,90,82,94,135,11,36,90,60,134,110,128,126,111,53,114,24,80,43,35,99,87,33,33,182,25,98,91,80,136,38,96,210,20,102,216,216,90,8,95,176,135,37,4,49,13,49,130,241,104,49,2,132,65,112,188,58,136,21,4,14,34,48,80,63,136,151,110,101,64,126,115,14,75,35,111,104,88,151,101,222,69,216,96,139,109,76,252,0,143,0,238,123,115,165,88,96,101,7,82,201,130,207,19,129,118,128,107,5,66,133,48,104,39,92,37,75,145,133,92,162,104,38,154,106,169,106,39,196,136,95,110,3,180,204,80,182,70,30,38,98,100,135,104,17,131,86,130,116,184,0,133,131,27,254,155,31,222,224,135,84,120,137,37,131,55,40,0,184,139,41,68,37,75,20,208,114,15,73,111,85,51,77,3,95,157,153,84,142,104,90,164,147,254,169,109,127,136,87,24,234,52,90,85,169,197,246,10,111,49,86,91,94,111,64,35,40,0,100,172,169,162,49,131,94,90,115,139,89,234,131,16,64,85,87,99,3,11,111,91,14,38,33,133,19,117,102,53,170,78,162,25,145,96,105,11,65,41,41,194,60,59,141,45,92,240,60,120,104,250,64,93,136,134,95,33,134,84,133,151,140,145,139,252,216,182,143,83,183,50,168,127,70,144,12,84,139,207,23,131,125,103,133,30,1,0,107,37,252,100,227,134,46,130,134,80,111,19,21,64,80,242,100,87,60,83,77,90,35,38,145,63,151,30,68,11,103,125,102,126,3,100,110,131,95,124,31,90,14,133,145,197,129,104,90,98,49,73,27,131,232,70,70,11,115,179,134,80,86,33,158,51,38,52,86,90,112,15,169,17,114,101,187,9,65,60,114,134,85,32,252,141,25,41,24,11,89,109,102,57,56,79,82,19,130,57,116,60,0,18,41,228,117,167,53,109,119,145,240,50,5,43,111,125,147,33,253,253,38,30,112,127,1,100,63,85,179,11,243,135,171,180,189,252,87,3,107,70,139,88,32,254,137,73,150,249,85,165,33,106,173,250,62,32,80,127,83,126,43,67,58,133,138,29,86,87,141,77,133,131,69,175,20,102,87,52,87,132,200,103,135,130,254,241,87,135,2,23,104,142,224,32,104,59,136,96,155,131,41,58,133,30,116,0,18,3,17,58,40,31,163,216,135,252,195,84,214,2,216,33,174,167,184,157,10,72,16,117,133,49,110,114,0,36,169,63,80,178,41,32,9,41,126,169,183,117,228,60,100,167,209,80,84,114,117,112,93,39,128,93,87,106,74,148,74,197,71,33,80,74,80,135,71,49,100,100,39,39,59,252,71,86,191,91,71,7,136,169,157,17,79,212,5,172,111,6,100,177,85,0,163,100,65,105,92,19,250,139,65,144,0,101,131,40,129,104,129,32,137,191,8,212,112,191,94,8,27,137,27,116,103,137,191,135,191,101,95,197,196,15,106,90,145,142,224,104,196,39,105,195,134,149,112,201,152,0,90,90,196,19,70,58,94,87,19,121,39,141,121,157,103,168,235,90,200,70,87,174,86,131,136,41,193,33,250,102,142,50,136,172,1,66,176,70,93,131,106,50,2,169,25,201,142,53,49,76,135,175,175,222,85,72,129,83,83,83,87,139,41,0,87,0,0,90,87,246,87,144,125,144,93,62,246,4,93,155,71,5,41,129,59,112,112,119,60,66,60,12,12,44,86,255,104,129,145,252,54,41,81,129,58,92,122,78,141,169,113,174,177,120,94,44,90,41,4,139,32,200,176,134,235,17,134,134,10,169,32,17,32,136,103,98,60,44,158,153,76,80,133,42,5,30,48,148,134,107,48,107,144,76,129,64,136,179,183,120,112,55,97,148,161,252,30,43,103,131,55,169,64,85,248,32,250,44,144,94,78,20,129,76,1,100,143,131,129,20,100,139,128,38,131,76,73,84,103,90,38,131,96,124,144,44,140,32,72,180,169,89,32,243,152,133,103,254,141,122,124,143,171,139,241,177,107,133,107,131,131,60,128,129,131,104,131,133,32,52,203,169,136,169,99,5,8,100,130,84,188,174,136,150,90,133,70,136,226,134,4,34,104,136,203,169,136,165,165,140,152,101,76,89,204,157,134,174,115,39,148,136,114,20,200,38,164,109,109,197,148,98,140,1,180,21,195,162,166,144,197,70,95,95,145,84,104,98,82,38,165,205,180,243,39,129,0,253,133,5,88,104,254,83,140,69,139,58,90,188,98,98,184,169,35,9,5,10,34,114,138,10,101,84,114,0,0,92,92,10,49,0,93,240,139,61,96,249,3,237,90,100,56,138,2,14,122,11,210,38,84,138,104,115,132,146,132,157,121,86,4,83,107,255,25,157,85,121,111,107,158,126,58,158,171,171,53,72,138,4,85,241,4,104,3,100,15,216,4,39,91,252,217,22,147,38,84,126,8,39,85,78,122,90,78,11,147,143,90,58,188,208,58,129,20,43,21,157,15,21,126,143,180,110,180,85,103,83,90,134,134,80,4,2,133,191,106,80,34,129,6,181,39,140,90,95,135,6,84,129,77,1,85,0,33,112,77,68,169,129,3,169,99,99,100,35,133,124,236,0,148,99,133,27,236,64,169,35,120,68,149,55,85,112,27,112,88,100,169,141,30,65,41,138,0,Line truncated
};
}
}
@@ -0,0 +1,15 @@
namespace TinyPinyin.Data
{
public static class PinyinCode2
{
public static byte[] PINYIN_CODE_PADDING = new byte[]
{
128,246,161,5,7,104,160,33,238,10,249,237,47,84,4,100,17,69,72,130,1,130,75,32,241,216,3,90,242,67,177,93,36,246,6,0,112,249,113,65,8,163,225,0,210,4,48,4,72,134,138,68,234,193,77,2,33,56,161,199,34,232,109,212,162,76,68,79,40,185,135,215,208,52,4,200,103,40,14,10,0,182,145,32,207,24,167,0,18,181,26,127,37,23,40,43,62,50,44,195,130,191,9,170,102,4,162,67,222,113,67,130,48,144,27,75,168,165,41,253,130,148,91,236,144,5,7,144,195,161,128,56,40,7,251,166,13,71,243,86,194,112,48,2,201,20,168,34,107,110,46,248,229,105,47,197,41,2,170,2,95,243,202,100,228,66,128,215,78,71,206,127,123,8,108,120,200,133,142,12,36,147,0,17,186,34,22,46,98,218,180,36,166,177,171,60,153,3,48,65,67,114,51,4,204,130,178,97,6,126,128,17,0,171,10,132,154,79,128,144,18,23,99,22,129,232,6,198,118,69,65,0,27,23,44,182,13,234,252,127,188,132,169,224,136,180,91,17,128,34,244,87,21,80,12,63,210,207,93,48,110,62,199,28,130,185,45,162,62,186,164,102,134,183,99,15,139,136,40,184,70,40,166,133,60,12,196,65,2,9,39,197,71,65,3,16,102,129,50,65,34,227,208,96,33,200,103,79,206,141,16,148,71,168,247,197,140,53,3,1,224,46,248,0,45,90,30,128,38,162,66,17,134,248,100,128,189,203,17,202,97,158,12,75,226,4,16,17,19,113,93,182,217,124,42,98,146,240,216,114,12,0,70,46,174,193,46,212,4,82,64,32,0,152,66,230,156,243,19,27,112,136,200,66,8,16,48,5,34,8,192,3,120,138,41,65,26,152,181,84,156,201,90,65,128,221,226,197,210,64,88,246,193,128,68,60,2,44,240,3,0,6,0,64,130,12,1,106,44,54,149,9,76,4,3,210,13,14,208,39,231,116,99,18,94,27,112,172,149,6,96,6,152,10,225,120,4,80,154,30,73,49,64,64,197,188,5,137,38,4,8,122,132,219,8,128,17,12,196,100,98,28,212,4,102,12,192,148,96,22,87,8,142,24,115,22,5,16,24,77,16,140,9,69,48,33,162,32,239,245,202,199,237,240,154,10,34,168,94,191,156,212,42,120,183,159,57,201,192,192,111,230,26,49,244,56,6,206,192,105,7,134,210,139,153,42,3,185,91,51,141,54,34,14,223,27,3,72,35,96,6,103,22,3,38,162,126,66,23,48,72,64,8,186,112,134,40,3,238,47,144,8,148,135,4,68,25,183,83,68,34,208,24,129,10,64,161,11,82,18,9,0,109,128,4,97,7,55,113,224,168,199,53,97,145,0,219,182,144,32,32,46,147,208,172,24,48,73,168,92,223,200,50,37,74,109,107,30,166,2,152,148,119,150,144,189,215,96,20,152,186,21,8,236,209,212,140,216,168,161,27,30,13,249,183,254,192,75,176,181,236,124,81,51,26,64,15,6,91,232,5,90,57,50,219,242,99,36,132,122,53,90,122,139,20,5,25,4,219,160,20,188,93,26,16,16,119,12,200,16,164,35,89,159,55,87,160,2,21,127,152,172,13,40,80,33,96,26,49,32,8,81,7,145,83,65,192,48,16,156,176,255,39,240,147,12,24,29,227,31,204,146,51,121,32,161,73,166,200,160,19,51,14,2,34,64,104,32,234,160,221,21,228,248,66,0,104,154,192,202,13,73,12,89,210,164,4,24,111,219,121,31,244,76,128,152,64,178,130,129,65,16,32,204,20,49,120,233,188,133,202,144,163,172,200,55,5,166,120,6,144,2,5,56,134,11,0,39,179,137,160,130,9,168,204,98,116,138,228,32,29,217,16,120,7,73,8,2,133,104,48,160,115,16,64,93,20,240,123,182,65,156,11,224,7,81,145,92,115,75,32,119,56,153,82
};
public static byte[] PINYIN_CODE = new byte[]
{
205,191,41,121,121,171,168,68,208,111,110,193,50,96,96,107,131,242,67,129,134,103,41,47,111,50,80,193,63,129,134,144,3,107,16,118,179,166,0,107,245,0,92,59,163,114,111,36,184,139,0,0,0,90,130,104,126,90,228,196,164,15,102,131,139,57,102,131,9,30,112,144,201,52,130,102,134,125,93,85,124,252,187,55,112,90,15,39,133,53,114,68,177,15,60,6,125,49,38,57,148,85,102,250,147,95,55,235,61,96,98,65,225,129,48,101,45,253,30,252,123,193,95,94,95,143,75,254,101,65,130,131,149,0,178,18,136,0,85,250,171,126,109,252,109,92,92,72,122,250,28,53,13,110,121,132,121,124,158,104,104,95,146,107,107,26,189,135,63,140,166,164,36,49,169,94,1,45,83,69,105,129,56,167,180,172,196,179,34,52,148,41,60,16,180,138,41,124,41,127,194,203,219,101,66,240,136,30,85,5,118,131,49,0,0,253,0,107,80,91,133,254,149,49,249,41,122,158,131,150,113,126,85,98,65,82,93,203,84,254,10,175,177,254,87,20,83,126,75,165,114,47,73,37,60,195,139,37,52,141,141,59,126,129,228,88,75,222,19,136,69,123,86,2,8,118,28,102,177,165,95,41,0,0,0,0,129,71,0,180,0,0,112,57,41,132,59,175,224,106,85,54,32,149,96,37,93,139,149,104,0,249,100,167,87,126,59,129,54,20,14,68,41,137,107,129,130,130,1,129,131,253,60,104,16,50,35,175,169,30,195,106,61,58,197,250,0,18,128,129,252,196,222,104,95,131,106,80,1,100,250,196,128,107,46,129,158,247,62,41,0,197,110,133,106,252,167,167,22,180,0,52,184,199,126,232,184,133,95,72,112,169,85,136,51,171,0,41,0,136,180,100,169,136,36,134,77,52,123,220,111,178,162,0,87,0,139,89,176,107,131,106,110,107,88,224,20,32,163,139,45,35,80,49,5,4,0,0,111,183,169,117,181,59,141,0,163,163,136,167,169,6,209,111,176,0,252,48,124,91,111,111,252,232,95,34,41,250,144,104,141,85,95,86,111,155,164,156,48,85,81,104,250,121,38,0,156,145,156,145,43,30,0,106,91,90,111,252,89,104,96,84,59,135,232,121,64,124,95,239,85,89,121,35,0,171,41,104,145,111,47,124,133,132,48,21,84,250,21,21,77,139,252,38,27,38,27,134,54,54,169,55,111,169,107,57,57,49,131,129,203,203,102,203,51,101,65,38,57,33,95,98,41,24,141,243,251,3,52,1,114,171,104,89,133,103,24,127,58,129,103,0,114,81,40,18,64,135,33,106,66,51,126,52,104,58,72,132,171,18,72,131,131,41,232,73,136,106,1,104,30,148,148,102,98,65,126,126,102,6,129,104,232,126,73,104,89,84,105,205,193,193,2,151,72,111,14,14,14,241,241,14,32,191,0,0,241,0,130,126,23,205,75,98,80,15,87,139,205,114,3,71,0,59,238,139,76,53,14,24,0,191,54,41,111,75,115,131,57,24,184,205,169,69,15,33,249,180,126,26,126,195,180,4,67,39,63,200,141,190,250,244,207,250,60,87,43,86,14,212,0,90,37,70,1,250,106,85,111,101,3,99,148,1,99,70,58,184,1,101,114,114,46,46,132,19,109,59,46,166,216,74,83,107,68,133,3,18,120,197,52,126,66,112,30,143,43,78,83,57,85,75,190,3,250,32,234,112,252,84,84,98,49,17,100,132,38,84,0,0,60,133,38,252,196,102,79,1,136,19,142,119,58,5,129,15,15,93,81,134,84,93,141,121,0,250,86,95,95,0,104,83,114,112,113,100,37,36,58,110,75,121,128,250,205,16,193,57,197,84,39,65,14,30,133,238,235,141,115,101,177,193,120,87,232,169,182,25,87,80,121,203,193,41,139,117,75,75,76,115,169,196,104,90,85,110,65,133,128,102,126,100,49,157,137,139,90,3,104,130,134,5,20,126,196,12,143,41,166,110,102,119,93,82,191,49,121,246,48,83,133,52,222,37,176,237,20,250,49,216,139,113,136,38,167,96,97,90,235,200,58,101,129,109,105,13,97,178,180,58,183,90,247,120,136,106,135,134,104,126,126,149,37,106,80,120,131,101,174,49,129,80,92,90,22,144,133,106,84,85,147,136,132,183,0,3,70,104,204,167,104,196,5,135,247,80,89,90,119,95,52,157,61,255,139,80,135,17,246,98,93,133,106,124,201,128,76,251,234,84,4,177,104,130,136,252,132,133,131,39,143,27,0,126,129,177,32,127,121,224,98,135,174,3,80,129,142,112,9,190,143,110,85,20,71,124,119,104,249,107,163,114,164,112,169,99,90,84,169,46,87,104,111,69,165,111,135,130,39,82,193,54,37,238,87,37,71,159,245,220,112,136,89,35,217,71,109,89,87,68,77,52,74,140,28,64,169,46,49,167,46,61,72,165,162,171,80,83,114,124,63,0,20,131,127,27,90,105,92,40,44,99,47,127,103,151,78,112,75,70,115,83,139,93,172,127,57,143,65,255,61,140,144,85,89,250,102,109,112,175,60,135,240,114,33,41,250,84,38,180,210,139,61,49,167,133,240,85,50,65,241,58,111,216,111,180,93,196,136,176,182,106,53,4,123,111,217,37,243,46,121,112,162,127,1,100,252,65,198,133,106,196,12,112,81,11,85,1,112,148,129,35,112,27,180,144,72,0,131,197,241,85,111,112,37,37,16,124,136,131,227,111,3,68,4,72,87,124,20,83,83,63,80,81,17,90,27,80,96,94,100,119,96,196,148,191,107,100,110,119,198,92,193,116,127,207,158,39,77,234,193,171,250,250,193,65,50,80,24,72,197,95,203,95,129,239,131,131,121,87,88,104,106,126,39,134,131,125,59,111,14,200,39,71,38,109,139,134,0,3,133,0,106,60,178,128,112,208,8,110,5,45,172,32,100,46,220,144,78,39,189,107,161,158,0,131,95,179,107,92,0,0,247,135,107,60,112,56,112,171,102,195,142,38,148,58,57,76,177,101,26,201,124,39,104,182,187,249,80,169,23,84,85,81,253,27,39,52,104,131,51,114,94,106,15,127,52,52,216,57,39,17,80,235,41,41,236,82,25,199,115,65,114,125,85,144,63,133,107,90,134,122,96,144,61,63,54,14,133,126,108,106,95,178,133,100,35,189,107,46,184,163,181,89,18,59,111,94,121,14,16,133,132,15Line truncated
};
}
}
@@ -0,0 +1,15 @@
namespace TinyPinyin.Data
{
public static class PinyinCode3
{
public static byte[] PINYIN_CODE_PADDING = new byte[]
{
176,9,83,102,151,4,40,15,197,134,34,208,132,7,1,50,66,128,4,45,104,73,121,252,179,147,135,146,136,110,52,222,137,42,137,128,202,46,52,188,148,25,143,53,9,143,186,1,104,1,128,3,140,76,112,150,114,97,11,206,76,152,80,208,19,140,225,33,1,59,226,34,169,11,219,193,172,131,48,70,244,154,216,192,106,173,222,189,32,194,242,107,155,33,31,12,78,113,59,136,234,35,20,200,27,14,55,187,162,0,16,67,169,209,97,184,60,75,157,12,233,125,130,194,230,145,11,53,36,96,33,8,50,235,129,12,136,52,98,194,226,192,56,85,192,58,20,37,11,4,198,46,44,211,11,64,192,209,126,4,131,74,72,104,130,80,5,142,213,230,51,196,190,230,151,235,159,222,129,40,159,184,79,66,168,98,36,119,24,217,92,28,233,89,144,234,146,168,163,100,21,23,118,160,98,244,75,204,96,12,248,34,177,237,5,138,103,41,110,154,26,109,17,247,66,89,226,215,166,24,93,182,178,147,199,7,111,69,46,36,40,4,1,208,62,15,66,153,162,1,50,0,37,212,88,180,5,50,128,166,103,219,36,148,11,101,185,239,204,82,89,69,24,56,153,197,4,253,103,227,35,79,128,15,71,207,232,225,14,192,233,64,72,4,137,42,10,194,106,165,12,0,23,181,177,144,84,172,16,52,145,68,34,2,38,48,138,220,205,136,1,36,81,202,181,129,37,8,48,119,130,231,110,86,4,96,67,213,59,134,64,98,128,154,112,125,19,72,236,10,33,0,18,1,108,236,105,50,28,208,99,168,15,246,83,183,19,1,203,69,205,15,165,50,167,122,97,79,45,96,102,13,164,84,163,198,162,10,154,4,148,146,161,84,105,222,118,193,108,178,16,82,161,218,154,227,38,224,25,105,89,99,94,202,138,138,201,22,42,111,144,100,16,5,220,193,112,132,136,163,83,160,65,92,222,71,85,66,97,226,97,32,53,238,110,60,108,12,216,143,112,23,240,215,169,48,170,68,64,196,145,134,40,40,3,100,2,209,164,181,74,64,66,214,3,106,123,162,46,111,23,10,186,130,16,80,40,49,8,112,66,156,48,137,34,162,164,128,84,42,33,20,1,8,21,164,140,37,67,137,96,33,5,44,19,135,48,149,94,203,210,41,11,17,203,0,162,17,74,20,136,8,164,200,0,128,66,144,76,128,39,1,1,192,151,202,61,11,33,136,96,78,7,25,32,219,164,2,125,226,56,26,3,66,41,241,108,51,193,252,190,137,229,150,151,7,39,154,63,34,74,178,48,167,36,192,241,64,39,86,137,29,131,20,0,0,38,33,96,41,151,203,161,10,232,50,236,2,42,68,32,25,194,83,218,156,123,236,106,23,3,0,57,32,20,230,8,158,252,3,18,153,176,62,20,40,105,27,178,176,50,95,2,136,117,128,177,79,62,89,198,174,128,167,218,64,40,147,151,53,181,145,202,79,213,8,33,20,89,130,200,41,118,63,241,89,145,237,120,7,145,113,228,20,109,128,111,37,243,173,160,179,220,186,89,225,230,156,11,112,56,141,216,94,33,32,152,0,28,64,52,168,193,226,217,7,68,6,16,2,131,12,192,173,64,23,105,41,137,24,133,136,9,42,207,68,128,190,56,12,212,64,8,200,176,18,186,56,74,74,217,203,128,53,122,153,29,21,78,80,96,152,1,6,209,240,96,49,1,14,47,73,33,174,72,8,196,208,66,167,96,122,244,119,12,39,13,80,181,20,6,41,228,140,32,16,209,130,149,128,207,161,78,139,108,202,187,12,185,123,233,67,6,23,84,226,252,20,193,7,24,242,228,144,17,34,65,17,0,99,8,192,20,2,129,37,232,160,119,104,73,130,58,42,132,225,147,255,240,51,245,40,64,29,113,80,180,69,196,27,66,33,49,0,0,0,0,0,0,0,0,0,0,0,0
};
public static byte[] PINYIN_CODE = new byte[]
{
235,249,249,206,48,85,86,114,132,131,129,21,25,79,196,114,55,131,201,0,95,177,85,102,164,15,82,92,55,39,60,183,17,56,87,11,102,107,14,145,108,0,94,27,0,163,0,129,169,24,164,111,0,107,203,66,94,84,69,136,240,3,171,49,85,246,3,135,46,134,80,73,26,111,104,129,173,14,180,95,21,127,183,253,189,100,168,129,16,108,112,63,52,180,35,33,63,199,98,189,20,63,55,129,28,96,111,121,5,196,179,51,134,23,247,132,196,49,219,126,142,106,131,217,41,106,158,36,121,30,152,252,187,25,120,253,71,171,175,152,2,14,169,80,129,252,38,83,195,232,32,65,98,133,19,19,167,111,254,41,122,131,188,114,10,110,4,180,10,126,77,64,85,141,149,82,17,86,158,47,19,99,95,90,141,59,84,69,49,89,85,109,81,34,250,131,114,55,176,111,86,82,129,119,49,62,167,96,114,111,85,114,20,114,2,80,126,80,129,48,88,181,199,104,168,132,24,37,253,171,148,112,56,64,60,97,14,126,55,171,83,68,89,102,90,41,131,147,119,22,91,98,102,7,51,41,98,74,245,80,214,40,188,17,243,58,250,119,36,131,80,216,107,95,49,89,250,82,131,95,158,134,15,63,196,163,135,118,198,2,255,87,0,232,87,52,184,104,95,90,169,104,161,167,133,63,133,241,63,102,203,124,16,83,51,59,73,2,254,80,177,126,41,95,114,0,169,136,136,2,175,119,96,36,162,128,75,84,19,19,250,118,180,52,73,190,114,245,111,98,129,137,96,203,0,52,137,0,220,20,9,104,87,111,139,171,171,106,253,107,178,70,80,113,176,4,102,83,196,163,158,163,129,59,0,167,167,69,87,133,80,158,126,123,169,129,196,167,123,183,129,210,180,131,0,0,167,5,89,104,181,194,17,121,121,180,230,184,133,89,252,41,121,95,55,90,95,95,180,121,111,114,133,139,109,10,100,126,126,158,8,85,41,39,3,66,129,3,68,39,0,63,129,35,111,93,144,119,126,195,96,54,86,31,39,104,187,88,84,77,233,38,252,81,81,19,9,241,113,113,142,250,8,106,250,24,112,141,126,252,250,134,99,114,205,79,86,86,113,100,119,90,8,134,90,38,71,10,87,216,62,3,110,124,246,38,110,115,112,139,169,90,8,124,101,241,20,87,136,66,17,19,102,8,4,35,67,176,102,58,102,107,169,37,4,204,96,48,126,126,190,90,101,76,174,136,104,87,160,133,169,104,246,129,110,35,104,79,203,2,255,107,100,133,189,0,129,130,210,49,250,92,174,3,34,109,130,73,35,7,77,112,41,96,87,37,131,90,87,129,84,90,169,164,14,41,112,110,134,58,100,68,65,73,126,83,133,50,85,139,196,250,250,111,142,161,195,253,49,85,182,169,67,66,61,159,93,112,111,9,85,241,80,75,104,112,52,5,252,19,216,1,80,172,109,78,70,77,8,143,79,24,101,107,109,32,0,161,148,129,90,68,188,95,197,111,134,39,96,125,100,15,16,80,90,112,193,80,90,18,90,110,110,191,86,107,39,39,60,136,145,64,90,112,158,195,161,72,121,3,67,169,103,114,139,208,179,46,15,107,132,74,164,236,49,85,85,85,112,82,171,51,8,15,15,129,84,94,112,0,104,14,122,60,104,72,207,115,121,85,187,201,104,81,107,63,111,25,0,0,189,0,33,39,26,38,63,5,180,80,133,60,36,243,4,241,111,131,183,179,0,134,106,132,43,81,133,78,129,129,187,187,109,173,191,85,49,169,189,89,0,127,190,88,203,144,49,3,62,133,240,240,133,4,17,173,235,107,85,129,139,125,85,165,141,141,126,106,32,133,32,10,89,84,39,98,49,0,37,59,169,90,32,104,136,56,32,250,144,88,35,183,0,107,31,169,120,96,171,136,120,90,190,90,250,15,131,195,114,17,128,143,134,18,115,199,83,167,134,161,196,169,46,169,3,220,180,72,89,136,178,169,178,87,103,241,9,102,140,107,0,85,24,4,5,72,24,189,4,134,136,124,97,125,229,238,229,91,134,203,80,199,199,39,92,100,145,112,0,176,96,41,87,69,178,134,87,99,121,80,60,39,80,60,142,118,4,104,104,21,93,111,16,30,104,32,36,90,108,86,43,134,206,134,58,14,134,134,90,250,90,8,133,12,0,135,142,112,193,31,5,219,126,8,130,75,176,57,10,237,102,151,14,34,59,139,115,97,94,131,104,233,20,63,76,90,108,133,133,8,235,104,191,0,206,152,96,32,4,11,111,85,0,20,0,90,104,38,153,12,132,130,54,203,134,80,96,17,136,107,106,23,174,0,0,140,59,0,159,149,219,41,130,159,37,169,140,37,248,96,104,111,36,177,3,7,129,104,21,140,46,30,7,169,171,171,153,131,89,59,14,108,61,112,176,38,33,40,76,16,172,33,238,238,85,112,183,109,100,72,64,133,139,250,129,133,103,149,0,64,39,90,39,90,15,67,159,73,94,94,115,112,10,10,90,111,72,100,126,11,41,184,0,0,114,57,101,56,123,15,112,17,207,137,56,8,74,38,25,219,107,134,252,123,153,171,163,169,129,39,184,104,67,90,87,80,16,26,129,253,28,10,88,0,249,131,139,131,149,129,58,115,83,20,88,91,17,10,189,163,5,76,126,26,54,224,32,171,14,135,59,41,59,14,163,249,17,133,0,41,76,39,7,90,20,36,162,178,85,87,163,129,57,0,117,39,131,235,104,0,99,85,85,102,87,59,0,0,90,6,115,129,129,131,105,15,100,107,141,242,191,196,196,199,39,49,126,183,141,203,66,171,102,133,142,85,32,80,85,65,111,163,77,72,0,236,129,201,246,101,4,126,135,105,62,131,183,4,131,80,141,4,183,163,37,63,105,131,105,100,107,196,39,32,163,141,129,85,63,65,111,101,135,4,133,138,135,51,141,133,34,129,102,58,149,63,33,122,5,96,133,134,107,100,41,134,124,3,92,52,216,129,180,133,136,14,92,121,33,100,133,97,41,85,104,169,141,85,100,85,100,100,68,90,3,3,133,119,129,83,98,66,119,30,61,95,134,104,12,98,106,32,250,75,129,98,106,77,86,99,102,50,37,106,91,141,89,211,36,110,133,93,84,91,34,35,87,22,142,95,104,104,52,38,115,37,115,95,131,136,132,101,144,144,126,102,90,131,67,176,63,101,169,210,235,135,93,39,139,5,124,75,104,4,128,246,Line truncated
};
}
}
@@ -0,0 +1,69 @@
using System;
namespace TinyPinyin.Data
{
public static class PinyinData
{
/// <summary>
/// 汉字 Unicode 编码最小值
/// </summary>
public static char MIN_VALUE = (char)19968;
/// <summary>
/// 汉字 Unicode 编码最小值
/// </summary>
public static char MAX_VALUE = (char)40869;
/// <summary>
/// 特殊的汉字“〇”,编码为 12295
/// </summary>
public static String PINYIN_12295 = "LING";
public static char CHAR_12295 = (char)12295;
public static int PINYIN_CODE_1_OFFSET = 7000;
public static int PINYIN_CODE_2_OFFSET = 7000 * 2;
public static int[] BIT_MASKS = new int[] { 1, 2, 4, 8, 16, 32, 64, 128 };
public static short PADDING_MASK = 256;
//CHECKSTYLE:OFF
public static string[] PINYIN_TABLE = new string[]{"", "A", "AI", "AN", "ANG", "AO", "BA", "BAI",
"BAN", "BANG", "BAO", "BEI", "BEN", "BENG", "BI", "BIAN", "BIAO", "BIE", "BIN", "BING",
"BO", "BU", "CA", "CAI", "CAN", "CANG", "CAO", "CE", "CEN", "CENG", "CHA", "CHAI",
"CHAN", "CHANG", "CHAO", "CHE", "CHEN", "CHENG", "CHI", "CHONG", "CHOU", "CHU", "CHUAI",
"CHUAN", "CHUANG", "CHUI", "CHUN", "CHUO", "CI", "CONG", "COU", "CU", "CUAN", "CUI",
"CUN", "CUO", "DA", "DAI", "DAN", "DANG", "DAO", "DE", "DENG", "DI", "DIA", "DIAN",
"DIAO", "DIE", "DING", "DIU", "DONG", "DOU", "DU", "DUAN", "DUI", "DUN", "DUO", "E",
"EI", "EN", "ER", "E^", "FA", "FAN", "FANG", "FEI", "FEN", "FENG", "FO", "FOU", "FU",
"GA", "GAI", "GAN", "GANG", "GAO", "GE", "GEI", "GEN", "GENG", "GONG", "GOU", "GU",
"GUA", "GUAI", "GUAN", "GUANG", "GUI", "GUN", "GUO", "HA", "HAI", "HAN", "HANG", "HAO",
"HE", "HEI", "HEN", "HENG", "HONG", "HOU", "HU", "HUA", "HUAI", "HUAN", "HUANG", "HUI",
"HUN", "HUO", "JI", "JIA", "JIAN", "JIANG", "JIAO", "JIE", "JIN", "JING", "JIONG",
"JIU", "JU", "JUAN", "JUE", "JUN", "KA", "KAI", "KAN", "KANG", "KAO", "KE", "KEN",
"KENG", "KONG", "KOU", "KU", "KUA", "KUAI", "KUAN", "KUANG", "KUI", "KUN", "KUO", "LA",
"LAI", "LAN", "LANG", "LAO", "LE", "LEI", "LENG", "LI", "LIA", "LIAN", "LIANG", "LIAO",
"LIE", "LIN", "LING", "LIU", "LONG", "LOU", "LU", "LUAN", "LUN", "LUO", "LV", "LVE",
"M", "MA", "MAI", "MAN", "MANG", "MAO", "ME", "MEI", "MEN", "MENG", "MI", "MIAN",
"MIAO", "MIE", "MIN", "MING", "MIU", "MO", "MOU", "MU", "NA", "NAI", "NAN", "NANG",
"NAO", "NE", "NEI", "NEN", "NENG", "NG", "NI", "NIAN", "NIANG", "NIAO", "NIE", "NIN",
"NING", "NIU", "NONG", "NOU", "NU", "NUAN", "NUO", "NV", "NVE", "O", "OU", "PA", "PAI",
"PAN", "PANG", "PAO", "PEI", "PEN", "PENG", "PI", "PIAN", "PIAO", "PIE", "PIN", "PING",
"PO", "POU", "PU", "QI", "QIA", "QIAN", "QIANG", "QIAO", "QIE", "QIN", "QING", "QIONG",
"QIU", "QU", "QUAN", "QUE", "QUN", "RAN", "RANG", "RAO", "RE", "REN", "RENG", "RI",
"RONG", "ROU", "RU", "RUAN", "RUI", "RUN", "RUO", "SA", "SAI", "SAN", "SANG", "SAO",
"SE", "SEN", "SENG", "SHA", "SHAI", "SHAN", "SHANG", "SHAO", "SHE", "SHEI", "SHEN",
"SHENG", "SHI", "SHOU", "SHU", "SHUA", "SHUAI", "SHUAN", "SHUANG", "SHUI", "SHUN",
"SHUO", "SI", "SONG", "SOU", "SU", "SUAN", "SUI", "SUN", "SUO", "TA", "TAI", "TAN",
"TANG", "TAO", "TE", "TENG", "TI", "TIAN", "TIAO", "TIE", "TING", "TONG", "TOU", "TU",
"TUAN", "TUI", "TUN", "TUO", "WA", "WAI", "WAN", "WANG", "WEI", "WEN", "WENG", "WO",
"WU", "XI", "XIA", "XIAN", "XIANG", "XIAO", "XIE", "XIN", "XING", "XIONG", "XIU", "XU",
"XUAN", "XUE", "XUN", "YA", "YAN", "YANG", "YAO", "YE", "YI", "YIAO", "YIN", "YING",
"YO", "YONG", "YOU", "YU", "YUAN", "YUE", "YUN", "ZA", "ZAI", "ZAN", "ZANG", "ZAO",
"ZE", "ZEI", "ZEN", "ZENG", "ZHA", "ZHAI", "ZHAN", "ZHANG", "ZHAO", "ZHE", "ZHEI",
"ZHEN", "ZHENG", "ZHI", "ZHONG", "ZHOU", "ZHU", "ZHUA", "ZHUAI", "ZHUAN", "ZHUANG",
"ZHUI", "ZHUN", "ZHUO", "ZI", "ZONG", "ZOU", "ZU", "ZUAN", "ZUI", "ZUN", "ZUO"};
//CHECKSTYLE:ON
}
}
+169
View File
@@ -0,0 +1,169 @@
using System;
using System.Collections.Generic;
using System.Text;
using TinyPinyin.Data;
namespace TinyPinyin
{
public static class Engine
{
public static string[] PinyinFromDict(string wordInDict, List<IPinyinDict> pinyinDictSet)
{
if (pinyinDictSet != null)
{
foreach (IPinyinDict dict in pinyinDictSet)
{
if (dict != null && dict.Words() != null && dict.Words().Contains(wordInDict))
{
return dict.ToPinyin(wordInDict);
}
}
}
throw new ArgumentException("No pinyin dict contains word: " + wordInDict);
}
/// <summary>
/// 获取给定的字符串的拼音
/// </summary>
/// <param name="inputStr"></param>
/// <param name="trie"></param>
/// <param name="pinyinDictList"></param>
/// <param name="separator"></param>
/// <returns></returns>
public static string ToPinyin(string inputStr, string trie, List<IPinyinDict> pinyinDictList, string separator)
{
if (inputStr == null || inputStr.Length == 0)
{
return inputStr;
}
if (trie == null)
{
// 没有提供字典或选择器,按单字符转换输出
var builder1 = new StringBuilder();
for (int i = 0; i < inputStr.Length; i++)
{
builder1.Append(GetPinyinByChar(inputStr[i]));
if (i != inputStr.Length - 1)
{
builder1.Append(separator);
}
}
return builder1.ToString();
}
return null;
//List<Emit> selectedEmits = selector.select(trie.parseText(inputStr));
//Collections.sort(selectedEmits, EMIT_COMPARATOR);
//StringBuffer resultPinyinStrBuf = new StringBuffer();
//int nextHitIndex = 0;
//for (int i = 0; i < inputStr.length();)
//{
// // 首先确认是否有以第i个字符作为begin的hit
// if (nextHitIndex < selectedEmits.size() && i == selectedEmits.get(nextHitIndex).getStart())
// {
// // 有以第i个字符作为begin的hit
// String[] fromDicts = pinyinFromDict(selectedEmits.get(nextHitIndex).getKeyword(), pinyinDictList);
// for (int j = 0; j < fromDicts.length; j++)
// {
// resultPinyinStrBuf.append(fromDicts[j].toUpperCase());
// if (j != fromDicts.length - 1)
// {
// resultPinyinStrBuf.append(separator);
// }
// }
// i = i + selectedEmits.get(nextHitIndex).size();
// nextHitIndex++;
// }
// else
// {
// // 将第i个字符转为拼音
// resultPinyinStrBuf.append(Pinyin.toPinyin(inputStr.charAt(i)));
// i++;
// }
// if (i != inputStr.length())
// {
// resultPinyinStrBuf.append(separator);
// }
//}
//return resultPinyinStrBuf.toString();
}
private static int GetPinyinCode(char c)
{
int offset = c - PinyinData.MIN_VALUE;
if (0 <= offset && offset < PinyinData.PINYIN_CODE_1_OFFSET)
{
// 前 7000 个汉字
return decodeIndex(PinyinCode1.PINYIN_CODE_PADDING, PinyinCode1.PINYIN_CODE, offset);
}
else if (PinyinData.PINYIN_CODE_1_OFFSET <= offset && offset < PinyinData.PINYIN_CODE_2_OFFSET)
{
// 7000 ~ 14000 个汉字
return decodeIndex(PinyinCode2.PINYIN_CODE_PADDING, PinyinCode2.PINYIN_CODE, offset - PinyinData.PINYIN_CODE_1_OFFSET);
}
else
{
// 14000 之后的汉字
return decodeIndex(PinyinCode3.PINYIN_CODE_PADDING, PinyinCode3.PINYIN_CODE, offset - PinyinData.PINYIN_CODE_2_OFFSET);
}
}
private static short decodeIndex(byte[] paddings, byte[] indexes, int offset)
{
//CHECKSTYLE:OFF
int index1 = offset / 8;
int index2 = offset % 8;
short realIndex;
realIndex = (short)(indexes[offset] & 0xff);
//CHECKSTYLE:ON
if ((paddings[index1] & PinyinData.BIT_MASKS[index2]) != 0)
{
realIndex = (short)(realIndex | PinyinData.PADDING_MASK);
}
return realIndex;
}
/// <summary>
/// 判断是否是汉字
/// </summary>
/// <param name="c"></param>
/// <returns></returns>
public static bool IsChinese(char c)
{
return (PinyinData.MIN_VALUE <= c && c <= PinyinData.MAX_VALUE && GetPinyinCode(c) > 0) || PinyinData.CHAR_12295 == c;
}
/// <summary>
/// 获取单个汉字的完整拼音
/// </summary>
/// <param name="c"></param>
/// <returns></returns>
public static string GetPinyinByChar(char c)
{
if (IsChinese(c))
{
if (c == PinyinData.CHAR_12295)
{
return PinyinData.PINYIN_12295;
}
else
{
return PinyinData.PINYIN_TABLE[GetPinyinCode(c)];
}
}
else
{
return c.ToString();
}
}
}
}
+16
View File
@@ -0,0 +1,16 @@
using System.Collections.Generic;
namespace TinyPinyin
{
public interface IPinyinDict
{
/// <summary>
/// 转换文本为到拼音
/// </summary>
/// <param name="word"></param>
/// <returns></returns>
string[] ToPinyin(string word);
List<string> Words();
}
}
+34
View File
@@ -0,0 +1,34 @@
using System.Collections.Generic;
using System.Linq;
namespace TinyPinyin
{
public abstract class PinyinDict : IPinyinDict
{
public abstract Dictionary<string, string[]> Mapping();
public string[] ToPinyin(string word)
{
var mappingResult = Mapping();
if (mappingResult == null)
{
return null;
}
if (mappingResult.TryGetValue(word, out var result))
{
return result;
}
return null;
}
public List<string> Words()
{
var mappingResult = Mapping();
if (mappingResult == null)
{
return null;
}
return mappingResult.Keys.Select(key => key).ToList();
}
}
}
@@ -0,0 +1,51 @@
using System.Linq;
namespace TinyPinyin
{
public static class PinyinHelper
{
/// <summary>
/// 判断给定字符是否是中文
/// </summary>
/// <param name="c"></param>
/// <returns></returns>
public static bool IsChinese(char c)
{
return Engine.IsChinese(c);
}
/// <summary>
/// 获取单个字符的拼音
/// </summary>
/// <param name="c"></param>
/// <returns></returns>
public static string GetPinyin(char c)
{
return Engine.GetPinyinByChar(c);
}
/// <summary>
/// 获取文本的拼音
/// </summary>
/// <param name="str">要获取拼音的文本</param>
/// <param name="separator">单个拼音分隔符</param>
/// <returns></returns>
public static string GetPinyin(string str, string separator = " ")
{
return Engine.ToPinyin(str, null, null, separator);
}
/// <summary>
/// 获取拼音首字母
/// </summary>
/// <param name="str"></param>
/// <param name="separator">拼音分割符,默认空字符串(不分割)</param>
/// <returns></returns>
public static string GetPinyinInitials(string str, string separator = "")
{
var result = GetPinyin(str, "|");
// 修复:获取首字母时字符串中含有|字符会报超出索引范围,https://github.com/hstarorg/TinyPinyin.Net/issues/5
return string.Join(separator, result.Split('|').Select(x => !string.IsNullOrWhiteSpace(x) && x.Length > 0 ? x.Substring(0, 1) : x).ToArray());
}
}
}
@@ -0,0 +1,315 @@
<UserControl x:Class="_29WxOrderWpf.Ctrls.Commons.ShopFilterComboBox"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
x:Name="Root"
MinHeight="30"
KeyDown="Root_KeyDown"
d:DesignWidth="220"
d:DesignHeight="32">
<UserControl.Resources>
<!-- 下拉按钮外观:与 BaseComboBox 类似的白底圆角 + 右侧箭头 -->
<ControlTemplate x:Key="DropDownToggleTemplate" TargetType="ToggleButton">
<Border x:Name="bd"
Background="White"
BorderBrush="#FFACACAC"
BorderThickness="1"
CornerRadius="4"
SnapsToDevicePixels="True">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0"
Text="{Binding HeaderText}"
Margin="10,0,6,0"
VerticalAlignment="Center"
FontSize="14"
Foreground="#FF333333"
TextTrimming="CharacterEllipsis"
ToolTip="{Binding HeaderText}" />
<Path x:Name="ArrowPath"
Grid.Column="1"
Margin="0,0,10,0"
VerticalAlignment="Center"
Data="M 0 0 L 4 4 L 8 0"
Stroke="#FF606060"
StrokeThickness="1.5"
Fill="White">
<Path.RenderTransform>
<RotateTransform Angle="0" CenterX="4" CenterY="2" />
</Path.RenderTransform>
</Path>
</Grid>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="bd" Property="BorderBrush" Value="#FF7EB4EA" />
</Trigger>
<Trigger Property="IsChecked" Value="True">
<Setter TargetName="bd" Property="BorderBrush" Value="#FF569DE5" />
<Setter TargetName="bd" Property="Background" Value="#FFF5F9FF" />
<Setter TargetName="ArrowPath" Property="RenderTransform">
<Setter.Value>
<RotateTransform Angle="180" CenterX="4" CenterY="2" />
</Setter.Value>
</Setter>
</Trigger>
<Trigger Property="IsEnabled" Value="False">
<Setter TargetName="bd" Property="Background" Value="#FFF0F0F0" />
<Setter TargetName="bd" Property="BorderBrush" Value="#FFD9D9D9" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</UserControl.Resources>
<Grid x:Name="RootGrid">
<ToggleButton x:Name="DropDownToggle"
Focusable="True"
Template="{StaticResource DropDownToggleTemplate}" />
<Popup x:Name="DropDownPopup"
IsOpen="{Binding IsChecked, ElementName=DropDownToggle, Mode=TwoWay}"
StaysOpen="False"
AllowsTransparency="True"
Placement="Bottom"
PlacementTarget="{Binding ElementName=RootGrid}"
VerticalOffset="4"
PopupAnimation="Fade"
Opened="DropDownPopup_Opened">
<Border x:Name="DropDownRoot"
Background="White"
BorderBrush="#FFBFBFBF"
BorderThickness="1"
CornerRadius="6"
MaxWidth="420"
Focusable="True"
PreviewKeyDown="DropDownRoot_PreviewKeyDown">
<Border.Effect>
<DropShadowEffect BlurRadius="12" ShadowDepth="3" Opacity="0.18" Color="#000000" />
</Border.Effect>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<!-- 搜索框 -->
<Border Grid.Row="0" Padding="10,10,10,6">
<Border.Style>
<Style TargetType="Border">
<Setter Property="Visibility" Value="Collapsed" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsSearchable}" Value="True">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
<Grid>
<TextBox x:Name="SearchTextBox"
Padding="8,6"
FontSize="13"
VerticalContentAlignment="Center"
BorderBrush="#FFD0D0D0"
BorderThickness="1"
TextChanged="SearchTextBox_TextChanged" />
<TextBlock x:Name="SearchWaterMark"
Text="{Binding SearchPlaceholder}"
Margin="10,0"
VerticalAlignment="Center"
FontSize="13"
Foreground="#FF999999"
IsHitTestVisible="False">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Visibility" Value="Collapsed" />
<Style.Triggers>
<DataTrigger Binding="{Binding Text, ElementName=SearchTextBox}" Value="">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</Grid>
</Border>
<!-- 全部商铺 -->
<Border Grid.Row="1" Padding="10,4" Background="#FFFAFAFA" BorderBrush="#FFEEEEEE" BorderThickness="0,1,0,1">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<CheckBox x:Name="SelectAllCheckBox"
Content="{Binding SelectAllText}"
FontSize="13"
FontWeight="Bold"
VerticalAlignment="Center"
IsThreeState="True"
Click="SelectAllCheckBox_Click" />
<TextBlock Grid.Column="1"
Text="{Binding TotalCountText}"
Margin="8,0,0,0"
VerticalAlignment="Center"
FontSize="12"
Foreground="#FF888888" />
</Grid>
</Border>
<!-- 分组列表 -->
<ScrollViewer Grid.Row="2"
MaxHeight="{Binding MaxDropDownHeight}"
VerticalScrollBarVisibility="Auto"
HorizontalScrollBarVisibility="Disabled">
<ItemsControl ItemsSource="{Binding DropDownGroups}" Margin="0,2,0,2">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<!-- 分组头 -->
<Border x:Name="GroupHeaderBorder"
Padding="10,5"
Background="#FFF7F7F7"
Cursor="Hand"
MouseLeftButtonDown="GroupHeader_MouseLeftButtonDown">
<Border.Style>
<Style TargetType="Border">
<Setter Property="Visibility" Value="Collapsed" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsHeaderVisible}" Value="True">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock VerticalAlignment="Center"
Margin="0,0,6,0"
FontSize="10"
Foreground="#FF666666">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Text" Value="&#9654;" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsExpanded}" Value="True">
<Setter Property="Text" Value="&#9660;" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
<CheckBox Grid.Column="1"
VerticalAlignment="Center"
Margin="0,0,8,0"
IsThreeState="True"
IsChecked="{Binding IsChecked}" />
<TextBlock Grid.Column="2"
Text="{Binding Name}"
VerticalAlignment="Center"
FontSize="13"
FontWeight="Bold"
Foreground="#FF333333" />
<TextBlock Grid.Column="3"
Text="{Binding CountText}"
VerticalAlignment="Center"
FontSize="12"
Foreground="#FF888888" />
</Grid>
</Border>
<!-- 分组项 -->
<ItemsControl ItemsSource="{Binding Items}">
<ItemsControl.Style>
<Style TargetType="ItemsControl">
<Setter Property="Visibility" Value="Collapsed" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsExpanded}" Value="True">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</ItemsControl.Style>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid Margin="30,1,10,1">
<Grid.Style>
<Style TargetType="Grid">
<Setter Property="Background" Value="Transparent" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsHighlighted}" Value="True">
<Setter Property="Background" Value="#FFE3F0FF" />
</DataTrigger>
</Style.Triggers>
</Style>
</Grid.Style>
<CheckBox Content="{Binding DisplayName}"
IsChecked="{Binding IsSelected}"
Padding="4,3"
FontSize="13"
VerticalContentAlignment="Center"
Foreground="#FF333333"
Click="ItemCheckBox_Click" />
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
<!-- 底部:已选择 + 清空/全选/确定 -->
<Border Grid.Row="3" Padding="10,8" Background="#FFFAFAFA" BorderBrush="#FFEEEEEE" BorderThickness="0,1,0,0">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding SelectedCountText}"
VerticalAlignment="Center"
FontSize="13"
Foreground="#FF555555" />
<StackPanel Grid.Column="1" Orientation="Horizontal">
<Button Content="清空"
Padding="12,4"
Margin="4,0,0,0"
FontSize="13"
Style="{StaticResource BasePageButtonLightStyle}"
Click="ClearButton_Click" />
<Button Content="全选"
Padding="12,4"
Margin="8,0,0,0"
FontSize="13"
Style="{StaticResource BasePageButtonLightStyle}"
Click="SelectAllButton_Click" />
<Button Content="确定"
Padding="14,4"
Margin="8,0,0,0"
FontSize="13"
Style="{StaticResource BasePageButtonStyle}"
Click="ConfirmButton_Click" />
</StackPanel>
</Grid>
</Border>
</Grid>
</Border>
</Popup>
</Grid>
</UserControl>
@@ -0,0 +1,869 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using TinyPinyin;
namespace _29WxOrderWpf.Ctrls.Commons
{
/// <summary>
/// 商铺筛选 ComboBox 控件。
/// 只需绑定:
/// ItemsSource 数据源(如 ObservableCollection&lt;ShopOption&gt;)
/// SelectedItems 选中的项集合(TwoWay,如 ObservableCollection&lt;ShopOption&gt;)
/// DisplayMemberPath 显示名称的属性路径(如 ShopName)
/// IsSearchable 是否显示搜索框(默认 true)
/// IsGrouped 是否按分组显示(默认 false)
/// GroupByPath 分组名称的属性路径(IsGrouped=true 时生效,如 Region)
/// 搜索支持中文、全拼、拼音首字母;键盘:
/// ↑/↓ 选择(移动高亮),空格 选中/取消,Enter 确定,Esc 取消(还原打开前的选择)。
/// </summary>
public partial class ShopFilterComboBox : UserControl, INotifyPropertyChanged
{
private readonly ObservableCollection<ShopFilterGroup> _groups = new ObservableCollection<ShopFilterGroup>();
private readonly List<ShopFilterGroup> _allGroups = new List<ShopFilterGroup>();
private readonly List<ShopFilterItem> _allItems = new List<ShopFilterItem>();
private readonly List<ShopFilterItem> _visibleItems = new List<ShopFilterItem>();
private ShopFilterItem _highlightedItem;
private List<object> _openedSnapshot;
private bool _syncing;
private INotifyCollectionChanged _itemsNotifier;
private INotifyCollectionChanged _selectedNotifier;
public ShopFilterComboBox()
{
InitializeComponent();
// 内部 UI 统一绑定到控件自身(外部 ItemsSource/SelectedItems 等仍绑定在控件上,不受影响)
RootGrid.DataContext = this;
DropDownRoot.DataContext = this;
SelectedItems = new ObservableCollection<object>();
}
#region 依赖属性
public static readonly DependencyProperty ItemsSourceProperty =
DependencyProperty.Register(nameof(ItemsSource), typeof(IEnumerable), typeof(ShopFilterComboBox),
new PropertyMetadata(null, OnItemsSourceChanged));
/// <summary>数据源</summary>
public IEnumerable ItemsSource
{
get { return (IEnumerable)GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}
public static readonly DependencyProperty SelectedItemsProperty =
DependencyProperty.Register(nameof(SelectedItems), typeof(IList), typeof(ShopFilterComboBox),
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnSelectedItemsChanged));
/// <summary>选中的项集合(双向绑定,元素为 ItemsSource 中的原始对象)</summary>
public IList SelectedItems
{
get { return (IList)GetValue(SelectedItemsProperty); }
set { SetValue(SelectedItemsProperty, value); }
}
public static readonly DependencyProperty DisplayMemberPathProperty =
DependencyProperty.Register(nameof(DisplayMemberPath), typeof(string), typeof(ShopFilterComboBox),
new PropertyMetadata(null, OnViewPropertyChanged));
/// <summary>显示名称的属性路径(如 ShopName)</summary>
public string DisplayMemberPath
{
get { return (string)GetValue(DisplayMemberPathProperty); }
set { SetValue(DisplayMemberPathProperty, value); }
}
public static readonly DependencyProperty IsSearchableProperty =
DependencyProperty.Register(nameof(IsSearchable), typeof(bool), typeof(ShopFilterComboBox),
new PropertyMetadata(true));
/// <summary>是否显示搜索框</summary>
public bool IsSearchable
{
get { return (bool)GetValue(IsSearchableProperty); }
set { SetValue(IsSearchableProperty, value); }
}
public static readonly DependencyProperty IsGroupedProperty =
DependencyProperty.Register(nameof(IsGrouped), typeof(bool), typeof(ShopFilterComboBox),
new PropertyMetadata(false, OnViewPropertyChanged));
/// <summary>是否按分组显示</summary>
public bool IsGrouped
{
get { return (bool)GetValue(IsGroupedProperty); }
set { SetValue(IsGroupedProperty, value); }
}
public static readonly DependencyProperty GroupByPathProperty =
DependencyProperty.Register(nameof(GroupByPath), typeof(string), typeof(ShopFilterComboBox),
new PropertyMetadata(null, OnViewPropertyChanged));
/// <summary>分组名称的属性路径(IsGrouped=true 时生效,如 Region)</summary>
public string GroupByPath
{
get { return (string)GetValue(GroupByPathProperty); }
set { SetValue(GroupByPathProperty, value); }
}
public static readonly DependencyProperty AllTextProperty =
DependencyProperty.Register(nameof(AllText), typeof(string), typeof(ShopFilterComboBox),
new PropertyMetadata("全部商铺"));
/// <summary>未选择或全部选择时头部显示的文本(默认“全部商铺”)</summary>
public string AllText
{
get { return (string)GetValue(AllTextProperty); }
set { SetValue(AllTextProperty, value); }
}
public static readonly DependencyProperty UnitTextProperty =
DependencyProperty.Register(nameof(UnitText), typeof(string), typeof(ShopFilterComboBox),
new PropertyMetadata("个商铺"));
/// <summary>头部数量单位(默认“个商铺”,显示如“8 个商铺”)</summary>
public string UnitText
{
get { return (string)GetValue(UnitTextProperty); }
set { SetValue(UnitTextProperty, value); }
}
public static readonly DependencyProperty CountSuffixProperty =
DependencyProperty.Register(nameof(CountSuffix), typeof(string), typeof(ShopFilterComboBox),
new PropertyMetadata("家", OnViewPropertyChanged));
/// <summary>数量单位后缀(默认“家”,显示如“共28家”“12家”“已选择:8 家”)</summary>
public string CountSuffix
{
get { return (string)GetValue(CountSuffixProperty); }
set { SetValue(CountSuffixProperty, value); }
}
public static readonly DependencyProperty SelectAllTextProperty =
DependencyProperty.Register(nameof(SelectAllText), typeof(string), typeof(ShopFilterComboBox),
new PropertyMetadata("全部商铺"));
/// <summary>“全部”复选框文案(默认“全部商铺”)</summary>
public string SelectAllText
{
get { return (string)GetValue(SelectAllTextProperty); }
set { SetValue(SelectAllTextProperty, value); }
}
public static readonly DependencyProperty SearchPlaceholderProperty =
DependencyProperty.Register(nameof(SearchPlaceholder), typeof(string), typeof(ShopFilterComboBox),
new PropertyMetadata("搜索商铺..."));
/// <summary>搜索框占位文案</summary>
public string SearchPlaceholder
{
get { return (string)GetValue(SearchPlaceholderProperty); }
set { SetValue(SearchPlaceholderProperty, value); }
}
public static readonly DependencyProperty MaxDropDownHeightProperty =
DependencyProperty.Register(nameof(MaxDropDownHeight), typeof(double), typeof(ShopFilterComboBox),
new PropertyMetadata(300.0));
/// <summary>下拉列表最大高度</summary>
public double MaxDropDownHeight
{
get { return (double)GetValue(MaxDropDownHeightProperty); }
set { SetValue(MaxDropDownHeightProperty, value); }
}
public static readonly DependencyProperty ExpandGroupsByDefaultProperty =
DependencyProperty.Register(nameof(ExpandGroupsByDefault), typeof(bool), typeof(ShopFilterComboBox),
new PropertyMetadata(true));
/// <summary>分组默认是否展开</summary>
public bool ExpandGroupsByDefault
{
get { return (bool)GetValue(ExpandGroupsByDefaultProperty); }
set { SetValue(ExpandGroupsByDefaultProperty, value); }
}
#endregion
#region 路由事件
public static readonly RoutedEvent SelectionChangedEvent =
EventManager.RegisterRoutedEvent("SelectionChanged", RoutingStrategy.Bubble,
typeof(RoutedEventHandler), typeof(ShopFilterComboBox));
/// <summary>选中集合内容变化时触发</summary>
public event RoutedEventHandler SelectionChanged
{
add { AddHandler(SelectionChangedEvent, value); }
remove { RemoveHandler(SelectionChangedEvent, value); }
}
public static readonly RoutedEvent ConfirmedEvent =
EventManager.RegisterRoutedEvent("Confirmed", RoutingStrategy.Bubble,
typeof(RoutedEventHandler), typeof(ShopFilterComboBox));
/// <summary>点击“确定”或按 Enter 时触发(可在此执行查询)</summary>
public event RoutedEventHandler Confirmed
{
add { AddHandler(ConfirmedEvent, value); }
remove { RemoveHandler(ConfirmedEvent, value); }
}
public static readonly RoutedEvent CancelledEvent =
EventManager.RegisterRoutedEvent("Cancelled", RoutingStrategy.Bubble,
typeof(RoutedEventHandler), typeof(ShopFilterComboBox));
/// <summary>按 Esc 取消时触发(已还原打开前的选择)</summary>
public event RoutedEventHandler Cancelled
{
add { AddHandler(CancelledEvent, value); }
remove { RemoveHandler(CancelledEvent, value); }
}
#endregion
#region 内部绑定属性
/// <summary>当前显示的分组集合</summary>
public ObservableCollection<ShopFilterGroup> DropDownGroups
{
get { return _groups; }
}
/// <summary>头部文本(全部商铺 / N 个商铺)</summary>
public string HeaderText
{
get
{
var sel = SelectedItems;
int total = _allItems.Count;
int count = sel == null ? 0 : sel.Count;
if (count == 0 || (total > 0 && count >= total)) return AllText;
return count + " " + UnitText;
}
}
/// <summary>总数文本(如“共28家”)</summary>
public string TotalCountText
{
get { return "共" + _allItems.Count + CountSuffix; }
}
/// <summary>已选择文本(如“已选择:8 家”)</summary>
public string SelectedCountText
{
get { return "已选择:" + (SelectedItems == null ? 0 : SelectedItems.Count) + " " + CountSuffix; }
}
#endregion
#region 依赖属性回调
private static void OnItemsSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var c = (ShopFilterComboBox)d;
c.UnsubscribeItems();
c.SubscribeItems();
c.Rebuild();
}
private static void OnSelectedItemsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var c = (ShopFilterComboBox)d;
c.UnsubscribeSelected();
c.SubscribeSelected();
c.OnSelectionContentChanged();
}
private static void OnViewPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((ShopFilterComboBox)d).Rebuild();
}
private void SubscribeItems()
{
_itemsNotifier = ItemsSource as INotifyCollectionChanged;
if (_itemsNotifier != null) _itemsNotifier.CollectionChanged += OnItemsSourceCollectionChanged;
}
private void UnsubscribeItems()
{
if (_itemsNotifier != null) _itemsNotifier.CollectionChanged -= OnItemsSourceCollectionChanged;
_itemsNotifier = null;
}
private void SubscribeSelected()
{
_selectedNotifier = SelectedItems as INotifyCollectionChanged;
if (_selectedNotifier != null) _selectedNotifier.CollectionChanged += OnSelectedItemsCollectionChanged;
}
private void UnsubscribeSelected()
{
if (_selectedNotifier != null) _selectedNotifier.CollectionChanged -= OnSelectedItemsCollectionChanged;
_selectedNotifier = null;
}
private void OnItemsSourceCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
Rebuild();
}
private void OnSelectedItemsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (_syncing) return;
_syncing = true;
try { SyncItemStatesFromSelection(); }
finally { _syncing = false; }
RefreshSelectionState();
RaiseSelectionChanged();
}
#endregion
#region 构建与过滤
private void Rebuild()
{
_allItems.Clear();
_allGroups.Clear();
bool grouped = IsGrouped && !string.IsNullOrEmpty(GroupByPath);
Dictionary<string, ShopFilterGroup> groupMap = null;
ShopFilterGroup flatGroup = null;
if (grouped)
{
groupMap = new Dictionary<string, ShopFilterGroup>(StringComparer.Ordinal);
}
else
{
flatGroup = new ShopFilterGroup { Name = string.Empty, CountSuffix = CountSuffix, IsExpanded = true };
_allGroups.Add(flatGroup);
}
var source = ItemsSource;
if (source != null)
{
foreach (var obj in source)
{
string name = GetDisplayText(obj, DisplayMemberPath);
string pinyinFull = string.Empty;
string initials = string.Empty;
try
{
pinyinFull = PinyinHelper.GetPinyin(name, "").ToLowerInvariant();
initials = PinyinHelper.GetPinyinInitials(name, "").ToLowerInvariant();
}
catch (Exception)
{
// 拼音转换失败时退化为仅名称匹配
}
var item = new ShopFilterItem
{
Source = obj,
DisplayName = name,
GroupName = grouped ? GetDisplayText(obj, GroupByPath) : null,
NameSearch = name.Replace(" ", "").ToLowerInvariant(),
PinyinSearch = pinyinFull,
InitialsSearch = initials
};
item.PropertyChanged += OnItemPropertyChanged;
_allItems.Add(item);
if (grouped)
{
string gname = string.IsNullOrEmpty(item.GroupName) ? "未分组" : item.GroupName;
ShopFilterGroup group;
if (!groupMap.TryGetValue(gname, out group))
{
group = new ShopFilterGroup
{
Name = gname,
CountSuffix = CountSuffix,
IsExpanded = ExpandGroupsByDefault
};
group.PropertyChanged += OnGroupPropertyChanged;
groupMap[gname] = group;
_allGroups.Add(group);
}
group.AllItems.Add(item);
}
else
{
flatGroup.AllItems.Add(item);
}
}
}
_syncing = true;
try { SyncItemStatesFromSelection(); }
finally { _syncing = false; }
ApplyFilter();
RefreshSelectionState();
}
private void ApplyFilter()
{
string keyword = SearchTextBox == null ? string.Empty : (SearchTextBox.Text ?? string.Empty).Trim();
bool searching = keyword.Length > 0;
// 搜索关键词去空格并小写,可同时匹配 中文/全拼/首字母(如 “sh”“shang hai”“上海”)
string kw = keyword.Replace(" ", "").ToLowerInvariant();
_groups.Clear();
_visibleItems.Clear();
foreach (var group in _allGroups)
{
group.Items.Clear();
foreach (var item in group.AllItems)
{
bool match = !searching
|| item.NameSearch.Contains(kw)
|| item.PinyinSearch.Contains(kw)
|| item.InitialsSearch.Contains(kw);
if (match)
{
group.Items.Add(item);
_visibleItems.Add(item);
}
}
if (searching && group.Items.Count > 0)
group.IsExpanded = true;
group.RefreshCountText();
if (!searching || group.Items.Count > 0)
_groups.Add(group);
}
// 高亮项不在可见列表时,重置到第一项
if (_visibleItems.Count == 0)
SetHighlight(null);
else if (!_visibleItems.Contains(_highlightedItem))
SetHighlight(_visibleItems[0]);
}
private void SyncItemStatesFromSelection()
{
var sel = SelectedItems;
foreach (var item in _allItems)
{
bool selected = sel != null && sel.Contains(item.Source);
item.IsSelected = selected;
}
}
#endregion
#region 选择逻辑
private void OnItemPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName != nameof(ShopFilterItem.IsSelected)) return;
if (_syncing) return;
var item = (ShopFilterItem)sender;
if (item.IsSelected) AddToSelection(item.Source);
else RemoveFromSelection(item.Source);
RefreshSelectionState();
RaiseSelectionChanged();
}
private void OnGroupPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName != nameof(ShopFilterGroup.IsChecked)) return;
if (_syncing) return;
var group = (ShopFilterGroup)sender;
bool select = group.IsChecked == true;
_syncing = true;
try
{
foreach (var item in group.AllItems)
item.IsSelected = select;
foreach (var item in group.AllItems)
{
if (select) AddToSelection(item.Source);
else RemoveFromSelection(item.Source);
}
}
finally { _syncing = false; }
RefreshSelectionState();
RaiseSelectionChanged();
}
private void AddToSelection(object source)
{
var sel = SelectedItems;
if (sel == null || source == null) return;
if (!sel.Contains(source)) sel.Add(source);
}
private void RemoveFromSelection(object source)
{
var sel = SelectedItems;
if (sel == null || source == null) return;
if (sel.Contains(source)) sel.Remove(source);
}
/// <summary>全选</summary>
public void SelectAll()
{
_syncing = true;
try
{
foreach (var item in _allItems) item.IsSelected = true;
foreach (var item in _allItems) AddToSelection(item.Source);
}
finally { _syncing = false; }
RefreshSelectionState();
RaiseSelectionChanged();
}
/// <summary>清空选择</summary>
public void ClearSelection()
{
_syncing = true;
try
{
foreach (var item in _allItems) item.IsSelected = false;
if (SelectedItems != null)
{
try { SelectedItems.Clear(); }
catch (NotSupportedException) { }
}
}
finally { _syncing = false; }
RefreshSelectionState();
RaiseSelectionChanged();
}
private void RefreshSelectionState()
{
var sel = SelectedItems;
int total = _allItems.Count;
int count = sel == null ? 0 : sel.Count;
_syncing = true;
try
{
foreach (var group in _allGroups)
{
int selected = group.AllItems.Count(i => i.IsSelected);
group.IsChecked = selected == 0 ? false
: (selected >= group.AllItems.Count ? (bool?)true : null);
}
if (SelectAllCheckBox != null)
{
SelectAllCheckBox.IsChecked = count == 0 ? false
: (total > 0 && count >= total ? true : (bool?)null);
}
}
finally { _syncing = false; }
OnPropertyChanged(nameof(HeaderText));
OnPropertyChanged(nameof(TotalCountText));
OnPropertyChanged(nameof(SelectedCountText));
}
private void RaiseSelectionChanged()
{
RaiseEvent(new RoutedEventArgs(SelectionChangedEvent));
}
/// <summary>确定:关闭下拉并触发 Confirmed</summary>
private void Confirm()
{
DropDownToggle.IsChecked = false;
RaiseEvent(new RoutedEventArgs(ConfirmedEvent));
}
/// <summary>取消:还原打开前的选择,关闭下拉并触发 Cancelled</summary>
private void Cancel()
{
_syncing = true;
try
{
var sel = SelectedItems;
if (sel != null)
{
try { sel.Clear(); }
catch (NotSupportedException) { }
if (_openedSnapshot != null)
{
foreach (var obj in _openedSnapshot)
sel.Add(obj);
}
}
}
finally { _syncing = false; }
RefreshSelectionState();
RaiseSelectionChanged();
DropDownToggle.IsChecked = false;
RaiseEvent(new RoutedEventArgs(CancelledEvent));
}
#endregion
#region 键盘
/// <summary>下拉打开时:↑/↓ 选择,空格 选中/取消,Enter 确定,Esc 取消</summary>
private void DropDownRoot_PreviewKeyDown(object sender, KeyEventArgs e)
{
// 输入法合成中(如拼音输入法)时,Enter/Esc 交给输入法处理,避免误触发确定/取消
if ((e.Key == Key.Enter || e.Key == Key.Escape) && e.ImeProcessedKey != Key.None)
return;
switch (e.Key)
{
case Key.Escape:
e.Handled = true;
Cancel();
break;
case Key.Enter:
e.Handled = true;
Confirm();
break;
case Key.Down:
e.Handled = true;
MoveHighlight(1);
break;
case Key.Up:
e.Handled = true;
MoveHighlight(-1);
break;
case Key.Space:
if (_highlightedItem != null)
{
e.Handled = true;
_highlightedItem.IsSelected = !_highlightedItem.IsSelected;
}
break;
}
}
/// <summary>下拉关闭时按 ↑/↓/Enter/空格 打开下拉</summary>
private void Root_KeyDown(object sender, KeyEventArgs e)
{
if (DropDownToggle.IsChecked == true) return;
if (e.Key == Key.Down || e.Key == Key.Up || e.Key == Key.Enter || e.Key == Key.Space)
{
e.Handled = true;
DropDownToggle.IsChecked = true;
}
}
private void SetHighlight(ShopFilterItem item)
{
if (ReferenceEquals(_highlightedItem, item)) return;
if (_highlightedItem != null) _highlightedItem.IsHighlighted = false;
_highlightedItem = item;
if (_highlightedItem != null)
{
_highlightedItem.IsHighlighted = true;
// 高亮项所在分组若被折叠则展开
foreach (var group in _allGroups)
{
if (group.AllItems.Contains(_highlightedItem) && !group.IsExpanded)
{
group.IsExpanded = true;
break;
}
}
BringHighlightedIntoView();
}
}
private void MoveHighlight(int delta)
{
if (_visibleItems.Count == 0) return;
int idx;
if (_highlightedItem == null)
{
idx = delta >= 0 ? 0 : _visibleItems.Count - 1;
}
else
{
idx = _visibleItems.IndexOf(_highlightedItem);
if (idx < 0) idx = delta >= 0 ? 0 : _visibleItems.Count - 1;
else idx = Math.Max(0, Math.Min(_visibleItems.Count - 1, idx + delta));
}
SetHighlight(_visibleItems[idx]);
}
private void BringHighlightedIntoView()
{
if (_highlightedItem == null) return;
var check = FindVisualChild<CheckBox>(DropDownRoot, cb => cb.DataContext == _highlightedItem);
if (check != null) check.BringIntoView();
}
private static T FindVisualChild<T>(DependencyObject parent, Func<T, bool> predicate) where T : DependencyObject
{
int count = VisualTreeHelper.GetChildrenCount(parent);
for (int i = 0; i < count; i++)
{
var child = VisualTreeHelper.GetChild(parent, i);
if (child is T t && predicate(t)) return t;
var result = FindVisualChild(child, predicate);
if (result != null) return result;
}
return null;
}
#endregion
#region 事件处理
private void SearchTextBox_TextChanged(object sender, TextChangedEventArgs e)
{
ApplyFilter();
}
private void SelectAllCheckBox_Click(object sender, RoutedEventArgs e)
{
var cb = (CheckBox)sender;
if (cb.IsChecked == true) SelectAll();
else ClearSelection();
}
/// <summary>鼠标点击某项时,把键盘高亮同步到该项</summary>
private void ItemCheckBox_Click(object sender, RoutedEventArgs e)
{
var item = (sender as FrameworkElement)?.DataContext as ShopFilterItem;
if (item != null) SetHighlight(item);
}
private void ClearButton_Click(object sender, RoutedEventArgs e)
{
ClearSelection();
}
private void SelectAllButton_Click(object sender, RoutedEventArgs e)
{
SelectAll();
}
private void ConfirmButton_Click(object sender, RoutedEventArgs e)
{
Confirm();
}
private void GroupHeader_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
var group = (sender as FrameworkElement)?.DataContext as ShopFilterGroup;
if (group != null) group.IsExpanded = !group.IsExpanded;
}
private void DropDownPopup_Opened(object sender, EventArgs e)
{
// 下拉宽度不小于按钮宽度
if (DropDownRoot != null)
DropDownRoot.MinWidth = Math.Max(300, RootGrid.ActualWidth);
// 记录打开时的选中快照,Esc 取消时还原
_openedSnapshot = SelectedItems == null ? new List<object>() : SelectedItems.Cast<object>().ToList();
// 每次打开清空搜索,显示完整列表
if (SearchTextBox != null)
SearchTextBox.Text = string.Empty;
// 重新同步外部对 SelectedItems 的修改
OnSelectionContentChanged();
// 高亮第一项
if (_visibleItems.Count > 0)
SetHighlight(_visibleItems[0]);
if (SearchTextBox != null && IsSearchable)
{
SearchTextBox.Focus();
SearchTextBox.SelectAll();
}
else
{
DropDownRoot.Focus();
}
}
private void OnSelectionContentChanged()
{
if (_syncing) return;
_syncing = true;
try { SyncItemStatesFromSelection(); }
finally { _syncing = false; }
RefreshSelectionState();
}
#endregion
#region 反射取值
private static readonly Dictionary<Type, Dictionary<string, PropertyInfo>> _propertyCache =
new Dictionary<Type, Dictionary<string, PropertyInfo>>();
private static readonly object _cacheLock = new object();
private static PropertyInfo FindProperty(Type type, string path)
{
lock (_cacheLock)
{
Dictionary<string, PropertyInfo> map;
if (!_propertyCache.TryGetValue(type, out map))
{
map = new Dictionary<string, PropertyInfo>(StringComparer.Ordinal);
_propertyCache[type] = map;
}
PropertyInfo cached;
if (map.TryGetValue(path, out cached)) return cached;
PropertyInfo pi = null;
Type current = type;
foreach (var part in path.Split('.'))
{
pi = current.GetProperty(part, BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
if (pi == null) break;
current = pi.PropertyType;
}
map[path] = pi;
return pi;
}
}
private static string GetDisplayText(object item, string path)
{
if (item == null) return string.Empty;
if (string.IsNullOrEmpty(path))
return Convert.ToString(item, CultureInfo.CurrentCulture);
var pi = FindProperty(item.GetType(), path);
if (pi == null) return string.Empty;
var value = pi.GetValue(item);
return value == null ? string.Empty : Convert.ToString(value, CultureInfo.CurrentCulture);
}
#endregion
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
}
@@ -0,0 +1,146 @@
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
namespace _29WxOrderWpf.Ctrls.Commons
{
/// <summary>
/// 商铺筛选下拉 - 单项视图模型(包装原始数据项,附加显示名/分组名/选中状态/搜索键/高亮)。
/// 由 <see cref="ShopFilterComboBox"/> 内部维护,不直接使用。
/// </summary>
public class ShopFilterItem : INotifyPropertyChanged
{
private bool _isSelected;
private bool _isHighlighted;
/// <summary>原始数据项(ItemsSource 中的元素)</summary>
public object Source { get; set; }
/// <summary>显示名称(由 DisplayMemberPath 解析)</summary>
public string DisplayName { get; set; }
/// <summary>所属分组名称(由 GroupByPath 解析,未分组模式下为 null)</summary>
public string GroupName { get; set; }
/// <summary>搜索键:名称(去空格小写,支持中文片段匹配)</summary>
public string NameSearch { get; set; }
/// <summary>搜索键:完整拼音(无空格小写,如“上海旗舰店”=“shanghaiqijiandian”)</summary>
public string PinyinSearch { get; set; }
/// <summary>搜索键:拼音首字母(小写,如“上海旗舰店”=“shqjd”)</summary>
public string InitialsSearch { get; set; }
/// <summary>是否选中</summary>
public bool IsSelected
{
get { return _isSelected; }
set
{
if (_isSelected == value) return;
_isSelected = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsSelected)));
}
}
/// <summary>是否高亮(键盘上下选择时)</summary>
public bool IsHighlighted
{
get { return _isHighlighted; }
set
{
if (_isHighlighted == value) return;
_isHighlighted = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsHighlighted)));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
/// <summary>
/// 商铺筛选下拉 - 分组视图模型(分组头:展开/收起、全选复选框、数量)。
/// 由 <see cref="ShopFilterComboBox"/> 内部维护,不直接使用。
/// </summary>
public class ShopFilterGroup : INotifyPropertyChanged
{
private bool _isExpanded;
private bool? _isChecked;
private string _countSuffix = "家";
public ShopFilterGroup()
{
_isExpanded = true;
}
/// <summary>分组名称;空字符串表示未分组(隐藏分组头)</summary>
public string Name { get; set; }
/// <summary>分组内全部原始项(未过滤,用于计数与全选/清空)</summary>
public List<ShopFilterItem> AllItems { get; } = new List<ShopFilterItem>();
/// <summary>分组内当前显示的项(受搜索过滤影响)</summary>
public ObservableCollection<ShopFilterItem> Items { get; } = new ObservableCollection<ShopFilterItem>();
/// <summary>是否展开</summary>
public bool IsExpanded
{
get { return _isExpanded; }
set
{
if (_isExpanded == value) return;
_isExpanded = value;
Raise(nameof(IsExpanded));
}
}
/// <summary>分组头复选框状态:true=全选,false=全不选,null=部分选中</summary>
public bool? IsChecked
{
get { return _isChecked; }
set
{
if (_isChecked == value) return;
_isChecked = value;
Raise(nameof(IsChecked));
}
}
/// <summary>数量单位(如“家”)</summary>
public string CountSuffix
{
get { return _countSuffix; }
set
{
if (_countSuffix == value) return;
_countSuffix = value;
Raise(nameof(CountText));
}
}
/// <summary>是否显示分组头(未分组时隐藏)</summary>
public bool IsHeaderVisible
{
get { return !string.IsNullOrEmpty(Name); }
}
/// <summary>分组数量文本(如“12家”)</summary>
public string CountText
{
get { return Items.Count + CountSuffix; }
}
/// <summary>过滤后刷新数量文本</summary>
public void RefreshCountText()
{
Raise(nameof(CountText));
}
public event PropertyChangedEventHandler PropertyChanged;
private void Raise(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
@@ -15,8 +15,8 @@ namespace _29WxOrderWpf.Ctrls.Statistics.Models
/// <summary>结束时间</summary>
public DateTime EndDate { get; set; }
/// <summary>店铺 shop_id(B.shop_id),空/Null 表示全部店铺</summary>
public string ShopKey { get; set; }
/// <summary>选中的店铺 app_key 列表;空/Null 表示全部店铺</summary>
public List<string> ShopKeys { get; set; }
}
/// <summary>
@@ -2,10 +2,12 @@
x:Class="_29WxOrderWpf.Ctrls.Statistics.RevenueStatisticsSearchBar"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:c="clr-namespace:_29WxOrderWpf.Ctrls.Commons"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="clr-namespace:_29WxOrderWpf.Ctrls.Statistics.ViewModels"
d:DataContext="{d:DesignInstance Type=vm:ShopRevenueStatisticsViewModel,IsDesignTimeCreatable=True}"
d:DataContext="{d:DesignInstance Type=vm:ShopRevenueStatisticsViewModel,
IsDesignTimeCreatable=True}"
mc:Ignorable="d">
<UserControl.Resources>
<!-- 日期只允许从日历选择,禁止手工输入 -->
@@ -42,21 +44,23 @@
FontSize="14"
Foreground="#666666"
Text="时间" />
<ComboBox IsEnabled="False"
<ComboBox
Width="110"
Height="32"
VerticalContentAlignment="Center"
DisplayMemberPath="Text"
IsReadOnly="True"
BorderThickness="1"
ItemsSource="{Binding TimeRangeOptions}"
SelectedItem="{Binding SelectedTimeRange, Mode=TwoWay}"
Style="{StaticResource BaseComboBoxStyle}" />
Style="{StaticResource RoundedComboBox}" />
<TextBlock
Margin="14,0,6,0"
VerticalAlignment="Center"
FontSize="14"
Foreground="#666666"
Text="日期" />
<DatePicker
<DatePicker BorderBrush="{StaticResource pageColorBrush}"
Height="32"
VerticalContentAlignment="Center"
SelectedDate="{Binding StartDate, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
@@ -66,7 +70,7 @@
FontSize="14"
Foreground="#666666"
Text="~" />
<DatePicker
<DatePicker BorderBrush="{StaticResource pageColorBrush}"
Height="32"
VerticalContentAlignment="Center"
SelectedDate="{Binding EndDate, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
@@ -76,14 +80,21 @@
FontSize="14"
Foreground="#666666"
Text="店铺" />
<ComboBox IsEnabled="False"
<c:ShopFilterComboBox
Width="130"
Height="32"
DisplayMemberPath="ShopName"
IsGrouped="True"
ItemsSource="{Binding ShopOptions}"
SelectedItems="{Binding SelectedShops}" />
<!--<ComboBox IsEnabled="False"
Width="130"
Height="32"
VerticalContentAlignment="Center"
DisplayMemberPath="ShopName"
ItemsSource="{Binding ShopOptions}"
SelectedItem="{Binding SelectedShop, Mode=TwoWay}"
Style="{StaticResource BaseComboBoxStyle}" />
Style="{StaticResource BaseComboBoxStyle}" />-->
<Button
Height="32"
Margin="18,0,0,0"
@@ -431,8 +431,8 @@ namespace _29WxOrderWpf.Ctrls.Statistics.Services
}
}
// 3. 按天从缓存拼装完整范围,再按所选店铺过滤(ShopKey 为空 = 全部店铺)
return FilterByShop(GetCachedRows(startDay, endDay), query.ShopKey);
// 3. 按天从缓存拼装完整范围,再按所选店铺过滤(ShopKeys 为空 = 全部店铺)
return FilterByShop(GetCachedRows(startDay, endDay), query.ShopKeys);
}
/// <summary>
@@ -477,21 +477,24 @@ namespace _29WxOrderWpf.Ctrls.Statistics.Services
return result;
}
/// <summary>按所选店铺过滤明细:ShopKey 为空返回全部;否则按店铺名(ShopName)精确匹配。</summary>
private static List<ShopRevenueStatisticsRow> FilterByShop(List<ShopRevenueStatisticsRow> rows, string shopKey)
/// <summary>按所选店铺过滤明细:ShopKeys 为空/Null 返回全部;否则按店铺名(ShopName)精确匹配。</summary>
private static List<ShopRevenueStatisticsRow> FilterByShop(List<ShopRevenueStatisticsRow> rows, List<string> shopKeys)
{
if (rows == null)
return new List<ShopRevenueStatisticsRow>();
if (string.IsNullOrEmpty(shopKey))
if (shopKeys == null || shopKeys.Count == 0)
return new List<ShopRevenueStatisticsRow>(rows);
var shop = GetShopOptions().FirstOrDefault(a => a.AppKey == shopKey);
if (shop == null || string.IsNullOrEmpty(shop.ShopName))
var shops = GetShopOptions()
.Where(a => shopKeys.Contains(a.AppKey))
.Where(a => !string.IsNullOrEmpty(a.ShopName))
.ToList();
if (shops.Count == 0)
return new List<ShopRevenueStatisticsRow>();
return rows
.Where(r => string.Equals(r.ShopName, shop.ShopName, StringComparison.OrdinalIgnoreCase))
.Where(r => shops.Any(s => string.Equals(r.ShopName, s.ShopName, StringComparison.OrdinalIgnoreCase)))
.ToList();
}
@@ -20,7 +20,7 @@ namespace _29WxOrderWpf.Ctrls.Statistics.ViewModels
public class PeriodRevenueStatisticsViewModel : PropertyNotifyObject
{
/// <summary>每页行数</summary>
private const int PageSize = 20;
private const int PageSize = 50;
private readonly IShopRevenueStatisticsService _service;
private readonly PeriodStatisticType _periodType;
@@ -29,7 +29,7 @@ namespace _29WxOrderWpf.Ctrls.Statistics.ViewModels
private readonly DelegateCommand _nextPageCommand;
private readonly DelegateCommand _lastPageCommand;
private ShopOption _selectedShop;
private ObservableCollection<ShopOption> _selectedShops;
private ShopRevenueSummary _summary;
private ObservableCollection<ShopPeriodStatisticsItem> _statistics;
private List<ShopPeriodStatisticsItem> _allItems = new List<ShopPeriodStatisticsItem>();
@@ -192,17 +192,17 @@ namespace _29WxOrderWpf.Ctrls.Statistics.ViewModels
set { this.SetValue(x => x.EndDate, value); }
}
/// <summary>店铺选项(含“全部店铺”)</summary>
/// <summary>店铺选项(“全部店铺”由多选控件“全部商铺”表示)</summary>
public ObservableCollection<ShopOption> ShopOptions { get; }
/// <summary>选中的店铺</summary>
public ShopOption SelectedShop
/// <summary>选中的店铺(多选,绑定 ShopFilterComboBox.SelectedItems)</summary>
public ObservableCollection<ShopOption> SelectedShops
{
get { return _selectedShop; }
get { return _selectedShops; }
set
{
_selectedShop = value;
OnPropertyChanged(nameof(SelectedShop));
_selectedShops = value;
OnPropertyChanged(nameof(SelectedShops));
}
}
@@ -378,12 +378,11 @@ namespace _29WxOrderWpf.Ctrls.Statistics.ViewModels
{
try
{
// 店铺选项
ShopOptions.Add(new ShopOption { AppKey = string.Empty, ShopName = "全部店铺" });
// 店铺选项(全部店铺由多选控件“全部商铺”表示,不再额外加入“全部店铺”项)
var shops = await _service.GetShopOptionsAsync();
foreach (var shop in shops)
ShopOptions.Add(shop);
SelectedShop = ShopOptions.FirstOrDefault();
SelectedShops = new ObservableCollection<ShopOption>(ShopOptions);
// 默认时间范围
SelectedTimeRange = TimeRangeOptions.FirstOrDefault(t => t.Value == GetDefaultTimeRangeValue(_periodType));
@@ -477,6 +476,23 @@ namespace _29WxOrderWpf.Ctrls.Statistics.ViewModels
}
}
/// <summary>根据选中店铺生成查询店铺 key 列表:未选/全选/选中“全部店铺”时返回空列表(表示全部店铺)</summary>
private List<string> BuildSelectedShopKeys()
{
var selected = SelectedShops;
if (selected == null || selected.Count == 0)
return new List<string>();
var keys = selected.Select(s => s == null ? string.Empty : s.AppKey).ToList();
if (keys.Any(string.IsNullOrEmpty))
return new List<string>();
if (selected.Count >= ShopOptions.Count)
return new List<string>();
return keys;
}
/// <summary>执行查询,刷新周期统计表格并回到第一页</summary>
private async Task LoadAsync()
{
@@ -494,7 +510,7 @@ namespace _29WxOrderWpf.Ctrls.Statistics.ViewModels
{
StartDate = StartDate,
EndDate = EndDate,
ShopKey = SelectedShop == null ? string.Empty : SelectedShop.AppKey,
ShopKeys = BuildSelectedShopKeys(),
};
var table = await _service.GetPeriodStatisticsAsync(query, _periodType);
@@ -518,7 +534,7 @@ namespace _29WxOrderWpf.Ctrls.Statistics.ViewModels
/// <summary>重置查询条件并重新加载</summary>
private async void Reset()
{
SelectedShop = ShopOptions.FirstOrDefault();
SelectedShops = new ObservableCollection<ShopOption>(ShopOptions);
SelectedTimeRange = TimeRangeOptions.FirstOrDefault(t => t.Value == GetDefaultTimeRangeValue(_periodType));
ApplyTimeRange();
await LoadAsync();
@@ -25,7 +25,7 @@ namespace _29WxOrderWpf.Ctrls.Statistics.ViewModels
private ObservableCollection<RevenueTrendPoint> _trendData;
private ObservableCollection<ShopSalesProportionItem> _shopProportionData;
private ObservableCollection<ShopStatisticsItem> _shopStatistics;
private ShopOption _selectedShop;
private ObservableCollection<ShopOption> _selectedShops;
private string _selectedPlatform;
public ShopRevenueStatisticsViewModel()
@@ -92,17 +92,17 @@ namespace _29WxOrderWpf.Ctrls.Statistics.ViewModels
set { this.SetValue(x => x.EndDate, value); }
}
/// <summary>店铺选项(含“全部店铺”)</summary>
/// <summary>店铺选项(“全部店铺”由多选控件“全部商铺”表示)</summary>
public ObservableCollection<ShopOption> ShopOptions { get; }
/// <summary>选中的店铺</summary>
public ShopOption SelectedShop
public ObservableCollection<ShopOption> SelectedShops
{
get { return _selectedShop; }
get { return _selectedShops; }
set
{
_selectedShop = value;
OnPropertyChanged(nameof(SelectedShop));
_selectedShops = value;
OnPropertyChanged(nameof(SelectedShops));
}
}
@@ -179,12 +179,11 @@ namespace _29WxOrderWpf.Ctrls.Statistics.ViewModels
{
try
{
// 店铺选项
ShopOptions.Add(new ShopOption { AppKey = string.Empty, ShopName = "全部店铺" });
// 店铺选项(全部店铺由多选控件“全部商铺”表示,不再额外加入“全部店铺”项)
var shops = await _service.GetShopOptionsAsync();
foreach (var shop in shops)
ShopOptions.Add(shop);
SelectedShop = ShopOptions.FirstOrDefault();
SelectedShops = new ObservableCollection<ShopOption>(ShopOptions);
// 默认时间范围:本月
SelectedTimeRange = TimeRangeOptions.FirstOrDefault(t => t.Value == "month");
@@ -233,6 +232,23 @@ namespace _29WxOrderWpf.Ctrls.Statistics.ViewModels
}
}
/// <summary>根据选中店铺生成查询店铺 key 列表:未选/全选/选中“全部店铺”时返回空列表(表示全部店铺)</summary>
private List<string> BuildSelectedShopKeys()
{
var selected = SelectedShops;
if (selected == null || selected.Count == 0)
return new List<string>();
var keys = selected.Select(s => s == null ? string.Empty : s.AppKey).ToList();
if (keys.Any(string.IsNullOrEmpty))
return new List<string>();
if (selected.Count >= ShopOptions.Count)
return new List<string>();
return keys;
}
/// <summary>执行查询,刷新页面所有统计数据</summary>
private async Task LoadAsync()
{
@@ -250,7 +266,7 @@ namespace _29WxOrderWpf.Ctrls.Statistics.ViewModels
{
StartDate = StartDate,
EndDate = EndDate,
ShopKey = SelectedShop == null ? string.Empty : SelectedShop.AppKey,
ShopKeys = BuildSelectedShopKeys(),
};
Summary = await _service.GetSummaryAsync(query);
@@ -277,7 +293,7 @@ namespace _29WxOrderWpf.Ctrls.Statistics.ViewModels
/// <summary>重置查询条件并重新加载</summary>
private async void Reset()
{
SelectedShop = ShopOptions.FirstOrDefault();
SelectedShops = new ObservableCollection<ShopOption>(ShopOptions);
SelectedTimeRange = TimeRangeOptions.FirstOrDefault(t => t.Value == "month");
ApplyTimeRange();
await LoadAsync();